Для чого потрібні одиничні тести для тестування методів сховища?


19

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

Скажімо, я створив такий блок тесту:

[TestMethod]
public void GetOrderByIDTest()
{
   //Uses Moq for dependency for getting order to make sure 
   //ID I set up in 'Arrange' is same one returned to test in 'Assertion'
}

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

Я отримаю такий аргумент:

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

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

Будь-яка допомога з цього питання дуже вдячна, дякую!


2
я кажу, надішліть його прямо на тестування користувачів ... вони все одно змінять вимоги ...
nathan hayfield

+1 за сарказм, щоб час від часу освітлювати речі
atconway

2
Проста відповідь полягає в тому, що кожен метод може мати x кількість кращих випадків (скажімо, ви хочете перевірити позитивні ідентифікатори, ідентифікатор 0 і негативні ідентифікатори). Якщо ви хочете перевірити деяку функціональність, яка використовує цей метод, який має три крайові регістри, вам потрібно буде написати 9 тестових випадків, щоб перевірити кожну комбінацію крайових справ. Виділяючи їх, потрібно лише написати 6. Крім того, тести дають точніше уявлення про те, чому щось зламалося. Можливо, ваш сховище повертає null у випадку відмови, і нульовим винятком є ​​викинуте кілька сотень рядків вниз код.
Роб

Я думаю, ви занадто суворо ставитеся до свого визначення "одиничного" тесту. Що таке "одиниця роботи" для класу сховища?
Калеб

І переконайтеся, що ви це враховуєте: якщо ваш тест на пристрій вилучає все, що ви намагаєтесь протестувати, що ви насправді тестуєте?
Калеб

Відповіді:


20

Тести блоку та тести інтеграції мають різні цілі.

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

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

  1. Будьте складніше писати, загалом,
  2. Будьте більш крихкими через усі необхідні залежності та
  3. Запропонуйте менше покриття коду.

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


Все, що сказано, вам може не знадобитися тестування одиниць для певних методів сховища. Тестування одиниць не слід проводити за тривіальними методами; якщо все, що ви робите, - це передача запиту на об'єкт до коду, згенерованого ORM, і повернення результату, вам не потрібно проводити тест, який у більшості випадків; інтеграційний тест є адекватним.


Так, дякую за швидку відповідь. Я ціную це. Моя єдина критика відповіді - це те, що вона стосується останнього мого абзацу. Моя опозиція все ще почує абстрактні пояснення та визначення, а не деякі глибші міркування. Наприклад, чи могли б ви надати міркування, застосовані до мого випадку використання тестування збереженої процедури / БД стосовно мого коду, і чому одиничні тести все ще цінні стосовно цього конкретного випадку використання?
atconway

1
Якщо SP просто повертає результат із бази даних, тест інтеграції може бути достатнім. Якщо в ньому є нетривіальна логіка, вказуються одиничні тести. Дивіться також stackoverflow.com/questions/1126614 / ... і msdn.microsoft.com/en-us/library/aa833169(v=vs.100).aspx (конкретний SQL Server).
Роберт Харві

12

Я на боці прагматиків. Не тестуйте свій код двічі.

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

  1. Вони можуть замінити одиничні тести при виконанні TDD. Хоча є ще кілька тестових кодів, які слід написати перед тим, як почати з реального коду, він дуже добре працює з підходом червоного / зеленого / рефактора, коли все буде на місці.
  2. Вони перевіряють реальний код сховища - код, що міститься в рядках SQL або командах ORM. Важливіше перевірити правильність запиту, ніж перевірити, чи дійсно ви надіслали якийсь рядок до StatementExecutor.
  3. Вони чудові як регресійні тести. Якщо вони не спрацьовують, це завжди пов'язано з реальною проблемою, як-от зміною схеми, яку не враховували.
  4. Вони незамінні при зміні схеми бази даних. Ви можете бути впевнені, що ви не змінили схему таким чином, що порушує вашу програму доти, доки пройде тест. (Тест на одиницю в цьому випадку марний, оскільки коли збережена процедура більше не існує, тест одиниці все одно пройде.)

