Одной из характеристик фронтенд-разработки является то, что она включает в себя больше пользовательского интерфейса.Когда масштаб разработки достигает определенного уровня, его сложность почти обречена на экспоненциальный рост.
Будь то первоначальная конструкция кода или неизбежный процесс рефакторинга и исправления ошибок, часто бывает трудно разобраться в логике и уловить глобальную взаимосвязь.
В качестве основного средства «очерчивания и сопровождения» модульное тестирование обеспечивает «стены и леса» для разработки, которые могут эффективно решить эти проблемы.
В качестве классического метода разработки и рефакторинга модульное тестирование широко признано и применяется в области разработки программного обеспечения; в области внешнего интерфейса постепенно накапливаются богатые среды тестирования и лучшие практики.
В этой статье я объясню в следующем порядке:
- I. Введение в модульное тестирование
- II. Инструменты, используемые в модульном тестировании React
- III. Реконструкция компонентов React с помощью Test Drive React Components
- IV. Распространенные варианты использования React Unit Testing
I. Введение в модульное тестирование
Модульное тестирование относится к проверке и проверке наименьшей тестируемой единицы программного обеспечения.
Проще говоря,单元
Это самый маленький протестированный функциональный модуль, созданный людьми. Модульное тестирование — это самый низкий уровень тестирования, который должен выполняться во время разработки программного обеспечения, когда отдельные модули программного обеспечения тестируются изолированно от остальной части программы.
тестовая среда
Роль среды тестирования заключается в предоставлении удобного синтаксиса для описания тестовых случаев и группировки тестовых случаев.
утверждения
Утверждения являются основной частью среды модульного тестирования, и ошибка утверждения приведет к сбою теста или сообщит об ошибке.
Ниже приведены некоторые примеры общих утверждений:
-
Равенство утверждает
- expect(sth).toEqual(value)
- expect(sth).not.toEqual(value)
-
Сравнение утверждает
- expect(sth).toBeGreaterThan(number)
- expect(sth).toBeLessThanOrEqual(number)
-
Тип утверждает
- expect(sth).toBeInstanceOf(Class)
-
Проверка состояния
- expect(sth).toBeTruthy()
- expect(sth).toBeFalsy()
- expect(sth).toBeDefined()
Библиотека утверждений
Библиотека утверждений в основном предоставляет семантические методы для вышеуказанных утверждений, которые используются для вынесения различных суждений о значениях, задействованных в тесте. Эти семантические методы возвращают результат теста, успешный или неуспешный. Общие библиотеки утверждений включают Should.js, Chai.js и т. д.
прецедент
Набор тестовых входных данных, условий выполнения и ожидаемых результатов, написанных для конкретной цели, чтобы протестировать путь программы или убедиться, что определенное требование выполнено.
Общая форма:
it('should ...', function() {
...
expect(sth).toEqual(sth);
});
Test Suite Test Suite
Набор связанных тестов часто называют набором тестов.
Общая форма:
describe('test ...', function() {
it('should ...', function() { ... });
it('should ...', function() { ... });
...
});
spy
так как
spy
Буквально, мы используем этого «шпиона» для «мониторинга» вызовов функций.
Обертывая отслеживаемую функцию, вы можете четко знать, сколько раз функция вызывалась, какие параметры передавались, какие результаты возвращались и даже вызывались исключения.
var spy = sinon.spy(MyComp.prototype, 'componentDidMount');
...
expect(spy.callCount).toEqual(1);
stub
иногда используется
stub
Чтобы внедрить или напрямую заменить некоторый код для достижения цели изоляции
Одинstub
Этот модульный тест можно смоделировать с помощью подхода с минимальной зависимостью. Например, метод может зависеть от выполнения другого метода, который для нас прозрачен. Хорошей практикой является изолировать его заглушкой. Это обеспечивает более точное модульное тестирование.
var myObj = {
prop: function() {
return 'foo';
}
};
sinon.stub(myObj, 'prop').callsFake(function() {
return 'bar';
});
myObj.prop(); // 'bar'
mock
mock
Обычно это относится к методу тестирования, который использует виртуальный объект для создания метода тестирования для тестирования некоторых объектов, которые нелегко создать или получить в процессе тестирования.
Вообще говоря, вышеупомянутые шпион и заглушка, а также некоторые симуляции модулей, симуляции возвращаемых значений ajax и симуляции таймеров называются макетами.
Тестовое покрытие (покрытие кода)
Он используется для подсчета проверки кода тест-кейсами и формирования соответствующих отчетов, таких какistanbul
является распространенным статистическим инструментом тестового покрытия.
II. Инструменты, используемые в модульном тестировании React
Jest
В отличие от «традиционных» (на самом деле, их не существует уже несколько лет) фреймворков для тестирования переднего плана, таких как jasmine/Mocha/Chai —Jest
проще в использовании, обеспечивает более высокую интеграцию и более богатые функции.
Jest — это среда тестирования, созданная Facebook.По сравнению с другими средами тестирования, одной из ее основных особенностей является то, что она имеет встроенные общие инструменты тестирования, такие как встроенные утверждения и инструменты покрытия тестами, которые можно использовать «из коробки». .
Кроме того, тестовые случаи Jest выполняются параллельно, и выполняются только тесты, соответствующие измененным файлам, что повышает скорость тестирования.
четыре основных слова
Синтаксис написания модульных тестов обычно очень прост.jest
Например, поскольку его внутреннее использованиеJasmine 2
для тестирования, поэтому его синтаксис варианта использования такой же, как у Jasmine.
На самом деле, просто заучить эти четыре слова достаточно для большинства тестовых ситуаций:
-
describe
: определить набор тестов -
it
: определить тестовый пример -
expect
: условие суждения утверждения -
toEqual
: результат сравнения утверждения
describe('test ...', function() {
it('should ...', function() {
expect(sth).toEqual(sth);
expect(sth.length).toEqual(1);
expect(sth > oth).toEqual(true);
});
});
настроить
Jest утверждает, что является «платформой для тестирования нулевой конфигурации», простоnpm scripts
настроен внутриtest: jest
, Вы можете запуститьnpm test
, автоматически определять и тестировать на соответствие своим правилам (обычно__test__
каталог) использовать файлы прецедентов.
В реальном использовании правильная пользовательская конфигурация приведет к более подходящему тестовому сценарию:
//jest.config.js
module.exports = {
modulePaths: [
"<rootDir>/src/"
],
moduleNameMapper: {
"\.(css|less)$": '<rootDir>/__test__/NullModule.js'
},
collectCoverage: true,
coverageDirectory: "<rootDir>/src/",
coveragePathIgnorePatterns: [
"<rootDir>/__test__/"
],
coverageReporters: ["text"],
};
В этом простом конфигурационном файле мы указываем «корень» тестов, настраиваем покрытие (встроенныйistanbul
) и указать оригинальную ссылку на файл стиля в webpack на пустой модуль, тем самым пропустив эту безобидную ссылку для тестирования
//NullModule.js
module.exports = {};
Также стоит упомянуть, что посколькуjest.config.js
это воляnpm
Обычный JS-файл, вызываемый в скрипте, а неXXX.json
или.XXXrc
В форме , поэтому можно выполнять соответствующие операции nodejs, такие как введение fs для предварительной обработки чтения и записи и т. Д., Гибкость очень высока, и она может быть хорошо совместима с различными проектами.
babel-jest
Потому что он ориентированsrc
Код React тестируется в каталоге, также используется синтаксис ES6, поэтому проект должен существовать..babelrc
документ:
{
"presets": ["env", "react"]
}
Выше приведена базовая конфигурация, но на самом деле, поскольку webpack может компилировать модули es6, обычно устанавливайте babel как{ "modules": false }
, конфигурация на данный момент такова:
//package.json
"scripts": {
"test": "cross-env NODE_ENV=test jest",
},
//.babelrc
{
"presets": [
["es2015", {"modules": false}],
"stage-1",
"react"
],
"plugins": [
"transform-decorators-legacy",
"react-hot-loader/babel"
],
"env": {
"test": {
"presets": [
"es2015", "stage-1", "react"
],
"plugins": [
"transform-decorators-legacy",
"react-hot-loader/babel"
]
}
}
}
Enzyme
Enzyme принадлежит Airbnb, активно участвующему в сообществе JavaScript с открытым исходным кодом, и представляет собой пакет официальной библиотеки тестовых инструментов (react-addons-test-utils).
Лондонское произношение этого слова['enzaɪm]
, значение энзимов или энзимов, Airbnb не разрабатывал для него значок, предполагается, что они хотят использовать его для разложения компонентов React.
Он имитирует API jQuery, который очень интуитивно понятен, прост в использовании и освоении.Он предоставляет некоторые отличительные интерфейсы и несколько методов для сокращения шаблонного кода тестирования, что удобно для оценки, манипулирования и обхода выходных данных компонентов React, а также сокращения код тестирования и связь между кодом реализации.
Обычно используйте Enzyme вmount
илиshallow
способ преобразования целевого компонента вReactWrapper
object и вызовите его различные методы в тесте:
import Enzyme,{ mount } from 'enzyme';
...
describe('test ...', function() {
it('should ...', function() {
wrapper = mount(
<MyComp isDisabled={true} />
);
expect( wrapper.find('input').exists() ).toBeTruthy();
});
});
sinon
"Я веду коня" на картине - это не генерал Ша Вуцзин... На самом деле сюжет в картине - это всем известный "Троянский конь"; вероятно, это означает, что греки осаждали троянцев более десяти лет, не имея возможности атаковать долгое время, он решил отвести лагерь, оставив только огромного троянского коня (с солдатами в нем) и этого человека, достаточно раздетого и избитого, о котором здесь пойдет речь. обманул им троянцев --- всем известен сюжет.
Таким образом, этот названный инструмент тестирования также представляет собой набор различных методов маскировки проникновения, предоставляя независимые и богатые методы шпионажа, заглушки и имитации для модульного тестирования, совместимые с различными средами тестирования.
Хотя в самом Jest есть некоторые средства реализации шпионажа и т. д., sinon удобнее в использовании.
III. Рефакторинг компонента React через тестирование
Мы не будем обсуждать здесь классическую теорию «разработки через тестирование» (TDD — разработка через тестирование) — проще говоря, хорошо известно, что тестирование применяется к разработке, сначала создавая варианты использования, а затем реализуя их шаг за шагом.
И когда мы вернемся, дополним существующий код тестовыми примерами, чтобы постоянно улучшать тестовое покрытие, и в процессе улучшать исходный дизайн, устранять потенциальные проблемы и в то же время гарантировать, что оригинальный интерфейс не будет затронут, такого рода TDD Хотя никто не называет поведение «рефакторингом, управляемым тестами», само понятие «рефакторинг» включает в себя смысл сопровождения тестами, что является существенным смыслом в вопросе.
Для некоторых компонентов и общих функций полные тесты также являются лучшим руководством по эксплуатации.
Неудача - Код - Три песни
Поскольку в результатах тестирования успешные варианты использования будут показаны зеленым цветом, а неудачные части — красным, поэтому модульное тестирование часто называют «красно-зеленым тестированием» или «красно-зеленым рефакторингом», что также является общий термин в TDD Сексуальные шаги:
- добавить тест
- Запустите все тесты, чтобы убедиться, что только что добавленный не дал сбой; если он прошел успешно, повторите шаг 1.
- В соответствии с отчетом об ошибках напишите или перепишите код целенаправленно; единственная цель этого шага — пройти тест, вам не нужно сначала беспокоиться о деталях.
- Запустите тест еще раз; в случае успеха перейдите к шагу 5, в противном случае повторите шаг 3.
- Рефакторинг кода, который прошел тесты, чтобы сделать его более читабельным и удобным в сопровождении, не влияя на прохождение тестов.
- повторить шаг 1
Интерпретация тестового покрытия
Этоjest
Встроенныйistanbul
Результаты покрытия вывода.
Причина, по которой его называют «Стамбул», заключается в том, что турецкие ковры известны во всем мире, а ковры используются для «покрытия» 🤦♀️.
Колонки со 2 по 5 в таблице соответствуют четырем измерениям:
- Покрытие операторов: было ли выполнено каждое выражение
- охват филиала: каждый ли
if
блоки кода выполняются - покрытие функций: вызывается ли каждая функция
- Покрытие строки: выполняется ли каждая строка
Результаты теста делятся на три типа: «зеленый, желтый и красный» в зависимости от уровня охвата. Уровень охвата тестом соответствующего модуля должен быть максимально улучшен в зависимости от конкретной ситуации.
Оптимизируйте зависимости, чтобы компоненты React можно было тестировать.
Разумное написание компонентного React и использование достаточно независимых и функционально специфичных компонентов в качестве тестовых модулей упростит модульное тестирование;
Наоборот, процесс тестирования облегчает нам прояснение взаимосвязей и рефакторинг или разложение исходных компонентов на более разумные структуры. Отдельные подкомпоненты также часто легче писать.stateless
Компонент без сохранения состояния, который делает производительность и фокус более оптимизированными.
Явно укажите PropTypes
Для некоторых компонентов, которые не были четко определены ранее, они могут быть введены единообразно.prop-types
, давая понять, что компонент может приниматьprops
С одной стороны, ошибку можно найти в любой момент в процессе разработки/компиляции или иначе сформировать четкий список, когда на компоненты можно будет ссылаться в команде.
IV. Распространенные варианты использования React Unit Testing
Пре- или пост-обработка вариантов использования
Можно использоватьbeforeEach
а такжеafterEach
Выполните некоторые унифицированные настройки и последствия, которые автоматически вызываются до и после каждого варианта использования:
describe('test components/Comp', function() {
let wrapper;
let spy;
beforeEach(function() {
jest.useFakeTimers();
spy = sinon.spy(Comp.prototype, 'componentDidMount');
});
afterEach(function() {
jest.useRealTimers();
wrapper && wrapper.unmount();
didMountSpy.restore();
didMountSpy = null;
});
it('应该正确显示基本结构', function() {
wrapper = mount(
<Comp ... />
);
expect(wrapper.find('a').text()).toEqual('HELLO!');
});
...
});
Вызвать "частный" метод компонента
Для некоторых компонентов, если вы хотите вызвать некоторые из его внутренних методов на этапе тестирования, но не хотите слишком сильно менять исходный компонент, вы можете использоватьinstance()
Получите экземпляр класса компонента:
it('应该正确获取组件类实例', function() {
var wrapper = mount(
<MultiSelect
name="HELLOKITTY"
placeholder="select sth..." />
);
var wi = wrapper.instance();
expect( wi.props.name ).toEqual( "HELLOKITTY" );
expect( wi.state.open ).toEqual( false );
});
Тестирование асинхронных операций
Как компоненты пользовательского интерфейса, некоторые операции в компонентах React необходимо отложить, напримерonscroll
илиoninput
Для такого высокочастотного триггерного действия требуется функция анти-дрожания или дросселирования, например обычно используемая функция устранения дребезга lodash.
Так называемая асинхронная операция вообще относится к этому типу операций без учета интеграционного теста, интегрированного с ajax.Только setTimeout недостаточно, и его нужно сопоставить сdone
Использование функции:
//组件中
const Comp = (props)=>(
<input type="text" id="searchIpt" onChange={ debounce(props.onSearch, 500) } />
);
//单元测试中
it('应该在输入时触发回调', function(done) {
var spy = jest.fn();
var wrapper = mount(
<Comp onChange={ spy } />
);
wrapper.find('#searchIpt').simulate('change');
setTimeout(()=>{
expect( spy ).toHaveBeenCalledTimes( 1 );
done();
}, 550);
});
Некоторые глобальные и одноэлементные макеты
Некоторые модули могут иметь связанные парыwindow.xxx
Такая ссылка на глобальный объект и полная деинстансация этого объекта могут повлечь за собой множество других проблем, которые трудно выполнить; в настоящее время вы можете увидеть уловки и только смоделировать свернутый глобальный объект, чтобы обеспечить ход теста:
//fakeAppFacade.js
var facade = {
router: {
current: function() {
return {name:null, params:null};
}
}, appData: {
symbol: "¥"
}
};
window._appFacade = facade;
module.exports = facade;
//测试套件中
import fakeFak from '../fakeAppFacade';
Кроме того, такие объекты, как LocalStroage, не имеют встроенной поддержки в тестовой среде, и вы можете просто смоделировать ее:
//fakeStorage.js
var _util = {};
var fakeStorage = {
"set": function(k, v) {
_util['_fakeSave_'+k] = v;
},
"get": function(k) {
return _util['_fakeSave_'+k] || null;
},
"remove": function(k) {
delete _util['_fakeSave_'+k];
},
"has": function(k) {
return _util.hasOwnProperty('_fakeSave_'+k);
}
};
module.exports = fakeStorage;
хитрая реакция-бутстрап/модальный
используется в проектеreact-bootstrap
Библиотека интерфейса, при тестировании компонента, поскольку она содержит егоModal
Модальное всплывающее окно и всплывающие компоненты по умолчанию отображаются вdocument
средний, что затрудняет использование обычныхfind
метод получения
Решение состоит в том, чтобы имитировать обычный компонент, который отображается вместо компонента-контейнера:
//FakeReactBootstrapModal.js
import React, {Component} from 'react';
class FakeReactBootstrapModal extends Component {
constructor(props) {
super(props);
}
render() { //原生的 react-bootstrap/Modal 无法被 enzyme 测试
const {
show,
bgSize,
dialogClassName,
children
} = this.props;
return show
? <div className={
`fakeModal ${bgSize} ${dialogClassName}`
}>{children}</div>
: null;
}
}
export default FakeReactBootstrapModal;
В то же время, когда компонент визуализируется, добавляется логика оценки, чтобы он мог поддерживать пользовательский класс вместо модального класса:
//ModalComp.js
import { Modal } from 'react-bootstrap';
...
render() {
const MyModal = this._modalClass || Modal;
return (<MyModal
bsSize={props.mode>1 ? "large" : "middle"} dialogClassName="custom-modal">
...
</MyModal>;
}
В наборе тестов реализуйте специфичный для теста подкласс:
//myModal.spec.js
import ModalComp from 'components/ModalComp';
class TestModalComp extends ModalComp {
constructor(props) {
super(props);
this._modalClass = FakeReactBootstrapModal;
}
}
Таким образом, тест может быть проведен плавно, неважные эффекты пользовательского интерфейса будут пропущены, а все виды логики могут быть охвачены.
Имитация запроса на выборку
В процессе модульного тестирования неизбежно возникают некоторые ситуации, требующие удаленного запроса данных, например, компоненты, получающие данные инициализации, отправляющие данные об изменениях и т. д.
Следует отметить, что целью этого теста является проверка производительности самого компонента, а не сосредоточение внимания на интеграционном тесте реальных удаленных данных, поэтому мы можем просто смоделировать некоторые сценарии запросов без реальных запросов.
В sinon есть несколько методов для имитации запросов XMLHttpRequest, а в jest также есть несколько сторонних библиотек для решения теста выборки;
В нашем проекте, в соответствии с фактическим использованием, мы реализуем класс для имитации ответа на запрос:
//FakeFetch.js
import { noop } from 'lodash';
const fakeFetch = (jsonResult, isSuccess=true, callback=noop)=>{
const blob = new Blob(
[JSON.stringify(jsonResult)],
{type : 'application/json'}
);
return (...args)=>{
console.log('FAKE FETCH', args);
callback.call(null, args);
return isSuccess
? Promise.resolve(
new Response(
blob,
{status:200, statusText:"OK"}
)
)
: Promise.reject(
new Response(
blob,
{status:400, statusText:"Bad Request"}
)
)
}
};
export default fakeFetch;
//Comp.spec.js
import fakeFetch from '../FakeFetch';
const _fc = window.fetch; //缓存“真实的”fetch
describe('test components/Comp', function() {
let wrapper;
afterEach(function() {
wrapper && wrapper.unmount();
window.fetch = _fc; //恢复
});
it("应该在远程请求时响应onRemoteData", (done)=>{
window.fetch = fakeFetch({
brand: "GoBelieve",
tree: {
node: '总部',
children: null
}
});
let spy = jest.fn();
wrapper = mount(
<Comp onRemoteData={ spy } />
);
jest.useRealTimers();
_clickTrigger(); //此时应该发起请求
setTimeout(()=>{
expect(wrapper.html()).toMatch(/总部/);
expect(spy).toHaveBeenCalledTimes(1);
done();
}, 500);
});
});
V. Резюме
В качестве классического метода разработки и рефакторинга модульное тестирование широко признано и применяется в области разработки программного обеспечения; фронтенд-область постепенно накапливает богатые среды и методы тестирования.
Модульное тестирование может предоставить базовую гарантию для нашей разработки и обслуживания, чтобы мы могли завершить построение и реконструкцию кода с ясным умом и практическим результатом;
Следует отметить, что лекарства от всех болезней в мире не существует, и юнит-тестирование ни в коем случае не является панацеей, только при осторожном, добросовестном и ответственном отношении мы можем принципиально гарантировать прогресс своей работы.
Дальнейшее чтение:
Рефакторинг примерTickets.WeChat.QQ.com/Yes?__Author=mz i…
VI. Ссылки
- https://juejin.cn/post/6844903494911000584
- https://baike.baidu.com/Item/ Модульный тест
- http://www.cnblogs.com/jianglingli83/archive/2013/03/15/2961847.html
- http://blog.csdn.net/hustzw07/article/details/74178051
- https://zhuanlan.zhihu.com/p/28247899
- https://en.wikipedia.org/wiki/Test-driven_development
- http://www.ruanyifeng.com/blog/2015/06/istanbul.html
- https://www.eurobricks.com/forum/index.php?/forums/topic/52658-lego-construction-site/
Длительный нажмите QR-код или найдите меньше, чтобы следовать нами