我终于明白了是什么让我担心依赖注入和类似的技术,它们可以让单元测试变得更容易。让我们举个例子:
public interface IRepository { void Item Find(); a lot of other methods here; }
[Test]
public void Test()
{
var repository = Mock<IRepository>();
repository.Expect(x => x.Find());
var service = new Service(repository);
service.ProcessWithItem();
}现在,上面的代码有什么问题?这是因为我们的测试大致可以窥视ProcessWithItem()的实现。如果它想要做"from x in GetAll() where x...“-但是不,我们的测试知道那里会发生什么。这只是一个简单的例子。想象一下我们的测试现在绑定了几个调用,当我们想要在方法中从GetAll()更改为更好的GetAllFastWithoutStuff()时...我们的测试失败了。请更改它们。很多糟糕的工作经常发生,却没有任何真正的需求。
这就是我经常停止编写测试的原因。我只是不知道如何在不知道实现细节的情况下进行测试。了解了它们,测试现在非常脆弱,做起来很痛苦。
当然,这不仅仅是关于接口(或DI)。POCOs (和POJO,为什么不)也有同样的问题,但它们现在与数据捆绑在一起,而不是与接口绑定。但原理是相同的-我们的最终断言与我们对SUT将要做什么的了解紧密地结合在一起。“是的,先生,您必须提供此字段,并且此字段最好具有此值”。
因此,测试很快就会失败,而且会经常失败。这就是痛苦。问题是。
有什么技术可以解决这个问题吗?AutoMockingContainer (它基本上负责所有的方法和嵌套的DI层次结构)看起来很有前途,但也有它自己的缺点。还要别的吗?
发布于 2009-09-13 17:06:47
依赖注入本身将允许您注入IRepository的实现,该实现接受对其进行的任何调用,检查是否满足不变量和前提条件,并返回满足后置条件的结果。当您选择注入一个对将要调用的方法有非常明确的期望的模拟对象时,是的,您正在进行高度特定于实现的测试--但是依赖项注入在这个问题上是完全无害的,因为它从来没有规定您应该注入什么;相反,您的抱怨似乎与模拟有关--实际上,特别是您选择使用的某种程度上的自动化模拟方法,这是一种基于非常具体的期望的方法。
使用非常具体的期望进行模拟确实只对白盒测试有用。根据您正在使用的工具/框架/库(您甚至没有在标记中指定确切的编程语言,所以我假设您的问题完全是开放式的),您可以指定允许的自由度(这些调用可以以任何顺序进行,这些参数只能满足以下前提条件,等等)。然而,我不知道有什么自动化的工具来执行你的不透明测试所需要的东西,那就是“通用的,容忍的接口实现,需要所有的‘’按合同编程‘’检查,而不需要其他的”。
在项目的整个生命周期中,我倾向于为所需的主要接口建立一个“不完全模拟”的库。在某些情况下,这些可能从一开始就很明显,但在其他情况下,随着我考虑一些主要的重构,它们会逐渐出现,如下所示(典型场景):
重构的早期阶段打破了我最初廉价实施的脆弱的强期望嘲笑的某些方面,我在思考是只调整期望还是全力以赴,如果我决定这不是一次性的(即未来重构和测试的回报将证明投资是合理的),那么我会手工编写一个很好的“不完全模拟”,并将其隐藏在项目的特定技巧包中--实际上通常会在项目之间重用;MockFilesystem、MockBigtable、MockDom、MockHttpClient、MockHttpServer等类/包进入一个与项目无关的存储库中,并重用于测试所有类型的未来项目(实际上,如果几个团队正在使用文件系统接口、bigtable接口、DOM、http客户端/服务器接口等,这些接口在各个团队中是统一的,那么这些类/包可能会与公司内的其他团队共享)。
我承认在这里使用"mock“这个词可能有点不合适,如果你把"mock”特指接口的“为测试目的而假实现”这种精确期望的风格。也许Stub、Shim、Fake、Test或其他一些前缀可能更可取(出于历史原因,我确实倾向于使用Mock,除非我特别记得将其称为Fake或类似的;-)。
如果我使用具有清晰和精确方式的语言来在语言本身中表达,并且在接口中使用不同的契约式设计规范,我想我会得到自动工具支持来处理大多数这种伪装/填空/等等;但是我主要是用其他语言编写代码,所以我不得不在这里做一些更多的手动工作。但我认为这是另一个问题。
发布于 2009-09-13 21:02:27
我读了一本很棒的书http://www.manning.com/rainsberger/。我想提供一些我从中获得的见解。我相信一些建议可以帮助你减少你的测试和你的实现之间的耦合。
编辑的:此耦合中包括断言被测代码调用某些方法的测试。调用某些方法从来都不是一个功能需求,而是一个实现问题。它涉及的接口不是被测试的接口。
顺便说一下,这本书有这么多关于如何设计测试(比如JUnit测试)的优秀实用建议,如果不是公司提供的,我会自己花钱买的!;-)
methods.
在每个外部资源(例如数据库)中只测试一次,在不是单元测试的专用测试中,而是集成测试中(尽管它仍然可以使用相同的JUnit技术(如果合适的话))。
测试足够多的专用测试,以信任资源正在工作。然后,其他测试永远不应该再次测试它,这是一种浪费,他们应该相信它。
请注意,当前的Maven最佳实践给出了类似的建议(请参阅免费书籍"Better builds with “)。我相信这不是coincidence:
- The JUnits in the test directory of a project are real unit tests. They run every time you do something with your project (except just compile).
- The integration and functional tests should be provided in a different project, an integration-test project. They only run in a much later (optional) phase, after you have deployed your whole application in the container.发布于 2009-09-13 23:07:12
因此,测试很快就会失败,而且会经常失败。这就是痛苦。问题是。
是的,单元测试可以依赖于内部实现细节。当然,这样的“白盒”测试比只依赖外部发布的合同的“黑盒”测试更脆弱。
但我不同意这一定会导致常规的测试失败。想一想您最初是如何使用mock进行测试的:您已经使用依赖注入来限制类的职责,减少与其他代码的耦合,并在隔离中启用测试类。
有什么技术可以解决这个问题吗?
好的单元测试只有在更改被测类的情况下才会失败,即使它依赖于内部实现细节。您可以限制您的类的职责和耦合(到其他类),这样您就很少需要更改它。
在实践中,您必须要务实;您将时不时地编写“单元测试”,这实际上是涉及多个类或超大类的集成测试。在这种情况下,依赖于内部实现细节的脆弱性测试更加危险。但对于真正的TDD风格的类,就没那么多了。
https://stackoverflow.com/questions/1418146
复制相似问题