Це має бути “Arrange-Assert-Act-Assert”?


94

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

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

Чи дотримуються ваші тести за цією схемою? Чому чи чому б ні?

Оновлення Пояснення: початкове твердження по суті є протилежним до кінцевого твердження. Це не твердження, що Arrange працював; це твердження, що Закон ще не працював.

Відповіді:


121

Це не найпоширеніша справа, але все ж досить поширена, щоб мати своє ім’я. Ця методика називається гвардійським твердженням . Докладний опис цього опису ви знайдете на сторінці 490 у чудовій книзі xUnit Test Patterns Gerard Meszaros (настійно рекомендується).

Як правило, я сам не використовую цей зразок, оскільки вважаю правильнішим написати специфічний тест, який підтверджує будь-яку передумову, яку я потребую забезпечити. Такий тест повинен завжди бути невдалим, якщо попередня умова виходить з ладу, і це означає, що мені не потрібно, щоб він був вбудований у всі інші тести. Це дає кращу ізоляцію проблем, оскільки один тестовий випадок підтверджує лише одне.

Для даного тестового випадку може бути виконано багато передумов, тому вам може знадобитися більше ніж одне твердження про охорону. Замість того, щоб повторювати їх у всіх тестах, наявність одного (і лише одного) тесту для кожної попередньої умови робить ваш тест тестом більш зручним для управління, оскільки у вас буде менше повторень таким чином.


+1, дуже хороша відповідь. Остання частина особливо важлива, оскільки вона показує, що ви можете охороняти речі як окремий одиничний тест.
murrekatt

3
Як правило, я теж робив це таким чином, але існує проблема з окремим тестом для забезпечення передумов (особливо з великою базою кодів із мінливими вимогами) - тест попередніх умов буде модифікований з часом і вийде з синхронізації з "основним" тест, який передбачає ці передумови. Таким чином, передумови можуть бути все чудовими та зеленими, але ці передумови не виконуються в основному тесті, який тепер завжди показує зелене та добре. Але якби передумови були в головному тесті, вони були б невдалими. Ви стикалися з цією проблемою і натрапили на приємне її рішення?
nchaud

2
Якщо ви сильно змінюєте тести, у вас можуть виникнути інші проблеми , оскільки це, як правило, зробить ваші тести менш надійними. Навіть за умови зміни вимог розгляньте можливість проектування коду в додатку .
Марк Семанн

@MarkSeemann Ви маєте рацію, ми повинні звести до мінімуму повторення, але з іншого боку може бути багато речей, які можуть вплинути на Arrange для конкретного тесту, хоча тест для Arrange сам пройшов би. Наприклад, очищення для тесту Arrange або після іншого тесту було несправним, і Arrange не буде таким, як у тесті Arrange.
Рекшино

32

Він також може бути визначений як Arrange- Assume -Act-Assert.

Для цього в NUnit є технічна обробка, як у прикладі тут: http://nunit.org/index.php?p=theory&r=2.5.7


1
Приємно! Мені подобається четверте - і різне - і точне - "А". Дякую!
Карл Манастер

+1, @Ole! Мені це подобається і для певних особливих випадків! Я спробую!
Джон Тоблер

8

Ось приклад.

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. І оскільки охоплення є одиницею, яку ми перевіряємо, я думаю, це має деяке (невелике) значення.

Я приймаю власну відповідь; багато інших неправильно розтлумачили моє запитання про тестування налаштування. Я думаю, це дещо інакше.


Дякую, що повернувся з прикладом, Карл. Ну, у червоній частині циклу TDD, поки encompass () дійсно щось не робить; перше твердження безглуздо, це лише дублювання другого. У зеленому кольорі він починає бути корисним. Це набуває сенсу під час рефакторингу. Було б непогано мати рамку UT, яка робить це автоматично.
благодійник

Припустимо, ви отримаєте TDD цього класу Range, чи не відбудеться чергове невдале тестування тестування ctor діапазону, коли ви його порушите?
благодійник

1
@philippe: Я не впевнений, що розумію питання. Конструктор діапазону та включає () мають власні одиничні тести. Не могли б ви детальніше розглянути?
Карл Манастер

Для першого твердження assrtFalse (range.includes (7)) не вдалося, вам потрібно мати дефект у конструкторі діапазону. Тож я хотів запитати, чи тести конструктора Range не будуть порушуватися одночасно з цим твердженням. А як щодо твердження після Закону щодо іншого значення: наприклад, assertFalse (range.includes (6))?
благодійник

