部分模仿是坏的,为什么呢? [英] Partial Mocks are bad, why exactly?

查看:134
本文介绍了部分模仿是坏的,为什么呢?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

场景



我有一个类称为 Model ,表示许多其他对象的复杂的复合对象不同类型。您可以将它视为 Car ,其中门[] 轮胎[] 引擎驱动程序等等。这些对象又有子对象,如code>引擎有 SparkPlug 离合器生成器等。



我有一个 Metrics 类,它计算一些或多或少的复杂度量关于模型,本质上看起来像这样:

  public类别度量{
私人最终模型模型;

public Metrics(model aModel){model = aModel;}

public double calculateSimpleMetric1(){...}
public double calculateSimpleMetric2(){.. 。}
public double calculateSimpleMetricN(){...}

public double calculateComplexMetric(){
/ *使用对多个calculateSimpleMetricX调用
的函数计算a更复杂的指标。 * /
}
}

我已经为 calculateSimpleMetricX 函数,并且每个函数都需要非平凡但可管理的数量(10-20行)的设置代码来正确模拟 Model



问题



由于模型 class stubbing / mocking所有依赖关系 calculateComplexMetric()将创建一个非常大且难以维护的测试(超过100行的设置代码测试一个合理的代表性场景,我需要测试很多场景)。



我的想法是创建一个部分模拟 Model 并存入相关的 calculateSimpleMetricX()函数,将设置代码减少到可管理的10行代码。因为这些功能已经单独测试。



但是,Mockito文档指出,部分模拟是一种代码气味



问题



个人我会在这里削减角落,只是部分模拟指标类。但是我有兴趣知道什么是纯粹主义的方式来组织这个代码和单元测试?



解决方案



  public class AbstractMetric {
abstract public double calculate();
}

public class ComplexMetric extends AbstractMetric {
private final SimpleMetric1 sm1;
私人终端SimpleMetric2 sm2;

public ComplexMetric(SimpleMetric1 asm1,SimpleMetric2 asm2){
sm1 = asm1;
sm2 = asm2;
}

@Ovrerride
public double calculate(){
return functionof(sm1.calculate(),sm2.calculate());
}
}

这是可测试的,我可以让每个实现 AbstractMetric 如果需要在稍后阶段提高性能,实施缓存。我发现,将这些指标添加到构造函数中是可以接受的,而不是在每次调用中将它们传递给计算函数。即使这样可以用工厂方法隐藏。

解决方案

我猜这个背后的原因可以如下:if你需要部分地模拟一个类来测试一些忽略其行为的一部分,然后这个类正在做一件以上的事情。这违反了单一责任原则,这是一个代码气味。



如果一个对象可以对其内部部分进行任何有用的部分截断(因此,实际上不存在),那么这个对象具有低内聚力,这也是一个代码气味。



您的指标类正在执行多项操作 - 它会计算不同类型的度量。考虑将它重构成一个抽象的 Metric 和适当的子类 - 每个都计算自己的度量标准。



部分模拟的一些参数有用性可以在 here 找到。但是,这些都是建立在 SRP 已被违反的情况下,您无法做任何事情它。


Scenario

I have a class call it Model that represents a complex, composite object of many other objects of varying types. You can think of it as a Car which has Door[], Tire[], Engine, Driver etc. And these objects in turn have sub objects such as Engine has SparkPlug, Clutch, Generator etc.

I have a Metrics class which calculates some more or less complex metrics about the Model, in essence it looks something like this:

public class Metrics{
    private final Model model;

    public Metrics(Model aModel){model = aModel;}

    public double calculateSimpleMetric1(){...}
    public double calculateSimpleMetric2(){...}
    public double calculateSimpleMetricN(){...}

    public double calculateComplexMetric(){
        /* Function that uses calls to multiple calculateSimpleMetricX to 
           calculate a more complex metric. */
    }
}

I have already written tests for the calculateSimpleMetricX functions and each of them require non-trivial, but manageable amounts (10-20 lines) of setup code to properly mock the related parts of the Model.

Problem

Due to the unavoidable complexity of the Model class stubbing/mocking all the dependencies for calculateComplexMetric() would create a very large and difficult to maintain test (over 100 lines of setup code to test a reasonable representative scenario, and I need to test quite a few scenarios).

My thought is to create a partial mock of Model and stub the relevant calculateSimpleMetricX() functions to reduce the setup code to a manageable 10 or so lines of code. As these functions are already tested separately.

However, Mockito documentation states that partial mocks are a code smell.

Question

Personally I would cut the corner here and just partial mock the Metrics class. But I'm interested in knowing what is the "purist" way to structure this code and unit test?

Solution

I ended up doing splitting into smaller, more cohesive classes as suggested by the accepted answer and using dependency injection:

public class AbstractMetric{
    abstract public double calculate();
}

public class ComplexMetric extends AbstractMetric{
    private final SimpleMetric1 sm1;
    private final SimpleMetric2 sm2;

    public ComplexMetric(SimpleMetric1 asm1, SimpleMetric2 asm2){
        sm1 = asm1;
        sm2 = asm2;
    }

    @Ovrerride
    public double calculate(){
        return functionof(sm1.calculate(), sm2.calculate());
    }
}

It is testable, and I can let each implementation of AbstractMetric implement caching if they need to to improve performance at a later stage. I find that adding the metrics to the constructor is acceptable comparing to passing them along to the calculate function in every call. Even this can be hidden with a factory method.

解决方案

I'm guessing that the reasoning behind this can be as follows: if you need to partially mock a class to test something ignoring a part of its behavior then this class is doing more than one thing. This violates the Single Responsibility Principle, and that is a code smell.

Also if an object can do anything useful with its internals partially stubbed (thus, being effectively non-existent) then this object has low cohesion, which also is a code smell.

Your Metric class is doing more than one thing - it calculates different types of metric. Consider refactoring it into an abstract Metric and appropriate subclasses - each calculating its own metric.

Some arguments for partial mocks usefulness can be found here. However they are all founded on a scenario when SRP is already violated and you can't do anything about it.

这篇关于部分模仿是坏的,为什么呢?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