单元测试中的多个断言

发布于 2024-09-03 02:39:14 字数 754 浏览 7 评论 0原文

我刚刚读完 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 实体,它引入了一些不值得在这里讨论的复杂性。

再次感谢。

I've just finished reading Roy Osherove's "The Art of Unit Testing" and I am trying to adhere to the best practices he lays out in the book. One of those best practices is to not use multiple asserts in a test method. The reason for this rule is fairly clear to me, but it makes me wonder...

If I have a method like:

public Foo MakeFoo(int x, int y, int z)
{
     Foo f = new Foo();
     f.X = x;
     f.Y = y;
     f.Z = z;

     return f;
}

Must I really write individual unit tests to assert each separate property of Foo is initialized with the supplied value? Is it really all that uncommon to use multiple asserts in a test method?

FYI: I am using MSTest.

EDIT: Thanks for all the responses. I think I'll wind up going with the multiple asserts. In my situation the behavior being tested is that MakeFoo makes a proper Foo. So asserting that each property is getting the expected value should suffice. However, if there was a condition around setting one of the properties then I'd test each of the individual outcomes separately.

I still don't like it though.... The reason I like the idea of having one assert per test is that you know the exact reason a test failed. If you fix the problem then the test will pass. With multiple asserts you don't have the same guarantee. If you fix the problem alluded to by the failing assertion, there is nothing to stop another assertion later on in the test from failing next. If the asserts were split up, then you'd have known about both failures from the start.

And finally, the reason I'm not just using .Equals() is because in my situation Foo is a LINQ-To-SQL entity which introduces some complications that aren't worth getting into here.

Thank again.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

昵称有卵用 2024-09-10 02:39:14

每个单元测试只测试一个概念要重要得多。

测试一个概念可能需要多个断言,因此不必过度担心断言的数量。当然,如果您最终得到大量断言,您可能需要退一步思考一下您真正在测试什么。

It is far more important to test just one concept per unit test.

It may take more than one assertion to test a concept, so don't worry overly about the number of assertions. Of course if you end up with a large number of assertions, you may want to take a step back and think about what you are really testing.

破晓 2024-09-10 02:39:14

在这种情况下,如果我试图严格执行每个测试的一个断言,我将在 Foo 上断言相等,而不是在其组件上断言。这促使我编写 Foo.equals(),这本身通常是一件好事。但我并不总是严格限制每个测试的一个断言。看看什么让你感觉良好。

In situations like this, if I'm trying to be strict about one assert per test, I'll assert equality on the Foo, rather than its components. That drives me to write Foo.equals(), and that's usually a good thing in and of itself. But I'm not always strict about one assert per test. See what feels good to you.

梦里寻她 2024-09-10 02:39:14

AFAI 看,实践背后的原则是

  • 每次测试测试一个有凝聚力/逻辑的东西;导致简洁且易于阅读的测试。
  • 以获得更好的测试隔离。一项测试不应因 n 个原因之一而失败。如果确实如此,那么如何修复它并不是立即显而易见的;没有首先调查导致失败的原因。一个单一的改变不应该让一大堆看似无关的测试失败。
  • TDD 初学者规则手册中的入门规则;以便他们能够内化这个过程。

