Краткое изложение технических решений для фронтенд-тестирования

модульный тест тестовое задание
Краткое изложение технических решений для фронтенд-тестирования

Автор этой статьи:речная вода

Эта статья в основном знакомит с интерфейсоммодульный тестнекоторые технические решения.

Многие модульные тестирующие технические решения, существуют синергизм между различными инструментами друг друга, есть также функции перекрываются, чтобы не дать нам небольшие трудности с программой тестирования, а также сES6, TypeScriptПоявление юнит-тестов добавляет массу других шагов, полная настройка которых зачастую требует больших временных затрат. Я надеюсь, что благодаря освоению соответствующих ролей этих инструментов для полного внешнего тестирования технических решений. Переднего конца полевого модульного тестирования много, здесь в основном речь идет о том, как внешние компоненты для модульного тестирования, и, наконец, в основном будут представлены дляReactКраткое изложение некоторых методов тестирования компонентов.

Универсальный тест

Основная часть модульного тестирования состоит в том, чтобы делать утверждения, например, в традиционных языках.assertфункция, если определенное состояние текущей программы соответствуетassertОжидается, что эта программа может выполняться нормально, в противном случае приложение будет закрыто напрямую. Таким образом, мы можем напрямую использоватьNodeвключен вassertМодули делают утверждения.

Проверить на простейшем примере

function multiple(a, b) {
    let result = 0;
    for (let i = 0; i < b; ++i)
        result += a;
    return result;
}
const assert = require('assert');
assert.equal(multiple(1, 2), 3));

Этот пример подходит для использования в базовых сценариях, а также может использоваться в качестве метода модульного тестирования.

nodejsавтономныйassertМодуль предоставляет следующие методы утверждения, которые могут удовлетворить потребности только некоторых простых сценариев.

assert.deepEqual(actual, expected[, message])
assert.deepStrictEqual(actual, expected[, message])
assert.doesNotMatch(string, regexp[, message])
assert.doesNotReject(asyncFn[, error][, message])
assert.doesNotThrow(fn[, error][, message])
assert.equal(actual, expected[, message])
assert.fail([message])
assert.ifError(value)
assert.match(string, regexp[, message])
assert.notDeepEqual(actual, expected[, message])
assert.notDeepStrictEqual(actual, expected[, message])
assert.notEqual(actual, expected[, message])
assert.notStrictEqual(actual, expected[, message])
assert.ok(value[, message])
assert.rejects(asyncFn[, error][, message])
assert.strictEqual(actual, expected[, message])
assert.throws(fn[, error][, message])

автономныйassertОн не используется специально для модульного тестирования, а предоставляемая информация об ошибках плохо документирована.demoОкончательное выполнение выдаст следующий отчет:

$ node index.js
assert.js:84
  throw new AssertionError(obj);
  ^

AssertionError [ERR_ASSERTION]: 2 == 3
    at Object.<anonymous> (/home/quanwei/git/index.js:4:8)
    at Module._compile (internal/modules/cjs/loader.js:778:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:789:10)
    at Module.load (internal/modules/cjs/loader.js:653:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:593:12)
    at Function.Module._load (internal/modules/cjs/loader.js:585:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:831:12)
    at startup (internal/bootstrap/node.js:283:19)
    at bootstrapNodeJSCore (internal/bootstrap/node.js:623:3)

Из-за встроенных зависимостей модуляNodeЕго собственная версия не может быть обновлена ​​свободно, поэтому гибкости использования встроенного пакета иногда недостаточно.Кроме того, многие наши функции утверждений также необходимо выполнять на стороне браузера, поэтому нам необходимо поддерживать как браузер, вNodeбиблиотека побочных утверждений. Глядя одновременно на приведенный выше вывод, вы можете видеть, что этот отчет больше похож на отчет об ошибке программы, чем на отчет модульного тестирования. Когда мы проводим модульное тестирование, нам часто нужна библиотека утверждений для предоставления хороших отчетов о тестировании, чтобы мы могли сразу увидеть, какие утверждения проходят, а какие нет, поэтому по-прежнему необходимо использовать профессиональную библиотеку утверждений для модульных тестов.

chai

chai

chaiЭто очень популярная библиотека утверждений, которая более заметна, чем аналогичные продукты.chaiпри условииTDD(Разработка через тестирование) иBDD(Разработка, основанная на поведении) Существует два стиля функций утверждений. Мы не будем вводить здесь преимущества и недостатки этих двух стилей. Эта статья в основном посвященаBDDстиль презентации.

Чай в стиле TDD

var assert = require('chai').assert
  , foo = 'bar'
  , beverages = { tea: [ 'chai', 'matcha', 'oolong' ] };

