对我来说,关键的区别在于集成测试可以揭示一个功能是正常工作还是出现故障,因为它们在接近实际场景的情况下测试代码。它们调用一个或多个软件方法或功能,并测试它们是否按预期工作。
相反,单元测试测试单个方法,依赖于(通常错误的)假设其余软件已经正确工作,因为它明确地模拟了每个依赖项。
因此,当一个实现某个功能的方法的单元测试通过时,这并不意味着该功能正在工作。
假设您在某个地方有一个类似于以下内容的方法:
public SomeResults DoSomething(someInput) {
var someResult = [Do your job with someInput];
Log.TrackTheFactYouDidYourJob();
return someResults;
}
DoSomething
对于你的客户非常重要:它是一个功能,也是唯一重要的事情。这就是为什么你通常会编写Cucumber规范来断言它:你希望验证和传达该功能是否正常工作。
Feature: To be able to do something
In order to do something
As someone
I want the system to do this thing
Scenario: A sample one
Given this situation
When I do something
Then what I get is what I was expecting for
毫无疑问:如果测试通过,您可以断言您正在交付一个可工作的功能。这就是所谓的“业务价值”。
如果您想为
DoSomething
编写单元测试,您应该假装(使用一些模拟对象)其余的类和方法都在工作(也就是说,方法正在正确使用所有依赖项),并断言您的方法在工作。
在实践中,您会这样做:
public SomeResults DoSomething(someInput) {
var someResult = [Do your job with someInput];
FakeAlwaysWorkingLog.TrackTheFactYouDidYourJob(); // Using a mock Log
return someResults;
}
你可以使用依赖注入、工厂方法、模拟框架或仅扩展被测试的类来实现这一点。
假设
Log.DoSomething()
中存在一个错误,幸运的是,Gherkin规范将发现它,并且你的端到端测试将失败。
该功能将无法正常工作,因为
Log
已损坏,而不是因为
[使用某些输入执行你的工作]
没有完成任务。顺便说一下,
[使用某些输入执行你的工作]
是该方法的唯一责任。
此外,假设
Log
在其他100个特性中使用,在其他100个类的100个其他方法中使用。
是的,100个特性将失败。但是,幸运的是,100个端到端测试也会失败并揭示问题。而且,没错:
它们在说真话。
这是非常有用的信息:我知道我的产品已经损坏了。这也是非常令人困惑的信息:它告诉我问题所在。它向我传达了症状,而不是根本原因。
然而,
DoSomething
的单元测试是绿色的,因为它使用了一个假的
Log
,被构建成永远不会出错。是的:
它显然是在欺骗。它传递了一个损坏的功能正在运行。它怎么可能有用呢?
(如果
DoSomething()
的单元测试失败,请确保:
[以某些输入执行你的工作]
有一些错误。)
假设这是一个存在损坏类的系统:
![A system with a broken class](https://istack.dev59.com/611ZY.webp)
一个单一的缺陷将破坏多个功能,并导致多个集成测试失败。
![A single bug will break several features, and several integration tests will fail](https://istack.dev59.com/rKhSr.webp)
另一方面,同一个错误可能只会破坏一个单元测试。
![The same bug will break just one unit test](https://istack.dev59.com/M64Vb.webp)
现在,比较这两种情况。
同样的错误只会导致一个单元测试失败。
- 所有使用受损
Log
的功能都是红色的
- 所有的单元测试都是绿色的,只有
Log
的单元测试是红色的
实际上,所有使用受损功能的模块的单元测试都是绿色的,因为它们使用mocks删除了依赖项。换句话说,它们在一个理想的、完全虚构的世界中运行。这是隔离漏洞并寻找漏洞的唯一方法。单元测试意味着模拟。如果你没有模拟,你就没有进行单元测试。
区别
集成测试告诉你哪些不起作用。但是它们在猜测问题可能出在哪里方面没有用处。
单元测试是唯一能够告诉你bug出现在哪里的测试。要获取这些信息,它们必须在模拟环境中运行该方法,在该环境中,所有其他依赖项应该正确工作。
这就是为什么我认为你的句子“或者它只是跨越2个类的单元测试”有点错位。一个单元测试永远不应该跨越两个类。
这个回复基本上是我在这里写的内容的总结:
单元测试会撒谎,所以我喜欢它们。