我刚刚阅读了Roy Osherove的“单元测试艺术”,我试图坚持他在书中列出的最佳实践。其中一个最佳实践是不在测试方法中使用多个断言。这个规则的原因对我来说相当清楚,但这让我很奇怪......
如果我有类似的方法:
public Foo MakeFoo(int x, int y, int z)
{
Foo f = new Foo();
f.X = x;
f.Y = y;
f.Z = z;
return f;
}
我是否必须真正编写单独的单元测试以断言Foo的每个单独属性是否使用提供的值进行初始化?在测试方法中使用多个断言真的很少见吗?
仅供参考:我正在使用MSTest。
编辑:感谢所有回复。我想我最终将继续使用多个断言。在我的情况下,正在测试的行为是MakeFoo制作一个合适的Foo。因此断言每个属性都达到预期值就足够了。但是,如果设置其中一个属性存在条件,那么我将分别测试每个单独的结果。
我仍然不喜欢它....我喜欢每次测试一个断言的想法是你知道测试失败的确切原因。如果您解决了问题,那么测试将通过。有多个断言,您没有相同的保证。如果你修复了失败的断言所提到的问题,那么在测试中的下一次失败就没有什么可以阻止另一个断言了。如果断言被分开,那么你从一开始就知道这两个失败。
最后,我不仅仅使用.Equals()的原因是因为在我的情况下,Foo是一个LINQ-To-SQL实体,它引入了一些不值得进入的复杂功能。
再次感谢。
答案 0 :(得分:16)
每单元测试只测试一个概念更为重要。
测试一个概念可能需要不止一个断言,所以不要过分担心断言的数量。当然,如果您最终得到大量断言,您可能需要退一步思考一下您的测试内容。
答案 1 :(得分:5)
在这样的情况下,如果我试图对每个测试的一个断言严格,我将在Foo上声明相等,而不是它的组件。这促使我写了Foo.equals(),这通常是一件好事。但是我并不总是严格要求每次测试一个断言。看看对你有什么好处。
答案 2 :(得分:4)
AFAI看,这种做法背后的原理是
关键不在于每个测试需要一个'NUNit'/'xUnit'断言;相反,每次测试都有一个逻辑断言。
[Test]
public void MakeFoo_SeedsInstanceWithSpecifiedAttributes()
{
Assert.AreEqual( new Foo(1,2,3), Foo.MakeFoo(1,2,3), "Objects should have been equal );
}
这里我有一个NUnit断言(保持断言警察高兴):然而它正在测试幕后的三件事(在Foo.Equals中我会检查所有变量是否相等。)
该指南旨在防止类似的测试(即伪装成单元测试的集成测试)
[Test]
public void ITestEverything()
{
// humongous setup - 15 lines
payroll.ReleaseSalaries();
// assert that the finance department has received a notification
// assert employee received an email
// assert ledgers have been updated
// statements have been queued to the printqueue
// etc.. etc..
}
故事的道德:这是一个很好的目标。尝试将所有想要测试的东西组合成一个具有良好名称的逻辑/有凝聚力的Assert。例如Assert.True(AreFooObjectsEqual(f1,f2)
。如果您发现在命名内聚的验证/断言时遇到困难 - 也许您需要检查测试并查看是否需要拆分它。
答案 3 :(得分:2)
我实际上已经编写了一个NUnit插件来帮助解决这个问题。在http://www.rauchy.net/oapt
处试试答案 4 :(得分:1)
在测试中使用多个断言很常见,但我不认为这是“最佳做法”。
我会说是的,你确实想对上面的函数使用多个测试,所以你可以看到问题出在哪里。我最近一直在使用一个相当大的测试套件,它有一些奇怪的故障,我还没有跟踪,而我遇到的问题是每个测试用例都做得太多了。我把它们分开了,现在我可以禁用那些失败的特定的那些,这样我就可以在有机会的时候回到它们身上。
但是,您仍然可以使用夹具来分解设置和拆卸的通用性。我无法与MSTest交谈,但在UnitTest ++中你会做类似的事情:
class FooFixture
{
public:
FooFixture() : f(MakeFoo(kX, kY, kZ)) { }
static const int kX = 1;
static const int kY = 2;
static const int kZ = 3;
Foo f;
};
TEST_FIXTURE(FooFixture, IsXInitializedCorrectly)
{
CHECK_EQUAL(kX, f.X);
}
// repeat for Y and Z
这不是更多的打字,尤其是剪切和粘贴。哎呀,在vim中它只是esc y ctrl-{ pp
然后进行了轻微的编辑。但是,通过这种设置,您可以看到它是否只是一个字段失败然后深入了解原因。
答案 5 :(得分:1)
我通常发现自己在一个方法中有多个断言,但它们通常都与手头的测试有关。我有时会在断言中查明我知道会破坏测试的前提条件,因为我不希望测试成功。
[Test] // NUnit style test.
public void SearchTypeAndInventory()
{
var specification = new WidgetSearchSpecification
{
WidgetType = Widget.Screw,
MinimumInventory = 10
};
var widgets = WidgetRepository.GetWidgets(specification);
if( !widgets.Any() )
Assert.Inconclusive("No widgets were returned.");
Assert.IsTrue(
widgets.All( o => o.WidgetType == Widget.Screw),
"Not all returned widgets had correct type");
Assert.IsTrue(
widgets.All( o => o.InventoryCount >= 10),
"Not all returned widgets had correct inventory count.");
*虽然我可以合并Asserts,但我发现将错误分开会更有用。
我认为坚持1-assert-per-test的硬规则非常有用。更重要的是,测试只测试一件事。我已经看过许多超级测试,这是一种测试课程所有内容的巨大方法。这些超级测试很脆弱,难以维护。
您应该认为您的测试与他们测试的代码一样重要且编写得很好。例如。应用相同的重构技术,以确保测试做一件事并做得好,并不是一个测试所有东西的瘦腿。
答案 6 :(得分:0)
我读书的方式,你应该做“红色,绿色,重构”。在“重构”部分中,您应该重构被测代码和单元测试。
我认为以下重构没有错:
[TestMethod]
public void TestOne()
{
LetsDoSomeSetup();
AssertSomething();
}
[TestMethod]
public void TestTwo()
{
LetsDoSomeSetup(); // Same setup
AssertSomethingElse();
}
[TestMethod]
public void TestOneTwo()
{
LetsDoSomeSetup();
AssertSomething();
AssertSomethingElse();
}
当然,这假设两个断言是相关的,当然也依赖于相同的场景。