重点不是每次测试都需要一个“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.True(AreFooObjectsEqual(f1,f2)。如果您发现很难命名内聚验证/断言 - 也许您需要检查您的测试并看看是否需要拆分它。

AFAI see, the principle behind the practice was

  • to test one cohesive/logical thing per test ; leading to concise and easy to read tests.
  • to have better test isolation. One test should not fail for 1 of n reasons. If it does, then it is not immediately apparent as to how to fix it ; without first investigating which reason caused it to fail. A single change should not fail a truckload of seemingly unrelated tests.
  • a starter-rule from the rulebook for TDD beginners ; so that they can internalize the process.

the point is not that you need one 'NUNit'/'xUnit' assert per test ; rather you have one logical assert per test.

[Test]
public void MakeFoo_SeedsInstanceWithSpecifiedAttributes()
{
  Assert.AreEqual( new Foo(1,2,3), Foo.MakeFoo(1,2,3), "Objects should have been equal );
}

Here I have one NUnit assert (keeping the assert police happy) : However it is testing three things behind the scenes (in Foo.Equals I'd be checking if all the variables are equal.)
The guideline is to prevent tests like (i.e. an integration test masquerading as a unit test)

[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..
}

Moral of the story: It is a nice target to aim for. Try and compose all the things you want to test into one logical/cohesive Assert with a good name. e.g. Assert.True(AreFooObjectsEqual(f1,f2). If you find that you're having a tough time naming the cohesive verification/assert - maybe you need to review your test and see if you need to split it.

甜宝宝 2024-09-10 02:39:14

我实际上已经编写了一个 NUnit 插件来帮助解决这个问题。尝试一下 http://www.rauchy.net/oapt

I’ve actually written an NUnit addin to help with this. Try it out at http://www.rauchy.net/oapt

兮子 2024-09-10 02:39:14

在测试中使用多个断言是很常见的,但我不认为这是“最佳实践”。

我想说的是,您确实想对上述函数使用多次测试,这样您就可以看到问题出在哪里。我最近一直在使用一个相当大的测试套件,其中有一些我尚未追踪到的奇怪故障,而我遇到的问题是每个测试用例都做了太多事情。我已将它们分开,现在我可以禁用失败的特定部分,以便我可以在有机会时返回它们。

但是,您仍然可以使用夹具来分析设置和拆卸的共性。我不能与 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 然后进行少量编辑即可。不过,通过这种设置,您可以查看是否只是一个字段出现故障,然后深入了解原因。

It's common to use multiple asserts in a test, but I don't feel that it's a "best practice."

I would say that yes, you do want to use multiple tests for the above function, exactly so you can see where the problem is. I've recently been working with a fairly large test suite that has some weird failures in it that I have yet to track down, and the problem I'm experiencing is that the test cases each do too much. I've split them up, and now I can disable the specific ones that are failing so that I can get back to them when I have the opportunity.

However, you can still use a fixture to factor out the commonality of your setup and teardown. I can't speak to MSTest, but in UnitTest++ you'd do something like:

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

It's not a ton more typing, especially given cut&paste. Heck, in vim it's just esc y ctrl-{ pp and then minor edits. With this setup, though, you can see if it's just one field failing and then dig in to see why.

山有枢 2024-09-10 02:39:14

我通常发现自己在一个方法中有多个断言,但它们通常都与手头的测试相关。有时我会插入一个断言来验证我知道会破坏测试的先决条件,因为我不希望测试意外成功。

[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.");

* 虽然我可以合并断言,但我发现分离出问题的地方更有用。


我认为坚持每次测试 1 次断言的硬性规则不是很有用。更重要的是,测试只是测试一件事。我见过许多超级测试,它们是一种测试类的所有内容的巨大方法。这些超级测试很脆弱且难以维护。

您应该认为您的测试与它们正在测试的代码一样重要且编写良好。例如,应用相同的重构技术来确保测试只做一件事并且做得很好,而不是测试所有可见内容的烟囱。

I typically find myself having multiple asserts in a method, but they're usually all related to the test at hand. I sometimes chuck in an assert to verify a precondition that I know will break the test because I don't want the test to succeed by accident.

[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.");

* While I could have combined the Asserts, I find it's more useful to separate what went wrong.


I don't think sticking to a hard rule of 1-assert-per-test is very useful. What's more important is that a test is only testing 1 thing. I've seen many uber-tests that are one enormous method that tests everything about a class. These uber-tests are fragile and hard to maintain.

You should think of your tests as being as important and well-written as the code they are testing. E.g. apply the same refactoring techniques to ensure that a test does one thing and does it well, and isn't a stovepipe that tests everything in sight.

白昼 2024-09-10 02:39:14

按照我看书的方式,你应该做“红、绿、重构”。在“重构”部分中,您应该重构被测代码和单元测试。

我认为以下重构没有任何问题:

Original

[TestMethod]
public void TestOne()
{
    LetsDoSomeSetup();
    AssertSomething();
}

[TestMethod]
public void TestTwo()
{
    LetsDoSomeSetup(); // Same setup
    AssertSomethingElse();
}

Refactored

[TestMethod]
public void TestOneTwo()
{
    LetsDoSomeSetup();
    AssertSomething();
    AssertSomethingElse();
}

当然,假设两个断言是相关的,并且当然依赖于相同的场景。

The way I read the books, you're supposed to do "Red, Green, Refactor". In the "Refactor" part, you're supposed to refactor both the code under test and the unit tests.

I see nothing wrong with the following refactoring:

Original

[TestMethod]
public void TestOne()
{
    LetsDoSomeSetup();
    AssertSomething();
}

[TestMethod]
public void TestTwo()
{
    LetsDoSomeSetup(); // Same setup
    AssertSomethingElse();
}

Refactored

[TestMethod]
public void TestOneTwo()
{
    LetsDoSomeSetup();
    AssertSomething();
    AssertSomethingElse();
}

Of course, that assumes that the two asserts are related, and of course relies on the same scenario.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文