在实际实现的地方注入模拟对象

Inject the mock object where an actual implementation

我想知道这段代码是什么意思:

mathApplication.setCalculatorService(calcService);

为什么要使用接口并从中创建对象?还有这个注入是什么意思?

这是我的测试代码:

import org.easymock.EasyMock;
import org.easymock.EasyMockRunner;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(EasyMockRunner.class)
public class MathApplicationTester {

    private MathApplication mathApplication;
    private CalculatorService calcService;

    @Before
    public void setUp() {
        mathApplication = new MathApplication();
        calcService = EasyMock.createMock(CalculatorService.class);
        mathApplication.setCalculatorService(calcService);
    }

    @Test
    public void testAddAndSubtract() {

        //add the behavior to add numbers
        EasyMock.expect(calcService.add(20.0, 10.0)).andReturn(30.0);

        //subtract the behavior to subtract numbers
        EasyMock.expect(calcService.subtract(20.0, 10.0)).andReturn(10.0);

        //activate the mock
        EasyMock.replay(calcService);

        //test the subtract functionality
        Assert.assertEquals(mathApplication.subtract(20.0, 10.0), 10.0, 0);

        //test the add functionality
        Assert.assertEquals(mathApplication.add(20.0, 10.0), 30.0, 0);

        //verify call to calcService is made or not
        EasyMock.verify(calcService);
    }
}

MathApplication 依赖 CalculatorService ,顾名思义,CalculatorService 提供计算服务。

然而,在单元测试中,你只想测试class被测(MathApplication),所以你想将所有依赖项替换为你自己的实现,你可以完全控制它。为此,您使用模拟。

calcService = EasyMock.createMock(CalculatorService.class);

依赖注入是一种将你所依赖的对象"injects"注入主对象的模式。

可通过三种方法获取您 class 所依赖的对象的实例。

public class MathApplication {
    // I need an instance of CalculatorService inside the code of MathApplication
    ...
}

1:在MathApplication的代码中实例化对象(calculatorService是本地的还是class的属性都没有关系)。这种方式不是很推荐。

public double subtract(double a, double b) {
    CalculatorService calculatorService = new SomeFastCalculatorService();
    return calculatorService.subtract(a, b);
}

2:将实例化任务委托给外部提供者,称为factory:

public double subtract(double a, double b) {
    CalculatorService calculatorService = CalculatorServiceFactory.getInstance();
    return calculatorService.subtract(a, b);
}

3:让外部人员通过提供注入点来注入实例,通过构造函数或通过 setter 方法。

它通常用于构建整个应用程序的 "dependency tree"(通常使用 Spring Dependency Injection or JavaEE CDI 等框架)。在这里,它用于将您的模拟对象注入到被测 class 中:

mathApplication.setCalculatorService(calcService);

稍后,在您的 @Test 方法中,您准确地设置了模拟对象的行为。

EasyMock.expect(calcService.add(20.0, 10.0)).andReturn(30.0);

读作 "when calcService.add() is called with 20 and 10, give 30"。

最后你:

  1. 测试你的测试方法是否符合预期 returns - assertXXX()
  2. 测试是否使用了calcService - verify()

顺便说一句,代码

Assert.assertEquals(mathApplication.subtract(20.0, 10.0), 10.0, 0);

包含错误 - 查看 documentation。正确应该是

Assert.assertEquals(10.0, mathApplication.subtract(20.0, 10.0), 0);

它的工作原理几乎相同,只是如果测试不起作用,您会收到更正确的错误消息:

Assertion error - expected 10.0, but was 11.0.

无论如何,如果写成这样会更好读:

double expected = 30.0;
double actual = mathApplication.subtract(20.0, 10.0);
Assert.assertEquals(expected, actual, 0.0000000001); // never expect exact floating point result

为什么你应该接口:这是一个很好的做法:)框架模拟接口比 class 更容易(有些模拟框架甚至 cannot模拟 class)。它会引导您学习将接口与其实现分开并编写更好的可测试性 classes.