繁体   English   中英

在测试复杂行为时,可以在一个单元测试中包含多个断言吗?

[英]Is it OK to have multiple assertions in a unit test when testing complex behavior?

这是我的具体情况。

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

接收并处理所有结果后, QueryQueue将调度完成的事件。 我班的接口很简单。

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;
}

为了使QueryQueue.execute方法成功,必须发生一些事情。

  1. 必须对每个查询任务一次调用task.execute
  2. 结果等待处理时, inProgress = true
  3. 处理结果后, inProgress = false
  4. completed结果后调度completed
  5. canceled从未被调用
  6. 队列中完成的处理正确处理并打包了查询结果

我正在努力将这些测试分解为可读,逻辑和可维护的测试。

从逻辑上讲,我正在测试一种状态 ,即成功执行状态。 这表明一个单元测试可以断定上面的#1至#6是正确的。

[Test] public mustReturnQueryQueueEventArgsWithResultsAndNoErrorsWhenAllQueriesAreSuccessful:void

但是,测试的名称并不能说明所有问题,因为它没有描述要通过测试就必须真实的所有事物。

在线阅读(包括在此处以及在developers.stackexchange.com上 ),有一个相当大的阵营,它断言单元测试应该只有一个断言(作为准则)。 结果,当测试失败时,您会确切地知道失败的原因(即,inProgress未被设置为true,完成显示多次,等等。)您可能会得到更多(但从理论上讲更简单更清晰)的测试,如下所示:

[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

在测试中可能会产生大量重复的代码,但是可以通过适当的setupteardown方法将其最小化。

尽管此问题与其他问题类似,但我正在寻找针对此特定方案的答案,因为我认为它很好地表示了一个复杂的单元测试方案,该方案具有多个状态和行为,需要进行验证。 不幸的是,许多其他问题都没有示例,或者这些示例没有展示复杂的状态和行为。

我认为可能会有很多,这里有几件事:

  1. 如果您必须为一种方法测试很多东西,那么这可能意味着您的代码可能在一种方法中做得太多( 单一职责原则)
  2. 如果您不同意以上内容,那么我接下来要说的是,您所描述的更多是集成/验收测试。 它允许多个断言,并且那里没有问题。 但是请记住,如果您要进行自动测试(安全测试与不安全测试),则可能需要将其放到单独的测试部分中。
  3. 和/或,是的,首选方法是分别测试每个部分,因为这就是单元测试。 我可以建议的最接近的东西,这是关于您对代码的容忍度,以使其具有完美的测试...是针对一个对象检查一个对象(因此,您需要做一个断言,实质上就是对所有对象进行一次测试)。 但是,反对这一观点的论点是,是的,它通过了每个测试的一个断言,但是您仍然失去了表现力。

最终,您的目标应该是通过专注于SOLID原则来追求理想的状态(每个单元测试一个断言),但是最终您确实需要完成工作,否则编写软件没有任何实际意义(至少我认为: ))。

让我们专注于您首先确定的测试。 除了最后一个( mustReturnQueryQueueEventArgs... )之外,其他所有都是好东西,我可以立即告诉那里正在测试什么(这是一个很好的信号,表明它们具有描述性,并且很可能很简单)。

唯一的问题是您的最后一次测试。 请注意,测试名称中单词“ and”“ with”“ or”的广泛使用通常会引起问题。 目前尚不清楚应该做什么。 返回正确的结果首先想到的是,但有人可能会说这是一个模糊的术语? 这是正确的,这是模糊的。 但是,您经常会发现这确实是相当普遍的要求,并按方法/操作合同进行了详细说明。

在您的特定情况下,我将简化上一次测试,以验证是否返回了正确的结果,仅此而已。 您已经测试了导致结果建立的状态,事件和事物,因此无需再次进行测试。

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

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

该方法合同将执行这两个操作。 通过成功,我们了解要正确更新的实体。 如果其中一个由于某种原因失败,则将方法作为一个单元视为失败。 您可以看到前进的方向; 您将拥有两个断言,或者仅出于测试目的编写自定义比较器。

不要被单一的主张所欺骗; 这与代码行数或断言数量无关(但是,在大多数测试中,您编写的确会映射为1:1),而是断言单个单元 (在上面的示例中, update被视为一个单元)。 实际上,单位可能是多种事物,彼此之间毫无任何意义。

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

我的指导原则通常是每次测试都测试一个逻辑概念。 您可以在同一对象上具有多个断言。 它们通常是要测试的相同概念。

都是关于概念/责任; 不是断言的数量。

我不熟悉flex,但是我想我在单元测试方面有很好的经验,因此您必须知道单元测试是一种哲学,因此对于第一个答案,可以进行多重声明,但是如果您测试相同的行为,单元测试中的重点始终是非常易于维护和简单的代码,否则单元测试将需要单元测试来进行测试! 因此,我的建议是,如果您是单元测试的新手,请不要使用多个断言,但是如果您在单元测试方面有丰富的经验,那么您会知道何时需要使用它们。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM