3

我对单元测试真的很陌生,尽管我在研究上花了很多时间,但我无法找出适合我的情况的正确方法。

我的代码库很大(大约 3 年的工作),不幸的是,耦合度很高,很难测试,也没有对其进行过单元测试。

因此,例如,当尝试测试一个集合类ProductCollection时,更具体地说,bool MoveElementAtIndex(Product productToMove, int newIndex)它的,我遇到了以下问题:

  • 首先我必须初始化这个new ProductCollection()
  • 构造函数初始化另一个手工类:new KeyedList<ID, Product>. 我想这不应该在这个构造函数中调用,因为我不是在测试KeyedList.
  • 接下来,我正在尝试向其中添加 3 个产品ProductCollection
  • 然后我首先创建这些 3 new Product()
  • 但是Product类的构造函数做了几件事
  • 它为新创建的产品计算一个唯一 ID:this.ID = IDUtils.ComputeNewIDBasedOnTheMoonPhase()。我想我也不应该测试这个,因为它不是我的范围。我应该如何避免这种深度的调用?
  • 相同的 Product 构造函数为此产品分配了一些默认属性:this.Properties = new ProductProperties(folderPathToDefaultProperties). 这不应该从我的简单FieldCollection.MoveElementAtIndex测试中调用,对吧?
  • 假设我现在终于有了我的产品对象,我正在尝试将它们添加到我的收藏中。
  • ProductCollection.Add(MyProduct)检查底层证券是否KeyedList已经包含该产品。这也是我应该避免的业务逻辑,与我的测试无关。问题是如何?
  • 此外,在这种Add方法中,会引发一些事件,通知系统一些事情(例如,新产品已添加到集合中)。我想这些也不应该被解雇。
  • 最后,当我添加了我的产品时,我调用了所需的 SUT:移动元素方法。
  • 但是这个方法也有可能超出我的测试范围的逻辑:它验证底层KeyedList实际上包含这些字段,它调用KeyedList.Remove()KeyedList.Insert()为其移动逻辑,并触发像CollectionModified.

如果您能解释一下如何正确地进行此单元测试,如何避免调用底层对象,我将不胜感激。

我正在考虑微软的 Moles 框架(VS2010),因为我的印象是它不需要我重构所有东西,因为这绝对不是一个选择。但是已经尝试过了,仍然找不到合适的使用方法。

另外,我的印象是,这个具体的例子对我的情况有很多帮助,因为现实世界中的代码通常是这样的。

有什么想法吗?

4

4 回答 4

4

您的代码在设计时没有考虑到单元测试,这使得这样做非常困难。我建议您正确设计和单元测试您的新代码,并尝试重构最重要的事情,以便您可以对其进行单元测试。

例子:

但是 Product 类的构造函数做了几件事。它为新创建的产品计算一个唯一 ID:this.ID = IDUtils.ComputeNewIDBasedOnTheMoonPhase()。我想我也不应该测试这个,因为它不是我的范围。我应该如何避免这种深度的调用?

要解决此问题,您应该将接口 IUtils 传递给您的 Product 构造函数。要测试您的 Product 类,您可以创建一个返回设定值的 IUtils 模拟。您可以对 ProductProperties 执行相同的操作。

此外,在这个 Add 方法中,会引发一些事件,通知系统一些事情(例如,新产品已添加到集合中)。我想这些也不应该被解雇。

这归结为设计。您可以在单元测试时使用观察者模式并且没有任何观察者。

于 2013-01-12T15:53:54.023 回答
2

这是一个常见问题,也是我们有框架为我们伪造对象的原因。存根或模拟允许我们围绕类创建测试工具,而无需实例化许多其他类和服务。有许多这样的框架。这是一个非常有用的三个常见框架的博客。 http://www.richard-banks.org/2010/07/mocking-comparison-part-1-basics.html

我自己对单元测试很陌生,发现 Roy Osherove 的《单元测试的艺术》一书非常有帮助。Roy 有一个博客,这些是他关于单元测试的一些帖子http://osherove.com/display/Search?moduleId=10002929&searchQuery=Unit+Testing

需要考虑的另一件事是您的类的耦合程度。做起来可能不是那么容易,但你可能想看看 Dependency Injection。这允许类更松散耦合,因此更容易测试。我发现 Mark Seemann 的 .NET 中的 Dependency Injection 一书是一个很好的介绍。

在我的研究之后,我选择了NUnit作为测试框架,NSubstitute用于模拟和存根,Fluent Assertions使编写测试更容易,NInject用于依赖注入

于 2013-01-12T16:00:01.267 回答
2

我建议使用ApprovalTest。这是开始测试没有最佳设计的遗留系统的好工具。

现在不要在单元测试和集成测试的差异之间烦恼,也不要为完全隔离你的类而烦恼。您的代码可能不是最适合可测试性的,当您开始将所有内容相互隔离时 - 您最终会得到巨大的排列部分和非常脆弱的测试。

另一方面,您必须隔离外部资源(Web 服务、数据库、文件系统等)。此外,所有非确定性行为都应该被隔离(Random、当前时间、用户输入等)

我只是建议创建一个验证测试的安全网,这将帮助您朝着可测试性的方向更改软件,并告诉您是否对代码进行了重大更改。

阅读 Michael Feathers有效地使用遗留代码

于 2013-01-12T16:01:12.093 回答
1

您应该使用 microsoft moles 框架或 microsoft fakes 框架。这些框架使您能够更改函数的行为。

在适当的单元测试中,应模拟所有外部方法调用以隔离要测试的代码。Moles / Fakes 在创建模拟对象 / 存根等方面非常有用。

更新:理论上,最好模拟所有外部方法调用以隔离代码。即使在某些实际情况下,您也必须模拟所有外部方法:

void UpdateSomeX(X x)
{
   this.validator.Validate(x);
   x.UpdateDate = DateTime.Now;
   this.context.Attach(x);
   this.unitOfWork.Save();
}

你猜怎么着; 您最终将模拟所有外部方法调用。那你需要测试这个方法吗?答案是肯定的,但是“是”的细节超出了这个问题的范围。

于 2013-01-12T15:41:07.247 回答