Unit тестове #2: Какво да тестваме?

По мои наблюдения, когато хората за първи път се сблъскват с тестовете, нямат голяма идея какво да правят. Или се опитват да покрият абсолютно всеки ред код, прахосвайки много време за ненужни неща, или пък пишат твърде малко тестове и пропускат цялата идея. Както казах по-рано, на мен ми отне месец-два докато разбера защо наистина пиша тестове и доста повече, докато усвоя повече тънкости. Ето ви сбито списъче с неща, които се убедих от първа ръка.

  • Не тествайте методи, които директно делегират на framework-а. Ако просто викате save() метода на ORM tool-а, не правете тест за него. Ако framework-а ви не работи като хората или ще го разберете по-рано, или такъв семпъл тест въобще няма да ви помогне. Ако user е проста таблица, въобще не си губете времето да правите testCreateUser, testUpdateUser и testDeleteUser.
  • Не ползвайте произволни данни в тестовете си. Веднъж с този подход открихме, че един search engine не връща адекватно резултати като го питаме за различни интервали, но това е по-скоро изключение. Когато теста се провали и вие искате да оправите грешката, нямате интерес да ровите по лог файлове и да гледате с какви данни се е издънил. Още по-важното, не трябва да има разлика в резултатите от тях, ако не сте променили кода между две извиквания. Ако подозирате, че чрез агресивно натоварване с произволни данни можете да откриете някакъв дефект, направете отделно приложение и пускайте него. Но не слагайте това в unit тестовете, защото само си просите главоболие така.
  • Тествайте пропорционално на сложността на кода. Ако едно парче код е просто, не му отделяйте време да го разцъкате отсвякъде. Ако кодът обаче е труден (напр. ползва сложен алгоритъм или комуникира с твърде много неща), задължително напишете колкото можете повече тестове за него. Никога нямате достатъчно тестове за сложния код – дори месеци след като сте го имплементирали можете да открете нещо, което го чупи.
  • Не тествайте 3rd party модули. Ако ползвате някакъв search engine, не си правете индекси специално за unit тестовете. Това важи с пълна сила, ако някой разработва този модул паралелно с вас – не искате неговите промени да чупят вашите тестове. Вместо това си направете mock-up на същия интерфейс и тествайте с него – така хем ще ви е по-лесно да създавате различни тестови сценарии, хем тестовете ви ще работят много по-бързо.
  • Тествайте usage scenario-та – макар да казах, че няма смисъл да тествате всички методи на едно entity по отделно, си струва да направите един метод, в който да създавате, намирате, променяте и изтривате един потребител. Ще ви отнеме по-малко време (защото в другите случаи ще повтаряте един boilerplate код) и теста ще е по-близо до реалното използване на кода. Често ми се е случвало да откривам по този начин, че съм забравил някои constraint-и в базата или че нe съм настроил кеша като хората.
  • Тествайте по contract-а на вашите интерфейси, ако имате тази възможност. Задължително се уверявайте, че postcondition-ите и invariant-ите са изпълнени. Няма нужда да пишете тестове за precondition-ите, тъй като най-вероятно за тях правите асертации или хвърляте изключения. Когато пишете такива тестове, правете ги black-box – нека те не се интересуват как, а дали е спазен contract-а.
  • Пишете glass-box тестове, когато имате сложни методи. Задължително тествайте граничните стойности и покривайте поне по веднъж всяко разклонение на кода си, което не е очевидно просто. Знам че звучи като нещо извадено от книга с безполезни съвети, но има смисъл да покривате всичко разклонения на сложната логика, защото едва ли ще ви се губи време да ги разцъквате всеки път.
  • Ако очаквате един код да се променя, тествайте го по-усърдно. Не правете предположения за имплементацията му. Когато го промените е много вероятно да забравите нещо и ще се радвате, ако разберете това възможно най-рано.
  • Никога не пишете тестове, така че те да си влияят взаимно. Ако имате 20 теста, не трябва да има никакво значение реда или броя който пускате. Още повече, минимизирайте всякаква зависимост от някаква глобална среда (например база данни или индекс) – ако искате да тестване триенето и променянето на потребител, създавайте нов си нов в тест метода. А ако искате да имате някакви предефинирани данни за всички, то не ги променяйте в тестовете си.
  • Старайте се тестовете да зависят от възможно най-малката част от проекта ви. Не ги правете зависими от модули, които не тествате директно, освен ако не е твърде наложително. Ако имате възможност – направете mock-up. Веднъж ни се случи във фирмата да минаваме от EJB3 към Spring+JPA. Миграцията на 20 000 реда код беше половин час, понеже интерфейса бе идентичен и въпроса бе конфигурационен. Тестовете, обаче, бяха много по-трудоемки, тъй като трябваше да възстановим почти целия останал проект за да пуснем дори един от тях. В процеса се родиха няколко бая добри идеи и едно api, за което ще говоря друг път.
  • Предната точка важи и за слоевете на проекта – ако тествате някаква абстракция на по-високо ниво, няма нужда да разчитате на предположения за кода в по-долен слой. По-добре направете прост mock-up и се уверете, че до по-долните модули стигат подходящите данни. Целта на unit тестовете е да открият, че точно тази част от приложението ви не работи, а не че някъде по канала има проблем.
  • Причината да пишете тестове е да пестите време. Затова те нямат нужда от много коментари или излишни съобщения при асертациите. Слагайте такива само ако наистина имате нужда.

Не се сещам за друго в момента. Надявам се да ви е било от полза… или поне да ви е било интересно. Пишете, ако имате нещо да добавите.

2 thoughts on “Unit тестове #2: Какво да тестваме?

  1. Аз тествам в следните случаи:

    • Когато това, което трябва да направя ми се стори нетривиално. Тогава първо пиша тест, който да опише това, което искам да се случи. С това обикновено описвам и интерфейса. След което пиша код, който покрива теста.

    • Когато има бъг. Пиша тест, който по някакъв начин да възпроизвежда бъга. С него проверявам дали съм фикснал бъга.

    Признавам си – пиша недостатъчно тестове. Кода ми е слабо покрит, и често „пропускам“ да напиша тестове където трябва. Но се уча.

  2. Ей, как можах да го забравя това. Разбира се, че трябва да се пишат тестове за бъгове. Аргументацията е много проста – самите бъгфиксове често са няколко реда, които не влизат чисто в първоначалния ти замисъл и стоят като кръпка. Когато промениш кода (замениш го с по-качествен), често пропускаш тази кръпка. Не искам да казвам колко пъти ми се е случвало. Теста, разбира се, помага за това. Плюс това е хипер лош стил да генерираш един бъг два пъти.

    Иначе, пробвай следната мотивация за кода си – пиши тестовете в аванс. Доста помага. Но това е част от TDD, за което мисля да пиша като се прибера от Сибир.

Вашият коментар

Вашият имейл адрес няма да бъде публикуван. Задължителните полета са отбелязани с *