HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-03-10
1 <p>Для тестирования на JavaScript существует<a>множество фреймворков</a>. Если говорить о модульном тестировании, то одним из наиболее популярных является Mocha. Давайте посмотрим, как он работает.</p>
1 <p>Для тестирования на JavaScript существует<a>множество фреймворков</a>. Если говорить о модульном тестировании, то одним из наиболее популярных является Mocha. Давайте посмотрим, как он работает.</p>
2 <h2>Несколько слов о тестируемом приложении</h2>
2 <h2>Несколько слов о тестируемом приложении</h2>
3 <p>Для примера возьмём React-приложение<a>Calculator</a>. Оно имеет два юнит-компонента:<em>keypad</em>и<em>display</em>. Это точно юниты, не зависящие от других юнитов. Кроме того, компоненты разделены на презентационные (уже упомянутые<em>display</em>и<em>keypad</em>) и компоненты-контейнеры -<em>calculator-app</em>- единственный компонент, имеющий state, и определяющий, что отображается на экране при нажатии на кнопку.</p>
3 <p>Для примера возьмём React-приложение<a>Calculator</a>. Оно имеет два юнит-компонента:<em>keypad</em>и<em>display</em>. Это точно юниты, не зависящие от других юнитов. Кроме того, компоненты разделены на презентационные (уже упомянутые<em>display</em>и<em>keypad</em>) и компоненты-контейнеры -<em>calculator-app</em>- единственный компонент, имеющий state, и определяющий, что отображается на экране при нажатии на кнопку.</p>
4 <p>Но вышеописанный компонент отвечает только за логику отображения, а что насчёт вычислений? Ими занимается отдельный модуль<em>calculator</em>, не имеющий React-зависимостей. И такой модуль прекрасно подходит для юнит-тестирования, т. к. не содержит I/O- и UI-зависимостей.</p>
4 <p>Но вышеописанный компонент отвечает только за логику отображения, а что насчёт вычислений? Ими занимается отдельный модуль<em>calculator</em>, не имеющий React-зависимостей. И такой модуль прекрасно подходит для юнит-тестирования, т. к. не содержит I/O- и UI-зависимостей.</p>
5 <p>Что касается отделения логики калькулятора от компонента React, то это было сделано путём выделения логики в модуль<em>calculator</em>. Это простейший модуль, принимающий состояние калькулятора (объект), а также символ (цифру либо оператор). И, разумеется, возвращающий новое состояние калькулятора. Но если каждое состояние зависит от предыдущего, как получить самое первое состояние? Довольно просто - модуль экспортирует initialState, которое вы применяете для инициализации. То есть состояние калькулятора не является неизвестным, а включает в себя поле<em>display</em>, которое и надо показать приложению калькулятора для данного состояния.</p>
5 <p>Что касается отделения логики калькулятора от компонента React, то это было сделано путём выделения логики в модуль<em>calculator</em>. Это простейший модуль, принимающий состояние калькулятора (объект), а также символ (цифру либо оператор). И, разумеется, возвращающий новое состояние калькулятора. Но если каждое состояние зависит от предыдущего, как получить самое первое состояние? Довольно просто - модуль экспортирует initialState, которое вы применяете для инициализации. То есть состояние калькулятора не является неизвестным, а включает в себя поле<em>display</em>, которое и надо показать приложению калькулятора для данного состояния.</p>
6 <p>Вот, например, начало кода:</p>
6 <p>Вот, например, начало кода:</p>
7 module.exports.initialState = { display: '0', initial: true } module.exports.nextState = (calculatorState, character) =&gt; { if (isDigit(character)) { return addDigit(calculatorState, character) } else if (isOperator(character)) { return addOperator(calculatorState, character) } else if (isEqualSign(character)) { return compute(calculatorState) } else { return calculatorState } } //....<p>Здесь не так важна специфика алгоритма, как простота функции, которую экспортирует наш модуль, ведь, получив состояние, мы можем всегда проверить следующее.</p>
7 module.exports.initialState = { display: '0', initial: true } module.exports.nextState = (calculatorState, character) =&gt; { if (isDigit(character)) { return addDigit(calculatorState, character) } else if (isOperator(character)) { return addOperator(calculatorState, character) } else if (isEqualSign(character)) { return compute(calculatorState) } else { return calculatorState } } //....<p>Здесь не так важна специфика алгоритма, как простота функции, которую экспортирует наш модуль, ведь, получив состояние, мы можем всегда проверить следующее.</p>
8 <h2>Приступим к тестированию</h2>
8 <h2>Приступим к тестированию</h2>
9 <p>По большему счёту, все тестирующие фреймворки похожи: вы пишете тестовый код в функциях, а фреймворк их запускает. Причём конкретный код, выполняющий запуск, как правило, называют "runner".</p>
9 <p>По большему счёту, все тестирующие фреймворки похожи: вы пишете тестовый код в функциях, а фреймворк их запускает. Причём конкретный код, выполняющий запуск, как правило, называют "runner".</p>
10 <p>Что касается<strong>Mocha</strong>, то здесь "Runner" представляет собой скрипт под названием<em>mocha</em>. Его можно легко увидеть, посмотрев на<em>package.json</em>в тестовом скрипте:</p>
10 <p>Что касается<strong>Mocha</strong>, то здесь "Runner" представляет собой скрипт под названием<em>mocha</em>. Его можно легко увидеть, посмотрев на<em>package.json</em>в тестовом скрипте:</p>
11 "scripts": { ... "test": "mocha 'test/**/test-*.js' &amp;&amp; eslint test lib", ... },<p>Это обеспечит запуск всех тестов в тестовой папке, названия которых начинаются с префикса<em>test-</em>. При запуске вы увидите приблизительно такой результат:</p>
11 "scripts": { ... "test": "mocha 'test/**/test-*.js' &amp;&amp; eslint test lib", ... },<p>Это обеспечит запуск всех тестов в тестовой папке, названия которых начинаются с префикса<em>test-</em>. При запуске вы увидите приблизительно такой результат:</p>
12 <p>Понятное дело, что если тест не пройден, он помечается красным, и его надо будет исправить. Посмотрите на следующий код:</p>
12 <p>Понятное дело, что если тест не пройден, он помечается красным, и его надо будет исправить. Посмотрите на следующий код:</p>
13 const {describe, it} = require('mocha') const {expect} = require('chai') const calculator = require('../../lib/calculator') describe('calculator', function () { const stream = (characters, calculatorState = calculator.initialState) =&gt; !characters ? calculatorState : stream(characters.slice(1), calculator.nextState(calculatorState, characters[0])) it('should show initial display correctly', () =&gt; { expect(calculator.initialState.display).to.equal('0') }) it('should replace 0 in initialState', () =&gt; { expect(stream('4').display).to.equal('4') }) //...<p>Давайте первым делом импортируем<em>mocha</em>и библиотеку для проверок (assert’ов)<em>expect</em>. И выполним импорт нужных нам функций:<em>describe</em>,<em>it</em>,<em>except</em>. Далее выполним импорт тестируемого модуля -<em>calculator</em>.</p>
13 const {describe, it} = require('mocha') const {expect} = require('chai') const calculator = require('../../lib/calculator') describe('calculator', function () { const stream = (characters, calculatorState = calculator.initialState) =&gt; !characters ? calculatorState : stream(characters.slice(1), calculator.nextState(calculatorState, characters[0])) it('should show initial display correctly', () =&gt; { expect(calculator.initialState.display).to.equal('0') }) it('should replace 0 in initialState', () =&gt; { expect(stream('4').display).to.equal('4') }) //...<p>Давайте первым делом импортируем<em>mocha</em>и библиотеку для проверок (assert’ов)<em>expect</em>. И выполним импорт нужных нам функций:<em>describe</em>,<em>it</em>,<em>except</em>. Далее выполним импорт тестируемого модуля -<em>calculator</em>.</p>
14 <p>Потом пойдут тесты, описанные с помощью функции<em>it</em>, допустим:</p>
14 <p>Потом пойдут тесты, описанные с помощью функции<em>it</em>, допустим:</p>
15 it('should show initial display correctly', () =&gt; { expect(calculator.initialState.display).to.equal('0') })<p>Функция принимает строку, которая описывает тест, и функцию, которая и является непосредственно самим тестом. Однако it-тесты не могут быть "голыми", а должны быть в тестовых группах, определяемых посредством функции<em>describe</em>.</p>
15 it('should show initial display correctly', () =&gt; { expect(calculator.initialState.display).to.equal('0') })<p>Функция принимает строку, которая описывает тест, и функцию, которая и является непосредственно самим тестом. Однако it-тесты не могут быть "голыми", а должны быть в тестовых группах, определяемых посредством функции<em>describe</em>.</p>
16 <p>Что же находится в тестовой функции? На самом деле, всё, что мы пожелаем. В нашем случае мы проверяем, что исходное состояние<em>display</em>равняется нулю. Но как мы это выполняем? Ведь мы могли бы сделать что-то типа этого:</p>
16 <p>Что же находится в тестовой функции? На самом деле, всё, что мы пожелаем. В нашем случае мы проверяем, что исходное состояние<em>display</em>равняется нулю. Но как мы это выполняем? Ведь мы могли бы сделать что-то типа этого:</p>
17 if (calculator.initialState.display !== '0') throw 'failed'<p>И такое решение бы подошло. Тест в Mocha не срабатывает, когда генерирует исключение, и это довольно просто. Однако<em>expect</em>делает тест намного приятнее, ведь в функции есть много фич, которые облегчают тестирование данных - к примеру, можно проверить, что массив либо объект равны конкретному значению.</p>
17 if (calculator.initialState.display !== '0') throw 'failed'<p>И такое решение бы подошло. Тест в Mocha не срабатывает, когда генерирует исключение, и это довольно просто. Однако<em>expect</em>делает тест намного приятнее, ведь в функции есть много фич, которые облегчают тестирование данных - к примеру, можно проверить, что массив либо объект равны конкретному значению.</p>
18 <p>В этом и заключается<strong>суть модульного тестирования</strong>: - запуск функции либо набора функций (или создание экземпляра объекта с последующим вызовом некоторых его методов, если мы говорим об ООП); - сравнение фактического результата и ожидаемого результата.</p>
18 <p>В этом и заключается<strong>суть модульного тестирования</strong>: - запуск функции либо набора функций (или создание экземпляра объекта с последующим вызовом некоторых его методов, если мы говорим об ООП); - сравнение фактического результата и ожидаемого результата.</p>
19 <p><em>Источник: "<a>Testing Your Frontend Code: Part II (Unit Testing)</a>".</em></p>
19 <p><em>Источник: "<a>Testing Your Frontend Code: Part II (Unit Testing)</a>".</em></p>
20  
20