2009-06-20 3 views
79

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

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

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

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

ответ

7

Вот пример.

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 - это единица, которую мы тестируем, я думаю, что это часть (маленькое) значение.

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

+0

Спасибо, что вернулись с примером, Карл. Ну, в красной части цикла TDD, пока encompass() действительно что-то не сделает; первое утверждение бессмысленно, это всего лишь дублирование второго. В зеленом цвете он начинает полезен. Это приобретает смысл во время рефакторинга. Было бы неплохо иметь UT-структуру, которая сделает это автоматически. – philant

+0

Предположим, что TDD, что класс Range, не будет ли еще один неудачный тест тестирования Range ctor, когда вы его сломаете? – philant

+1

@philippe: Я не уверен, что понимаю вопрос. Конструктор Range и включает() имеют свои собственные модульные тесты. Не могли бы вы уточнить, пожалуйста? –

0

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

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

1

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

104

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

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

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

+0

+1, очень хороший ответ. Последняя часть особенно важна, потому что она показывает, что вы можете охранять вещи как отдельный юнит-тест. – murrekatt

+2

Я вообще делал это так, но есть проблема с наличием отдельного теста для обеспечения предварительных условий (особенно с большой базой кода с меняющимися требованиями) - тест предварительного условия будет изменяться со временем и не синхронизироваться с «основным» 'тест, который предполагает эти предварительные условия. Таким образом, предпосылки могут быть прекрасными и зелеными, но эти предварительные условия не удовлетворяются в основном тесте, который теперь всегда показывает зеленый и прекрасный. Но если бы предварительные условия были в основном тесте, они бы потерпели неудачу. Вы столкнулись с этой проблемой и нашли для нее хорошее решение? – nchaud

+2

Если вы сильно измените свои тесты, у вас могут возникнуть другие проблемы (http://blog.ploeh.dk/2013/04/02/why-trust-tests), потому что это, как правило, сделает ваши тесты менее заслуживающее доверие. Даже перед лицом меняющихся требований рассмотрите [проектирование кода только в виде добавления] (http://blog.ploeh.dk/2012/01/03/SOLIDisAppend-only). –

1

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

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

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

+0

Обычно я не заставляю исходное утверждение терпеть неудачу - в конце концов, это не должно терпеть неудачу, как должно утверждать TDD, прежде чем его метод будет написан. Я пишу это, когда пишу это, прежде всего, в обычном порядке написания теста, а не потом. Честно говоря, я не помню, чтобы это не сработало - возможно, это говорит о том, что это пустая трата времени. Я попытаюсь придумать пример, но сейчас у меня нет такого мнения. Спасибо за вопросы; они полезны. –

1

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

..

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

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

+0

Меньшая точка: я считаю слишком сложным Упорядочить больше дизайнерского запаха, чем запах кода - иногда дизайн таков, что только сложный Arrange позволит вам протестировать устройство. Я упоминаю об этом, потому что эта ситуация требует более глубокого исправления, чем простой запах кода. –

25

Его также можно указать как Упорядочить- Предположите -Act-Assert.

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

+1

Ницца! Мне нравится четвертый - и другой - и точный - «А». Благодаря! –

+0

+1, @Ole! Мне тоже нравится это для некоторых особых случаев! Я попробую! –

1

В общем, мне нравится «Arrange, Закон, Утверждают» очень много, и использовать его в качестве моего личного стандарта. Однако одна вещь, которую мне не напомнила мне, заключается в том, чтобы разобрать то, что я организовал, когда делаются утверждения. В большинстве случаев это не вызывает большого раздражения, так как большинство вещей автоматически отмахиваются через сбор мусора и т. Д. Однако, если вы установили соединения с внешними ресурсами, вам, вероятно, захочется закрыть эти соединения, когда вы закончите с вашими утверждениями, или у вас у многих есть сервер или дорогостоящий ресурс, где-то где-то держаться за соединения или жизненно важные ресурсы, которые он должен отдать кому-то другому. Это особенно важно, если вы one of those developers who does not use TearDown or TestFixtureTearDown для очистки после одного или нескольких тестов. Конечно, «Arrange, Act, Assert» не несет ответственности за то, что я не смог закрыть то, что открываю; Я упоминаю только этот «gotcha», потому что я еще не нашел хорошего синонима «A-word» для «распоряжаться», чтобы рекомендовать! Какие-либо предложения?

+0

Annul?Не совсем верно, но близко. –

+1

@ carlmanaster, вы на самом деле достаточно близко для меня! Я придерживаюсь этого в своем следующем TestFixture, чтобы попробовать его размер. Это похоже на то маленькое напоминание о том, что твоя мать должна была тебя научить: «Если вы откроете ее, закройте ее! Если вы ее испортите, очистите ее!» Возможно, кто-то еще может улучшить его, но, по крайней мере, он начинается с «a!» Спасибо за ваше предложение! –

+1

@ carlmanaster, я дал «Annul» попробовать. Это лучше, чем «срыв», и это похоже на работу, но я все еще ищу другое слово «А», которое прекрасно вписывается в мою голову так же, как «Упорядочить, действовать, утверждать». Может быть, «Уничтожить ?!» –

1

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

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 

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

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

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

0

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

Arrange 
Assert-Not 
Act 
Assert 

Может быть бессмысленным, потому что, мол, вы знаете, ваш Устройте часть работает правильно, а это значит, что все, что находится в 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)); 
} 
+0

Боюсь, вы не совсем поняли мой вопрос. Первоначальное утверждение не касается тестирования Arrange; это просто обеспечение того, чтобы этот закон заставлял государство утверждать в конце. –

+0

И я хочу сказать, что независимо от того, что вы добавили в часть Assert-Not, уже подразумевается в части Arrange, потому что код в части Arrange тщательно протестирован, и вы уже знаете, что он делает. –

+0

Но я верю, что есть ценность в части Assert-Not, потому что вы говорите: учитывая, что часть Arrange оставляет «мир» в «этом состоянии», тогда мой «Закон» оставит «мир» в этом «новом состоянии» «; и если реализация кода зависит от позиции Arrange, изменений, то тест также сломается. Но опять же, это может быть против DRY, потому что у вас (также есть) есть тесты для любого кода, который вы входите в часть Arrange. –

1

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

4

Испытание Arrange-Assert-Act-Assert всегда может быть переработан в двух тестов:

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

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

0

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

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

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

0

Я использую:

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

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