对我来说,关键的区别在于,集成测试揭示了一个特性是有效的还是坏的,因为它们在接近现实的场景中强调了代码。它们调用一个或多个软件方法或功能,并测试它们是否按预期工作。
相反,测试单个方法的单元测试依赖于(通常是错误的)软件其余部分正常工作的假设,因为它显式地模拟每个依赖项。
因此,当实现某个特性的方法的单元测试显示为绿色时,并不意味着该特性正在工作。
假设你有一个这样的方法:
public SomeResults DoSomething(someInput) {
var someResult = [Do your job with someInput];
Log.TrackTheFactYouDidYourJob();
return someResults;
}
做某事对你的客户来说非常重要:它是一个特性,是唯一重要的事情。这就是为什么您通常要编写一个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规范会找到它,您的端到端测试将失败。
这个特性不会工作,因为日志坏了,而不是因为[Do your job with someInput]没有做它的工作。顺便说一下,[Do your job with someInput]是该方法的唯一责任。
同样,假设Log在100个其他特性中使用,在100个其他类的100个其他方法中使用。
是的,100个功能都会失败。但幸运的是,100个端到端测试也失败了,并揭示了这个问题。是的,他们说的是实话。
这是非常有用的信息:我知道我的产品坏了。这也是非常令人困惑的信息:它没有告诉我问题在哪里。它告诉我的是症状,而不是根本原因。
然而,DoSomething的单元测试是绿色的,因为它使用了一个假的日志,被构建为永不崩溃。是的,这显然是在撒谎。它传达了一个坏掉的功能是有效的。它如何有用?
(如果DoSomething()的单元测试失败,请确认:[Do your job with someInput]有一些错误。)
假设这是一个有破碎类的系统:
一个bug就会破坏几个特性,几个集成测试就会失败。
另一方面,相同的错误只会破坏一个单元测试。
现在,比较这两种情况。
同样的错误只会破坏一个单元测试。
使用坏日志的所有功能都是红色的
所有的单元测试都是绿色的,只有Log的单元测试是红色的
实际上,所有使用坏特性的模块的单元测试都是绿色的,因为通过使用模拟,它们删除了依赖项。换句话说,它们运行在一个理想的、完全虚构的世界里。这是唯一隔离和寻找虫子的方法。单元测试意味着模拟。如果你不嘲讽,你就不是单元测试。
的区别
集成测试会告诉你什么是不正常的。但在猜测问题可能在哪里时,它们毫无用处。
单元测试是唯一能告诉您错误确切位置的测试。要绘制此信息,他们必须在模拟环境中运行该方法,在该环境中所有其他依赖项都应该正确工作。
这就是为什么我认为你的句子“或者它只是一个跨越2个类的单元测试”在某种程度上被取代了。一个单元测试不应该跨越2个类。
这个回答基本上是我在这里写的一个总结:单元测试说谎,这就是我喜欢它们的原因。