测试复杂行为时,在单元测试中可以有多个断言吗? [英] Is it OK to have multiple assertions in a unit test when testing complex behavior?

查看:34
本文介绍了测试复杂行为时,在单元测试中可以有多个断言吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是我的具体场景.

我有一个 QueryQueue 类,它在 ArcGIS API for Flex 中包装了 QueryTask 类.这使我能够轻松地将多个查询任务排队执行.调用 QueryQueue.execute() 遍历队列中的所有任务并调用它们的 execute 方法.

当所有的结果都被接收并处理完毕后,QueryQueue 将分派完成的事件.我的类的界面非常简单.

公共接口IQueryQueue{函数 get inProgress():Boolean;函数获取计数():int;功能得到完成():ISignal;函数取消():ISignal;函数添加(查询:查询,网址:字符串,令牌:对象=空):无效;函数取消():无效;函数执行():无效;}

要使 QueryQueue.execute 方法成功,必须发生几件事.

  1. task.execute 必须对每个查询任务调用一次且仅一次
  2. inProgress = true 等待结果
  3. inProgress = false 处理结果时
  4. completed 在结果处理完毕后发送
  5. canceled 永远不会被调用
  6. 在队列内完成的处理正确处理和打包查询结果

我正在努力将这些测试分解为可读、合乎逻辑和可维护的测试.

逻辑上我正在测试一个状态,即成功执行状态.这表明一个单元测试会断言上面的#1 到#6 是真的.

[Test] public mustReturnQueryQueueEventArgsWithResultsAndNoErrorsWhenAllQueriesAreSuccessful:void

然而,测试的名称并没有提供信息,因为它没有描述所有必须真实的事情才能被视为通过测试.

在线阅读(包括此处programmers.stackexchange.com)是一个相当大的阵营,它断言单元测试应该只有一个断言(作为指导).因此,当测试失败时,您确切地知道什么失败了(即 inProgress 未设置为 true、已完成多次显示等).您可能会得到更多(但理论上更简单、更清晰)的测试,如下所示:

[Test] public mustInvokeExecuteForEachQueryTaskWhenQueueIsNotEmpty():void[测试] public mustBeInProgressWhenResultsArePending():void[测试] public mustNotInProgressWhenResultsAreProcessedAndSent:void[测试] public mustDispatchTheCompletedEventWhenAllResultsProcessed():void[测试] public mustNeverDispatchTheCanceledEventWhenNotCanceled():void[测试] public mustReturnQueryQueueEventArgsWithResultsAndNoErrorsWhenAllQueriesAreSuccessful:void//... 等等

这可能会导致测试中出现大量重复代码,但可以通过适当的 setupteardown 方法将其最小化.

虽然这个问题与其他问题类似,但我正在寻找这个特定场景的答案,因为我认为它很好地代表了复杂的单元测试场景,展示了需要验证的多个状态和行为.遗憾的是,许多其他问题没有示例,或者示例没有展示复杂的状态和行为.

解决方案

让我们首先关注您确定的测试.除了最后一个 (mustReturnQueryQueueEventArgs...) 之外的所有内容都很好,我可以立即知道那里正在测试什么(这是非常好的迹象,表明它们是描述性的,而且很可能很简单).>

唯一的问题是你的最后一次测试.请注意,在测试名称中大量使用 "and""with""or" 通常会引起问题.目前还不清楚它应该做什么.首先想到的是返回正确的结果,但有人可能会认为这是一个模糊的术语?这是真的,它是模糊的.但是,您经常会发现这确实是非常普遍的要求,通过方法/操作合同进行了详细描述.

在您的特定情况下,我会简化上次测试以验证是否返回了正确的结果,仅此而已.您已经测试了导致结果构建的状态、事件和内容,因此无需再次测试.

现在,您提供的链接中的建议实际上非常好,通常,我建议坚持使用它们(一个测试的单一断言).问题是,单个断言真正代表什么?测试结束时的 1 行代码?让我们考虑这个简单的例子:

//更新我们自定义实体 MyEntity 的两个字段的方法公共无效更新(MyEntity 实体){entity.Name = "某个名字";entity.Value = "某个值";}

这个方法契约是为了执行这两个操作.通过成功,我们了解实体将被正确更新.如果其中之一由于某种原因失败,则方法作为一个单元被认为是失败的.你可以看到这是怎么回事;您要么有两个断言,要么编写纯粹用于测试目的的自定义比较器.

不要被单一断言所欺骗;这与代码行数或断言数量无关(但是,在大多数测试中,您将编写这确实会映射 1:1),而是关于断言单个单元(在上面的例子中,update 被认为是一个单位).并且单位实际上可能是多种事物,如果没有彼此,则根本没有任何意义.

