我最近开始在我工作的地方推动TDD。到目前为止事情进展顺利。我们正在编写测试,我们让它们在提交时自动运行,我们一直在寻求改进我们的流程和工具。
我发现可以改进的一件事是我们如何设置我们的测试数据。在严格的单元测试中,我们经常发现自己实例化和填充复杂的CLR对象。这是一种痛苦,通常只在少数情况下进行测试。
我想推荐的是数据驱动测试。我认为我们应该能够从文件中加载我们的测试数据,甚至可以从模式中动态生成它们(尽管如果我可以生成对象的所有可能配置,那么我只考虑动态执行它)配置很小)。还有我的问题。
我还没有找到一个为C#CLR对象生成测试数据的好策略。
我研究了从XSD生成XML数据,然后使用DataSourceAttribute
将其加载到我的测试中。这似乎是一个很好的方法,但我遇到了生成XSD文件的麻烦。 xsd.exe
因为我们的类有接口成员而失败。我也尝试在我们的程序集上使用svcutil.exe
,但由于我们的代码是单片的,因此输出是巨大且棘手的(许多相互依赖的.xsd文件)。
生成测试数据的其他技术有哪些?理想情况下,生成器将遵循模式(可能是xsd,但最好是类本身),并且可以编写脚本。 技术说明(不确定这是否相关,但不能伤害):
Microsoft.VisualStudio.TestTools.UnitTesting
中定义)。由于
额外信息
我对此感兴趣的一个原因是测试我们拥有的 Adapter 类。它需要一个复杂而复杂的遗留实体并将其转换为DTO。遗留实体是意大利面条的一团糟,不能轻易地分成由接口定义的逻辑子单元(如建议的那样)。这将是一个很好的方法,但我们没有那么奢侈。
我希望能够生成此遗留实体的大量配置,并通过适配器运行它们。配置数量越多,当下一个开发人员(无视应用程序的90%)更改旧实体的架构时,我的测试失败的可能性就越大。
更新
为了澄清,我不打算为每次执行测试生成随机数据。我希望能够生成数据以涵盖复杂对象的多种配置。我希望离线生成此数据并将其存储为我的测试的静态输入。
我刚刚重新阅读了我的问题并注意到我实际上最初要求随机生成。我很惊讶我要求了!我已经更新了问题以解决这个问题。对此感到抱歉。
答案 0 :(得分:8)
您需要的是 NBuilder (http://code.google.com/p/nbuilder)等工具。
这允许您描述对象,然后生成它们。这非常适合单元测试。
这是一个非常简单的例子(但您可以根据需要使其变得复杂):
var products = Builder<Product>
.CreateListOfSize(10)
.All().With(x => x.Title = "some title")
.And(x => x.AnyProperty = RandomlyGeneratedValue())
.And(x => x.AnyOtherProperty = OtherRandomlyGeneratedValue())
.Build();
答案 1 :(得分:3)
根据我的经验,您希望实现的目标最终实际上比在逐个测试的代码中生成对象更难实现和维护。
我曾与一个有类似问题的客户合作,他们最终将对象存储为JSON并对其进行反序列化,期望它更容易维护和扩展。事实并非如此。你知道在编辑JSON时你没有得到什么吗?编译时语法检查。由于JSON因语法错误而无法反序列化,他们最终导致测试失败。
减少痛苦的一件事就是编写小型接口。如果你有一个拥有大量属性的巨型对象,那么你想要测试的给定方法可能只需要少量。因此,不是采用SomeGiantClass
的方法,而是采用实现ITinySubset
的类。使用较小的子集可以更加明显地填充需要的内容,以使测试具有任何有效性。
我同意其他人说过生成随机数据是一个坏主意。我会说这是真的坏主意。单元测试的目标是可重复性,它会在您生成随机数据的第二个窗口缩小。即使您正在“离线”生成数据然后将其输入,这也是一个坏主意。您无法保证您生成的测试对象实际上正在测试其他测试中未涵盖的任何有价值的内容,或者它是否正在测试有效条件
更多测试并不意味着您的代码更好。 100%的代码覆盖率并不意味着您的代码没有错误且工作正常。您的目标应该是测试您知道对您的应用程序很重要的逻辑,而不是试图涵盖每个可以想象的案例。
答案 2 :(得分:2)
这与你所说的有点不同,但是你看过Pex了吗? Pex将尝试生成涵盖代码所有路径的输入。
答案 3 :(得分:2)
生成测试数据通常是不恰当且不太有用的测试方法 - 特别是如果您生成一组不同的测试数据(例如每次随机),因为有时测试运行会失败,有时候不会。它也可能与您正在做的事情完全无关,并且会对一组令人困惑的测试产生影响。
测试应该有助于记录+正式化一个软件的规范。如果通过用数据轰炸系统来找到软件的边界,那么这些不会被正确记录。它们还提供了一种通过与代码本身不同的代码进行通信的方式,因此如果它们非常具体且易于阅读和理解,则通常最有用。
如果你真的想这样做,虽然通常你可以把你自己的生成器编写为测试类。我过去已经做了几次这样做很好,还有额外的好处,你可以看到它正在做什么。您还已经知道数据的约束,因此尝试概括方法没有问题
根据你的说法,你所拥有的痛苦就是设置对象。这是一个常见的测试问题 - 我建议通过为您的常见对象类型创建流畅的构建器来专注于此 - 这为您提供了一种很好的方式来每次填充更少的细节(您通常只提供有趣的数据(对于给定的测试用例)并且其他所有内容都有有效的默认值。它们还减少了测试代码中构造函数的依赖性数量,这意味着如果需要更改它们,测试不太可能在以后重构。你可以从这种方法中获得很多好处。您可以通过为构建器提供常用的设置代码来进一步扩展它,当您获得很多构建器时,这是开发人员挂起可重用代码的自然点。
在我研究过的一个系统中,我们最终将所有这些类型的东西聚合成一些东西,可以在应用程序中打开+关闭不同的接缝(文件访问等),为对象提供构建器并设置一套全面的假视图类(用于wpf)位于我们的演示者之上。它有效地提供了一个测试友好的界面,用于编写和测试整个应用程序,从非常高级的东西到非常低级的东西。一旦你到达那里,你真的处于最佳位置,因为你可以编写有效地镜像应用程序中的按钮点击的测试,但你很容易重构代码,因为在你的真实类中几乎没有直接的依赖关系。测试
答案 4 :(得分:1)
实际上,微软有一种在标记中表达对象实例的方式,即XAML。
不要害怕文档中的WPF范例。您需要做的就是在单元测试中使用正确的类来加载对象。
为什么我会这样做?因为当您添加此文件时,Visual Studio项目将自动为您提供XAML语法和可能的智能感知支持。
什么是小问题?标记元素类必须具有无参数构造函数。但是这个问题总是存在,并且有解决方法(例如here)。
供参考,请看:
我希望我能告诉你我在这件事上所做的事情,但我不能。