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

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

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

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

Уточнение уточнения: первоначальное утверждение по сути является противоположностью окончательного утверждения. Это не утверждение, что Arrange работал; это утверждение, что закон еще не сработал.

14 ответов

Решение

Вот пример.

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

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

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

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

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

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

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

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

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

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

1. Arrange-Assert

а также

2. Arrange-Act-Assert

Первый тест будет утвержден только на том, что было установлено в фазе Arrange, а второй тест будет утверждать только на том, что произошло в фазе Act.

Это дает преимущество более точной обратной связи о том, что это - фаза Arrange или Act, которая потерпела неудачу, в то время как в оригинале Arrange-Assert-Act-Assert они взаимосвязаны, и вам придется копать глубже и исследовать, что именно провалилось утверждение и почему оно провалилось, чтобы узнать, потерпели ли неудачу договоренности или действия.

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

И наконец, имейте в виду, что всякий раз, когда вы видите похожие разделы Arrange в разных тестах, вы должны попытаться использовать их в общих вспомогательных методах, чтобы ваши тесты были более СУХИМЫМИ и более удобными в обслуживании в будущем.

Я сейчас делаю это. АААА другого рода

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, который устанавливает assert.

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

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

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

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

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

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

Взгляните на статью Википедии о дизайне по контракту. 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

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

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

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

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

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

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

Я использую:

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

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

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

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

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

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

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

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

Другие вопросы по тегам