Можно ли иметь несколько утверждений в модульном тесте при тестировании сложного поведения?

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

У меня есть класс QueryQueue, который является оболочкой класса QueryTask в ArcGIS API for Flex. Это позволяет мне легко ставить в очередь несколько задач запроса для выполнения. Вызов QueryQueue.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.

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


person Ryan Taylor    schedule 16.03.2012    source источник
comment
Идея о том, что каждый модульный тест должен иметь только одно утверждение, непрактична и недальновидна.   -  person Raedwald    schedule 05.04.2016


Ответы (3)


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

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

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

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

person k.m    schedule 16.03.2012

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

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

В конечном счете, ваша цель должна состоять в том, чтобы стремиться к идеалу (одно утверждение на модульный тест), сосредоточив внимание на SOLID принципы, но в конечном итоге вам нужно добиться цели, иначе нет никакого смысла в написании программного обеспечения (по крайней мере, мое мнение :)).

person Justin Pihony    schedule 16.03.2012

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

person Mohamed.Radwan -MVP    schedule 17.03.2012