1
Побудова діапазону, на мій погляд, постає перед такими функціями, як include (). Тому, хоча я погоджуюся, лише несправний конструктор (або несправний включає ()) призведе до того, що перше твердження вийде з ладу, тест конструктора не буде включати виклик include (). Так, усі функції до першого твердження вже перевірені. Але це початкове негативне твердження - це щось донести, і, на мій погляд, щось корисне. Навіть якщо кожне таке твердження проходить, коли воно спочатку написане.
Карл Манастер

7

Arrange-Assert-Act-AssertТест завжди може бути перероблений в двох тестів:

1. Arrange-Assert

і

2. Arrange-Act-Assert

Перший тест буде стверджувати лише про те, що було встановлено у фазі "Впорядкувати", а друге випробування буде затверджуватися лише для того, що сталося на фазі "Дія".

Це має перевагу в тому, щоб дати точніші відгуки про те, що це фаза "Упорядкувати" або "Акт", яка не вдалася, тоді як в оригіналі Arrange-Assert-Act-Assertвони пов'язані, і вам доведеться копати глибше і вивчити, яке саме твердження не вдалося і чому воно не вдалося, щоб знати, чи це не вдалося домовитись чи дію.

Це також задовольняє намір краще перевірити одиницю, оскільки ви розділяєте свій тест на менші незалежні одиниці.

Нарешті, майте на увазі, що кожного разу, коли ви побачите подібні розділи «Впорядкувати» в іншому тесті, вам слід спробувати перетягнути їх на спільні допоміжні методи, щоб ваші тести були більш сухими та більш рентабельними в майбутньому.


3

Я зараз цим займаюся. AAAA іншого виду

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 for theserser, я закликаю ASSEMBLE для затвердження. Це робиться для того, щоб не плутати розділ ARRANGE

ASSERT повинен містити лише твердження. Це залишає ASSEMBLE між ACT і ASSERT, який встановлює ствердження.

Нарешті, якщо ви не працюєте в «Упорядкувати», ваші тести є невірними, тому що ви повинні мати інші тести для запобігання / пошуку цих тривіальних помилок. Тому що для сценарію, який я наявний, вже повинні існувати інші тести, які перевіряють ПРОЧИТАННЯ та СТВОРЕННЯ. Якщо ви створите "Заява про охорону", ви можете зламати DRY та створити технічне обслуговування.


1

Закидання твердження "перевірки здоровості" для перевірки стану перед виконанням дії, яку ви тестуєте, - стара технологія. Зазвичай я записую їх як тестові ліси, щоб довести собі, що тест робить те, що я очікую, і видаляю їх пізніше, щоб уникнути захаращення тестів тестовими риштуваннями. Іноді залишення лісів допомагає тесту слугувати розповіддю.


1

Я вже читав про цю техніку - можливо, у вас, до речі, - але я не використовую її; здебільшого тому, що я звик до потрійної форми А для моїх тестових одиниць.

Тепер мені стає цікаво, і у вас є питання: як ви пишете тест, чи спричиняєте ви це твердження невдалим після циклу червоно-зелений-червоний-зелений-рефактор чи додаєте його згодом?

Чи не вдається ви інколи, можливо, після рефактора коду? Що це вам говорить? Можливо, ви могли б поділитися прикладом, коли це допомогло. Дякую.


Я, як правило, не змушую початкового твердження провалюватися - адже він не повинен провалюватися, як це має твердження TDD, перш ніж його метод буде написаний. Я ж пишу це, коли я пишу це, перед тим , як раз в процесі написання тесту, а не пізніш. Чесно кажучи, я не можу пригадати, щоб він провалився - можливо, це говорить про те, що це марна трата часу. Я спробую придумати приклад, але я цього не маю на увазі. Дякую за запитання; вони корисні.
Карл Манастер

1

Я робив це раніше, коли досліджував тест, який не вдався.

Після значного подряпин голови я визначив, що причиною стали методи, названі під час "Упорядкувати", які працюють не правильно. Невдача тесту вводила в оману. Я додав Assert після домовленості. Це зробило тест невдалим у місці, яке висвітлило актуальну проблему.

Я думаю, що тут є і запах коду, якщо частина тесту "Упорядкувати" занадто довга і складна.


Незначний момент: я б вважав надто складним Arrange більше дизайнерського запаху, ніж запаху коду - іноді дизайн такий, що лише складний Arrange дозволить вам протестувати пристрій. Я згадую це, тому що така ситуація вимагає глибшого виправлення, ніж простий запах коду.
Карл Манастер

1

