3 ошибки, которые вы, возможно, допускаете при модульном тестировании

0
299
views

Перевод статьи «3 Mistakes You’re Probably Making When Unit Testing».

Модульное тестирование

Модульное тестирование это одна из тех вещей, которые разработчики либо любят, либо ненавидят — третьего не дано. Также это одна из тех задач, которые во многих командах поручают «новичку», разработчику-джуниору или любому, кто не занят ничем другим, более важным.

Обычно так происходит, потому что модульное тестирование считается тривиальной задачей, чем-то таким, в чем никто не сумеет ошибиться. Но знаете, что? Таки есть способы напортачить с тестами! В этой статье я расскажу о трех ошибках, которые вы (как и любой другой) можете допустить в ходе тестирования.

1. Тестирование больше одной вещи за раз

Это распространенная ошибка, которую допускают джуниоры, когда вы посылаете их протестировать большие куски кода. Обычно они находят большие функции и пытаются протестировать их сверху донизу. А поскольку эти функции большие, джуниоры имеют тенденцию использовать один (или, может, два) test case для каждой, и в результате получают сценарии тестирования, выискивающие что-то совсем ненужное.

Позвольте спросить: кто в этом виноват? Ваш разработчик, занимающийся тестами? Или тот, кто написал функцию на сотни строк?

Модульные тесты подразумевают фокус на отдельных модулях вашего кода. Если функция, которую вам нужно протестировать, не позволяет это сделать, нужно не тесты менять — это неверный подход. Вместо этого уделите время рефакторингу кода, разбейте его на более поддерживаемые модули, а затем разделите и test cases, чтобы точно иметь возможность тестировать все части по отдельности.

Для примера возьмем следующую функцию:

function sayHi(first_name, last_name) {
    if(!first_name) return false;
    if(!last_name) return false;

    [fn_fl, ...fn_tail] = first_name.split("");
    first_name = [fn_fl.toUppercase(), ...fn_tail].join("");

    [ln_fl, ...ln_tail] = last_name.split("");
    last_name = [ln_fl.toUppercase(), ...ln_tail].join("");


    console.log("Hello there " + first_name + " " + last_name)
    return true
}

Функция в полном порядке, но если вы попробуете проверить ее при помощи модульного тестирования, вы столкнетесь с трудностями. Вам будет сложно изолировать части ее поведения. Просто протестировать валидацию или трансформацию переменных не так легко. Вместо этого можно сделать рефакторинг и разбить вашу функцию на более мелкие:

function validateData(fn, ln) {
    if(!fn) return false
    if(!ln) return false
    return true
}

function transform(string) {
    [fl, ...tail] = string.split("");
    return [fl.toUppercase(), ...tail].join("");
}

function sayHi2(first_name, last_name) {
    if(!validateData(first_name, last_name)) {
        return false;
    }

    first_name = transform(first_name)
    last_name = transform(last_name)

    console.log("Hello there " + first_name + " " + last_name)
    return true
}

Благодаря этому вы сможете протестировать различные части предыдущего поведения.

2. Принятие в расчет внешних сервисов

Чаще всего код пишется не для того, чтобы работать изолированно. По факту, скорее всего он будет работать со внешними сервисами. Под словом «сервисы» здесь следует понимать что угодно, от запроса к Google Map’s API до сохранения файла на вашем локальном жестком диске.

То есть, ваш код будет взаимодействовать с чем-то вне среды его выполнения, но ваши модульные тесты не должны это учитывать. Почему? Просто потому, что в ходе выполнения тестов вы не отвечаете (или не должны отвечать) за статус сторонних сервисов. Другими словами, если Google внезапно решит отключить свой Maps API на несколько часов, почему в таком случае ваши модульные тесты должны провалиться?

Если ваши тесты полагаются на взаимодействие со внешними сервисами, вы тестируете не только свой код, но и надежность этих сервисов, а это не должно быть вашей целью. Внешние сервисы это совершенно отдельные продукты, они тестируются в ходе своего цикла разработки, вам-то зачем сейчас о них беспокоиться?

Возможно, вы думаете: «Так я же тестирую код, связанный с этими сервисами!» Ну, если только вы не пишете драйвера для них, это сторонние библиотеки, которые тестируются их создателями.

В общем, что я хочу сказать: фокусируйтесь на собственном коде. Если код взаимодействует с базой данных, сделайте заглушку (mock), если посылаете запрос к какому-нибудь API, сделайте макеты ответов. В общем, вы поняли суть.

Модули

3. Цель — покрытие тестами

Наконец, еще одна классическая ошибка это написание тестов ради покрытия тестами. Сколько раз вам доводилось слышать призвыв «Пиши тесты, пока не покроешь ими 90% своего кода»?

Покрытие это лишь один из показателей для ваших тестов, так же как количество строк кода — показатель сложности функций. Но, я уверен, вам встречался очень краткий код, совершенно непригодный для чтения.

Не нужно рассматривать покрытие как способ определить, сколько кода покрыто тестами. Этот показатель следует использовать для понимания того, сколько кода тестами НЕ покрыто, а также для понимания того, нужно ли его покрывать.

Если ищете показатель для определения успешности вашей работы, лучше обратите внимание на точность. То есть, вместо того чтобы следить за тем, все ли части кода покрыты тестами, убедитесь, что вы проверили каждый возможный путь во всех его вариациях. Что я имею в виду?

Представьте, что у вас есть совершенно реальная и полезная функция:

function div(a, b) {
    if(a > 0) {
        console.log("Debug: a is a positive number")
    }
    return a / b;
}

Проверив, что div(1,1) возвращает 1, а console.log вызывается, вы можете достигнуть 100% покрытия тестами. Это прекрасно, однако я уверен, что вы видите, насколько нерелевантными окажутся эти тесты в ту минуту, когда вы в качестве второго аргумента функции передадите 0.

Будет гораздо лучше, если вы добавите несколько тестов, проверяющих, что все работает с различными значениями a и b, пускай даже и не сделаете тестов для контента вашего if-блока.

Помните, что концентрироваться нужно на функциональности!

Заключение

Я описал три ошибки, часто допускаемые разработчиками в ходе модульного тестирования (естественно, сам я их тоже допускал не раз). Знаете и другие варианты ошибок при написании тестов? Поделитесь в комментариях!

ОСТАВЬТЕ ОТВЕТ

Please enter your comment!
Please enter your name here