Можно ли иметь несколько утверждений в модульном тесте при тестировании сложного поведения? - PullRequest
0 голосов
/ 16 марта 2012

Вот мой конкретный сценарий.

У меня есть класс QueryQueue, который включает класс QueryTask в ArcGIS API для 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 отправляется после обработки результатов
  5. canceled никогда не называется
  6. Обработка, выполненная в очереди, корректно обрабатывает и упаковывает результаты запроса

Я борюсь с тем, чтобы разбить эти тесты на удобочитаемые, логичные и поддерживаемые тесты.

Логически я тестирую одно состояние , то есть состояние успешного выполнения. Это предполагает, что один модульный тест, который подтвердит # 1 - # 6 выше, является верным.

[Test] public mustReturnQueryQueueEventArgsWithResultsAndNoErrorsWhenAllQueriesAreSuccessful:void

Однако название теста не является информативным, так как оно не описывает всех вещей, которые должны быть правдой, чтобы считаться проходным тестом.

В режиме онлайн (включая здесь и programmers.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

Это может привести к большому количеству повторяющегося кода в тестах, но его можно минимизировать с помощью соответствующих методов setup и teardown.

Хотя этот вопрос похож на другие вопросы, я ищу ответ для этого конкретного сценария, поскольку я думаю, что он является хорошим представлением сложного сценария модульного тестирования, демонстрирующего несколько состояний и поведений, которые необходимо проверить. Многие другие вопросы, к сожалению, не имеют примеров или примеров, не демонстрирующих сложное состояние и поведение.

Ответы [ 3 ]

2 голосов
/ 16 марта 2012

На мой взгляд, и, вероятно, их будет много, здесь есть пара вещей:

  1. Если вы должны проверить так много вещей для одного метода, это может означать, что ваш код может делать слишком много в одном методе ( Принцип единой ответственности)
  2. Если вы не согласны с вышесказанным, то следующее, что я бы сказал, это то, что вы описываете, это скорее интеграционный / приемочный тест. Который учитывает несколько утверждений, и у вас нет проблем там. Но имейте в виду, что это может потребоваться отнести к отдельному разделу тестов, если вы выполняете автоматизированные тесты (безопасные или небезопасные тесты)
  3. И / или, да, предпочтительный метод состоит в том, чтобы тестировать каждый кусочек отдельно, так как это тест unit . Наиболее близкая вещь, которую я могу предложить, и это о вашей терпимости к написанию кода только для того, чтобы иметь совершенные тесты ... это проверять объект на предмет объекта (чтобы вы могли сделать одно утверждение, которое по существу проверяет все это в одном). Тем не менее, аргумент против этого заключается в том, что, да, он проходит одно утверждение за тестовый тест, но вы все равно теряете выразительность.

В конечном счете, ваша цель должна состоять в том, чтобы стремиться к идеалу (одно утверждение на единицу тест), сосредотачиваясь на ТВЕРДЫХ принципах , но в конечном итоге вам действительно нужно что-то сделать или иначе нет никакого смысла в написании программного обеспечения (по крайней мере, мое мнение :)).

1 голос
/ 16 марта 2012

Давайте сосредоточимся на тестах, которые вы определили первыми. Все, кроме последнего (mustReturnQueryQueueEventArgs...), хороши, и я мог сразу же сказать, что там тестируется (и это очень хороший знак, указывающий, что они описательны и, скорее всего, просты).

Единственная проблема - ваш последний тест. Обратите внимание, что широкое использование слов "и" , "с" , "или" в имени теста обычно вызывает проблемы. Не очень понятно, что он должен делать. Сначала возвращаются правильные результаты, но кто-то может поспорить, что это неопределенный термин? Это верно, это расплывчато. Однако вы часто узнаете, что это действительно довольно распространенное требование, подробно описанное в контракте метода / операции.

В вашем конкретном случае я бы упростил последний тест, чтобы проверить, возвращаются ли правильные результаты, и это все. Вы уже тестировали состояния, события и прочее, что приводит к получению результатов, поэтому в этом больше нет необходимости.

Теперь, советы в ссылках, которые вы предоставили, на самом деле довольно хорошие, и , как правило, , я предлагаю придерживаться их (одно утверждение для одного теста). Вопрос в том, что на самом деле означает единственное утверждение ? 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 считается единицей). И единица может быть на самом деле множеством вещей, которые не имеют никакого смысла без друг друга.

И это именно то, на что один из вопросов вы связали цитаты (Роя Ошерова):

Обычно я рекомендую тестировать одну логическую КОНЦЕПЦИЮ на тест. Вы можете иметь несколько утверждений на один и тот же объект. как правило, это будет та же концепция, что и при тестировании.

Это все о концепции / ответственности; не количество утверждений.

0 голосов
/ 18 марта 2012

Я не знаком с flex, но я думаю, что у меня есть хороший опыт в модульном тестировании, поэтому вы должны знать, что модульное тестирование - это философия, поэтому для первого ответа да, вы можете сделать несколько утверждений, но если вы тестируетето же самое, главное в модульном тестировании всегда быть очень понятным и простым кодом, в противном случае модульному тесту потребуется модульный тест для его тестирования!Поэтому я советую вам, если вы новичок в модульном тестировании, не используйте множественные утверждения, но если у вас есть хороший опыт в модульном тестировании, вы будете знать, когда вам нужно будет их использовать

...