Дійсно дуже прагматично. Я пережив випадок, що база даних в пам'яті насправді поводиться по-різному (не з точки зору продуктивності) в порівнянні з моєю реальною базою даних через дещо інший ORM. Подбайте про це у своїх інтеграційних тестах.
Марсель

1
@Marcel, я наткнувся на це. Я вирішив це, іноді запускаючи всі мої тести на реальній базі даних.
Вінстон Еверт

4

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

Крім того, включаючи тестування приладів, є такі переваги:

  1. Елементні тести дозволяють швидко розкласти невдалий тест на інтеграцію (можливо, через помилку регресії) та встановити причину. Більше того, це повідомить проблему всій команді швидше, ніж діаграми чи інша документація.
  2. Тестові одиниці можуть слугувати прикладами та документацією (типом документації, яка фактично складається ) разом із вашим кодом. На відміну від іншої документації, ви дізнаєтесь миттєво, коли вона застаріла.
  3. Експериментальні тести можуть слугувати базовими показниками ефективності при спробі розкласти більші проблеми з продуктивністю, тоді як інтеграційні тести, як правило, вимагають багато інструментів для пошуку проблеми.

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


4

Тести корисні, коли вони ламаються: Активно чи Повторно.

Тестові одиниці є активними і можуть бути постійною функціональною перевіркою, тоді як інтеграційні тести реагують на поетапні / підроблені дані.

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

Наприклад, якщо ми будуємо систему ETL, найбільш релевантні тести будуть проводитись навколо даних (поетапно, підроблено або в прямому ефірі). Ця ж система матиме кілька тестів, але лише навколо перевірки, фільтрації тощо.

Шаблон сховища не повинен мати обчислювальної чи ділової логіки. Це дуже близько до бази даних. Але сховище також близьке до бізнес-логіки, яка його використовує. Тож мова йде про ураження БАЛАНСУ.

Тести блоку можуть перевірити, як поводиться сховище. Інтеграційні тести можуть перевірити, ЩО СТІЛЬКО відбулося, коли викликався сховище.

Тепер, "ЩО СТІЛЬ ВИСТАВИЛО" може здатися дуже привабливим. Але це може бути дуже дорогим для постійного та повторного бігу. Класичне визначення крихких тестів застосовується тут.

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


Мені подобається це: "Зараз" ЩО ВДІЧНО ДАЄТЬСЯ "може здатися дуже привабливим. Але це може бути дуже дорогим для постійного та повторного бігу."
atconway

2

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

Отже (це дуже спрощений приклад):

interface IOrderRepository
{
    Order GetOrderByID(Guid id);
}

class OrderRepository : IOrderRepository
{
    private readonly ISqlExecutor sqlExecutor;
    public OrderRepository(ISqlExecutor sqlExecutor)
    {
        this.sqlExecutor = sqlExecutor;
    }

    public Order GetOrderByID(Guid id)
    {
        var sql = "SELECT blah, blah FROM Order WHERE OrderId = @p0";
        var dataset = this.sqlExecutor.Execute(sql, p0 = id);
        var result = this.orderFromDataset(dataset);
        return result;
    }
}

Потім під час тестування OrderRepositoryпередавайте насмішку ISqlExecutorта переконайтеся, що тестуваний об’єкт проходить у правильному SQL (це його завдання) та повертає належний Orderоб'єкт із заданим набором даних результатів (також знущається). Ймовірно, єдиний спосіб перевірити конкретний SqlExecutorклас - це тести інтеграції, досить справедливі, але це тонкий клас обгортки і рідко коли-небудь зміниться, настільки багато.

Ви все ще повинні перевірити свої збережені процедури.


0

Я можу звести це до дуже простої думки: Тести блоку фаворитів, оскільки це допомагає знаходити помилки. І робіть це послідовно, оскільки невідповідності викликають плутанину.

Подальші причини випливають із тих же правил, що керують розробкою ОО, оскільки вони також застосовуються до тестів. Наприклад, принцип єдиної відповідальності.

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

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

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