Що робить хороший одиничний тест? [зачинено]


97

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

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

Мовні агностичні пропозиції заохочуються.

Відповіді:


93

Почніть з підключення джерел - Прагматичне тестування модулів на Java з JUnit (Є версія і з C # -Nunit .. але у мене є ця .. її агностик здебільшого. Рекомендується.)

Хороші тести повинні бути ПОЇЗДКОЮ (The acronymn НЕ липкі досить - у мене є роздруківка Cheatsheet в книзі , яку я повинен був витягнути , щоб переконатися , що я отримав це право ..)

  • Автоматично : виклик тестів, а також перевірка результатів PASS / FAIL повинні бути автоматичними
  • Ретельне : Покриття; Хоча помилки схильні кластеризуватися навколо певних регіонів у коді, переконайтесь, що ви протестуєте всі ключові шляхи та сценарії. Використовуйте інструменти, якщо ви повинні знати неперевірені регіони
  • Повторюваність : Тести повинні давати однакові результати кожен раз. Тести не повинні покладатися на некеровані парами.
  • Незалежний : Дуже важливо.
    • Тести повинні перевіряти лише одне . Кілька тверджень добре, якщо всі вони перевіряють одну особливість / поведінку. Якщо тест не вдається, він повинен точно визначити місце проблеми.
    • Тести не повинні покладатися один на одного - Ізольовані. Ніяких припущень щодо порядку виконання тесту. Переконайтесь, що "чистий аркуш" перед кожним випробуванням, використовуючи налаштування / відрив належним чином
  • Професіонал : у перспективі у вас буде стільки тестового коду, скільки у виробництві (якщо не більше), тому дотримуйтесь того самого стандарту гарного дизайну для вашого тестового коду. Добре обгрунтовані методи-класи з виявленням намірів імен, без дублювання, тести з хорошими іменами тощо.

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

Оновлення 2010-08:

  • Читання : Це можна вважати частиною Професіонала, однак це не може бути підкреслено достатньо. Тест на кислотність мав би знайти когось, хто не є членом вашої команди, і попросити його / її з’ясувати тестування поведінки протягом декількох хвилин. Тести потрібно підтримувати так само, як і виробничий код - тому полегшуйте його читання, навіть якщо це потребує більше зусиль. Тести повинні бути симетричними (слідувати шаблону) та стислими (перевіряти одну поведінку за раз). Використовуйте послідовну конвенцію іменування (наприклад, стиль TestDox). Уникайте захаращувати тест "випадковими деталями" .. станьте мінімалізмом.

Крім них, більшість інших є рекомендаціями, які скорочують роботу з низькою вигодою: наприклад, "Не тестуйте код, яким ви не володієте" (наприклад, сторонні DLL-файли). Не займайтеся тестуванням геттерів та сетерів. Слідкуйте за співвідношенням витрат та вигод або ймовірністю дефектів.


Ми можемо не погодитися щодо використання Mocks, але це було дуже приємним складанням найкращих методів тестування одиниць.
Джастін Стандарт

Я відповім на це як відповідь, тому що вважаю акронім "A TRIP" корисним.
Спойк

3
Я згодний здебільшого, але хотів би зазначити, що тестування коду, яким ви не володієте, є користь ... Ви тестуєте, що він відповідає вашим вимогам. Як ще можна бути впевненим, що оновлення не зламає ваші системи? (Але, звичайно, майте на увазі співвідношення витрат та вигод.)
Розчарований

@Craig - Я вважаю, що ви маєте на увазі регресійні тести (рівень інтерфейсу) (або тести для учнів у деяких випадках), які поведінку документа, від яких ви залежите. Я б не писав "одиничні" тести для стороннього коду, оскільки продавець знає про цей код більше, ніж я b. Постачальник не зобов'язаний зберігати будь-яку конкретну реалізацію. Я не контролюю зміни в цій кодовій базі і не хочу витрачати свій час на виправлення зламаних тестів за допомогою оновлення. Тож я б скоріше
зашифрував

@Gishu: Так, абсолютно! Тести повинні виконуватися лише на рівні інтерфейсу; і насправді вам слід максимум перевірити функції, якими ви насправді користуєтесь. Крім того, вибираючи, з чим писати ці тести; Я виявив, що прості рамки тестування прямого передбачення зазвичай підходять до рахунку.
Розчарований

42
  1. Не пишіть великі тести. Як підказує «одиниця» в «тестовій одиниці», зробіть кожен якнайбільше атомним та ізольованим . Якщо потрібно, створіть передумови за допомогою макетних об'єктів, а не відтворюйте занадто багато типового середовища користувача вручну.
  2. Не перевіряйте речі, які, очевидно, працюють. Уникайте тестування класів від сторонніх постачальників, особливо тих, що постачають основні API рамки, в яку ви кодуєте. Наприклад, не тестуйте додавання елемента до класу Hashtable постачальника.
  3. Спробуйте скористатися інструментом покриття коду, таким як NCover, щоб допомогти виявити кращі випадки, які ви ще перевіряли.
  4. Спробуйте написати тест перед реалізацією. Розгляньте тест як більшу специфікацію, якої буде дотримуватися ваша реалізація. Ср. також поведінка, орієнтована на поведінку, більш специфічну галузь тестового розвитку.
  5. Будьте послідовними. Якщо ви пишете тести лише для деяких кодів, це навряд чи корисно. Якщо ви працюєте в команді, а деякі або всі інші не пишуть тестів, це теж не дуже корисно. Переконайте себе і всіх інших у важливості (і економії часу ) властивостей тестування, або не турбуйтеся.

1
Хороша відповідь. Але це не так вже й погано, якщо ви не підкажете тест на все, що відповідає. Зрозуміло, що це краще, але потрібно бути балансом і прагматизмом. Re: отримання колег на борту; іноді просто потрібно це зробити, щоб продемонструвати цінність і як орієнтир.
Мартін Кларк

1
Я згоден. Однак у перспективі потрібно мати можливість розраховувати на тести, які там є, тобто вміти припускати, що загальні підводні камені будуть спіймані ними. В іншому випадку переваги масово зменшуються.
Sören Kuklau

2
"Якщо ви пишете тести лише для свого коду, це навряд чи корисно." Це справді так? У мене є проекти з покриттям коду на 20% (критично важливі / схильні до виходу з ладу ділянок), і вони допомогли мені масово, і проекти також прекрасні.
д-р. зло

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

41

Більшість відповідей тут, мабуть, стосуються загальної практики тестування одиниць взагалі (коли, де, чому і що), а не написання самих тестів (як). Оскільки питання здавалося досить специфічним щодо частини "як", я подумав, що опублікую це, взяте з презентації "коричневої сумки", яку я провів у своїй компанії.

5 законів написання тестів Womp:


1. Використовуйте довгі описові назви методів тестування.

   - Map_DefaultConstructorShouldCreateEmptyGisMap()
   - ShouldAlwaysDelegateXMLCorrectlyToTheCustomHandlers()
   - Dog_Object_Should_Eat_Homework_Object_When_Hungry()

2. Напишіть свої тести у стилі « Упорядкувати / Діяти» .

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

3. Завжди надайте повідомлення про помилку зі своїми програмами.

Assert.That(x == 2 && y == 2, "An incorrect number of begin/end element 
processing events was raised by the XElementSerializer");
  • Проста, але корисна практика, яка дає зрозуміти, що у вашій програмі бігуна не вдалося. Якщо ви не надаєте повідомлення, у висновку про помилку, як правило, ви отримаєте щось на кшталт "Очікуване, що було помилково", що змушує вас насправді прочитати тест, щоб з’ясувати, що не так.

4. Прокоментуйте причину тесту - яке ділове припущення?

  /// A layer cannot be constructed with a null gisLayer, as every function 
  /// in the Layer class assumes that a valid gisLayer is present.
  [Test]
  public void ShouldNotAllowConstructionWithANullGisLayer()
  {
  }
  • Це може здатися очевидним, але така практика захистить цілісність ваших тестів від людей, які в першу чергу не розуміють причину тесту. Я бачив, що багато тестів видаляються або модифікуються, що було чудово, просто тому, що людина не розуміла припущень, які перевіряє тест.
  • Якщо тест є тривіальним або назва методу є достатньо описовою, це може бути дозволено залишити коментар вимкненим.

5. Кожен тест повинен завжди повертати стан будь-якого ресурсу, який він торкається

  • Використовуйте глузування, де це можливо, щоб не мати справу з реальними ресурсами.
  • Очищення потрібно проводити на рівні тесту. Тести не повинні залежати від порядку виконання.

2
+1, оскільки пункти 1, 2 і 5 є важливими. 3 та 4 здаються досить надмірними для одиничних тестів, якщо ви вже використовуєте описові назви методів тестування, але я рекомендую документацію тестів, якщо вони мають великий обсяг (функціональне або приймальне тестування).
Спайк

+1 за земні та практичні знання та приклади
Філ

17

Пам'ятайте про ці цілі (адаптовано з книги xUnit Test Patterns від Meszaros)

  • Тести повинні зменшити ризик, а не вводити його.
  • Тести повинні легко проводитись.
  • Тести повинні бути простими в обслуговуванні, оскільки система розвивається навколо них

Деякі речі для полегшення цього:

  • Тести повинні провалюватися лише з однієї причини.
  • Тести повинні перевірити лише одне
  • Звести до мінімуму тестові залежності (відсутність баз даних, файлів, інтерфейсу тощо)

Не забувайте, що ви можете робити тестування на інтеграцію і з вашою рамкою xUnit, але тримайте інтеграційні тести та одиничні тести окремо


Напевно, ви мали на увазі, що ви адаптувались із книги "xUnit Test Patterns" Джерарда Месароса. xunitpatterns.com
Спойк

Так, ти маєш рацію. Я
зрозумію,

Відмінні бали. Експериментальні тести можуть бути дуже корисними, але дуже важливо уникнути потрапляння в пастку складних взаємозалежних тестів, які створюють величезний податок за будь-які спроби змінити систему.
Клин

9

Тести повинні бути ізольованими. Один тест не повинен залежати від іншого. Крім того, тест не повинен покладатися на зовнішні системи. Іншими словами, протестуйте свій код, а не код, від якого залежить ваш код. Ви можете протестувати ці взаємодії як частину ваших інтеграційних або функціональних тестів.


9

Деякі властивості чудових одиничних тестів:

  • Якщо тест не вдається, слід відразу зрозуміти, в чому проблема. Якщо вам доведеться використовувати налагоджувач для відстеження проблеми, то ваші тести недостатньо деталізовані. Тут допомагає рівно одне твердження за тест.

  • Коли ви рефактор, жоден тест не повинен провалюватися.

  • Тести повинні працювати так швидко, що ви ніколи не соромитесь їх виконувати.

  • Усі тести повинні проходити завжди; відсутність недетермінованих результатів.

  • Тести одиниць повинні бути добре враховані, як і ваш виробничий код.

@Alotor: Якщо ви пропонуєте, що бібліотека повинна мати лише тести одиниць у своєму зовнішньому API, я не згоден. Я хочу одиничні тести для кожного класу, включаючи класи, які я не піддаю зовнішнім абонентам. (Однак, якщо я відчуваю необхідність писати тести для приватних методів, тоді мені потрібно переробити. )


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

Я не приймаю такого підходу. Натомість я використовую тестові пристосування за сценарієм . Ось приблизний приклад:

[TestFixture]
public class StackTests
{
    [TestFixture]
    public class EmptyTests
    {
        Stack<int> _stack;

        [TestSetup]
        public void TestSetup()
        {
            _stack = new Stack<int>();
        }

        [TestMethod]
        [ExpectedException (typeof(Exception))]
        public void PopFails()
        {
            _stack.Pop();
        }

        [TestMethod]
        public void IsEmpty()
        {
            Assert(_stack.IsEmpty());
        }
    }

    [TestFixture]
    public class PushedOneTests
    {
        Stack<int> _stack;

        [TestSetup]
        public void TestSetup()
        {
            _stack = new Stack<int>();
            _stack.Push(7);
        }

        // Tests for one item on the stack...
    }
}

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

Я думаю, ми погоджуємось, що DRY застосовується до одиничних тестів. Як я вже говорив, "Одиничні тести повинні бути добре враховані". Однак існує кілька способів вирішення дублювання. Один, як ви вже згадували, - це тест на одиницю, який спочатку викликає тестований код, а потім стверджує кілька разів. Альтернативою є створення нового "тестового кріплення" для сценарію, який викликає тестовий код під час кроку ініціалізації / налаштування, а потім має ряд тестів, які просто стверджують.
Джей Базузі

Моє правило: якщо ви використовуєте copy-paste, ви робите щось не так. Одна з моїх улюблених приказок - «Копіювати-вставляти - це не шаблон дизайну». Я також погоджуюсь, що одне твердження за одиницю тесту - це загальнодобрана ідея, але я не завжди наполягаю на цьому. Мені подобається більш загальне "тестування однієї речі на одиницю тесту". Хоча це зазвичай перекладається на одне твердження за одиницю тесту.
Джон Тернер

7

Те, що ви хочете, - це визначення поведінки тестуваного класу.

  1. Перевірка очікуваної поведінки.
  2. Перевірка випадків помилок.
  3. Покриття всіх кодових шляхів у класі.
  4. Виконання всіх функцій учасників у класі.

Основний намір - підвищити вашу впевненість у поведінці класу.

Це особливо корисно при перегляді рефакторингу коду. У Мартіна Фаулера є цікава стаття щодо тестування на його веб-сайті.

HTH.

ура,

Роб


Роб - механічний, це добре, але він упускає наміри. Чому ти все це робив? Мислення таким чином може допомогти іншим пройти шлях TDD.
Марк Левісон

7

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


@Rismo Не є ексклюзивним. За визначенням Quarrelsome написав тут виключно методологію "Test First", яка є частиною TDD. TDD також враховує рефакторинг. Найбільш визначення "розумних штанів", яке я прочитав, - це те, що TDD = Test First + Refactor.
Спойк

Так, це не повинно бути TDD, просто переконайтеся, що ваш тест спочатку закінчується. Потім дріт в решті потім. Найчастіше це відбувається, коли ви робите TDD, але ви можете застосовувати його і тоді, коли не використовуєте TDD.
Дивовижний

6

Мені подобається правильна абревіатура BICEP з вищезгаданої книги тестування прагматичних одиниць :

  • Правильно : чи правильні результати ?
  • Б : Чи правильні всі умови б) ?
  • I : Чи можемо ми перевірити i nverse відносини?
  • C : Чи можна Ĉ результати рос-перевірки з використанням інших засобів?
  • E : Чи можемо ми змусити відбутися умови електронного жаху?
  • P : Є р найкращих показників характеристики в межах?

Особисто я відчуваю, що можна дістатися досить далеко, перевіривши, чи отримаєте правильні результати (1 + 1 повинен повернути 2 у функції додавання), спробувавши всі граничні умови, які ви можете придумати (наприклад, використовуючи два числа, сума яких є більшим за ціле максимальне значення у функції додавання) та вимушує умови помилок, такі як збої в мережі.


6

Хороші тести повинні бути ретельними.

Я не зовсім зрозумів, як це зробити у складних умовах.

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

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

Хороша архітектура може контролювати деякий вибух взаємодії, але неминуче, коли системи стають складнішими, автоматична система тестування зростає разом із нею.

Тут ви починаєте стикатися з компромісами:

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

Вам також потрібно вирішити:

де ви зберігаєте тестові приклади у своїй кодовій базі?

  • як ви документуєте свої тестові справи?
  • чи можна повторно використовувати тестові пристосування для економії обслуговування тестових випадків?
  • що відбувається, коли нічне виконання тестового випадку не вдається? Хто робить тріаду?
  • Як ви підтримуєте макетні об’єкти? Якщо у вас є 20 модулів, які використовують власний аромат API макетування, швидко змінюючи брижі API. Змінюються не лише тестові випадки, але змінюються і 20 макетних об'єктів. Ці 20 модулів були написані протягом декількох років багатьма різними командами. Це класична проблема повторного використання.
  • люди та їх команди розуміють цінність автоматизованих тестів, їм просто не подобається, як це робить інша команда. :-)

