Тестові одиниці та бази даних: У який момент я фактично підключаюся до бази даних?


37

Є відповіді на запитання про тестові класи, які підключаються до бази даних, наприклад, "Чи повинні підключатись тестові класи сервісу ..." та "Тестування модулів - додаток, пов'язане з базою даних" .

Отже, коротше, припустимо, у вас клас A, який потрібно підключити до бази даних. Замість того, щоб дозволити A фактично підключитися, ви надаєте A інтерфейс, який A може використовувати для підключення. Для тестування ви реалізуєте цей інтерфейс з деякими речами - без підключення, звичайно. Якщо клас B інстанціює A, він повинен передати "реальне" підключення до бази даних до A. Але це означає, що B відкриває з'єднання з базою даних. Це означає, що для випробування B ви вводите з'єднання в B. Але B є екземпляром у класі C тощо.

Тож у який момент я повинен сказати "тут я отримую дані з бази даних, і я не буду писати одиничний тест для цього фрагмента коду"?

Іншими словами: Десь у коді в якомусь класі я повинен зателефонувати sqlDB.connect()або щось подібне. Як перевірити цей клас?

І це те саме з кодом, який має мати справу з GUI або файловою системою?


Я хочу зробити Unit-Test. Будь-який інший вид тесту не пов'язаний з моїм запитанням. Я знаю, що з ним випробую лише один клас (я так погоджуюся з тобою Кіліана). Тепер деякий клас повинен підключитися до БД. Якщо я хочу перевірити цей клас і запитати "Як це зробити", багато хто каже: "Використовуйте ін'єкційне залежність!" Але це лише переносить проблему на інший клас, чи не так? Тому я запитую, як я перевіряю клас, який справді, справді встановлює зв'язок?

Питання про бонус: Деякі відповіді тут зводяться до "Використовуйте макетні об'єкти!" Що це означає? Я знущаюся над класами, від яких залежить тестування класу. Чи можу я зараз знущатися над тестуванням класу та фактично перевіряти макет (що наближається до ідеї використання методів шаблонів, див. Нижче)?


Це ви підключення до бази даних, яке ви тестуєте? Чи допустимо створення тимчасової бази даних пам'яті (наприклад, дербі )?

@MichaelT Ще мені потрібно замінити тимчасовий БД пам'яті справжньою базою даних. Де? Коли? Як тестується пристрій? Або гаразд не тестувати цей код?
TobiMcNamobi

3
Немає нічого "одиничного тестування" щодо бази даних. Його підтримують інші люди, і якби в ньому була помилка, вам доведеться дозволити їх виправити, а не робити це самостійно. Єдине, що повинно відрізнятися між фактичним використанням вашого класу та використанням під час тестів, повинні бути параметри підключення до бази даних. Навряд чи код зчитування файлу властивостей, або механізм весняного вприскування або все, що ви використовуєте для з’єднання вашої програми, порушено (і якби це не було, ви не могли виправити це самостійно, див. Вище) - тому я вважаю прийнятним не перевіряти цю функцію сантехніки.
Кіліан Фот

2
@KilianFoth, що суто пов'язане з робочим середовищем та ролями співробітників. Це насправді не має нічого спільного з питанням. Що робити, якщо немає жодної особи, відповідальної за базу даних?
Реакційний

Деякі глузливі рамки дозволяють вводити макетні об’єкти в майже все, навіть у приватні та статичні члени. Це значно спрощує тестування таких речей, як макет db-з'єднань. Mockito + Powermock - це те, що працює для мене сьогодні (вони Java, не впевнені, у чому ти працюєш).
FrustratedWithFormsDesigner

Відповіді:


21

Суть одиничного тесту полягає в тестуванні одного класу (насправді зазвичай він повинен перевірити один метод ).

Це означає, що під час тестування класу Aви вводите в нього тестову базу даних - щось самостійно написане або блискавична база даних у пам'яті, що б не виконало завдання.

