我对单元测试真的很陌生,虽然我花了很多时间在研究上,但我无法找到适合我的情况的方法。
我的代码库非常庞大(大约3年的工作),很遗憾,很难测试,并且没有对它进行任何单元测试。
例如,在尝试测试集合类ProductCollection
时,更具体地说是bool MoveElementAtIndex(Product productToMove, int newIndex)
,我遇到了以下问题:
new ProductCollection()
new KeyedList<ID, Product>
。我想这不应该在这个构造函数中调用,因为我没有测试KeyedList
。ProductCollection
添加3个产品。new Product()
。Product
类的构造函数做了几件事this.ID = IDUtils.ComputeNewIDBasedOnTheMoonPhase()
。我想我不应该测试这个,因为它不是我的范围。我应该如何在这种深度上避免这种呼叫?this.Properties = new ProductProperties(folderPathToDefaultProperties)
。这不应该从我简单的FieldCollection.MoveElementAtIndex
测试调用,对吧?ProductCollection.Add(MyProduct)
检查基础KeyedList
是否已包含该产品。这也是我应该避免的业务逻辑,与我的测试无关。问题是如何?Add
方法中,会引发一些事件,通知系统有几件事(例如,新产品已添加到集合中)。根据我的想法,这些也不应该被解雇。KeyedList
实际上是否包含这些字段,它调用KeyedList.Remove()
,KeyedList.Insert()
来移动它逻辑,它会触发CollectionModified
等事件。如果你能解释我如何正确地进行单元测试,如何避免调用底层对象,我会非常感激。
我正在考虑微软的Moles框架(VS2010),因为我的印象是它不需要我重构所有东西,因为这绝对不是一个选择。但是已经尝试过,仍然找不到合适的方法来使用它。
此外,我的印象是这个具体的例子在我的情况下会帮助很多人,因为这就是现实世界中的代码通常是如此。
有什么想法吗?
答案 0 :(得分:4)
您的代码的设计并未考虑单元测试,因此很难这样做。我建议你正确地设计和单元测试你的新代码并尝试重构最重要的东西,这样你就可以对它进行单元测试。
示例:
但是Product类的构造函数做了几件事。 它计算新创建的产品的唯一ID:this.ID = IDUtils.ComputeNewIDBasedOnTheMoonPhase()。我想我不应该测试 这也不是,因为这不是我的范围。我应该如何避免这样的电话 在这种深度??
要解决此问题,您应该将接口IUtils传递给Product构造函数。要测试Product类,可以创建一个返回设置值的IUtils的模拟。您可以对ProductProperties执行相同的操作。
此外,在此Add方法中,会引发一些事件,通知系统 关于几件事(例如,新产品被添加到 采集)。我想这些也不应该被解雇。
这归结为设计。您可以使用观察者模式,而不是在单元测试时使用任何观察者。
答案 1 :(得分: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
需要考虑的另一件事是你的课程有多紧密结合。它可能不那么容易,但你可能想看看依赖注入。这允许类更松散地耦合,因此更容易测试。我发现Mark Seemann在.NET中的Dependency Injection这本书是一个很好的介绍。
在我的研究之后,我确定了NUnit作为测试框架,NSubstitute用于模拟和存根,Fluent Assertions使写入测试更容易,NInject用于依赖注入3} p>
答案 2 :(得分:2)
我建议使用ApprovalTest。这是开始测试没有最佳设计的遗留系统的好工具。
现在不要在单元和集成测试的差异之间烦恼,也不要费心完全隔离你的课程。您的代码可能不是最适合可测试性的,当您开始将所有内容彼此隔离时 - 您将最终得到大量的安排部分并且测试非常脆弱。
另一方面,您必须隔离外部资源(Web服务,数据库,文件系统等)。此外,所有非确定性行为都应该是隔离的(Random
,当前时间,用户输入等)
我建议您创建一个验证测试安全网,它将帮助您在可测试性方面更改软件,并告诉您是否对代码进行了重大更改。
阅读Michael Feathers Working Effectively with Legacy Code
答案 3 :(得分: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();
}
猜猜是什么;你将最终模拟所有外部方法调用。那你需要测试这个方法吗?答案是肯定的,但“是”的细节超出了这个问题的范围。