Чому я хочу поговорити про тестування? В моїй попередній команді було 6 розробників і 1 тестувальник. При цьому time to market у нового функціонала був маленький.
Зараз я змінив команду. В ній 3 розробника і 3 тестувальника. На перший погляд, час реалізації нового функціоналу значно більший. Здається, - це через відсутнійсть правильної тест стратегії і подходів. Тому хочу поділитись інструментами, які допомагають писати тести швидше і якісніше.
Почнемо від Unit тестів і до E2E, від всім знайомих інструментів до тих, які ви, ймовірно, не знали.
Unit test
-
На цьому рівні тестуємо всю бізнес логіку проекту. Всі валідації, конвертації, виклики функцій і результати їх роботи.
-
Що не тестуємо? Будь-яку взаємодію з базою, чергою, стороніми сервісами.
Dependencies
-
Як не тестувати залежності? Всі залежності на бази, сервіси, черги повинні передаватися до модулю ззовні. Взаємодія з ними здійснюється через інтерфейси. Підмінити реалізацію інтерфейсу можна за допомогою бібліотек Moq або NSubstitute.
Ці бібліотеки надають можливість повернути конкретне значення виклику функції. Або перевірити з якими аргументами і скільки разів викликалась функція. -
А у функціональних мовах? Всі залежності функції мають передаватись до неї у вигляді аргументів. Тип такого фргументу - функція. Тож під час тестування можна передати свою тестову функцію яка повртає конкретне значення.
Test Scenario
-
Скільки сценаріїв може тестувати тест? Один. Притримуємось підходу AAA. Arrange - підготували дані для тесту. Act - виконали дію. Assert - перевірили результат. Як тільки хочеться ще далі написати Act, Assert - зупитяємо себе, та пишемо наступний тест.
-
Допоміжний інструмент для цього правила - ім`я тесту. По імені повинно бути зрозуміло що він перевіряє. Як тільки тест називається абстрактно filter should work correct - це сигнал, що в тесті більше одного бізнес правила, або немає явного сценарію перевірки.
Test Data
-
В тесті повинні заповнюватись тільки ті дані, що безпосередньо впливають на його результат. Якщо потрібно лише одне значення для одного поля, а всі інші дані не важливі - то іх заповнення можна опустити. Як це зробити?
-
AutoFixture - бібліотека, яка вміє створювати складні обєкти і наповнювати їх даними. Дані можуть бути випадкові або згенеровані по заданим правилам.
У бібліотеки є інтеграції з усіма популярними тест фреймворками (XUnit, NUnit, MSTest). Після підключення достатньо позначити тест атрибутом AutoData, і додати параметр до тестової функції. Бібліотека автоматично створить обєкт і передасть його в якості аргументу до тесту. Далі достатньо змінити тільки окремі дані, що впливають на результат.
[Theory, AutoData] void should_not_authorize_underage(Person person)
-
Імутабельні структури даних. Якщо це C# - то використовуємо records (в F#, Scala є щось подібне). Створюємо обєкти з даними за замовчуванням, а в тесті змінюємо тільки поля, які впливають на результат:
var underage = _defaultPerson with { Age = 17};
Integration test
-
На цьому етапі тестуємо… інтеграцію. Інтеграцію модулів між собою, реєстацію залежностей, потік даних. В деяких випадках інтеграцію з базами, чергами.
-
Важливо, що на цьому етапі вже має бути протестована основна бізнес логіка і інтеграційні тести не повинні її дублювати. Дуже часто на Integration рівні достатньо пари тестів.
-
Важливим інструментом на етапі інтеграційного тестування є WebApplicationFactory. Він дозволяє запустити хост веб додатку з тесту. Без PowerShell, Docker-Compose і інших скриптів.
Окрім просто запуску, важливою є можливість доналаштування хосту: зміна environment variables, реєстрація додаткових залежностей, або зміна існуючих. Наприклад у нас є модуль з капчою і щоб можна було якось потестити додаток, то цей модуль можна замінити на mock який завжди повертає позитивний результат. Ну або підмінити модуль по взаємодіє зі сороннім банківським сервісом.
E2E
-
Тут вже все по-дорослому. Треба запускати всі сервіси, бази, черги. Оскільки на попередніх етапах ми вже перевірили всю бізнес логіку, взаємодію між модулями, між компонентами, то залишається зовсім мало: конфігурації, потік даних… ну може ще щось.
-
Важливо, щоб тести запускались однією командою (dotnet test) або кнопкою в вашій IDE. Без додаткових налаштувань і конфігурацій, бо ніхто не буде читати мануали. Інструмент який в цьому допоможе - Testcontainers або FluentDocker. Це бібліотеки, що дають можливість управляти Docker контейнерами прямо з тесту.
-
Перед початком підняли всі залежності у вигляді окремих Docker контейнерів, або docker-compose. Прогнали E2E тести. Вимкнули контейнери.
Comments