Однак, якщо ви перевіряєте клас B, який є клієнтом A, зазвичай ви знущаєтесь над усім Aоб’єктом ще чимось іншим, імовірно, тим, що виконує свою роботу примітивним, заздалегідь запрограмованим способом - без використання фактичного Aоб'єкта і, звичайно, без використання даних база (якщо тільки не Aпередає все з'єднання бази даних назад своєму абоненту - але це так жахливо, що я не хочу про це думати). Так само, коли ви пишете одиничний тест для класу C, який є клієнтом B, ви б знущалися над тим, що приймає роль B, і Aвзагалі забували б .

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


11

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

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

Для тестування одиниць у CakePHP є речі, які називаються fixtures. Світильники - це тимчасові таблиці бази даних, створені під час руху для одиничного тесту. Кріплення має зручні методи їх створення. Вони можуть відтворити схему з виробничої бази даних всередині тестової бази даних, або ви можете визначити схему, використовуючи просту нотацію.

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

Деякі додаткові посилання.

http://en.wikipedia.org/wiki/Test_fixture

http://phpunit.de/manual/3.7/uk/database.html

http://book.cakephp.org/2.0/en/development/testing.html#fixtures


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

5
Називайте мене пуристом, але я дотримуюсь принципу, що тестовий блок не повинен виконувати жодних дій, які залишають "пісочницю" середовища тестування. Вони не повинні торкатися баз даних, файлових систем, мережевих сокетів тощо. Це з кількох причин, не останньою з яких є залежність тесту від зовнішнього стану. Інша - продуктивність; ваш тестовий набір пристроїв повинен працювати швидко, і взаємодія з цими зовнішніми сховищами даних повільно проводить тести на порядки. У власній розробці я використовую часткові макети для тестування таких речей, як мої сховища, і мені зручно визначати "край" моєї пісочниці.
KeithS

2
@gbjbaanb - Спочатку це звучить чудово, але, на мій досвід, це дуже небезпечно. Навіть у найкращих архітектурних тестових наборах та фреймворках код для відкату цієї транзакції може не виконуватись. Якщо тестовий бій виходить з ладу або переривається в тесті, або тест викидає SOE або OOME, найкращий випадок - у вас є висяче з'єднання та транзакція в БД, яка заблокує таблиці, до яких ви торкнулися, поки з'єднання не загине. Способи запобігання цього спричиняють проблеми, такі як використання SQLite в якості тестової БД, мають свої мінуси, наприклад, факт, що ви насправді не використовуєте реальну БД.
KeithS

5
@KeithS Я думаю, що ми дискутуємо над семантикою. Справа не в тому, що таке визначення одиничного тесту або тесту інтеграції. Я використовую світильники для тестування коду, залежного від підключення до бази даних. Якщо це інтеграційний тест, то я з цим добре. Мені потрібно знати, що тест проходить. Мені було не байдуже на залежність, продуктивність чи ризики. Я не знаю, чи працює цей код, якщо цей тест не пройде. Для більшості тестів немає залежностей, але для тих, де вони є, тоді ці залежності не можна відключити. Неважко сказати, що вони повинні бути, але їх просто не може бути.
Реакційний

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

4

Десь у вашій кодовій базі є рядок коду, який виконує фактичну дію підключення до віддаленої БД. Цей рядок коду 9 разів по 10 викликає "вбудований" метод, що надається бібліотеками часу виконання, характерними для вашої мови та оточення. Таким чином, це не "ваш" код, тому вам не потрібно його тестувати; для цілі одиничного тестування ви можете довіряти, що виклик цього методу буде виконуватись правильно. Що ви все ще можете і що слід перевірити у своєму тестовому наборі одиниць - такі речі, як забезпечення параметрів, які будуть використовуватися для цього виклику, - це те, що ви очікуєте від них, наприклад, переконання, що рядок підключення правильний, або оператор SQL або збережена назва процедури.

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

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


