Говоря о фронтенд-тестировании

внешний интерфейс модульный тест Jest тестовое задание
Говоря о фронтенд-тестировании

Front-end тестирование может быть неправильно понято многими людьми, и, возможно, все более склонны писать back-end-ориентированные тесты, которые логичны и удобны для тестирования и т. д.

Разговоры об этом привели к тому, что многие фронтенды никогда не писали тесты (все тесты идут рука об руку~~)

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

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

  • среда узла
  • vue-среда
  • среда рендеринга на стороне сервера nuxt
  • реагировать на окружающую среду
  • следующая среда рендеринга на стороне сервера
  • угловая среда

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

среда узла

Рекомендуемая среда тестированияjest

jest — один из шедевров FB, который удобен для тестирования кода js в различных сценариях, Jest выбран здесь, потому что он действительно удобен

Информацию об использовании и конфигурации можно найти в официальной документации.

Рекомендации по конфигурации

{
  testEnvironment: 'node' // 如不声明默认浏览器环境
}

Говорите только о модульном тестировании для узла, e2e-тестирование относительно редко

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

mock

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

Например:

const { readFileSync } = require('fs')

const getFile = () => {
  try {
    const text = readFileSync('text.txt', 'utf8')
  } catch (err) {
    throw new Error(err)
  }

  console.log(text)
}

module.exports = getFile

Нам не нужно заботитьсяtext.txtСуществует ли он на самом деле, это не имеет значенияtextКаково конкретное содержимое файла, наше внимание должно быть сосредоточено на том, может ли исключение быть вызвано вовремя при чтении ошибки файла, иconsole.log()Работает ли он так, как ожидалось

соответствует тесту

const getFile = require('./getFile')

describe('readFile', () => {
  const mocks = {
    fs: {
      readFileSync: jest.fn()
    },
    other: {
      text: 'Test text'
    }
  }

  beforeAll(() => {
    jest.mock('fs', () => mocks.fs)
  })

  test('read file success run console.log', () => {
    mocks.fs.readFileSync.mockImplementation(() => this.mocks.other.text)

    getFile()

    expect(console.log).toBeCalled()
  })
})

Вышеприведенный код просто реализует проверку успешности чтения файла.Не спешите сначала исправлять ошибку.Сам этот тест неверен.Давайте разберем его не спеша.

Изначально мы создалиmocksобъект, используемый для имитации данных, посколькуreadFileSyncМетод может иметь несколько возвращаемых результатов (успех или ошибка), поэтому временно используйтеjest.fn()моделирование

В другом случае есть некоторые фиксированные тестовые данные (не изменятся в процессе тестирования).

beforeAllВыполнение нашего макета в хуке и перехват модуля fs, который требует входа, также является ключевым шагом в этом тестовом примере.

В первом тесте мы переписываемmocks.fs.readFileSyncИспользуемая здесь форма возвратаmockImplementationОн напрямую имитирует функцию выполнения, конечно, он также может имитировать возвращаемое значение, вы можете перейти на официальный сайт jest.

expectиспользуется для утверждения нашегоconsole.logметод выполнен

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

  1. mockImplementationЛучше заменить наmockReturnValueOnce, обратите внимание, что здесь появляется окончание Once, то есть возвращаемое значение моделируется только один раз,mockImplementationЛучше всего использовать в сложных сценариях, так называемая сложность заключается в том, что мы вручную реализуемreadFileSyncМетод позволяет тесту достичь намеченной цели.В этом простом сценарии нам нужно только смоделировать возвращаемое значение.
  2. expect(console.log)Здесь будет сообщено об ошибке, потому что содержание шутливого утверждения может быть только mock function или spy, где console — это метод глобального объекта global, и мы не вводили его с помощью require, так что шутка.beforeAllВыполнить прямо в хукеjest.spyOn(global.console, 'log')Тогда мы сможем слушатьconsole.logказненexpect(global.console.log)
  3. Цель утверждений состоит в том, чтобы проверитьconsole.logреализации, которая не является тщательным тестированием, нам нужно использоватьtoBeCalledWithзаменитьtoBeCalled, не только для проверки выполнения, но и для проверки правильности параметров, просто измените его наexpect(global.console.log).toBeCalledWith(this.mocks.other.text)

Наверстаем неудачный тест чтения файла

test('read file fail throw error', () => {
  mocks.fs.readFileSync.mockImplementationOnce(() => { throw new Error('readFile error') })

  expect(getFile()).toThrow()
  expect(global.console.log).not.toBeCalled()
})

Тест, который не может прочитать файл, легче понять.jest.fn()Многократное изменение приведет к взаимодействию между тестовыми примерами. Попробуйте использовать здесь метод «Однократное завершение». Сложные сценарии могут быть следующими.

beforeEach(() => {
  mocks.fs.readFileSync.mockReset()
})

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

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

ссылки на внешние файлы

Просто подведите итоги и расскажите о проблеме require.До Node 9 импорт es6 не поддерживался, и я не буду подробно объяснять это здесь.

require сам по себе не сложный, но если не знать когда выполнять, то тест не пройдет, давайте на примере

