- Взаимное влияние тестов
- Условные конструкции в тесте
- Тест вне тестов
- Слишком сильная детализация
- Глубокая вложенность
- Код с тестами писать дольше, чем код без тестов
Тесты, как и любой другой код, можно писать по-разному, в том числе очень плохо. Помимо каких-то общих практик и стандартов кодирования у тестов есть свои особенности, о которых надо знать. В этом уроке мы пройдёмся по некоторым из них.
Взаимное влияние тестов
Одно из ключевых правил: тесты не должны влиять друг на друга. Это значит, что любой тест выполняется так, как будто других тестов не существует в природе.
Нарушить это правило очень просто. Один тест может создать файл, изменить переменную или записать что-то в базу. Если остальные тесты наткнутся на эти изменения, то они могут упасть там, где не должны падать, или наоборот — успешно пройти там, где не должны проходить. Кроме этого, в такой ситуации вводится неопределённость. Такие тесты могут падать эпизодически без видимых на то причин. Например, когда тест запускают изолированно, то он работает, а когда вместе с остальными — падает:
let user
test('first', () => {
user = { name: 'Vasya' }
// ...
})
test('second', () => {
// Используется пользователь, которого создал другой тест!
// Этот тест зависит от того, как работает предыдущий тест,
// и не может работать без последовательного запуска обоих тестов.
user.name = 'Petya'
})
Особенно часто такая ситуация возникает в тестах, активно взаимодействующих с внешней средой: базой данных или файловой системой. Тестирование побочных эффектов имеет свои хитрости и рассматривается в курсе по продвинутому тестированию.
Условные конструкции в тесте
test('something', () => {
if (/* что-нибудь */) {
// Выполняем код одним способом
// Проверка может быть тут
} else {
// Выполняем код другим способом
// Проверка может быть тут
}
// Проверка может быть тут
});
Любое ветвление внутри тестов это фактически несколько тестов в рамках одного теста. От этого надо избавляться и никогда так не писать.
Тест вне тестов
Задача beforeEach
— готовить данные и среду для тестирования, а задача test
— вызывать код, который тестируется, и проводить проверки. Но иногда разработчики переусердствуют:
let result
beforeEach(() => {
// Вызывается тестируемый код. Это противоречит идее beforeEach.
result = sum(5, 9)
})
test('result', () => {
// Здесь только проверка
expect(result).toEqual(14)
})
В этом примере тестируемый код вызывается в beforeEach
. Такой подход усложняет анализ тестов, так как переворачивает всё с ног на голову.
Слишком сильная детализация
Программисты под влиянием голосов из интернета стремятся максимально разносить код по файлам, модулям и функциям. То же самое наблюдается и в тестах. Вместо одного теста содержащего все необходимые проверки, программист создаёт 5 тестов, в каждом из которых ровно одна проверка:
test('create user', () => {
const user = { name: 'Mark', age: 28 }
// Тут код, добавляющий пользователя в базу данных
expect(user.age).toEqual(28)
})
test('create user 2', () => {
const user = { name: 'Mark', age: 28 }
// Тут код, добавляющий пользователя в базу данных
expect(user.name).toEqual('Mark')
})
Чаще всего, единственным результатом такого разделения будет большее количество кода и усложнение рефакторинга в будущем, когда кода станет по-настоящему много.
Глубокая вложенность
Jest позволяет группировать тесты в блоки describe
:
describe('User', () => {
test('should be valid', () => { /* ... */ })
})
Они помогают структурировать сложные тесты и задать для каждого блока describe
свой собственный beforeEach
. Хотя такая возможность бывает полезна, но очень легко начать использовать её во вред:
describe('', () => {
describe('...', () => {
describe('...', () => {
test('should be valid', () => { /* ... */ })
})
})
})
Глубокая иерархия тестов очень тяжело поддаётся анализу и фиксирует структуру. Из-за этого возникают сложности при добавлении новых проверок. Становится непонятно, к чему она относится. Это проблема любых иерархий, которые рассматривают систему только с одной точки зрения.
Код с тестами писать дольше, чем код без тестов
Это очень интересный вопрос, по которому можно понять, насколько хорошо программист умеет писать тесты. Несмотря на то, что некоторые виды тестирования действительно сложны и требуют дополнительного времени, ежедневные тесты, которые пишутся вместе с кодом, должны приводить к ускорению разработки. И на это есть пять причин:
- Тесты влияют на дизайн кода. Они помогают выявить неудачные решения намного раньше.
- Подготовка входных данных может занимать значительное время. С тестами это нужно сделать один раз.
- Проверка результата работы кода может быть сложной и разнообразной. Тесты позволяют об этом не думать, они сами проверяют, что всё хорошо, включая пограничные случаи.
- Если в проекте тесты пишутся регулярно, то проще и быстрее делать рефакторинг, так как не придётся проверять вручную другие части кода.
- Тесты снижают уровень стресса.
Дополнительные материалы
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.