Взагалі мені дуже подобається "Впорядкувати, діяти, потверджувати" і використовую це як мій особистий стандарт. Однак одне, що мені не нагадує зробити, - це не впорядковувати те, що я домовлявся, коли зроблено твердження. У більшості випадків це не викликає особливого роздратування, оскільки більшість речей автоматично магічно проходять через збирання сміття і т. Д. Якщо ви встановили зв’язок із зовнішніми ресурсами, можливо, ви, ймовірно, захочете закрити ці зв’язки, коли закінчите. з вашими твердженнями, або у вас є десь сервер або дорогий ресурс, який тримає зв’язки або життєво важливі ресурси, які він повинен мати можливість передати комусь іншому. Це особливо важливо, якщо ви один з тих розробників, які не використовують TearDown або TestFixtureTearDownочистити після одного або декількох тестів. Звичайно, "Упорядкувати, діяти, стверджувати" не несе відповідальності за мою неспроможність закрити те, що я відкриваю; Я згадую лише про цю "готчу", оскільки я ще не знайшов хорошого синоніма "A-word" для "розпоряджатися", щоб рекомендувати! Будь-які пропозиції?


1
@carlmanaster, ти насправді досить близький для мене! Я дотримуюся цього у своєму наступному TestFixture, щоб спробувати його за розміром. Це як маленьке нагадування робити те, чого повинна вас навчила ваша мати: "Якщо ви відкриєте це, закрийте його! Якщо ви зіпсуєте це, почистіть!" Можливо, хтось інший може покращити це, але принаймні це починається з "а!" Дякуємо за Вашу пропозицію!
Джон Тоблер

1
@carlmanaster, я спробував "Annul" спробувати. Це краще, ніж "сходити", і це працює, але я все одно шукаю ще одне слово "А", яке впирається в мою голову так само досконало, як "Упорядкувати, діяти, підтвердити" Можливо, "знищити ?!"
Джон Тоблер

1
Отже, у мене є "Упорядкувати, припустити, діяти, затвердити, знищити". Хммм! Я надмірно ускладнюю речі, так? Можливо, я б краще просто відключити і повернутися до "Влаштовуй, дію та підтверджуй!"
Джон Тоблер

1
Може використовувати R для скидання? Я знаю, що це не A, але це звучить як піратська приказка: Ааарх! та Скинути рими за допомогою Assert: o
Marcel Valdez Orozco

1

Подивіться на запис Вікіпедії про дизайн за контрактом . Свята трійця «Впорядкуй-Дій-Стверджуй» - це спроба закодувати деякі з тих самих понять і полягає у доведенні правильності програми. Зі статті:

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 є корисним нагадуванням про необхідні мінімальні кроки, але не повинен перешкоджати нікому створювати додаткові кроки.


0

Залежить від вашого тестового середовища / мови, але зазвичай, якщо щось у частині "Упорядкувати" не вдається, викид викидається, і тест не відображає його замість запуску частини Act. Так ні, я зазвичай не використовую другу частину Assert.

Крім того, якщо ваша Arrange частина є досить складною і не завжди є винятком, ви можете подумати, як загорнути її в якийсь метод і написати власний тест для цього, так що ви можете бути впевнені, що він не вийде з ладу (без кидання винятку).


0

Я не використовую цей зразок, тому що думаю, роблячи щось на кшталт:

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-Not, вже мається на увазі в частині Arrange, оскільки код у частині Arrange ретельно перевірено, і ви вже знаєте, що вона робить.
Marcel Valdez Orozco

Але я вважаю, що в частині Assert-Not є цінність, тому що ви говорите: Враховуючи, що частина Arrange залишає "світ" у "цьому стані", тоді мій "акт" залишить "світ" у цьому "новому стані" ; і якщо реалізація коду, від якого буде впорядкована частина, зміниться, тест теж порушиться. Але знову ж таки, це може бути проти DRY, оскільки ви (повинні) також маєте тести на будь-який код, який ви залежите в частині Упорядкувати.
Марсель Вальдес Ороско

Можливо, у проектах, де кілька команд (або велика команда) працюють над одним проектом, такий пункт був би дуже корисним, інакше я вважаю його непотрібним і зайвим.
Marcel Valdez Orozco

Ймовірно, такий пункт було б кращим у тестах інтеграції, системних тестах або тестах прийняття, де частина "Упорядкувати", як правило, залежить від кількох компонентів, і існує більше факторів, які можуть спричинити несподівані зміни початкового стану "світу". Але я не бачу місця для цього в тестах Unit.
Marcel Valdez Orozco

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

Тому що в іншому випадку вам не вистачає стільки можливостей для помилок ... наприклад, після обшивки, діапазон включає лише 7 і т. Д. ... Існують також тести на довжину діапазону (щоб переконатися, що він також не включав випадкове значення), і ще один набір тестів, цілком для спроб охопити 5 у діапазоні ... що ми очікуємо - виняток у охопленні, або діапазон не зміниться?

У будь-якому разі, справа в тому, якщо в акті є якісь припущення, які ви хочете перевірити, поставте їх у власний тест, так?


0

Я використовую:

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

Тому що чисте налаштування дуже важливо.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.