const env = process.env.NODE_ENV

module.export = () => env

Тест выглядит следующим образом

const getEnv = require('./getEnv')

describe('env', () => {
  test('env will be dev', () => {
    process.env.NODE_ENV = 'dev'

    expect(getEnv()).toBe('dev')
  })

  test('env will be pord', () => {
    process.env.NODE_ENV = 'pord'

    expect(getEnv()).toBe('pord')
  })
})

Очень простой тест, помимо процесса имитации, здесь сообщит, что тест не пройден, потому что require и env были назначены какundefined, мы пытаемся изменитьNODE_ENVКогда переменная окружения установлена, программа больше не будет выполняться.Конечно, это также очень просто в обращении.

let getEnv

test('env will be dev', () => {
  process.env.NODE_ENV = 'dev'
  getEnv = require('./getEnv')

  expect(getEnv()).toBe('dev')
})

test('env will be pord', () => {
  process.env.NODE_ENV = 'pord'
  getEnv = require('./getEnv')

  expect(getEnv()).toBe('pord')
})

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

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

const packageFile = `${process.cwd()}/package.json`

const package = require(packageFile)

прочитать текущий путьpackage.json, когда тест действительно запустится для этого кода, он перейдет в текущий каталог, чтобы найтиpackage.json, попробуй поиздеваться здесьpackage.jsonДля наших собственных макетов данных, но jest не поддерживает макеты с динамическими путями, попробуйте написать этоjest.mock(${process.cwd()}/package.json, () => mockFile)Будет сообщено об ошибке, поэтому попробуйте использовать фиктивное решение, чтобы обеспечить бесперебойное модульное тестирование.

const path = require('path')

const filePath = path.join(process.cwd(), 'package.json')

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

покрытие

Если покрытие модульным тестом не соответствует стандарту, оно равно пустому тесту. Процесс тестирования должен максимально охватывать все условия суждения, а не игнорировать его, если все пройдены. На следующем этапе 100% покрытие тестами не доказывает, что покрытие должно быть на месте, потому что код случайного выполнения также учитывается при покрытии, например.

module.export = (list) => list.map(({ id }) => id)

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

const getId = require('./getId')
const mocks = {
  list: [{
    id: 1,
    name: 'vue'
  }, {
    id: 2,
    name: 'react'
  }]
}

test('return id', () => {
  expect(getId(mocks.list)).toEqual([1, 2])
})

Пока однажды код не станетmodule.export = (list) => [1, 2]

В это время тест еще может пройти, и степень покрытия 100%.Правда, что никто не настолько глуп, чтобы изменить код на это.Это просто пример.На самом деле логика будет сложнее, чем это.

Поговорим о решениях

  • Рандомизация фиктивных данных, каждый тест генерирует случайный список для тестирования, существующие библиотекиmockjs
  • Тест на сильную ассоциацию доказывает, что метод карты действительно выполняется и параметры верны, первый шпионspyOn(Array.prototype, 'map')затем утверждать

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

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

vue-среда

В сценарии использования Vue это не что иное, как библиотека компонентов и бизнес-логика.Библиотека компонентов смещена в сторону модульного тестирования, а бизнес-логика смещена в сторону e2e-тестирования.Конечно, они не конфликтуют.

модульный тест

Рекомендуемый артефакт:vue-test-utils

В README приведены примеры конфигураций нескольких тестовых библиотек, здесь рекомендуется использовать jest, приведите пример.

export default {
  props: ['value'],
  data () {
    return {
      currentValue: 0
    }
  },
  watch: {
    value (val) {
      this.currentValue = val
    }
  }
}

Тест выглядит следующим образом

import { mount } from '@vue/test-utils'
import Test from './Test.vue'

test('props value', () => {
  const options = { propsData: { value: 3 } }

  const wrapper = mount(Test)

  expect(wrapper.vm.currentValue).toBe(3)
})

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

Объем модульного тестирования vue ограничен тем, правильный ли поток данных, правильный ли логический рендеринг (v-if v-show v-for), правильный ли стиль и класс, нам не нужно касаться позиции этого компонента в рендеринге браузера, а также нет необходимости оказывать какое-либо влияние на другие компоненты, если сами компоненты верны, утверждения, упомянутые выше, vue-test-utils могут предоставить соответствующие решения, что экономит много времени. затраты на тестирование в целом.

e2e тест

Также рекомендуется, чтобы вы основывались на последних строительных лесах.@vue/cli-plugin-e2e-nightwatch

Смысл теста e2e состоит в том, чтобы оценить, соответствует ли настоящий DOM ожидаемым требованиям, и даже фиктивные сценарии случаются редко. Что необходимо, так это работающая среда браузера. Конкретные детали повторяться не будут. Вы можете обратиться к официальной документации.

среда рендеринга на стороне сервера nuxt

следующая официальная рекомендацияava, вывести решение ава

модульный тест

Беда в конфигурации, сначала дайте зависимости, которые нужно установить

"@vue/test-utils",
"ava",
"browser-env",
"require-extension-hooks",
"require-extension-hooks-babel",
"require-extension-hooks-vue",
"sinon"

