У реальному світі цілком нормально писати одиничні тести для чужого коду. Звичайно, оригінальний розробник повинен був це зробити вже, але часто ви отримуєте застарілий код, коли цього просто не було зроблено. До речі, не має значення, чи прийшов цей спадковий код десятиліття тому з галактики далеко-далеко, чи хтось із ваших колег перевіряв це на минулому тижні, чи ви це написали сьогодні, застарілий код - це код без тестів
Запитайте себе: чому ми пишемо одиничні тести? Йти Гріном, очевидно, є лише засобом для досягнення мети, кінцева мета - довести або спростувати твердження про тестований код.
Скажімо, у вас є метод, який обчислює квадратний корінь числа з плаваючою комою. У Java інтерфейс визначив би це як:
public double squareRoot(double number);
Не має значення, написали ви реалізацію, чи це зробив хтось інший, ви хочете затвердити кілька властивостей squareRoot:
- що він може повернути прості корені, як sqrt (4.0)
- що він може знайти справжній корінь типу sqrt (2.0) з розумною точністю
- що він знаходить, що sqrt (0,0) дорівнює 0,0
- що він видає IllegalArgumentException, коли подається від'ємне число, тобто на sqrt (-1.0)
Отже, ви починаєте писати їх як індивідуальні тести:
@Test
public void canFindSimpleRoot() {
assertEquals(2, squareRoot(4), epsilon);
}
На жаль, цей тест вже не працює:
java.lang.AssertionError: Use assertEquals(expected, actual, delta) to compare floating-point numbers
Ви забули про арифметику з плаваючою точкою. Гаразд, ти представиш double epsilon=0.01і підеш:
@Test
public void canFindSimpleRootToEpsilonPrecision() {
assertEquals(2, squareRoot(4), epsilon);
}
і додайте інші тести: нарешті
@Test
@ExpectedException(IllegalArgumentException.class)
public void throwsExceptionOnNegativeInput() {
assertEquals(-1, squareRoot(-1), epsilon);
}
і ой, знову:
java.lang.AssertionError: expected:<-1.0> but was:<NaN>
Ви повинні були протестувати:
@Test
public void returnsNaNOnNegativeInput() {
assertEquals(Double.NaN, squareRoot(-1), epsilon);
}
Що ми тут зробили? Ми почали з кількох припущень щодо того, як повинен вести себе метод, і виявили, що не всі були правдивими. Потім ми склали тестовий набір Green, щоб записати доказ того, що метод поводиться відповідно до наших виправлених припущень. Тепер клієнти цього коду можуть розраховувати на таку поведінку. Якби хтось обмінявся реальною реалізацією squareRoot з чимось іншим, чимось, що, наприклад, справді кинуло виняток замість повернення NaN, наші тести це негайно зрозуміли б.
Цей приклад є тривіальним, але часто ви успадковуєте великі фрагменти коду, коли незрозуміло, що він насправді робить. У цьому випадку нормально прокладати тестовий джгут навколо коду. Почніть з кількох основних припущень про те, як повинен вести себе код, напишіть для них одиничні тести, тестуйте. Якщо Зелений, добре, пишіть більше тестів. Якщо Червоний, то тепер у вас є невдале твердження, що ви можете протистояти спец. Можливо, у застарілому коді є помилка. Можливо, специфіка незрозуміла щодо цього конкретного вкладу. Можливо, у вас немає специфікації. У такому випадку перепишіть тест таким чином, щоб він документував несподівану поведінку:
@Test
public void throwsNoExceptionOnNegativeInput() {
assertNotNull(squareRoot(-1)); // Shouldn't this fail?
}
З часом у вас з’являється тестовий джгут, який документує, як насправді поводиться код, і стає на зразок закодованої специфікації. Якщо ви хочете коли-небудь змінити застарілий код або замінити його чимось іншим, у вас є тестовий ремінь, щоб переконатися, що новий код поводиться так само, або новий код поводиться по-різному очікуваними та контрольованими способами (наприклад, що він насправді виправляє помилку, яку ви очікуєте, щоб її виправити). Цей джгут не повинен бути завершеним у перший день, насправді мати неповний джгут майже завжди краще, ніж взагалі не мати джгута. Якщо увійти, значить, ви можете писати свій клієнтський код з легкістю, ви знаєте, де очікувати, що щось порушиться, коли ви щось зміните, і де вони зламаються, коли в кінцевому підсумку це зробиться.
Вам слід спробувати вийти з розуму, що ви повинні писати одиничні тести лише тому, що вам доведеться, як і ви заповнювали обов'язкові поля у формі. І не слід писати одиничні тести лише для того, щоб червона лінія стала зеленою. Одиничні тести не ваші вороги, одиничні тести - ваші друзі.