Должно ли это быть "Arrange-Assert-Act-Assert"?

Что касается классического тестового шаблона Arrange-Act-Assert, я часто обнаруживаю, что добавляю контр-утверждение, которое предшествует закону. Таким образом, я знаю, что передающее утверждение действительно проходит в результате действия.

Я думаю, что это похоже на красный в red-green-refactor, где только если я видел красную полосу в ходе моего тестирования, я знаю, что зеленый бар означает, что я написал код, который делает разница. Если я напишу тест прохождения, то любой код удовлетворит его; аналогично, в отношении Assange-Assert-Act-Assert, если мое первое утверждение не удастся, я знаю, что любой Закон передал бы окончательное Assert - так, чтобы он фактически не проверял ничего о Законе.

Выполняют ли ваши тесты эту схему? Почему или почему нет?

Обновление. Разъяснение: исходное утверждение, по сути, является противоположным окончательному утверждению. Это не утверждение, что Аранж работал; это утверждение, что Закон еще не сработал.

Ответ 1

Вот пример.

public void testEncompass() throws Exception {
    Range range = new Range(0, 5);
    assertFalse(range.includes(7));
    range.encompass(7);
    assertTrue(range.includes(7));
}

Возможно, я написал Range.includes(), чтобы просто вернуть true. Я этого не сделал, но могу себе представить, что мог бы. Или я мог бы написать это неправильно любым другим способом. Я хотел бы надеяться и ожидать, что с TDD я действительно правильно понял, что includes() просто работает, но, возможно, я этого не делал. Итак, первое утверждение - проверка здравомыслия, чтобы убедиться, что второе утверждение действительно имеет смысл.

Прочитайте сам, assertTrue(range.includes(7)); говорит: "утверждают, что модифицированный диапазон включает 7". Прочитайте в контексте первого утверждения, в котором говорится: "утверждайте, что вызов encompass() заставляет его включать 7. И поскольку encompass - это единица, которую мы тестируем, я думаю, что из некоторого (малого) значения.

Я принимаю свой собственный ответ; многие другие неверно истолковали мой вопрос о тестировании установки. Я думаю, что это немного другое.

Ответ 2

Это не самая распространенная вещь, но все же достаточно распространенная, чтобы иметь собственное имя. Этот метод называется Guard Assertion. Вы можете найти подробное описание его на стр. 490 в отличной книге xUnit Test Patterns от Gerard Meszaros (рекомендуется).

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

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

Ответ 3

Он также может быть указан как Arrange-Assume-Act-Assert.

В NUnit есть технический дескриптор, как в примере: http://nunit.org/index.php?p=theory&r=2.5.7

Ответ 4

Тест Arrange-Assert-Act-Assert всегда может быть реорганизован в два теста:

1. Arrange-Assert
2. Arrange-Act-Assert

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

Ответ 5

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

Ответ 6

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

Теперь мне становится любопытно, и у меня есть некоторые вопросы: как вы пишете свой тест, вызываете ли вы это утверждение неудачно, следуя циклу red-green-red-green-refactor или добавляете его впоследствии

Вы иногда терпите неудачу, возможно, после рефакторинга кода? Что это говорит вам? Возможно, вы могли бы поделиться примером, где это помогло. Благодарю.

Ответ 7

Я делал это раньше, когда исследую тест, который не прошел.

После значительных царапин на голове, я определил, что причиной были методы, называемые во время "Arrange", не работали правильно. Ошибка теста была введена в заблуждение. После аранжировки я добавил подтверждение. Это привело к отказу теста в месте, которое выявило актуальную проблему.

Я думаю, что здесь присутствует запах кода, если часть Arrange для теста слишком длинная и сложная.

Ответ 8

В общем, мне нравится "Arrange, Act, Assert" и я использую его как свой личный стандарт. Однако одна вещь, которую мне не напомнила мне, заключается в том, чтобы разобрать то, что я организовал, когда делаются утверждения. В большинстве случаев это не вызывает большого раздражения, так как большинство вещей автоматически отмахиваются через сбор мусора и т.д. Однако, если вы установили соединения с внешними ресурсами, вам, вероятно, захочется закрыть эти соединения, когда вы закончите с вашими утверждениями, или у вас у многих есть сервер или дорогостоящий ресурс, где-то где-то держаться за соединения или жизненно важные ресурсы, которые он должен отдать кому-то другому. Это особенно важно, если вы один из тех разработчиков, которые не используют TearDown или TestFixtureTearDown для очистки после одного или нескольких тестов. Конечно, "Arrange, Act, Assert" не несет ответственности за то, что я не смог закрыть то, что открываю; Я упоминаю только этот "gotcha", потому что я еще не нашел хорошего синонима "A-word" для "распоряжаться", чтобы рекомендовать! Любые предложения?