assert.typeOf(foo, 'string'); // without optional message
assert.typeOf(foo, 'number', 'foo is a number'); // with optional message
assert.equal(foo, 'bar', 'foo equal `bar`');
assert.lengthOf(foo, 3, 'foo`s value has a length of 3');
assert.lengthOf(beverages.tea, 3, 'beverages has 3 types of tea');

chaiСравниватьNodeавтономныйassertДобавлен параметр описания утверждения, который может улучшить читаемость отчета о тестировании.

$ node chai-assert.js

/home/quanwei/git/learn-tdd-bdd/node_modules/chai/lib/chai/assertion.js:141
      throw new AssertionError(msg, {
      ^
AssertionError: foo is a number: expected 'bar' to be a number
    at Object.<anonymous> (/home/quanwei/git/learn-tdd-bdd/chai-assert.js:6:8)
    at Module._compile (internal/modules/cjs/loader.js:778:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:789:10)
    at Module.load (internal/modules/cjs/loader.js:653:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:593:12)
    at Function.Module._load (internal/modules/cjs/loader.js:585:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:831:12)
    at startup (internal/bootstrap/node.js:283:19)
    at bootstrapNodeJSCore (internal/bootstrap/node.js:623:3)

Чай в стиле BDD

chaiизBDDиспользование стиляexpectКак начало семантики функции также почти всеBDDВсе библиотеки инструментов следуют этому стилю.

chaiизexpectСтиль утверждения следующий

expect(foo).to.be.a('string');
expect(foo).to.equal('bar');
expect(foo).to.have.lengthOf(3);

BDDИдея состоит в том, что написание модульных тестов похоже на написание требований к продукту, не заботясь о внутренней логике, и каждый вариант использования читается как документ. Например, следующий вариант использования:

  1. foo это строка ->expect(foo).to.be.a('string')
  2. строка foo содержит 'bar' ->expect(foo).to.include('bar')
  3. строка foo не содержит 'biz' ->expect(foo).to.not.include('biz')

Вы можете видеть, что этот стиль тестовых примеров более удобочитаем.

Другие библиотеки утвержденийexpect.js should.js better-assert , unexpected.jsЭти библиотеки утверждений предоставляют только чистые функции утверждений, и вы можете выбирать различные библиотеки для использования в соответствии со своими предпочтениями.

После того, как у нас есть библиотека утверждений, нам также нужно использовать среду тестирования, чтобы лучше организовать наши утверждения.

мокко и жасмин

mocha jasmine

mochaЭто классический тестовый фреймворк (Test Framework).Тестовый фреймворк предоставляет основу для модульного тестирования, которая может разделить различные подфункции на несколько файлов или выполнять различные функциональные тесты для разных подфункций подмодуля для генерации A структурированный отчет об испытаниях. Напримерmochaпри условииdescribeа такжеit Опишите структуру прецедентов, предоставивbefore, after, beforeEach, afterEachФункции жизненного цикла, которые обеспечиваютdescribe.only ,describe.skip , it.only, it.skipИспользуется для выполнения указанной части набора тестов.

const { expect } = require('chai');
const { multiple } = require('./index');

describe('Multiple', () => {
    it ('should be a function', () => {
        expect(multiple).to.be.a('function');
    })

    it ('expect 2 * 3 = 6', () => {
        expect(multiple(2, 3)).to.be.equal(6);
    })
})

Платформа тестирования не зависит от базовой библиотеки утверждений, даже если используется собственныйassertТакже доступны модули. Вручную импортировать каждый файлchaiЭто более хлопотно, в это время вы можете датьmochaНастройте глобальные сценарии в корневом каталоге проекта..mocharc.jsЗагрузите библиотеку утверждений в файл, чтобы каждый файл можно было использовать напрямую.expectфункция.

// .mocharc.js
global.expect = require('chai').expect;

Использование mocha может вывести наши модульные тесты в хороший отчет о тестировании.mocha *.test.js

Когда есть ошибка, вывод выглядит следующим образом

Поскольку форматы пакетов, необходимые для запуска в разных средах, различаются, нам нужно выполнять разные преобразования формата пакетов для разных сред.Чтобы понять, что нужно сделать для запуска модульных тестов на разных концах, вы должны сначала понять общие форматы пакетов. .

В настоящее время у нас есть три основных формата модулей:AMD, CommonJS, ES Module.

AMD

AMDдаRequireJSБолее старая спецификация, популярная в процессе продвижения. В настоящее время, будь то браузеры илиNodeНи один из них не поддерживается по умолчанию.AMDСтандарт определяетdefineа такжеrequireфункция,defineИспользуется для определения модулей и их зависимостей,requireИспользуется для загрузки модулей. Например

<!doctype html>
<html lang="en">
    <head>
        <meta charset="UTF-8"/>
        <title>Document</title>
+        <script
+			src="https://requirejs.org/docs/release/2.3.6/minified/require.js"></script>
+        <script src="./index.js" />
</head>
    <body></body>
</html>
// index.js
define('moduleA', ['https://some/of/cdn/path'], function() {
    return { name: 'moduleA' };
});

define(function(require) {
    const fs = require('fs');
    return fs;
})

define('moduleB', function() {
    return { name: 'module B' }
});

require(['moduleA', 'moduleB'], function(moduleA, moduleB) {
    console.log(module);
});

используется здесьRequireJSтак какAMDдвигатель видноdefineЭта функция определяет, от каких модулей в данный момент зависят, и асинхронно обращается к текущему модулю после его загрузки.Эта функция делает AMD особенно подходящей для асинхронной загрузки на стороне браузера.

мы можем использоватьwebpackупаковать одинamdПосмотрите на реальный код модуля

// entry.js
export default function sayHello() {
    return 'hello amd';
}
// webpack.config.js
module.exports = {
    mode: 'development',
    devtool: false,
    entry: './entry.js',
    output: {
        libraryTarget: 'amd'
    }
}

Окончательный сгенерированный код (упрощает ненужную логику)

// dist/main.js
define(() => ({
    default: function sayHello() {
        return 'hello amd';
    }
}));

в браузере/Nodeхочу использоватьAMDнужно импортировать глобальноRequireJS, типичная проблема модульного тестирования заключается в инициализацииkarmaКогда вас спросят, использовать лиRequireJS, но вообще мало кто сейчас им пользуется.

CommonJS

можно сократить какCJS, ЭтоТехнические характеристикиглавным образом для определенияNodeформат упаковки,CJSОпределены три ключевых слова, а именноrequire,exports, module, почти всеNodeпакеты и внешний интерфейсNPMпакет будет конвертирован в этот формат,CJSНеобходимо использовать на стороне браузераwebpackилиbrowserifyПодождите, пока инструмент будет упакован.

ES Module

ES ModuleдаES 2015Спецификация модуля, определенная в , которая определяет представление какimportа такжеexport, это формат, который обычно используется в нашей разработке. Хотя многие новые браузеры в настоящее время поддерживают<script type="module">Теперь он поддерживает запуск прямо в браузере.ES6код, но браузер не поддерживаетnode_modules, так что наш оригиналES6Код по-прежнему не может работать в браузере, поэтому я временно думаю, что браузер его не поддерживает.ES6код, все еще нужно сделать преобразование.

В следующей таблице показан поддерживаемый диапазон каждого формата, а скобки указывают на то, что он должен поддерживаться внешними инструментами.

Node браузер
AMD не поддерживается (require.js, r.js) Не поддерживается (require.js)
CommonJS служба поддержки Не поддерживается (webpack/browserify)
ESModule не поддерживается (бабель) Не поддерживается (веб-пакет)

Чтобы выполнять модульные тесты в разных средах, вам нужно набирать пакеты, соответствующие разным средам, поэтому при создании тестовой цепочки инструментов вы должны определить, в какой среде вы работаете.NodeПросто добавьте слойbabelконверсия, если она в реальном браузере, ее нужно увеличитьwebpackШаги обработки.

Итак, чтобы иметь возможностьNodeотносящийся к окружающей средеMochaиспользуется вES ModuleЕсть два способа

  1. NodeОкружение изначально благосклонноES Module (node version >= 15)
  2. использоватьbabelкод для преобразования

Первый метод пропускается, а второй метод использует следующую конфигурацию

npm install @babel/register @babel/core @babel/preset-env --save-dev
// .mocharc.js
+ require('@babel/register');
global.expect = require('chai').expect;
// .babelrc
+ {
+    "presets": ["@babel/preset-env" ,“@babel/preset-typescript”]
+ }

Аналогично, если использовать в проектеTypeScript, вы можете использоватьts-node/registerрешить, потому чтоTypeScriptродная поддержкаES ModuleПеревести вCJS, так что поддержитеTypeScriptтогда вам не нужно использоватьbabelдля преобразования. (Здесь предполагается, чтоTypeScriptконфигурация по умолчанию)

npm install ts-node typescript --save-dev
// .mocharc.js
require('ts-node/register');

Mochaизначально поддерживает браузеры иNodeДля стороннего тестирования, чтобы протестировать на стороне браузера, нам нужно написать html, который использует<script src="mocha.min.js"> , а затем вставить все локальные файлы в html для завершения теста.Эффективность ручного проектирования относительно низка, поэтому для выполнения этой задачи необходимо использовать инструменты.Этот инструментKarma.

KarmaПо сути, запуск веб-сервера локально, затем запуск внешнего браузера для загрузки сценария начальной загрузки, который загружает все наши исходные и тестовые файлы в браузер, который в конечном итоге выполнит наши тесты в коде варианта использования на стороне браузера. так что используйтеKarma + mocha +chaiВы можете создать полную цепочку инструментов модульного тестирования на стороне браузера.

npm install karma mocha chai karma-mocha karma-chai --save-dev
npx karma init
// Which testing framework do you want to use: mocha
// Do you want to use Require.js: no
// Do you want capture any browsers automatically: Chrome

здесьKarmaвыбрано при инициализацииMochaподдержка, затем втораяRequire.jsОбычно нет, если только не используется в бизнес-кодеamdтип упаковки. третий вариантChromeв качестве тестового браузера. Затем настройте его отдельно в кодеchai.

// karma.conf.js
module.exports = function(config) {
  config.set({

    // base path that will be used to resolve all patterns (eg. files, exclude)
    basePath: '',

    // frameworks to use
    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
-    frameworks: ['mocha'],
+    frameworks: ['mocha', 'chai'],

    // list of files / patterns to load in the browser
    files: [],

KarmaизframeworksРоль заключается в глобальном внедрении некоторых зависимостей.Mochaа такжеchaiПредоставленные инструменты, связанные с тестированием, доступны глобально для использования в коде.KarmaПросто отправьте наш файл в браузер для выполнения, но, согласно вышеизложенному, наш код должен пройти черезwebpackилиbrowserifyОн может работать только на стороне браузера после упаковки.

Если исходный код ужеCJS, вы можете использоватьbrowserifyДля поддержки работы на стороне браузера, в основном нулевой настройки, но часто реальный мир более сложен, у нас естьES6 ,JSXтак же какTypeScriptдля обработки, поэтому здесь мы используемwebpack.

Нижеwebpackинформация о конфигурации.

npm install karma-webpack@4 webpack@4 @babel/core @babel/preset-env @babel/preset-react babel-loader --save-dev
// karma.conf.js
module.exports = function(config) {
  config.set({

    // base path that will be used to resolve all patterns (eg. files, exclude)
    basePath: '',

    // frameworks to use
    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
    frameworks: ['mocha', 'chai'],


    // list of files / patterns to load in the browser
    files: [
+      { pattern: "test/*.test.js", watched: false }
    ],

    preprocessors: {
+      'test/**/*.js': [ 'webpack']
    },

+    webpack: {
+       module: {
+			rules: [{
+           test: /.*\.js/,
+           use: 'babel-loader'
+         }]
+     }
+    },
// .babelrc
{
    "presets": ["@babel/preset-env", "@babel/preset-react"]
}

Здесь мы тестируемReactКод программы следующий

// js/index.js
import React from 'react';
import ReactDOM from 'react-dom';

export function renderToPage(str) {
    const container = document.createElement('div');
    document.body.appendChild(container);
    console.log('there is real browser');
    return new Promise(resolve => {
        ReactDOM.render(<div>{ str } </div>, container, resolve);
    });
}

// test/index.test.js
import { renderToPage } from '../js/index';

describe('renderToPage', () => {
    it ('should render to page', async function () {
        let content = 'magic string';
        await renderToPage(content);
        expect(document.documentElement.innerText).to.be.contain(content);
    })
})

и откройте локальный браузер

karma browser

Вы можете видеть, что тестовая программа теперь работает в реальном браузере.

Потому что графические пары тестовCIМашина не дружелюбная, поэтому есть выборpuppeteerзаменятьChrome.

Кроме того, это очень тяжелые пакеты, если они не сильно зависят от реальных браузеров, их можно использовать.JSDOMсуществуетNodeКлиент имитирует среду браузера.

Обобщить небольшую цепочку инструментов

  • Цепочка инструментов тестирования в среде Node может быть:mocha + chai + babel
  • Смоделированная среда браузера может быть:mocha + chai + babel + jsdom
  • Набор тестовых инструментов в реальной среде браузера может быть:karma + mocha + chai + webpack + babel

Конвейер тестирования часто требует одновременного использования множества инструментов, что усложняет настройку, а также некоторых дополнительных инструментов, таких как модульное покрытие (Istanbul), моделирование функции/времени (sinon.js) и другие инструменты. Иногда согласование между инструментами может быть не идеальным, а выбор отнимает много времени и сил.

jasmineВнешний вид .jasmineОбеспечьте тестовую среду, которая включает в себя структуру процесса тестирования, функции утверждения, фиктивные инструменты и другие инструменты, которые будут встречаться при тестировании. можно аппроксимировать какjasmine = mocha + chai + 辅助工具.

попробуй дальшеjasmineрабочий процесс.

использоватьnpx jasmine init После инициализации он будет сгенерирован в текущем каталогеspecкаталог, который содержит файл конфигурации по умолчанию

// ./spec/support/jasmine.json
{
  "spec_dir": "spec",
  "spec_files": [
    "**/*[sS]pec.js"
  ],
  "helpers": [
    "helpers/**/*.js"
  ],
  "stopSpecOnExpectationFailure": false,
  "random": true
}

Если вы хотите загрузить некоторую глобальную конфигурацию, вы можетеspec/helpersположить некоторые в каталогjsфайл, как сказано в конфигурации, jasmine будет выполняться при запускеspec/helpersвсе в каталогеjsдокумент.

Например, мы часто используемes6грамматика, вам нужно добавитьes6служба поддержки.

новыйspec/helpers/babel.jsНапишите следующую конфигурацию.

npm install @babel/register @babel/core @babel/preset-env --save-dev
// spec/helpers/babel.js
require('babel-register');
// .babelrc
{
    "presets": ["@babel/preset-env"]
}

а такжеmochaто же самое, если нужноTypeScriptподдержку, вы можете использовать следующую конфигурацию

npm install ts-node typescript --save-dev
// spec/helpers/typescript.js
require('ts-node/register');

в файле конфигурацииspec_dirдаjasmineсогласованный каталог файлов прецедентов,spec_filesФормат файла варианта использования указан какxxx.spec.js.

С этой конфигурацией по умолчанию вы можете написать варианты использования по мере необходимости, например

// ./spec/index.spec.js
import { multiple } from '../index.js';

describe('Multiple', () => {
    it ('should be a function', () => {
        expect(multiple).toBeInstanceOf(Function);
    })

    it ('should 7 * 2 = 14', () => {
        expect(multiple(7, 2)).toEqual(14);
    })

    it ('should 7 * -2 = -14', () => {
        expect(multiple(7, -2)).toEqual(-14);
    })
})

jasmineСтиль утверждения иchaiОчень разные,jasmineизAPIследующим образом, сchaiНапишите намного меньше, чем., а поддерживаемые функции понятнее, без рассмотрения того, как их комбинировать, и далее описываетсяjestПлатформы тестирования также используют этот стиль.

nothing()
toBe(expected)
toBeCloseTo(expected, precisionopt)
toBeDefined()
toBeFalse()
toBeFalsy()
toBeGreaterThan(expected)
toBeGreaterThanOrEqual(expected)
toBeInstanceOf(expected)
toBeLessThan(expected)
toBeLessThanOrEqual(expected)
toBeNaN()
toBeNegativeInfinity()
toBeNull()
toBePositiveInfinity()
toBeTrue()
toBeTruthy()
toBeUndefined()
toContain(expected)
toEqual(expected)
toHaveBeenCalled()
toHaveBeenCalledBefore(expected)
toHaveBeenCalledOnceWith()
toHaveBeenCalledTimes(expected)
toHaveBeenCalledWith()
toHaveClass(expected)
toHaveSize(expected)
toMatch(expected)
toThrow(expectedopt)
toThrowError(expectedopt, messageopt)
toThrowMatching(predicate)
withContext(message) → {matchers}

бегатьjasmineОтчет о тестировании может быть сгенерирован

Отчет о тестировании по умолчанию не очень интуитивно понятен, если вы хотите предоставить что-то вродеMochaотчеты стиля могут быть установленыjasmine-spec-reporter,существуетspec/helpersДобавьте файл конфигурации в каталог, например.spec/helpers/reporter.js.

const SpecReporter = require('jasmine-spec-reporter').SpecReporter;

jasmine.getEnv().clearReporters();               // remove default reporter logs
jasmine.getEnv().addReporter(new SpecReporter({  // add jasmine-spec-reporter
  spec: {
    displayPending: true
  }
}));

Вывод отчета о вариантах использования в настоящее время выглядит следующим образом.

jasmine

если вJasmineЧтобы выполнить тестирование на уровне DOM вKarmaилиJSDOMКонкретная конфигурация здесь повторяться не будет.

Подводить итогиJasmineНабор инструментов

  1. Тест в среде Node:Jasmine + babel
  2. моделированиеJSDOMтестовое задание :Jasmine + JSDOM + babel
  3. Реальный тест браузера:Karma + Jasmine + webpack + babel

JEST

jest

JestдаfacebookПолное техническое решение для модульного тестирования, которое объединяет среду тестирования, библиотеку утверждений, средство запуска, моментальный снимок, песочницу и средство имитации.ReactОфициальный инструмент тестирования.Jestа такжеJasmineочень похожиAPI, так что вJasmineИнструменты, используемые вJestвсе еще можно использовать естественным путем. можно аппроксимировать какJest = JSDOM 启动器 + Jasmine .

Хотя Jest предоставляет множество функций, в нем нет встроенныхES6Поддержка, поэтому код все еще нужно преобразовать в соответствии с разными средами выполнения, потому что Jest в основном работает вNode, поэтому вам нужно использоватьbabel-jestБудуES ModuleПеревести вCommonJS.

Конфигурация Jest по умолчанию

npm install jest --save-dev
npx jest --init
√ Would you like to use Jest when running "test" script in "package.json"? ... yes
√ Would you like to use Typescript for the configuration file? ... no
√ Choose the test environment that will be used for testing » jsdom (browser-like)
√ Do you want Jest to add coverage reports? ... no
√ Which provider should be used to instrument code for coverage? » babel
√ Automatically clear mock calls and instances between every test? ... yes

существуетNodeилиJSDOMувеличить внизES6поддержка кода

npm install jest-babel @babel/core @babel/preset-env
// .babelrc
{
    "presets": ["@babel/preset-env"]
}
// jest.config.js
// 下面两行为默认配置,不写也可以
{
+    testEnvironment: "jsdom",
+    transform: {"\\.[jt]sx?$": "babel-jest"}
}

использоватьJestСоздать тестовый отчет

jest

дляReactа такжеTypeScriptПоддержка также может быть измененаbabelРешение настройки

npm install @babel/preset-react @babel/preset-typescript --save-dev
// .babrlrc
{
    "presets": ["@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript"]
}

Jest протестирован в реальной среде браузера

В настоящее времяJestНе поддерживает тестирование непосредственно в реальных браузерах, его пусковая установка по умолчанию предоставляет толькоJSDOMсреда, модульное тестирование в браузере в настоящее время толькоKarmaСхема может это сделать, значит, ее тоже можно использоватьKarma + JestРешение реализовано, но не рекомендуется, т.к.JestОн слишком тяжелый, используйтеKarma + Jasmineможно добиться в основном такого же эффекта.

Есть и более популярныйE2Eстроить планыJest + Puppeteer , из-заE2EОн не относится к категории юнит-тестирования и не будет здесь раскрываться.

JestСводка цепочки инструментов

  • Тест в среде Node:Jest + babel
  • JSDOMтестовое задание :Jest + babel
  • Тестирование в реальном браузере (не рекомендуется)
  • E2Eтестовое задание :Jest + Puppeteer
краткое изложение

Выше описанаchai , mocha , karma , jasmineа такжеjest, Каждому инструменту соответствует своя уникальная цепочка инструментов. Выбирая подходящий инструмент тестирования, выбирайте его в соответствии с реальными потребностями. В области тестирования все еще есть много инструментов, которых слишком много, чтобы сосчитать. Давайте посмотрим на некоторые методы React модульное тестирование.

Модульное тестирование React с помощью Jest + Enzyme

enzyme

EnzymeБазовая конфигурация выглядит следующим образом:

npm install enzyme enzyme-adapter-react-16 jest-enzyme jest-environment-enzyme jest-canvas-mock react@16 react-dom@16 --save-dev
// jest.config.js
{
- "testEnvironment": "jsdom",
+  setupFilesAfterEnv: ["jest-enzyme", "jest-canvas-mock"],
+  testEnvironment: "enzyme",
+  testEnvironmentOptions: {
+    "enzymeAdapter": "react16"
+  },
}

jest-canvas-mockЭтот пакет предназначен для решения некоторых задачJSDOMПроблема с нереализованным поведением, вызывающим предупреждения.

Вышеупомянутое строит использованиеEnzymeБолее дружелюбная среда, на которую можно напрямую ссылаться в глобальной области видимости.React , shallow, mountЖдатьAPI. такжеEnzymeТакже зарегистрирован ряд дружественных функций утверждений дляJest, как показано ниже,Справочный адрес

toBeChecked()
toBeDisabled()
toBeEmptyRender()
toExist()
toContainMatchingElement()
toContainMatchingElements()
toContainExactlyOneMatchingElement()
toContainReact()
toHaveClassName()
toHaveDisplayName()
toHaveHTML()
toHaveProp()
toHaveRef()
toHaveState()
toHaveStyle()
toHaveTagName()
toHaveText()
toIncludeText()
toHaveValue()
toMatchElement()
toMatchSelector()
// js/ClassComponent.js
import React from 'react';

export default class ClassComponent extends React.PureComponent {
    constructor() {
        super();
        this.state = { name: 'classcomponent' };
    }
    render() {
        return (
            <div>
                a simple class component
                <CustomComponent />
            </div>
        );
    }
}

// test/hook.test.js
import HookComponent from '../js/HookComponent';

describe('HookComponent', () => {
    it ('test with shallow', () => {
        const wrapper = shallow(<HookComponent id={1} />);
        expect(wrapper).toHaveState('name', 'classcomponent');
        expect(wrapper).toIncludeText('a simple class component');
        expect(wrapper).toContainReact(<div>a simple class component</div>);
        expect(wrapper).toContainMatchingElement('CustomComponent');
    })
})

EnzymeПредоставляет три метода компонента рендеринга

  • shallowиспользоватьreact-test-rendererРендеринг компонентов в виде объектов в памяти может быть выполнен легко.props, stateи другие тесты данных, соответствующие объекты операцииShallowWrapper, в этом режиме может быть воспринят только первый слой пользовательских подкомпонентов, а внутренняя структура пользовательских подкомпонентов не может быть воспринята.
  • mountиспользоватьreact-domРендеринг компонента создаст реальныйDOMузел, чемshallowПо сравнению с увеличением, вы можете использовать роднойAPIдействоватьDOMспособность, соответствующий объект операцииReactWrapper , то, что воспринимается в этом режиме, является полнымDOMДерево.
  • renderиспользоватьreact-dom-serverвизуализировать какhtmlСтрока, основанная на этом статическом документе для работы, соответствующий объект операцииCheerioWrapper.

Поверхностный рендеринг

потому чтоshallowШаблоны учитывают только первый уровень пользовательских подкомпонентов и часто используются только для простого тестирования компонентов. Например, следующие компоненты

// js/avatar.js
function Image({ src }) {
    return <img src={src} />;
}

function Living({ children }) {
    return <div className="icon-living"> { children } </div>;
}

function Avatar({ user, onClick }) {
    const { living, avatarUrl } = user;
    return (
        <div className="container" onClick={onClick}>
            <div className="wrapper">
              <Living >
                <div className="text"> 直播中 </div>
              </Living>
            </div>
            <Image src={avatarUrl} />
        </div>
    )
}

export default Avatar;

shallowХотя рендеринг не является реальным рендерингом, жизненный цикл его компонентов будет проходить через него полностью.

использоватьshallow(<Avatar />) Воспринимаемая структура выглядит следующим образом, обратите внимание, чтоdiv.textтак какLivingкомпонентchildrenможно обнаружить, ноLivingВнутренняя структура системы не может быть воспринята.

shallow

EnzymeПоддерживаемые селекторы поддерживают наши знакомыеcss selectorсинтаксис, и в этом случае мы можемDOMКонструкция проверяется следующим образом.

// test/avatar.test.js
import Avatar from '../js/avatar';

describe('Avatar', () => {
    let wrapper = null, avatarUrl = 'abc';

    beforeEach(() => {
        wrapper = shallow(<Avatar user={{ avatarUrl: avatarUrl }} />);
    })

    afterEach(() => {
        wrapper.unmount();
        jest.clearAllMocks();
    })

    it ('should render success', () => {
        // wrapper 渲染不为空
        expect(wrapper).not.toBeEmptyRender();
        // Image 组件渲染不为空, 这里会执行 Image 组件的渲染函数
        expect(wrapper.find('Image')).not.toBeEmptyRender();
        // 包含一个节点
        expect(wrapper).toContainMatchingElement('div.container');
        // 包含一个自定义组件
        expect(wrapper).toContainMatchingElement("Image");
        expect(wrapper).toContainMatchingElement('Living');
        // shallow 渲染不包含子组件的内部结构
        expect(wrapper).not.toContainMatchingElement('img');
        // shallow 渲染包含 children 节点
        expect(wrapper).toContainMatchingElement('div.text');
        // shallow 渲染可以对 children 节点内部结构做测试
        expect(wrapper.find('div.text')).toIncludeText('直播中');
    })
})

Если мы хотим протестировать соответствующий компонентprops / stateЕго также можно легко протестировать, но в настоящее время есть дефекты.Class Componentможет пройтиtoHaveProp, toHaveStateпрямой тест, ноHook Компонент не может быть протестированuseState.

it ('Image component receive props', () => {
  const imageWrapper = wrapper.find('Image');、
  // 对于 Hook 组件目前我们只能测试 props
  expect(imageWrapper).toHaveProp('src', avatarUrl);
})

wrapper.findвернет тот же самый, хотяShallowWrapperобъект, но подструктура этого объекта не расширяется, если вы хотите протестироватьimageWrapperвнутренняя структура, необходимо повторноshallow renderоднажды.

it ('Image momponent receive props', () => {
  const imageWrapper = wrapper.find('Image').shallow();

  expect(imageWrapper).toHaveProp('src', avatarUrl);
  expect(imageWrapper).toContainMatchingElement('img');
  expect(imageWrapper.find('img')).toHaveProp('src', avatarUrl);
})

Вы также можете изменить компонентprops, запускает перерисовку компонента

it ('should rerender when user change', () => {
    const newAvatarUrl = '' + Math.random();
    wrapper.setProps({ user: { avatarUrl: newAvatarUrl }});
    wrapper.update();
    expect(wrapper.find('Image')).toHaveProp('src', newAvatarUrl);
})

Другим распространенным сценарием является имитация события.Событие ближе к реальному тестовому сценарию.В этом сценарии используйтеshallowЕсть много недостатков, потому чтоshallowСобытия сцены не будут иметь того же процесса захвата и всплытия, что и реальные события, поэтому в настоящее время вы можете просто активировать соответствующийcallbackдостичь цели тестирования.

it ('will call onClick prop when click event fired', () => {
    const fn = jest.fn();

    wrapper.setProps({ onClick: fn });
    wrapper.update();

    // 这里触发了两次点击事件,但是 onClick 只会被调用一次。
    wrapper.find('div.container').simulate('click');
    wrapper.find('div.wrapper').simulate('click');
    expect(fn).toHaveBeenCalledTimes(1);
})

Некоторые люди в Интернете резюмировалиshallowНекоторые недостатки режима

  1. shallowРендеринг не пузыряет события, аmountМогу.
  2. shallowРендеринг, потому что не создает реальногоDOM, поэтому компонент используетrefsнельзя нормально получить, если очень надо использоватьrefs, вы должны использоватьmount.
  3. simulateсуществуетmountбыло бы более полезным, потому что он делает всплытие событий.

Фактически, приведенные выше пункты иллюстрируют явление, котороеshallowЧасто подходит только для идеального сценария, некоторые операции зависят от поведения браузера.shallowне могут быть удовлетворены, они, связанные с реальной средой, могут быть использованы толькоmount.

Рендеринг монтирования

MountВизуализированная структура объектаReactWrapperкоторый обеспечивает иShallowWrapperпочти то же самоеAPI, разница небольшая.

существуетAPIНекоторые различия в уровнях заключаются в следующем.

+ getDOMNode()        获取DOM节点
+ detach()            卸载React组件,相当于 unmountComponentAtNode
+ mount()             挂载组件,unmount之后通过这个方法重新挂载
+ ref(refName)        获取 class component 的 instance.refs 上的属性
+ setProps(nextProps, callback)
- setProps(nextProps)
- shallow()
- dive()
- getElement()
- getElements()

Кроме того, из-заmountиспользоватьReactDOMРендеринг, чтобы он был ближе к реальной сцене, в этом режиме мы можем наблюдать весьDOMСтруктура и структура узла компонента React.

mount

describe('Mount Avatar', () => {
    let wrapper = null, avatarUrl = '123';

    beforeEach(() => {
        wrapper = mount(<Avatar user={{ avatarUrl }} />);
    })

    afterEach(() => {
        jest.clearAllMocks();
    })

    it ('should set img src with avatarurl', () => {
        expect(wrapper.find('Image')).toExist();
        expect(wrapper.find('Image')).toHaveProp('src', avatarUrl);
        expect(wrapper.find('img')).toHaveProp('src', avatarUrl);
    })
})

существуетshallowПроблема триггера события, которую нельзя смоделировать вmountВниз больше не проблема.

it ('will call onClick prop when click event fired', () => {
    const fn = jest.fn();

    wrapper.setProps({ onClick: fn });
    wrapper.update();

    wrapper.find('div.container').simulate('click');
    wrapper.find('div.wrapper').simulate('click');
    expect(fn).toHaveBeenCalledTimes(2);
})

В заключениеshallowчто может быть сделаноmountсможет сделать,mountчто может быть сделаноshallow Не обязательно.

Оказывать

renderвнутреннее использованиеreact-dom-serverОтобразите в строку, а затем пройдите черезCherrioПреобразовать в структуру в памяти, вернутьCheerioWrapperэкземпляр, способный отобразить весьDOMдерево, но потеряет состояние внутреннего экземпляра, поэтому его также называютStatic Rendering. Этот тип рендеринга может выполнять относительно немного операций, и здесь он не будет подробно описываться.официальная документация.

Суммировать

Для реальных браузеров я бы рекомендовалKarma + JasmineСценарное тестирование, дляReactтестовое заданиеJest + EnzymeсуществуетJSDOMОкружающая среда смогла охватить большинство сцен. дополнительный тестReactкомпоненты, кромеEnzymeпредоставленные операции,JestЕсть много других полезных функций вmockОдинnpmРеализация компонента, настройкаsetTimeoutЧасы и т. д., эти инструменты также незаменимы при выполнении юнит-тестирования.Вся система технологии юнит-тестирования содержит много всего.Эта статья не может охватить все, а лишь знакомит с некоторыми родственными технологическими системами, наиболее близкими нам.

Ссылаться на

  1. medium.com/building-IB…
  2. medium.com/@внезапно крик.oz/…
  3. Ууху. Обратите внимание. Talent/2015/10/12/…
  4. jestjs.io/docs/en
  5. blog.bit SRC.IO/как протестировать…
  6. woohoo.бесплатный код camp.org/news/special Fitness…
  7. вместе.

Эта статья была опубликована сКоманда внешнего интерфейса NetEase Cloud Music, Любое несанкционированное воспроизведение статьи запрещено. Мы набираем front-end, iOS и Android круглый год.Если вы готовы сменить работу и любите облачную музыку, присоединяйтесь к нам на grp.music-fe(at)corp.netease.com!