Добавьте несколько строк конфигурации ava в package.json.

"ava": {
  "require": [
    "./tests/helpers/setup.js"
  ]
}

написать ниже./tests/helpers/setup.js

const hooks = require('require-extension-hooks')

// Setup browser environment
require('browser-env')()

// Setup vue files to be processed by `require-extension-hooks-vue`
hooks('vue').plugin('vue').push()
// Setup vue and js files to be processed by `require-extension-hooks-babel`
hooks(['vue', 'js']).plugin('babel').push()

Приведенный выше код - единственное, чего я не вижуsinonЭта библиотека, если говорить об аве, не имеет mock-функции, что вносит большие трудности в mock модульного тестирования, но мы можем представить ее, внедривsinonрешить проблему фиктивных данных, с точки зрения фиктивныхsinonДелает лучше, чем шутка, поддерживает режим песочницы, не влияет на внешние данные

Приведите простой пример

<template>
  <el-card v-for="item in topicList" :key="item.id">
    <div class="card-content">
      <span class="link" @click="toMember(item.member.username)">{{ item.member.username }}</span>
    </div>
  </el-card>
</template>

<script>
export default {
  props: {
    topicList: {
      type: Array,
      required: true
    }
  },
  methods: {
    toMember (name) {
      this.$router.push(`/member/${name}`)
    }
  }
}
</script>

Соответствующий тестовый код выглядит следующим образом

import { shallowMount } from '@vue/test-utils'
import test from 'ava'
import sinon from 'sinon'

test('methods: toMember', t => {
  const { topicList } = t.context
  const $router = {
    push: () => {}
  }
  const spy = sinon.spy($router, 'push')

  const wrapper = shallowMount(TopicListChalk, {
    propsData: { topicList },
    mocks: {
      $router
    }
  })

  topicList.forEach((item, index) => {
    const toMemberText = wrapper.findAll('.card-content').at(index).find('.link')

    toMemberText.trigger('click')

    t.true(spy.withArgs(`/member/${item.member.username}`).calledOnce)
  })
})

прямо здесь$routerиздевайся и используйsinon.spyКонтролировать выполнение, как дляthis.$router.pushПерескакивает браузер или нет, не имеет значения для модульного тестирования.Метод записи здесь также особенный.По умолчанию параметр тестового метода в обратном вызовеt, соответствующие методы смонтированы вtобъект, доступ к контексту можно получить черезt.contextпередача

Так много разговоров о модульном тестировании nuxt

e2e тест

Здесь есть неясность, официальный сайт nuxt дает только тестовый пример e2eend-to-end-testing

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

Но когда дело доходит до других фреймворков (express|koa), этого недостаточно, очень вероятно, что в запись кастомного сервера будет добавлено много промежуточного программного обеспечения, это огромный тест для примеров, приведенных на официальном сайте. Невозможно тестировать в каждом тесте, реализуйте это в файлеnew Nuxt, поэтому требуется более высокий уровень инкапсуляции, то есть игнорирование различий в процессе запуска сервера, и захват страницы прямо в браузере

рекомендовать:nuxt-jest-puppeteer

реагировать на окружающую среду

модульный тест

Для этой волны нет выбора, побеждает шутка, и на официальном сайте она естьReact, сопроводительная документация RN

Дело документа тоже очень объемное, о нем не надо говорить, не повторяться

e2e тест

По сути два вышеперечисленных варианта e2e-решений похожи: им нужен headless-браузер, который может работать на узле, официальной рекомендации нет.nightwatchjs

следующая среда рендеринга на стороне сервера

модульный тест

В основном говорят о том, как настроить, сначала зависит от пакета

"babel-core",
"babel-jest",
"enzyme",
"enzyme-adapter-react-16",
"jest",
"react-addons-test-utils",
"react-test-renderer"

Добавьте скрипт в package.json"test": "NODE_ENV=test jest"

Добавьте jest.config.js в путь

module.exports = {
  setupFiles: ['<rootDir>/jest.setup.js'],
  testPathIgnorePatterns: ['<rootDir>/.next/', '<rootDir>/node_modules/']
}

Добавьте jest.setup.js в путь

import { configure } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'

configure({
  adapter: new Adapter()
})

Тогда вы можете с удовольствием писать тесты

e2e тест

пропущен

угловая среда

Причина, по которой этот раздел добавлен, заключается в том, что я написал немного angular.Как фреймворк, angular сам по себе является всеобъемлющим.Вновь созданный проект cli имеет свой собственный модульный тест и тест e2e.

модульные тесты по умолчаниюkarma + jasminee2e тесты по умолчаниюprotractor

Спорить не о чем, это официальное решение, им легко пользоваться

Суммировать

После разговора о многих средах, на самом деле, цель текста состоит в основном в двух аспектах.

  • Идеи тестов, как писать хорошие модульные тесты, в основном сосредоточены на первой половине статьи.
  • Рекомендация по тестовому инструменту и соответствующая конфигурация

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

Использование лжи для проверки лжи остается ложью. . .

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