Фронтенд автоматизированное тестирование
Зачем нужно автоматизированное тестирование
После непрерывной разработки проект в конечном итоге станет стабильным.Внедрение автоматизированного тестирования в нужное время может выявить проблемы на ранней стадии и обеспечить качество продукта.
自动化的收益 = 迭代次数 * 全手动执行成本 - 首次自动化成本 - 维护次数 * 维护成本
тестовое задание
Будучи последним звеном в полном процессе разработки, тестирование является важным звеном для обеспечения качества продукта. Фронтенд-тестирование, как правило, является поздним звеном в процессе разработки продукта и относится к более высокому уровню во всей архитектуре разработки.Фронтенд-тестирование больше склоняется к функциям графического интерфейса, поэтому фронтенд-тестирование очень сложно.
метод тестирования
тестирование черного ящика
Тестирование методом черного ящика также часто называют функциональным тестированием.Тестирование методом черного ящика требует, чтобы тестировщик рассматривал программу как единое целое, независимо от ее внутренней структуры и характеристик, но чтобы убедиться, что программа работает должным образом. Тестирование методом черного ящика ближе к реальному сценарию, который использует пользователь, поскольку внутренности программы невидимы для пользователя.
В тестировании черного ящика обычно используются следующие методы тестирования:
-
Подразделение класса эквивалентности
Разделение класса эквивалентности в основном предназначено для определения допустимого входного и недопустимого интервала ввода для разработки тестовых случаев при условии существующих правил ввода.
Например: если пароль для входа на сайт должен состоять из 6 цифр
Допустимые классы эквивалентности: 6 цифр
Недопустимый класс эквивалентности: цифры > 6, цифры
-
Анализ граничных значений
Как следует из названия, дизайн тестовых случаев в основном основан на граничных значениях входного и выходного диапазонов. Причина в том,Большое количество ошибок имеет тенденцию возникать на границах входных или выходных диапазонов.(Программисты часто делают ошибки в этих местах), анализ граничных значений обычно используется в сочетании с разделением классов эквивалентности, а граница интервала деления классов эквивалентности обычно является граничным значением.
Например: Например, длина пароля для входа на сайт должна быть 6-12 цифр.
Допустимые классы эквивалентности: количество цифр [6-12]
Недопустимый класс эквивалентности: количество цифр 12
Граничное значение: 6 12
-
Предположение об ошибках, анализ исключений и т. д.
Тестирование черного ящика также включает в себя некоторые другие методы тестирования.Поскольку тестирование часто неисчерпаемо, то, как разработать тестовые примеры, чтобы гарантировать, что тесты охватывают как можно больше сценариев, основано не только на этих сводках.метод, а также проверить собственныйталант.
тестирование белого ящика
Тестирование белого ящика — это тест, основанный на самом коде, обычно относящийся к тесту логической структуры кода. Тестирование методом белого ящика — это тест, проводимый на основе понимания структуры кода, и цель состоит в том, чтобы пройти как можно больше исполняемых путей для получения тестовых данных. Существует много методов тестирования белого ящика, в основномлогическое покрытие, то есть проверять каждую строку кода и каждый результат оценки.
Методы покрытия логики отсортированы по способности находить ошибки в основном в следующих категориях:
- Покрытие операторов (позволить программе выполняться для каждой строки операторов)
- Покрытие суждения (пусть каждое утверждение суждения удовлетворяет истине или ложности)
- Покрытие условий (пусть каждое условие в каждом утверждении суждения принимает истинное или ложное значение)
- Покрытие решения/условия (соответствует требованиям 2 и 3)
- Покрытие комбинаций условий (каждая комбинация условий в формулировке суждения встречается хотя бы один раз)
- Покрытие пути (охватывает каждый путь выполнения программы)
Классификация испытаний
В соответствии с восходящей концепцией разработки программного обеспечения интерфейсное тестирование обычно делится на модульное тестирование (модульное тестирование), интеграционное тестирование (интеграционное тестирование) и сквозное тестирование (тестирование E2E). Как видно из рисунка ниже, сложность восходящего тестирования будет продолжать расти, а с другой стороны, польза от тестирования будет продолжать уменьшаться.
Модульное тестирование
Модульное тестирование относится кминимумТесты, выполняемые тестируемыми модулями, обычно относятся к тестам, выполняемым над функциями. Модульное тестирование сочетает в себе программирование и тестирование.Поскольку оно проверяет внутреннюю логику кода, оно использует больше методов тестирования белого ящика. Модульные тесты вынуждают разработчиков писать более тестируемый код, который, как правило, гораздо более читаем, а хорошие модульные тесты служат документацией для тестируемого кода.
Тестируемость функций. Функции с высокой тестируемостью обычно представляют собой чистые функции, то есть функции с предсказуемыми входными и выходными данными. То есть входящие параметры не изменяются внутри функции, запросы API или запросы ввода-вывода не выполняются, а другие нечистые функции, такие как Math.random(), не вызываются.
Наибольшая особенность тестируемой единицы крупности частиц объекта контроля, т. е. высокая независимость от измеряемого объекта, малая трудоемкость.
Фронтенд модульное тестирование
Самая большая разница между интерфейсным модульным тестированием и внутренним модульным тестированием заключается в том, что при интерфейсном модульном тестировании нельзя избежать проблем совместимости, таких как вызов API-интерфейсов совместимости браузера и вызов API-интерфейсов BOM (Browser Object Model). тестирование Требуется запуск в среде (псевдо) браузера.
Что касается классификации тестовой операционной среды, то в основном выделяют следующие тестовые схемы:
- на основеJSDOM
- преимущество: Быстрое и быстрое выполнение, потому что не требуется запуск браузера.
- недостаток: невозможно протестировать связанные операции, такие как просмотр или файлы cookie, и, поскольку это не настоящая среда браузера, нельзя гарантировать правильность некоторых операций, таких как операции, связанные с Dom и BOM, а JSDOM не реализует localStorage. Если его необходимо перезаписать, можно использовать только третье лицо. Куруnode-localStorage(У самой библиотеки есть некоторые проблемы с оценкой среды выполнения) для имитации.
- на основеPhantomJsждите безголовых браузеров
- преимущество: относительно быстрый и имеет реальную среду DOM
- недостаток: Он также не запускается в реальном браузере, его трудно отлаживать, и в проекте много проблем.После выпуска puppeteer автор объявил, что он больше не будет поддерживаться.
- Используйте такие инструменты, как Karma или puppeteer, чтобы вызвать реальную среду браузера для тестирования.
- преимущество: Конфигурация проста, тест можно запустить в реальном браузере, а karma может запускать тестовый код в нескольких браузерах, и его легко отлаживать.
- недостаток: Единственный недостаток в том, что он работает немного медленнее, чем первые два, но находится в пределах допустимого диапазона для модульного тестирования.
Инструменты внешнего модульного тестирования
В последние годы во фронтенде, как грибы после дождя, появилось множество тестовых фреймворков и связанных с ними инструментов.
- тестовая платформа
- karma– Платформа для запуска тестов, разработанная командой Google Angular, имеет простую и гибкую конфигурацию и может легко запускать тесты в нескольких реальных браузерах.
- тестовая среда
- mocha- Отличная среда тестирования, разработанная God Tj, с полной экосистемой, простой организацией тестирования, без ограничений на библиотеки и инструменты утверждений и очень гибкая.
- jasmine- Очень похож на синтаксис Mocha, самая большая разница в том, что он предоставляет самосозданные утверждения и шпионские и заглушки.
- jest— Большая и всеобъемлющая среда тестирования, созданная facebook, среда модульного тестирования, официально рекомендованная React, с простой конфигурацией и высокой скоростью работы. (Примечание: нельзя интегрировать с Karma)
- AVA— Самое большое отличие от приведенного выше тестового фреймворка в том, что он многопоточен и работает быстрее.
- Другие — существуют некоторые другие среды тестирования переднего плана, но сходство относительно велико.Это не что иное, как разница в интеграции таких инструментов, как утверждения и тестовые сваи.Если вы считаете стабильность и зрелость, рекомендуется выбрать Mocha, у которого очень высокие требования к скорости прохождения теста.Рассмотрите jest и AVA
- тестовые средства
- Библиотека утверждений -ChaiЕсли модульный тест не запускается в реальной среде браузера, вы можете просто использовать утверждение узла, но рекомендуется использовать Chai в качестве библиотеки утверждений (предоставляет несколько методов утверждений в стилях TDD и BDD, и экосистема процветает).
- Тестовые заглушки (иначе тестовые двойники) –Sinon,testDoubleДругие инструменты предоставляют такие функции, как тестовые стопки, перехват смоделированных запросов и «путешествие во времени», которые в основном используются для решения «нечистых функций» (например, правильно ли вызывается тестовый обратный вызов, правильно ли XHR инициирует запрос и правильно ли поведение функции корректно после временной задержки) ) тестовые вопросы.
- Инструмент покрытия тестами
- istanbulБазовая реализация istanbul предоставляет такие инструменты, как командная строка, но не может решить проблему компиляции кода и управления им.
- istanbul-instrumenter-loaderСтамбульский плагин Webpack, который может решить проблему компиляции кода и управления им, а также вывода тестового отчета.
Другие ссылки
- chai-as-promiseРасширение возможностей утверждения Chai для промисов
- sinon-chaiРасширьте функцию утверждения Chai с помощью Sinon
- chai-jqueryРасширение утверждений Chai в тестировании пользовательского интерфейса
- официальный сайт стамбулаПредставляет, как Стамбул интегрируется с мультитестовыми средами и поддерживает такие языки, как Typescript.
- Ruan Yifeng - Инструмент покрытия кода Стамбульское руководство по началу работыПредставлены концепции, связанные с покрытием кода и простым использованием Istanbul с Mocha.
Общие тестовые каштаны
При выборе рамы учитывайте следующие условия или проблемы:
- Тесты нужно запускать в реальном браузере
- Выполнение теста должно быть достаточно быстрым
- Тестируемый код представляет собой Typescript, поэтому его использование должно решить проблему компиляции и управления.
- Простая непрерывная интеграция
Наконец, выберите решение, используя Karma+Webpack+Mocha+Chai+Sion+istanbul-instrumenter-loader.
Структура проекта:
Карма может легко интегрироваться с WebPack, нужно только указать необходимые предварительно скомпилированные файлы и создавать инструменты для использования.
karma.conf.js настраивает компиляцию Webpack
module.export=funciton(config){
config.set({
frameworks: ['mocha', 'chai'],
files: [
'tests/setup.js'
],
preprocessors: {
'tests/setup.js': ['webpack']
},
webpack: {
resolve: {
extensions: ['.js','.ts'],
},
module: {
rules: [{
test: /\.ts$/,
loader: 'ts-loader',
query: {
transpileOnly: true
}
},
{
test: /\.ts$/,
exclude: /(node_modules|libs|\.test\.ts$)/,
loader: 'istanbul-instrumenter-loader',
enforce: 'post',
options: {
esModules: true,
produceSourceMap: true
}
}
]
},
devtool: 'inline-source-map',
},
// karma-server support ts/tsx mime
mime: {
'text/x-typescript': ['ts', 'tsx']
},
})
}
В приведенной выше конфигурации есть несколько вещей, на которые следует обратить внимание:
-
setup.js
// 导入所有测试用例 const testsContext = require.context("../src", true, /\.test\.ts$/); testsContext.keys().forEach(testsContext);
используя предоставленный веб-пакетrequire.context()Импортируйте все тестовые файлы единообразно, а затем karma использует setup.js в качестве записи для вызова веб-пакета для компиляции, а затем выполняется тест.
-
поддержка конфигурации mime для ts/tsx
-
Порядок вызова загрузчика istanbul-instrumenter-loader должен быть скомпилирован до компиляции ts или babel и других инструментов компиляции, иначе он будет неточным, за исключением самого тестового файла и расчета покрытия библиотеки зависимостей
- Используйте принудительно: 'post', чтобы гарантировать, что загрузчик вызывающей последовательности
- Используйте исключение, чтобы исключить сторонние библиотеки и сами тестовые файлы из расчета покрытия.
Для других конфигураций, пожалуйста, обратитесь к карме и веб-пакету, а также к соответствующей документации плагина.
-
чистый функциональный тест
/** * 验证邮箱 * @params input 输入值 * @return 返回是否是邮箱 */ export function mail(input: any): boolean { return /^[\w\-]+(\.[\w\-]+)*@[\w\-]+(\.[\w\-]+)+$/.test(input); } /* ↑↑↑↑被测函数↑↑↑↑ */ /* ↓↓↓↓测试代码↓↓↓↓ */ describe('验证邮箱#mail', () => { it('传入邮箱字符串,返回true', () => { expect(mail('abc@123.com')).to.be.true; expect(mail('abc@123.com.cn')).to.be.true; expect(mail('a-b-c@123.com.cn')).to.be.true; expect(mail('a_b_c@123.com.cn')).to.be.true; expect(mail('a_b_c@123-4-5-6.com.cn')).to.be.true; }); it('传入非邮箱字符串,返回true', () => { expect(mail('')).to.be.false; expect(mail('abc@')).to.be.false; expect(mail('@123.com')).to.be.false; expect(mail('abc@123')).to.be.false; expect(mail('123.com')).to.be.false; }); });
-
Тестирование промисов или других асинхронных операций
Mocha поддерживает асинхронное тестирование, в основном тремя способами:
-
использовать асинхронное ожидание
it(async ()=>{ await asynchronous() // 一些断言 })
-
Использовать функцию обратного вызова
it(done=>{ Promise.resolve().then(()=>{ // 断言 done() // 测试结束后调用回调标识测试结束 }) })
-
вернуть обещание
it(()=>{ // 直接返回一个Promise,Mocha会自动等待Promise resolve return Promise.resolve().then(()=>{ // 断言 }) })
-
-
Тест с HTTP-запросом
function http(method,url,body){ // 使用XHR发送ajax请求 } /* ↑↑↑↑被测函数↑↑↑↑ */ /* ↓↓↓↓测试代码↓↓↓↓ */ it('method为GET', (done) => { const xhr=fake.useFakeXMLHttpRequest() const request = [] xhr.onCreate = xhr => { requests.push(xhr); }; requests[0].respond(200, { "Content-Type": "application/json" },'[{ "id": 12, "comment": "Hey there" }]'); get('/uri').then(() => { xhr.restore(); done(); }); expect(request[0].method).equal('GET'); })
Или инкапсулировать fakeXMLHttpRequest
/** * 使用fakeXMLHttpRequest * @param callback 回调函数,接受请求对象作为参数 */ export function useFakeXHR(callback) { const fakeXHR = useFakeXMLHttpRequest(); const requests: Array<any> = []; // requests会将引用传递给callback,因此使用const,避免指针被改写。 fakeXHR.onCreate = request => requests.push(request); callback(requests, fakeXHR.restore.bind(fakeXHR)); } it('method为GET', (done) => { useFakeXHR((request, restore) => { get('/uri').then(() => { restore(); done(); }); expect(request[0].method).equal('GET'); request[0].respond(); }) })
useFakeXHR инкапсулирует useFakeXMLHttpRequest Sinon для реализации поддельных запросов XHR.
Fake XHR and server - Sinon.JSСправочник по официальной документации
Разница между Fake XHR и Fake server: последний представляет собой инкапсуляцию первого более высокого уровня, а гранулярность Fake server больше.
-
зависящие от времени тесты
// 延迟delay毫秒后调用callback function timer(delay,callback){ setTimeout(callback,delay); } /* ↑↑↑↑被测函数↑↑↑↑ */ /* ↓↓↓↓测试代码↓↓↓↓ */ it('timer',()=>{ const clock=sinon.useFakeTimers() const spy=sinon.spy() // 测试替身 timer(1000,spy) clock.tick(1000) // "时间旅行,去到1000ms后" expect(spy.called).to.be.true clock.restore() // 恢复时间,否则将影响到其他测试 })
-
Тестирование вызовов API, связанных с браузером
- сессионный каштан
/** * WebStorage封装 * @param storage 存储对象,支持localStorage和sessionStorage * @return 返回封装的存储接口 */ export function Storage(storage: Storage) { /** * 获取session * @param key * @return 返回JSON解析后的值 */ function get(key: string) { const item = storage.getItem(key); if (!item) { return null; } else { return JSON.parse(item); } } } export default Storage(window.sessionStorage); /* ↑↑↑↑被测函数↑↑↑↑ */ /* ↓↓↓↓测试代码↓↓↓↓ */ describe('session', () => { beforeEach('清空sessionStorage', () => { window.sessionStorage.clear(); }); describe('获取session#get', () => { it('获取简单类型值', () => { window.sessionStorage.setItem('foo', JSON.stringify('foo')) expect(session.get('foo')).to.equal('foo'); }); it('获取引用类型值', () => { window.sessionStorage.setItem('object', JSON.stringify({})) expect(session.get('object')).to.deep.equal({}); }); it('获取不存在的session', () => { expect(session.get('aaa')).to.be.null; }); }); })
- Установите каштан заголовка
/** * 设置tab页title * @param title */ export function setTitle(title) { // 当页面中嵌入了flash,并且页面的地址中含有“片段标识”(即网址#之后的文字)IE标签页标题被自动修改为网址片段标识 if (userAgent().app === Browser.MSIE || userAgent().app === Browser.Edge) { setTimeout(function () { document.title = title }, 1000) } else { document.title = title } } /* ↑↑↑↑被测函数↑↑↑↑ */ /* ↓↓↓↓测试代码↓↓↓↓ */ it('设置tab页title#setTitle', () => { // 这个测试不是个好的测试,测试代码更改了页面DOM结构 const originalTitle = document.title; // 保存原始title,测试结束后恢复 const clock = sinon.useFakeTimers() setTitle('test title'); clock.tick(1000) expect(document.title).to.equal('test title') setTitle(originalTitle); // 恢复原本的title clock.tick(1000) clock.restore() })
Модульное тестирование компонентов React
Тестирование компонентов React по сути не сильно отличается от других модульных тестов, но модульное тестирование компонентов React намного сложнее из-за вовлеченной проблемы «отрисовки в браузере», а компоненты React являются дополнением к основным компонентам пользовательского интерфейса (таким как Button, Link) и т. д. Вне базовых компонентов на самом деле трудно удерживать его «масштаб» в диапазоне «минимальной тестовой единицы», хотя сложность компонента можно уменьшить за счет «поверхностного рендеринга» (отсутствие рендеринга подкомпоненты). Фактически, с точки зрения объема и значимости модульного тестирования, большинство тестов компонентов пользовательского интерфейса трудно классифицировать как модульное тестирование. Поскольку «все считают это само собой разумеющимся», в этом документе тестирование компонентов пользовательского интерфейса по-прежнему рассматривается как модульное тестирование.
Мы не можем решить, жив ли кот, не открывая коробку.
React — это не черный ящик, поэтому, чтобы протестировать React, вы должны сначала понять, как React выполняет рендеринг. Конечно, нам не нужно разбираться в исходном коде React, достаточно знать об этом.
В двух словах, для отображения компонента React на странице требуются следующие шаги.
- Вычислить виртуальный объект DOM на основе состояния
- Создание реальной структуры Dom на основе виртуального DOM
- Гора Дом на страницу
Но в тесте нам не нужно монтировать компонент на страницу, просто есть среда Dom, если вам не нужен полный компонент рендеринга, вы можете даже не иметь среды Dom.
Enzyme (энзим, катализатор) – это инструмент для тестирования React, предоставляемый Airbnb. Он обеспечивает поверхностный рендеринг | полный рендеринг | статический рендеринг для тестирования компонентов React. Тестируйте с поверхностным рендерингом.
Прежде чем использовать Enzyme для тестирования, вам необходимо инициализировать адаптер React.Пожалуйста, обратитесь к официальному руководству для конкретной конфигурации.
-
Shallow Rendering
Отрисовывается только самая внешняя структура компонента, а подкомпоненты не визуализируются.
Пример полного теста рендеринга Shallow:
import * as React from 'react' import * as classnames from 'classnames' import { ClassName } from '../helper' import * as styles from './styles.desktop.css' const AppBar: React.StatelessComponent<any> = function AppBar({ className, children, ...props } = {}) { return ( <div className={classnames(styles['app-bar'], ClassName.BorderTopColor, className)} {...props}>{children}</div> ) } export default AppBar
import * as React from 'react'; import { shallow } from 'enzyme'; import { expect } from 'chai'; import AppBar from '../ui.desktop' describe('ShareWebUI', () => { describe('AppBar@desktop', () => { describe('#render', () => { it('默认渲染', () => { shallow(<AppBar></AppBar>) }); it('正确渲染子节点', () => { const wrapper = shallow(<AppBar><div>test</div></AppBar>) expect(wrapper.contains(<div>test</div>)).to.be.true }); it('允许设置ClassName', () => { const wrapper = shallow(<AppBar className='test'></AppBar>) expect(wrapper.hasClass('test')).to.be.true }); it('允许设置其他自定义props', () => { const wrapper = shallow(<AppBar name='name' age={123}></AppBar>) expect(wrapper.props()).to.be.include({ name: 'name', age: 123 }) }); }); }); });
-
Full Rendering
Отображает реальную структуру компонента Dom. Если вам нужно протестировать нативные события, вы должны использовать этот метод рендеринга.
-
Static Rendering
Аналогично результату, полученному сканером, получите HTML-строку страницы, используйтеCheerioработать.
Моделирование события в режиме поверхностного рендеринга Enzyme не является запуском реального события, а является реализацией «обмана». Например, buttonWrapper.simulate('click') просто вызывает функцию, переданную в параметр onClick компонента Button. Вот и все.
конкретное описание:воздух не NB.IO/enzyme/docs…
Много ям фермента:воздух не NB.IO/enzyme/docs…
Если вы вызываете setState() в асинхронной операции, вам нужно утверждать в следующем такте при тестировании (есть много подобных проблем):
> /* 模拟点击 */ > wrapper.find('UIIcon').simulate('click') > expect(wrapper.state('loadStatus')).to.equal(1) // 点击立即改变加载状态为正在加载 > /* 在promise.then 中设置setState,因此需要在下一个时钟进行断言 */ > setTimeout(() => { > expect(wrapper.state('loadStatus')).to.equal(2) > done() > }, 0) >
Интеграционное тестирование
Интеграционное тестирование относится к инкапсуляции высокоуровневых функций или классов путем объединения и интеграции протестированных функций модульного тестирования на основе модульного тестирования и тестирования этих функций или классов.
Самая большая сложность в интеграционном тестировании заключается в том, что гранулярность больше, логика сложнее, и больше внешних факторов, которые не могут гарантировать управляемость и независимость теста. Решение заключается в использовании тестовых заглушек (тестовых двойников), то есть в замене вызываемой подфункции или модуля, то есть в сокрытии деталей подмодуля и управлении поведением подмодуля для достижения ожидаемое испытание. (Здесь предполагается, что подмодуль прошел полные модульные тесты, поэтому можно предположить, что состояние подмодуля известно.)
Typescript компилируется в Commonjs:
// 编译前
import * as B from 'B'
import {fn} from 'C'
export function A() {
B.fn.name()
fn.name()
}
export class A1 {
}
// 编译后
define(["require", "exports", "B", "C"], function (require, exports, B, C_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function A() {
B.fn.name();
C_1.fn.name();
}
exports.A = A;
var A1 = /** @class */ (function () {
function A1() {
}
return A1;
}());
exports.A1 = A1;
});
Все экспортируемые функции и классы находятся под объектом экспорта.Поскольку вызов на самом деле называется exports.exportName, если вы хотите заглушку, вам нужны свойства под экспортом заглушки.Функция-заглушка, предоставляемая Sinon, позволяет нам изменять любой объект под объектом Атрибут, обратите внимание, что он должен находиться под объектом, то есть его нельзя напрямуюsinon.stub(fn)
, Толькоsinon.stub(obj,'fnName')
. А в ES6 можно пройтиimport * as moduleName from ‘moduleName ’
Экспорт всего модуля в один объект решает проблему заглушки.
Рассмотрите возможность наличия следующих зависимостей модулей:
Среди них модуль A зависит от модулей B и C, а модуль B зависит от модуля C. В этом случае мы можем выбрать только модуль-заглушку C. В это время модуль C в модуле B также будет затронут заглушкой. лучший способ - одновременно подключить модуль-заглушку B и модуль C.
Жареные яйца с каштанами
import { clientAPI } from '../../../clientapi/clientapi';
/**
* 通过相对路径获取缓存信息
* @param param0 参数对象
* @param param0.relPath 相对路径
*/
export const getInfoByPath: Core.APIs.Client.Cache.GetInfoByPath = function ({ relPath }) {
return clientAPI('cache', 'GetInfoByPath', { relPath });
}
/* ↑↑↑↑被测函数↑↑↑↑ */
/* ↓↓↓↓测试代码↓↓↓↓ */
import { expect } from 'chai'
import { stub } from 'sinon'
import * as clientapi from '../../../clientapi/clientapi';
import { getInfoByPath, getUnsyncLog, getUnsyncLogNum } from './cache'
describe('cache', () => {
beforeEach('stub', () => {
stub(clientapi, 'clientAPI')
})
afterEach('restore', () => {
clientapi.clientAPI.restore()
})
it('通过相对路径获取缓存信息#getInfoByPath', () => {
getInfoByPath({ relPath: 'relPath' })
expect(clientapi.clientAPI.args[0][0]).to.equal('cache') // 请求资源正确
expect(clientapi.clientAPI.args[0][1]).to.equal('GetInfoByPath') // 请求方法正确
expect(clientapi.clientAPI.args[0][2]).to.deep.equal({ relPath: 'relPath' }) // 请求体正确
})
})
Или используйте песочницу, предоставленную Sinon, которую легче восстанавливать и не нужно восстанавливать каждый заглушенный объект отдельно.
import { rsaEncrypt } from '../../util/rsa/rsa';
import { getNew} from '../apis/eachttp/auth1/auth1';
/**
* 认证用户
* @param account {string}
* @param password {string}
*/
export function auth(account: string, password: string, ostype: number, vcodeinfo?: Core.APIs.EACHTTP.Auth1.VcodeInfo): Promise<Core.APIs.EACHTTP.AuthInfo> {
return getNew({
account,
password: encrypt(password),
deviceinfo: {
ostype: ostype
},
vcodeinfo
});
}
/* ↑↑↑↑被测函数↑↑↑↑ */
/* ↓↓↓↓测试代码↓↓↓↓ */
import { createSandbox } from 'sinon'
import * as auth1 from '../apis/eachttp/auth1/auth1'
import * as rsa from '../../util/rsa/rsa'
const sandbox = createSandbox()
describe('auth', () => {
beforeEach('stub',()=>{
sandbox.stub(rsa,'rsaEncrypt')
sandbox.stub(auth1,'getNew')
})
afterEach('restore',()=>{
sandbox.restore()
})
it('认证用户#auth', () => {
auth('account', 'password', 1, { uuid: '12140661-e35b-4551-84cf-ce0e513d1596', vcode: '1abc', ismodif: false })
rsa.rsaEncrypt.returns('123') // 控制返回值
expect(rsa.rsaEncrypt.calledWith('password')).to.be.true
expect(auth1.getNew.calledWith({
account: 'account',
password: '123',
deviceinfo: {
ostype: 1
},
vcodeinfo: {
uuid: '12140661-e35b-4551-84cf-ce0e513d1596',
vcode: '1abc',
ismodif: false
}
})).to.be.true
})
}
Справочная документация:
- Stubs - Sinon.JSСвязанные концепции и использование Stub
- Sandboxes - Sinon.JSСвязанные понятия и использование песочницы
Сквозное тестирование (тестирование E2E)
Сквозное тестирование — это тест верхнего уровня, который рассматривает программу как полный черный ящик в качестве пользователя, открывает приложение для имитации ввода и проверяет правильность функциональности и интерфейса.
Некоторые проблемы, которые необходимо решить для сквозного тестирования:
-
Экологические проблемы
То есть, как сделать так, чтобы среда перед каждым тестом была "чистой". Например, необходимо проверить работоспособность списка будучи пустым. Если в предыдущем тесте добавляется новый список, то следующий тест не будет в состоянии получить состояние пустого списка.
Самое простое решение - вызвать внешний скрипт для очистки БД и т.п. до или после выполнения всех тестов, либо можно решить перехватом запроса и кастомизацией ответа (это приведет к высокой сложности теста и недостаточно "реальной ").
-
поиск элемента
Если код часто меняется, структура компонентов также часто меняется, и если вы ищете элементы действия, основанные на структуре DOM, вы окажетесь в аду поддержки селекторов. Лучше всего использовать метод test-id, но этот метод требует сотрудничества разработчиков и тестировщиков для определения семантических идентификаторов тестов для активных элементов.
-
ожидание операции
Например, изменения интерфейса, вызванные асинхронными сетевыми запросами или анимацией интерфейса и т. д., сделают неизвестным время получения элементов операции. Решение состоит в том, чтобы дождаться завершения запроса на прослушивание и успешного извлечения нужного элемента.
-
Используйте действия вместо утверждений
Следует больше полагаться на операции, а не на утверждения. Например, если операция зависит от существования элемента A, вам не нужно «судить, существует ли элемент A на странице», а следует перейти к «непосредственному получению элемента A и работе», потому что, если элемент A не существует , точно не получится. , операция после ассерта будет бессмысленной, поэтому можно использовать операцию напрямую вместо функции ожидания ассерта.
См. документацию по сквозному тестированию.