Тепер, наскільки чітко ви можете дотримуватися цього, залежить від вашої точної стратегії підключення до запиту і запиту до бази даних. У багатьох випадках, коли потрібно використовувати рамку доступу до даних «голі кістки», наприклад, об'єкти SqlConnection і SqlStatement ADO.NET, цілий розроблений вами метод може складатися з вбудованих викликів методів та іншого коду, який залежить від наявності підключення до бази даних, і так найкраще, що ви могли зробити в цій ситуації, - це знущатися над усією функцією та довіряти вашим наборам тестів інтеграції. Це також залежить від того, наскільки ви готові розробити свої класи, щоб дозволити замінювати конкретні рядки коду для тестування (наприклад, пропозиція Тобі щодо шаблону методу шаблону, який є хорошим, оскільки дозволяє "часткові макети"

Якщо ваша модель збереження даних покладається на код у вашому рівні даних (наприклад, тригери, збережені програми тощо), просто не існує іншого способу вправити код, який ви самі пишете, ніж розробити тести, які живуть всередині рівня даних або перетинають межа між часом виконання програми та СУБД. Пурист сказав, що цієї моделі слід уникати на користь чогось подібного до ОРМ. Я не думаю, що я б ішов зовсім так далеко; навіть в епоху мовних інтегрованих запитів та інших перевірених компілятором, залежних від домену операцій стійкості, я бачу цінність блокування бази даних лише до операцій, відкритих через збережену процедуру, і, звичайно, такі збережені процедури повинні бути перевірені за допомогою автоматизованих тести. Але такі випробування не є одиничними тестами. Вони є інтеграцією тести.

Якщо у вас є проблеми з цим розрізненням, воно, як правило, ґрунтується на великій важливості, що надається повному "кодовому покриттю" ака "покриттю одиничного тесту". Ви хочете переконатися, що кожен рядок вашого коду охоплюється одиничним тестом. Шляхетна мета на її обличчі, але я кажу горобку; що менталітет піддається анти-моделям, що виходять далеко за межі цього конкретного випадку, наприклад, написання тестів, що не підтверджуються, які виконуються, але не здійснюютьсвій код. Ці типи кінцевих пробіжок виключно заради кількості покриттів шкідливіші, ніж послаблення мінімального покриття. Якщо ви хочете переконатися, що кожен рядок вашої кодової бази виконується деяким автоматизованим тестом, це легко; при обчисленні показників покриття коду включайте тести інтеграції. Ви можете навіть піти на крок далі і виділити ці спірні тести "Itino" ("Інтеграція лише в ім'я"), а між вашим набором тестових пакетів і цією підкатегорією інтеграційних тестів (які все-таки повинні працювати досить швидко) вам слід заграти близько до повного покриття.


2

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

У семантиці осторонь є безліч причин, чому це вигідно:

  • Тести швидше виконують порядки
  • Цикл зворотного зв'язку стає миттєвим (<1 відгук для TDD, як приклад)
  • Тести можна проводити паралельно для систем побудови / розгортання
  • Тести не потребують запуску бази даних (значно спрощує або принаймні створити збірку)

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

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

Тож як ви перевіряєте речі з доступом до бази даних? Весь ваш код доступу до даних повинен знаходитися на певному шарі, тому ваш додаток може взаємодіяти з послугами, які можна скористатися замість фактичної бази. Це не має значення, чи підтримуються ці служби будь-яким типом баз даних SQL, даними тестової пам'яті або навіть даними віддаленої веб-служби. Це не їхня турбота.

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

Скажімо, кожен компонент складається приблизно з 5 класів - ви хочете перевірити всі точки відмови всередині них. Це прирівнюється до набагато менших тестів лише для того, щоб усе було правильно провіднано. Приклад - тест Ви можете знайти користувача з бази даних, вказавши ім'я користувача / пароль.

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

Подумайте про ці три типи тестів як про піраміду. Вам потрібно багато тестів, щоб підтримати все, і тоді ви працюєте звідти.


1

Модель шаблону може допомогти.

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

Таким чином, фактичні виклики до бази даних ніколи не проходять одиничні тести, це правильно. Але це лише ці кілька рядків коду. І це прийнятно.


1
Якщо вам цікаво, чому я відповідаю на власне запитання: Так, це може бути відповідь, але я абсолютно не впевнений, чи є він правильним.
TobiMcNamobi

-1

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

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