После трех лет в мире Ruby on Rails и роскоши контекстуализированного тестового фреймворка RSpec я вернулся к Javascript, немного разочарованный необходимостью возвращаться к повторяющемуся коду в тестах. Мне это не нравится. Поэтому я написал свой собственный тест-раннер. С помощью JSSpec вы можете настраивать контексты, окружающие тестируемый объект, и сохранять утверждения вокруг этого объекта четкими и лаконичными. Проще показать на примере.
Рассмотрим следующий простой тестовый пример в качестве примера того, как это обычно делается:
const ThreeDComputer = require('./magical_computing_thing'); | |
describe('ThreeDComputer', () => { | |
it('sets z to 0 when x is 0 and y is 0', () => { | |
// Arrange: set up x, set up y | |
const x = 0, y = 0; | |
// Act: set z | |
let z = new ThreeDComputer(x, y); | |
// Assert: test the result | |
expect(z).to.eql(0); | |
}); | |
it('sets z to 4 when x is 0 and y is 2', () => { | |
// Arrange: set up x, set up y | |
const x = 0, y = 2; | |
// Act: set z | |
let z = new ThreeDComputer(x, y); | |
// Assert: test the result | |
expect(z).to.eql(4); | |
}); | |
/* | |
* And more tests like this … especially edge cases as | |
* bugs are detected and corrected. | |
*/ | |
}); |
Обратите внимание, что здесь представлено несколько контекстов, но они очевидны только в описании примера блока it
. Вся настройка контекста также происходит в этом блоке. Единственное различие между тестами заключается в значении y
(изменение контекста) и результате z
(предмет теста). Контекстно это будет записано так:
const ThreeDComputer = require('./magical_computing_thing'); | |
describe('ThreeDComputer', () => { | |
// Arrange: set up the computer - (but I don't have x or y?) | |
context('when x is 0', () => { | |
// Arrange: (context specific) set up x - (fine, that's just a value) | |
context('and y is 0', () => { | |
// Arrange: (context specific) set up y - (again fine) | |
it('sets z to 0', () => { | |
// Act: set z - would probably have to arrange the computer here. | |
const z = computer.z; | |
// Assert: test the result | |
expect(z).to.eql(0); | |
}); | |
}); | |
context('and y is 2', () => { | |
// Arrange: (context specific) set up y - (sure, fine) | |
it('sets z to 4', () => { | |
// Act: set z - what state is the computer in now? What about x? | |
const z = computer.z; | |
// Assert: test the result | |
expect(z).to.eql(4); | |
}); | |
}); | |
}); | |
}); |
Это довольно распространенная практика в Ruby. Я предпочел эту структуру, поскольку она позволяет вам сосредоточиться на том, что имеет отношение к конкретному тесту, над которым вы работаете. Во втором тесте меняется только y
, поэтому я хочу видеть только информацию, относящуюся к y
в конечном контексте. Использование этой структуры может увеличить скорость написания и поддержки набора тестов, а также значительно упростить управление ими, когда рабочий код меняет поведение. Кроме того, написание пограничного теста становится проще простого.
В этом случае это довольно легко сделать, потому что x
и y
являются примитивными значениями. Если какие-либо входные данные являются сложными объектами, для достижения этого с помощью большинства фреймворков требуется тяжелая работа с перехватчиками до/после или написание метода настройки для обработки построения контекста. Это часто перемещает весь код объявления и часть компоновки в контекст самого высокого предка, что приводит к обратной проблеме.
Это простой тестовый пример. Теперь представьте, что тестируемый метод меняет объекты x
и y
при вычислении z
. Если вы будете запускать тесты в случайном порядке или где-то посередине произойдет сбой одного из них, все сломается. Дополнительное внимание должно быть уделено входному состоянию перед выполнением каждого теста. На этом этапе ваш тестовый код должен иметь свои собственные тесты. Когда начальное состояние не гарантировано, вы, вероятно, столкнетесь с ненадежными тестами. Сложность работы с начальным состоянием также является причиной того, что разработчики перестают писать тесты и потенциально могут создавать ошибки.
JSSpec устраняет необходимость в методах настройки или неуклюжих структурах перед хуками. Ленивые оценщики (методы set
и subject
) позволяют определить изменение контекста в блоке контекста, к которому оно относится. Давайте взглянем:
const ThreeDComputer = require('./magical_computing_thing'); | |
describe('ThreeDComputer', () => { | |
subject('computer', () => new ThreeDComputer(x, y)); // Arrange: set up the computer | |
context('when x is 0', () => { | |
set('x', 0); // Arrange: (context specific) set up x | |
context('and y is 0', () => { | |
set('y', 0); // Arrange: (context specific) set up y | |
it('sets z to 0', () => { | |
// Act: set z | |
const z = computer.z; | |
// Assert: test the result | |
expect(z).to.eql(0); | |
}); | |
}); | |
context('and y is 2', () => { | |
set('y', 0); // Arrange: (context specific) set up y | |
it('sets z to 4', () => { | |
// Act: set z | |
const z = computer.z; | |
// Assert: test the result | |
expect(z).to.eql(4); | |
}); | |
}); | |
}); | |
}); |
context
для теста определяется в этом контексте, а не в блоке примеров. Обратите внимание на вызов subject
в первом блоке describe
, обратный вызов вызывается при первом доступе к computer
(или, в данном случае, к специальной переменной subject
) в блоке примера. x
и y
выбираются из контекста предка, ближайшего к примеру, в котором они определены, но только после их первого вызова, что в данном случае происходит при построении объекта computer
. В конце каждого теста все лениво оцененные значения отбрасываются, и процесс начинается снова для следующего блока примера, таким образом гарантируя, что каждый вход будет таким, каким вы его ожидаете.
JSSpec по-прежнему имеет хуки before
, beforeEach
, afterEach
и after
, когда вам нужно настроить или снести какую-то внешнюю структуру, чтобы убедиться, что ваше состояние действительно. В частности, before
и after
полезны для установки состояния базы данных или файла для вашего набора тестов.
Это упрощенное введение в JSSpec. Пожалуйста, попробуйте его для вашего следующего проекта NodeJS. В следующем посте я дам обзор использования sharedContext
и sharedExamples
в JSSpec.