对我而言,主要区别在于集成测试可以揭示某个功能是否正常运行或已损坏,因为它们会在接近现实的情况下强调代码。他们调用一种或多种软件方法或功能,并测试它们是否按预期方式工作。
相反,测试单个方法的单元测试依赖于(通常是错误的)假设,即软件的其余部分正常工作,因为它明确地模拟了每个依赖关系。
因此,当实现一些功能的方法的单元测试是绿色的,它并不能意味着功能工作正常。
假设您在某处有这样的方法:
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;
}
您可以使用依赖注入,某种工厂方法或任何Mock框架,或者只是扩展被测类来实现。
假设中存在一个错误Log.DoSomething()
。幸运的是,Gherkin规范会找到它,并且您的端到端测试将失败。
该功能不起作用,是因为它Log
已损坏,而不是因为[Do your job with someInput]
它没有在做它的工作。顺便说一下,这[Do your job with someInput]
是该方法的唯一责任。
另外,假设Log
在100个其他类的100个其他方法中使用了100个其他功能。
是的,100个功能将失败。但是,幸运的是,100个端到端测试也失败了,并揭示了问题所在。而且,是的:他们说的是实话。
这是非常有用的信息:我知道我的产品坏了。这也是非常令人困惑的信息:它没有告诉我问题出在哪里。它传达了我的症状,而不是根本原因。
但是,DoSomething
单元测试是绿色的,因为它使用的是伪造的Log
,永不中断。而且,是的:它显然在说谎。它正在传达损坏的功能正在起作用。有什么用?
(如果DoSomething()
的单元测试失败,请确保:[Do your job with someInput]
存在一些错误。)
假设这是一个类损坏的系统:
一个错误将破坏多个功能,并且多个集成测试将失败。
另一方面,相同的错误只会破坏一个单元测试。
现在,比较两种情况。
相同的错误只会破坏一个单元测试。
- 您所有使用断线的功能
Log
都为红色
- 您所有的单元测试都是绿色的,只有单元测试
Log
是红色的
实际上,使用损坏功能的所有模块的单元测试都是绿色的,因为通过使用模拟,它们删除了依赖性。换句话说,它们运行在理想的,完全虚构的世界中。这是隔离错误并查找错误的唯一方法。单元测试意味着模拟。如果您不是在嘲笑,那么您就不是单元测试。
区别
集成测试可以告诉您什么不起作用。但是它们在猜测问题可能出在哪里没有用。
单元测试是唯一可以告诉您错误在哪里的测试。要绘制此信息,他们必须在模拟的环境中运行该方法,在该环境中所有其他依赖项都应正常工作。
这就是为什么我认为您的句子“或者仅仅是跨越2个类的单元测试”以某种方式被取代的原因。单元测试不得跨越2个类。
这个答复基本上是我在这里写的摘要:单元测试的谎言,这就是为什么我喜欢它们。