这正是您链接引用的问题之一(Roy Osherove):

<块引用>

我的指导方针通常是每次测试测试一个逻辑概念.你可以在同一个对象上有多个断言.它们通常是被测试的同一个概念.

一切都与概念/责任有关;不是断言的数量.

Here is my specific scenario.

I have a class QueryQueue that wraps the QueryTask class within the ArcGIS API for Flex. This enables me to easily queue up multiple query tasks for execution. Calling QueryQueue.execute() iterate through all the tasks in my queue and call their execute method.

When all the results have been received and processed QueryQueue will dispatch the completed event. The interface to my class is very simple.

public interface IQueryQueue
{
    function get inProgress():Boolean;
    function get count():int;

    function get completed():ISignal;
    function get canceled():ISignal;

    function add(query:Query, url:String, token:Object = null):void; 
    function cancel():void;
    function execute():void;
}

For the QueryQueue.execute method to be considered successful several things must occur.

  1. task.execute must be called on each query task once and only once
  2. inProgress = true while the results are pending
  3. inProgress = false when the results have been processed
  4. completed is dispatched when the results have been processed
  5. canceled is never called
  6. The processing done within the queue correctly processes and packages the query results

What I am struggling with is breaking these tests into readable, logical, and maintainable tests.

Logically I am testing one state, that is the successful execution state. This would suggest one unit test that would assert #1 through #6 above are true.

[Test] public mustReturnQueryQueueEventArgsWithResultsAndNoErrorsWhenAllQueriesAreSuccessful:void

However, the name of the test is not informative as it does not describe all the things that must be true in order to be considered a passing test.

Reading up online (including here and at programmers.stackexchange.com) there is a sizable camp that asserts that unit tests should only have one assertion (as a guideline). As a result when a test fails you know exactly what failed (i.e. inProgress not set to true, completed displayed multiple times, etc.) You wind up with potentially a lot more (but in theory simpler and clearer) tests like so:

[Test] public mustInvokeExecuteForEachQueryTaskWhenQueueIsNotEmpty():void
[Test] public mustBeInProgressWhenResultsArePending():void
[Test] public mustNotInProgressWhenResultsAreProcessedAndSent:void
[Test] public mustDispatchTheCompletedEventWhenAllResultsProcessed():void
[Test] public mustNeverDispatchTheCanceledEventWhenNotCanceled():void
[Test] public mustReturnQueryQueueEventArgsWithResultsAndNoErrorsWhenAllQueriesAreSuccessful:void
// ... and so on

This could wind up with a lot of repeated code in the tests, but that could be minimized with appropriate setup and teardown methods.

While this question is similar to other questions I am looking for an answer for this specific scenario as I think it is a good representation of a complex unit testing scenario exhibiting multiple states and behaviors that need to be verified. Many of the other questions have, unfortunately, no examples or the examples do not demonstrate complex state and behavior.

解决方案

Let's focus on the tests you have identified first. All except the last one (mustReturnQueryQueueEventArgs...) are good ones and I could immediatelly tell what's being tested there (and that's very good sign, indicating they're descriptive and most likely simple).

The only problem is your last test. Note that extensive use of words "and", "with", "or" in test name usually rings problems bell. It's not very clear what it's supposed to do. Return correct results comes to mind first, but one might argue it's vague term? This holds true, it is vague. However you'll often find out that this is indeed pretty common requirement, described in details by method/operation contract.

In your particular case, I'd simplify last test to verify whether correct results are returned and that would be all. You tested states, events and stuff that lead to results building already, so there is no need to that again.

Now, advices in links you provided are quite good ones actually, and generally, I suggest sticking to them (single assertion for one test). The question is, what single assertion really stands for? 1 line of code at the end of test? Let's consider this simple example then:

// a method which updates two fields of our custom entity, MyEntity
public void Update(MyEntity entity)
{
    entity.Name = "some name";
    entity.Value = "some value";
}

This method contract is to perform those 2 operations. By success, we understand entity to be correctly updated. If one of them for some reasons fails, method as a unit is considered to fail. You can see where this is going; you'll either have two assertions or write custom comparer purely for testing purposes.

Don't be tricked by single assertion; it's not about lines of code or number of asserts (however, in majority of tests you'll write this will indeed map 1:1), but about asserting single unit (in the example above, update is considered to be an unit). And unit might be in reality multiple things that don't make any sense at all without eachother.

And this is exactly what one of questions you linked quotes (by Roy Osherove):

My guideline is usually that you test one logical CONCEPT per test. you can have multiple asserts on the same object. they will usually be the same concept being tested.

It's all about concept/responsibility; not the number of asserts.

这篇关于测试复杂行为时,在单元测试中可以有多个断言吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