Я міг би продовжуватись назавжди, але моя думка полягає в тому, що:

Тести повинні бути ретельними.


5

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

Я визначаю "хороші" одиничні тести, якщо вони мають такі три властивості:

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

Рой, я від усієї думки згоден. Ці речі є набагато важливішими, ніж покриття крайових справ.
Метт Гінзе

довірливий - відмінний момент!
ratkok

4
  • Тестування блоку просто тестує зовнішній API вашого блоку, ви не повинні перевіряти внутрішню поведінку.
  • Кожен тест TestCase повинен перевірити один (і лише один) метод всередині цього API.
    • Додаткові випробувальні випадки повинні бути включені у випадках відмов.
  • Перевірте покриття ваших тестів: Після тестування одиниці повинні були виконати 100% рядків всередині цього пристрою.

2

Джей Філдс має багато корисних порад щодо написання одиничних тестів, і є посада, де він підсумовує найважливіші поради . Там ви прочитаєте, що слід критично замислюватися над своїм контекстом і судити, чи поради вам варті. Тут ви отримуєте безліч дивовижних відповідей, але саме ви вирішуєте, що найкраще підходить для вашого контексту. Спробуйте їх і просто переробляйте, якщо вам це погано пахне.

З повагою


1

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


1

Я другий відповідь "ДОПОМОГА", за винятком того, що тести ДОЛЖНІ покладатися один на одного !!!