Ответ 9

Теперь я это делаю. A-A-A-A другого типа

Arrange - setup
Act - what is being tested
Assemble - what is optionally needed to perform the assert
Assert - the actual assertions

Пример теста обновления:

Arrange: 
    New object as NewObject
    Set properties of NewObject
    Save the NewObject
    Read the object as ReadObject

Act: 
    Change the ReadObject
    Save the ReadObject

Assemble: 
    Read the object as ReadUpdated

Assert: 
    Compare ReadUpdated with ReadObject properties

Причина в том, что ACT не содержит чтения ReadUpdated, потому что он не является частью действия. Действие только меняется и сохраняется. Так что, ARRANGE ReadUpdated для утверждения, я вызываю ASSEMBLE для утверждения. Это делается для предотвращения путаницы раздела ARRANGE.

ASSERT должен содержать только утверждения. Это оставляет ASSEMBLE между ACT и ASSERT, который устанавливает утверждение.

Наконец, если вы не выполняете в Arrange, ваши тесты не являются правильными, потому что вы должны иметь другие тесты, чтобы предотвратить/найти эти тривиальные ошибки. Потому что для присутствующего сценария уже должны быть другие тесты, которые проверяют READ и CREATE. Если вы создадите "Защитное утверждение", вы можете сломать DRY и создать обслуживание.

Ответ 10

Взгляните на запись в Википедии Дизайн по контракту. Священная троица Arrange-Act-Assert - это попытка кодировать некоторые из тех же понятий и доказывает правильность программы. Из статьи:

The notion of a contract extends down to the method/procedure level; the
contract for each method will normally contain the following pieces of
information:

    Acceptable and unacceptable input values or types, and their meanings
    Return values or types, and their meanings
    Error and exception condition values or types that can occur, and their meanings
    Side effects
    Preconditions
    Postconditions
    Invariants
    (more rarely) Performance guarantees, e.g. for time or space used

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

Ответ 11

Зависит от вашей тестовой среды/языка, но обычно, если что-то в части Arrange не выполняется, генерируется исключение, и тест не отображает его, а не начинает действие части Act. Нет, я обычно не использую вторую часть Assert.

Кроме того, в случае, если ваша часть Arrange довольно сложна и не всегда генерирует исключение, возможно, вам стоит рассмотреть возможность ее обертывания внутри некоторого метода и написать собственный тест для этого, так что вы можете быть уверены, что он не будет fail (без исключения).

Ответ 12

Я не использую этот шаблон, потому что думаю что-то вроде:

Arrange
Assert-Not
Act
Assert

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

Используя ваш пример ответа:

public void testEncompass() throws Exception {
    Range range = new Range(0, 5);
    assertFalse(range.includes(7)); // <-- Pointless and against DRY if there 
                                    // are unit tests for Range(int, int)
    range.encompass(7);
    assertTrue(range.includes(7));
}

Ответ 13

Если вы действительно хотите протестировать все в этом примере, попробуйте больше тестов... например:

public void testIncludes7() throws Exception {
    Range range = new Range(0, 5);
    assertFalse(range.includes(7));
}

public void testIncludes5() throws Exception {
    Range range = new Range(0, 5);
    assertTrue(range.includes(5));
}

public void testIncludes0() throws Exception {
    Range range = new Range(0, 5);
    assertTrue(range.includes(0));
}

public void testEncompassInc7() throws Exception {
    Range range = new Range(0, 5);
    range.encompass(7);
    assertTrue(range.includes(7));
}

public void testEncompassInc5() throws Exception {
    Range range = new Range(0, 5);
    range.encompass(7);
    assertTrue(range.includes(5));
}

public void testEncompassInc0() throws Exception {
    Range range = new Range(0, 5);
    range.encompass(7);
    assertTrue(range.includes(0));
}

Потому что в противном случае у вас не так много возможностей для ошибки... например, после encompass, диапазон только включает 7 и т.д.... Существуют также тесты на длину диапазона (чтобы гарантировать, что он также не охватывает случайное значение), и еще один набор тестов полностью для того, чтобы попытаться охватить 5 в диапазоне... что бы мы ожидали - исключение в encompass или диапазон, который должен быть неизменным?

В любом случае, дело в том, есть ли какие-либо предположения в действии, которые вы хотите протестировать, поставить их в свой тест, да?

Ответ 14

Я использую:

1. Setup
2. Act
3. Assert 
4. Teardown

Потому что чистая настройка очень важна.