В цій статті я вас познайомлю з одною потужною тулзою для аналізу покриття коду тестами go test --cover
. Для тих хто займається програмуванням вже певний час слово cover є цілком зрозумілим, для інших дам його переклад – покрити.
Давайте для початку розберемо, що з себе представляє інформація про покриття коду тестами і для чого вона потрібна. Для цього напишемо маленьку програму:
package math
// Max is a function that returns bigger of two numbers.
func Max(a, b int) int {
if a > b {
return a
}
return b
}
Думаю пояснювати, що робить функція не потрібно. Тепер маючи функцію, давайте напишимо тест, щоб перевірити її коректність:
package math
import (
"testing"
)
func TestMax(t *testing.T) {
res := Max(5,2)
if res != 5 {
t.Errorf("Max(5, 2)=%v; want 5", res)
}
}
Перевіряємо чи тести проходять за допомогою go test
. А тепер скористаємося командою яку я навів на початку статті go test --cover
. Якщо ваш код відповідає вище написаному, то ви повинні побачити наступний результат:
PASS
coverage: 66.7% of statements
ok blog 0.007s
Найцікавіше знаходиться в другій лінійці – процент покриття коду тестами. В нашому випадку дві лінійки коду з 3-ох покрито тестами. Давайте уважніше глянемо на початковий код. На самій горі в нас знаходиться назва поточного пакету, цілком очевидно, що тут тестувати немає що. Наступна лінійка пуста, а за нею коментар до функції. Я собі слабо уявляю, як можна протестувати коментар (хіба що граматику і орфографію перевірити), тому цю стрічку теж не розглядаємо. Наступне це визначення функції, таку річ теж не зрозуміло як можна протестувати, тому переходимо в тіло функції. Спочатку в нас йде умова, а після цього дві дії: перша виконується коли умова правдива, а друга коли ні. Також в нас в коді є дві фігурні дужки, але їх теж немає сенсу тестувати (хоча їх правильне розставлення, часом буває критичним). Отже ми знайшли наші три лінійки коду, які було перевірені на покриття. Тепер ж глянемо на тест. До функції Max
ми передаємо два числа 5 і 2, оскільки перше з них є більше то в нас виконається return a
. Давайте трошки змінимо наш тест (наводжу тільки функцію):
func TestMax(t *testing.T) {
res := Max(5,2)
if res != 5 {
t.Errorf("Max(5, 2)=%v; want 5", res)
}
res = Max(2,5)
if res != 5 {
t.Errorf("Max(2, 5)=%v; want 5", res)
}
}
Запустимо ще раз перевірку go test –cover, і отримаємо покриття в 100%. Слід зауважити, що загалом ця утиліта є доволі примітивною, тому 100% покриття зовсім не означає, що ваш код перевірений на всі 100% і що в ньому немає помилок. Як приклад перепишемо нашу тестову функцію наступним чином:
func TestMax(t *testing.T) {
res := Max(5,2)
if res != 5 {
t.Errorf("Max(5, 2)=%v; want 5", res)
}
Max(2,5)
}
Покриття тестами і надалі 100%, хоча й останній результат нами повністю ігнорується. Тобто ми можемо у функції Max
, замість return b
написати return 0
і це ніяким чином не повпливає ні на правильність тесту, ні на покриття.
Але для чого потрібна ця метрика? З власного досвіду можу сказати, що є дуже сильна кореляція між якістю коду і процентом покриття тестами. Звичайно ця кореляція не завжди правдива, я бачив код без тестів, який працював роками, а також код зі 100% покриттям з великими логічними дірами. Але як правило високе покриття вказує на кілька фактів:
- Щоб програма не робила, її правильність була перевірена за допомогою тестів. Тому вона як мінімум відповідає їхнім вимогам.
- Є великий шанс, що під час супроводження і додаванню нової функціональності, попередня логіка не була випадково поломана.
- Програма запроектована з розумом. З досвіду скажу, що досягти високого покриття не так вже легко. Дуже часто необхідно переписувати структури та додавати інтерфейси, щоб була можливість використати фальшиві залежності у ваших тестах. А це здебільшого позитино впливає на програму.
Чи варто намагатися досягти 100% покриття? Вважаю, що ні. Якщо пакет або функція дозволяє легко покрити всі лінійки коду тестами, то я це роблю. Якщо якась частина коду не є критичною (наприклад внутрішні метрики) і для її тестування необхідно потратити дуже багато часу на переписування коду і добавленню лишніх абстракцій, то я немаю нічого проти, щоб пропустити тестування в даному випадку. Загалом я б радив тримати рівень покриття близько 80-90% для вашого проекту. Якщо рівень покриття падає нижче 65%, це є хорошим сигналом, що команді необхідно зупинитися і сфокусуватися на тестах, в іншому випадку чекай біди найближчим часом.
Наступного разу спробуємо розглянути як запускати аналіз на більш складних проектах і як аналізувати окремі функції та файли. А тим часом рекомендую почитати статтю про аналіз покриття на офіційному блозі Go.
Залишити відповідь