Есть большая вероятность, что вы делаете одну из этих ошибок и даже не заметили

Все разработчики ненавидят модульные тесты, верно? Это похоже на одно из тех невысказанных правил: если вы работаете разработчиком, ваше внимание должно быть сосредоточено на рабочем коде, позволяя другим, кто знает меньше вас, писать грязные тесты.

Я видел много разработчиков с таким мышлением за свой (почти) 20-летний опыт работы. Черт возьми, я знаю, что когда-то был одним из них. Но правда в том, что я научился на своих ошибках, я в конце концов понял роль модульных тестов в контексте программного проекта, и я просто хочу, чтобы вы тоже достигли этого.

Я хочу, чтобы вам нравилось писать тесты, потому что да, такое возможно. И чтобы показать вам, как мало внимания вы уделяете задаче модульного тестирования, вот 5 ошибок, которые вы, скорее всего, совершаете, даже не осознавая этого.

Тестирование чужого кода

Это классика, сейчас я, наверное, говорю фразу «вы тестируете код, который не писал» два раза в неделю. Но ничего страшного, лучше сначала разобраться в этом, так как это основная ошибка для более крупных.

Ключом к модульному тестированию является слово «модуль». Прежде всего, ваши тесты должны тестировать единицу кода. А что такое «единица» кода, спросите вы? Что ж, вот где начинается проблема, это подло рассматривать как наименьшую часть вашего кода, которую имеет смысл тестировать.

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

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

Конечно, это всего лишь скомбинированный JavaScript, за ним нет реальной логики, просто сосредоточьтесь на том, что он должен делать: проверять данные и сохранять в базе данных.

Теперь, если бы вы протестировали что-то вроде этого:

По сути, вы пытаетесь убедиться, что ваша функция возвращает правильное сообщение об ошибке. И это совершенно нормально, но если вы обратите внимание на выполнение программы, вы заметите, что вызываете метод validate пакета, который вы не писали. По сути, вы должны убедиться, что этот метод правильно отвечает на недействительное электронное письмо. Этот тест уже был написан создателем библиотеки. Это не ваша работа - снова проверять этот кусок.

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

То же самое и с методом save, но мы рассмотрим этот конкретный метод в следующей ошибке.

Тестирование подключения к базе данных

Я снова и снова сталкиваюсь с этой проблемой, когда разработчики пытаются протестировать функции, связанные с базами данных.

Посмотрите на пример сверху, та же функция saveUsertoDatabase. Вы можете увидеть, как он вызывает save метод подключения к базе данных. Это не странно, это нормально. Проблема в том, что тест. Если вы надеетесь проверить, что объект был сохранен в базе данных, выполнив запрос к нему и убедившись, что полученный объект совпадает с сохраненным, то вы делаете это неправильно.

Даже если вы запускаете тестовую базу данных, которая уничтожается постфактум, вы делаете это неправильно. Функционирование модульных тестов не может зависеть от внешних сервисов. Подумайте об этом: если по какой-либо причине соединение с базой данных не удастся во время выполнения теста, ваш тест не удастся. Означает ли это, что ваш код неправильный? Нет. Это просто означает, что вы хотели протестировать свой код, но в итоге проверили подключение к базе данных. И это неверно.

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

Это дает дополнительное преимущество: с помощью моков вы можете контролировать, что база данных возвращает каждый раз, поэтому вам будет проще писать тесты для случаев, когда запрос не выполняется, когда запрос завершается успешно, и даже в странных случаях, когда объект получает половину. (Не знаю, может быть, ты хочешь этим заняться, не смотри на меня так).

И все это действительно может быть применено к любой внешней службе, вызовам API, вызовам файловой системы и так далее. Каждый раз, когда вашему коду требуется выполнить внешний вызов и обработать результат, этот вызов необходимо подделать.

Тестирование нескольких вещей внутри тестового примера

Какое сообщение об ошибке вы считаете более понятным:

Error: Test "validate and save user" failed

Or

Error: Test "Validate name field on user object" failed

Я предполагаю, что вы отвечаете «второй», иначе вам придется переосмыслить свой ответ.

Наличие функций, которые выполняют несколько вещей, не редкость, на самом деле это вполне нормально, и вам не следует прекращать это делать. Но ваши тесты не предназначены для написания одного для каждой функции, они должны быть сосредоточены на «модуле» кода, который вы тестируете (помните из вышесказанного?).

Так что, если вы надеетесь проверить несколько вещей одновременно, потому что это имеет смысл, а в противном случае вам пришлось бы повторять много логики и кода в своих тестах, подумайте еще раз. Модульный тест должен фокусироваться на чем-то одном, и только на одном. Если этот тест не прошел, в нем должно быть четко указано, какой блок кода не прошел, чтобы вы могли исправить это. Но если вместо этого он охватывает 3 или 4 логических пути, точное определение фактической ошибки может стать беспорядочным.

Написание тестов постфактум

Модульное тестирование - это практика, которая должна выполняться либо во время разработки тестируемого кода, либо в лучшем случае: до.

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

Ваш рабочий код диктует логику вашего теста, а это означает, что вы с большей вероятностью будете проверять «счастливый путь» своей логики только потому, что это то, что вы видите. Отсутствие полностью написанного и работающего кода поможет вам больше усомниться в его логике. Это действительно бессознательная вещь, но вы можете сравнить это с тем, что разработчики тестируют свои собственные функции, с отдельным человеком, который не участвовал в ее создании, тестировании. Второй человек, скорее всего, сообщит о гораздо большем количестве проблем, чем его создатель, который уже предвзят и тестирует его так, как они это понимали и как он это кодировал.

Это не то, как вы тестируете функции, и определенно не то, как вы пишете свои тесты.

Невозможность обновить ваши тесты после рефакторинга кода

Наконец, согласованность также является частью написания хороших модульных тестов. Как и в предыдущем пункте, если вы не обновляете логику своих тестов после (или во время) изменения логики тестируемого кода, тесты теряют всякую валидность.

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

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

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

Модульное тестирование, безусловно, очень важная задача, которая должна быть частью каждого программного проекта. Независимо от того, насколько он мал или велик, они обеспечивают уровень уверенности в качестве написанного кода, который могут дать очень немногие другие инструменты. И они также добавляют этот дополнительный уровень безопасности на будущее, когда на нашем пути появляются обновления и рефакторы, мы можем быть уверены, что если мы написали наши тесты правильно и будем относиться к ним должным образом, они поднимут руку в ту минуту, когда что-то будет выглядеть не так. .

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

Если вам понравилось то, что вы прочитали, подумайте о подписке на мой список рассылки, и я буду держать вас в курсе, когда будут готовы новые статьи!