Содержание
- Что такое автоматизированное тестирование, и в чём отличие от ручного тестирования?
- Что такое unit-test?
- Что такое TDD?
- В чем преимущество TDD? Зачем писать сначала тесты, а потом код?
- Что такое регрессионные баги и как автоматические тесты помогают их решать?
- Почему сайд-эффекты ухудшают тестируемость кода?
- Что такое mock и когда их стоит применять?
- Что такое шпион и когда их стоит применять?
- Стоит ли тестировать приватные методы класса? Почему?
- Зачем группировать отдельные тесты внутри unit-тестов?
TESTING
Что такое автоматизированное тестирование, и в чём отличие от ручного тестирования?
Автоматизированное тестирование предполагает использование специального программного обеспечения (помимо тестируемого) для контроля выполнения тестов и сравнения ожидаемого фактического результата работы программы. Этот тип тестирования помогает автоматизировать часто повторяющиеся, но необходимые для максимизации тестового покрытия задачи. Некоторые задачи тестирования, такие как низкоуровневое регрессионное тестирование, могут быть трудозатратными и требующими много времени если выполнять их вручную. Кроме того, мануальное тестирование может недостаточно эффективно находить некоторые классы ошибок. В таких случаях автоматизация может помочь сэкономить время и усилия проектной команды. После создания автоматизированных тестов, их можно в любой момент запустить снова, причем запускаются и выполняются они быстро и точно. Таким образом, если есть необходимость частого повторного прогона тестов, значение автоматизации для упрощения сопровождения проекта и снижения его стоимости трудно переоценить. Ведь даже минимальные патчи и изменения кода могут стать причиной появления новых багов.
При ручном тестировании (manualtesting) тестировщики вручную выполняют тесты, не используя никаких средств автоматизации. Ручное тестирование – самый низкоуровневый и простой тип тестирования, не требующих большого количества дополнительных знаний. Тем не менее, перед тем как автоматизировать тестирование любого приложения, необходимо сначала выполнить серию тестов вручную. Мануальное тестирование требует значительных усилий, но без него мы не сможем убедиться в том, возможна ли автоматизация в принципе. Один из фундаментальных принципов тестирования гласит: 100% автоматизация невозможна. Поэтому, ручное тестирование – необходимость.
Что такое unit-test?
Их ещё называют модульными тестами или блочными тестами. Такие тесты проверяют работу конкретного модуля, функции или части программы. Особенность таких тестов в том, что они затрагивают лишь одну часть программы, никак не проверяя остальные. Если тестируемый модуль ссылается на другие модули, то вместо них мы будем использовать фиктивные объекты. Поведение таких объектов мы смоделируем таким, как нам требуется в каждом конкретном тесте. Как правило, большая часть тестов в проекте — это модульные тесты. Их проще писать, для них не требуется слишком сложной структуры. Они быстро проверяются и их можно запускать параллельно, потому что они не зависят друг от друга.
Что такое TDD?
Разработка через тестирование (Test Driven Development, TDD) — практика разработки программ, при которой мы вначале пишем тесты для функциональности, которую хотим создать, затем — реализацию этой функциональности. Когда все заработает — рефакторим код.
В чем преимущество TDD? Зачем писать сначала тесты, а потом код?
Плюсы TDD
Обычно написание тестов считается скучной дополнительной работой, которую «надо делать после основной работы». Если мы используем TDD, написание тестов встраивается в основной поток разработки, не отнимая сил на «дополнительную работу после». Кроме этого TDD заставляет нас сразу же думать о том, как нашу функцию будут использовать. Когда мы сперва пишем тест, мы продумываем публичное API, тем самым делая его удобнее. Также TDD позволяет безопасно рефакторить реализацию и код самих тестов. Конечно, тесты не исключают ошибки в принципе, но если есть ошибка в описанном сценарии, тесты о ней точно сообщат. Это делает код устойчивее и надёжнее.Издержки TDD
Надо думать наперёд и проектировать API заранее. Ещё кого-то могут напрягать пляски с тем, чтобы заставлять тесты падать. Но это необходимое условие, чтобы быть уверенными, что тесты проверяют то, что нам надо.Минусы TDD
Часто в минусы TDD записывают то, что он якобы провоцирует думать только об основном сценарии (happy path) кода, забивая на исключения и крайние случаи. Это не совсем так. Да, TDD сперва требует написать тест. Часто самый первый тест — это тест на главную функциональность, но это не значит, что следующий тест обязан быть таким же. Разработчики могут писать и отказные тесты до реализации, здесь всё зависит от опыта разработчиков, качества проектирования системы и требований.>
Что такое регрессионные баги и как автоматические тесты помогают их решать?
Регрессия — это ошибка, которая возникает в уже работающей части системы после изменений в коде. Это может быть добавление новой функциональности в старый код или доработка общих функций. В нашей голове может уместиться лишь небольшой кусочек системы, которую мы программируем. Большая часть системы всегда находится вне поля нашего зрения. Это значит, что при добавлении функциональности мы можем не учесть особенности работы уже существующего кода. Тесты закрывают такие ошибки, потому что падают при возникновении неучтённой ситуации и не дают ей отправиться в продакшен.
Почему сайд-эффекты ухудшают тестируемость кода?
Чистая функция (pure function) — это функция, которая не взаимодействует с окружением, то есть не производит сайд-эффектов. Такие функции всегда возвращают одинаковое значение при одинаковых аргументах. Например, все математические функции — чистые. sin(30°) всегда равен 1/2, безотносительно расположения звёзд на небе. Функции с побочными эффектами («нечистые») возвращают разный результат при разных состояниях среды:
function currentTime() {
return Date.now()
}
У функции currentTime есть побочные эффекты: она ссылается на текущее время, при каждом вызове это время будет
разным. Тестировать чистые функции проще остальных. Для теста нам нужны только исходные данные и ожидаемый
результат, никакой дополнительной инфраструктуры не требуется.
Что такое mock и когда их стоит применять?
Моки (англ. mock) тоже заменяют зависимости, но при этом позволяют проверять предположения. Они могут следить за вызовами методов, аргументами этих вызовов и т. д. Моки удобны при тестировании функций с побочными эффектами.
Использование мока:
//функция, которую надо протестировать
function toggleTheme() {
ourSuperApp.toggleClassName('dark-theme');
ourSuperApp.userChangedTheme = true;
}
// Создаём мок для объекта приложения:
const fakeApp = {
toggleClassName: jest.fn(),
userChangedTheme: false
}
// Подменяем приложение на мок:
beforeEach(() => {
global.ourSuperApp = fakeApp
})
// Проверяем...
describe('when called', () => {
toggleTheme();
// ...что вызван нужный метод с ожидаемым аргументом:
it('should call the theme toggler with a correct class name', () => {
expect(fakeApp.toggleClassName).toHaveBeenCalledWith("dark-theme");
})
// ...что значение поля стало ожидаемым:
it('should toggle the changed theme flag', () => {
expect(fakeApp.userChangedTheme).toEqual(true)
})
})
Что такое шпион и когда их стоит применять?
Шпионы - это разновидность фиктивных объектов, наряду со стабами и моками. Шпионы следят за тем, какие функции у зависимостей вызываются. По желанию могут также имитировать возвращаемые значения для этих методов.
Стаб:
const realMath = Object.create(global.Math)
const mathStub = {
random: () => 0.42
}
// Подменяем настоящий Math на стаб:
beforeEach(() => {
global.Math = mathStub;
})
afterEach(() => {
global.Math = realMath;
})
// Проверяем:
describe('when called with a number `x`', () => {
it('should return the sum of that `x` and a random number', () => {
const result = addRandom(2);
expect(result).toEqual(2.42);
})
})
Шпион:
beforeEach(() => {
jest.spyOn(global.Math, 'random').mockReturnValue(0.42);
});
Стоит ли тестировать приватные методы класса? Почему?
Обычно так не делается, это считается плохой практикой. Ну то есть если метод приватный, то есть какой-то public, который его вызывает. Вот и тестируйте этот public метод, он покроет и приватный тоже. Если уж есть private методы с какой-то очень сложной, специфичной логикой, то на время тестирования можете делать public get методы, которые вызывают приватный. Если в приватный метод напихивается столько, что появляется потребность его отдельно тестировать - обычно это сигнал о том, что надо проводить рефакторинг.
Зачем группировать отдельные тесты внутри unit-тестов?
Группировка позволяет разделять тесты на блоки. Это, в свою очередь, дает такие возможности, как:
- параллельный запуск для экономии времени
- выполнение неких действий, общих для всей группы: до/ после прогона всех тестов из группы - методы beforeAll(fn, timeout) / afterAll(fn, timeout); до/ после запуска каждого теста из группы - методы beforeEach(fn, timeout) / afterEach(fn, timeout) и другие, что в конечном счете позволяет писать меньше кода и не повторять его
- дать описание группе тестов - метод describe(name, fn). Это позволяет легче ориентироваться в результатах вывода, увидеть, какая часть функционала не прошла тесты