Чому?

СУХА - Не повторюйтесь - стосується і тестування! Залежності тесту можуть допомогти 1) заощадити час налаштування, 2) заощадити ресурси кріплення та 3) визначити неполадки. Звичайно, тільки враховуючи, що ваша тестова основа підтримує першокласні залежності. Інакше, зізнаюся, вони погані.

Слідкуйте за http://www.iam.unibe.ch/~scg/Research/JExample/


Я згоден з вами. TestNG - це ще одна основа, в якій залежність дозволена легко.
Девіде

0

Часто одиничні тести засновані на макетних даних або макетних даних. Мені подобається писати три типи одиничних тестів:

  • "тимчасові" одиничні тести: вони створюють власні макетні об'єкти / дані та перевіряють свою функцію за допомогою них, але знищують усе і не залишають слідів (як немає даних у тестовій базі даних)
  • "стійкий" модульний тест: вони тестують функції у вашому коді, створюючи об'єкти / дані, які згодом знадобляться для вдосконаленої функції для їх власного тестування одиниць (уникаючи того, щоб ця розширена функція відтворювала кожен раз свій власний набір макетних об'єктів / даних)
  • "стійкі на основі" одиничні тести: одиничні тести з використанням макетних об'єктів / даних, які вже є (тому що створені в іншому сеансі тестування одиниці) стійкими тестовими одиницями.

Сенс у тому, щоб уникнути повторного відтворення всього , щоб можна було протестувати всі функції.

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

0

Подумайте про два типи тестування та поводьтеся з ними по-різному - функціональне тестування та тестування продуктивності.

Використовуйте різні входи та показники для кожного. Можливо, вам доведеться використовувати різні програми для кожного типу тесту.


Тоді як щодо тестування одиниць?
Спойк

0

Я використовую послідовну конвенцію іменування тесту, описану стандартами імені Роя Ошерово, одиничні тестові позначення. Кожен метод у заданому класі тестового випадку має такий стиль іменування MethodUnderTest_Scenario_ExpectedResult.

    Перший розділ імені тесту - це назва методу в тестуваній системі.
    Далі йде конкретний сценарій, який тестується.
    Нарешті - результати цього сценарію.

Кожен розділ використовує верхній корпус верблюда і розмежований на нижчий бал.

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

Я також додаю параметри до назви методу, якщо досліджуваний метод був перевантажений.

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