Написанные модульные тесты, скорость разработки, поддержка проектов очень помогают. Инструмент внешнего тестирования был инновационным, и тестирование основных принципов было незначительным. Код продукта вместе с доставкой надежного тестового кода — идеальное место для каждого профессионального разработчика. В этой статье пойдет речь о тесте, зачем мы тестируем, тестируем и что в принципе хорошо, и как тестировать эти стратегии в напольном React проекте.
Используемая здесь тестовая среда, инструмент утверждения — Jest. Статья не претендует на знакомство с тестовым фреймворком, самой грамматикой, потому что статей много. В этой статье предполагается, что читатель имеет определенную базу, хотя бы знаком с синтаксисом, но не предполагает, что читатель напишет модульный тест. Когда вы будете рассказывать о том, что такое хороший модульный тест, я кратко расскажу о хорошей структуре модульного теста.
Обсуждение на гитхабе:Github.com/line --симп ...
Оригинальный адрес:blog.line — Тайвань/#/post/2018…
содержание
- Зачем проводить модульное тестирование
- контекст модульного теста
- Стратегия тестирования: тестовая пирамида
- Как писать хорошие модульные тесты: характеристики хороших тестов
- Есть одна и только одна причина неудачи
- очень выразительный
- быстро и стабильно
- Стратегия модульного тестирования React и реализация
- Стратегии модульного тестирования для приложений React
-
- Из официальной позиции ошибки
- правильная осанка
- проверка компонентов
- Бизнес-компоненты - рендеринг веток
- Бизнес-компонент — вызов события
- Функциональные компоненты -
children
компонент высокого порядка
- утилиты тест
- Суммировать
- Незаконченные темы и обсуждения приветствуются
Зачем проводить модульное тестирование
Хотя есть много статей о тестировании и много статей о React, относительно мало статей о подробном модульном тестировании приложений React. Более того, все больше статей более склонны объяснять сам инструмент, говоря лишь о том, что «мы можем тестировать так», но не отвечая на вопросы «почему мы тестируем так» и «хорошо ли тестировать так». Пробелы в этих вопросах неизбежно приводят людей к неправильной точке зрения, что тестирование бесполезно, дорого для тестирования и что тестирование замедляет разработку.В результате сегодня, когда «встроенное качество» постепенно стало люди до сих пор думают, что тестирование — это второе. Ожидание граждан — это издержки и вишенка на торте. В этом вопросе мое отношение всегда было ясным: не только писать тесты, но и хорошо писать юнит-тесты; иметь не только встроенную осведомленность о качестве test-forward, но и способность быстро получать обратную связь и разрабатывать на основе тестов. Код без автоматизированного тестирования не называется полным и не может быть принят.
«Зачем нам нужно проводить модульное тестирование» — ключевой вопрос. У каждого есть свое мнение о том, нужно ли делать тест, как его делать и в каком объеме, и невозможно попытаться всесторонне оценить эти мнения. Нам нужна перспектива, контекст, в котором можно говорить о модульном тестировании. Конечно, модульное тестирование имеет свои преимущества, но эта статья начнется не с того, что это за преимущества, а скорее с того, что плохого в том, чтобы не проводить модульное тестирование в интересующем нас контексте.
Итак, в каком контексте мы говорим о модульном тестировании? С какими проблемами мы столкнемся без модульного тестирования?
контекст модульного теста
Сначала поговорим о проблеме. Одна из самых больших проблем заключается в том, что без написания модульных тестов вы не решаетесь на рефакторинг, вы просто наблюдаете, как код портится. Не говоря уже о качестве кода, не говоря о постоянном улучшении, не говоря уже о личностном росте. Всегда оригинальный способ работы.
Давайте поговорим о контексте..Выходите в интернет быстрее,Выходите в интернет чаще,Онлайн.怎么样衡量这个「更快」呢?那就是第一图提到的 lead time,它度量的是一个 idea 从提出并被验证,到最终上生产环境面对用户获取反馈的时间。显然,这个时间越短,软件就能越快获得反馈,对价值的验证就越快发生。这个结论对我们写不写单元测试有什么影响呢?答案是,不写单元测试,你就快不起来。 Зачем?因为每次发布,你都要投入人力来进行手工测试;因为没有测试,你倾向于不敢随意重构,这又导致代码逐渐腐化,复杂度使得你的开发速度降低。
Затем рассмотрим следующие два важных факта:Люди будут течь, а заявки будут расти. Люди потекут, спрос возрастет, и уже никто не может понять ни одного сценария применения. следовательно,Намеренно полагаться на людей и полагаться на ручные методы для решения проблемы оперативности, во-первых, неэффективно и нереально с точки зрения временного измерения.. Итак, для того, чтобы обслуживать «силы быстрого реагирования» для достижения этой цели, нам нужен автоматизированный набор тестов, который может помочь нам обеспечить быструю обратную связь, сделать защитников качественными. Только решить труд, качество кольца, чтобы неуклонно повышать эффективность, и высокая группа реагирования предприятий, вероятно, будет.
Тогда в контексте «отзывчивости» мы можем говорить о том, использовать модульное тестирование или нет, и мы можем иметь для него хорошую основу, вместо того, чтобы использовать его, когда разработка идет хорошо, и не использовать такой расплывчатый ответ, когда он не хорошо:
- Если вы говорите, что моему бизнес-подразделению не нужно часто подключаться к сети, и у меня достаточно рабочей силы для проведения ручного тестирования, тогда вы можете пропустить модульное тестирование.
- Если вы говорите, что я небольшой проект и небольшой отдел, мне не нужна высокая отзывчивость, и я буду ходить каждый день, то можно обойтись и без юнит-тестирования.
- Если вы говорите, что меня не волнует повреждение кода и я не занимаюсь рефакторингом, то модульное тестирование вам не нужно.
- Если вы скажете, что я не забочусь о качестве кода, некоторые из них не защищены тестами.
if-else
Стричинг не проблема, если вы программист с плохим мозгом, то без юнит-тестирования можно обойтись.
На данный момент из «силы реагирования» в этом контексте, чтобы ответить «Почему нам нужно писать модульные тесты» проблемы. Тогда мы можем поговорить о следующем вопросе: «Почему это тестирование подразделения».
Стратегия тестирования: тестовая пирамида
Это хороший вопрос. Для достижения этой цели, конечно же, чтобы защитить качество, конечно, только один из способов тестирования и стабильного автоматического развертывания, интегрированной сборки, хорошую архитектуру кода, организационную структуру необходимые корректировки необходимы для сохранения объектов. Я никогда не думаю, что тестирование подразделения - это серебряная пуля для решения проблемы качества, арендаторы могут играть только усиление эффекта. Но вместо этого трудно представить, что тесты единиц не должны писать плохое проект, насколько высока сила реагирования.
Даже если мы говорим об автоматизированном тестировании, речь может идти, а может и не идти о написании модульных тестов. Мы возлагаем большие надежды на автоматизированный набор тестов, поскольку он может нам помочь.Безопасный рефакторинг существующего кода,Сохранить бизнес-контекст,быстрое возвращение. Тестируйте разнообразие сортов, почему я должен говорить о модульном тестировании? Потому что ~~ В этой статье речь пойдет о модульном тестировании... ~~ Писать относительно проще, скорость самая высокая, а эффект обратной связи самый прямой. Под этой картинкой я хочу, чтобы все услышали:
Это знаменитая тестовая пирамида. Для автоматизированного набора тестов он должен содержать тесты различных типов и проблем, такие как модульные тесты, ориентированные на модули, интеграционные тесты и контрактные тесты, ориентированные на интеграцию и контракты, а также сквозные тесты, ориентированные на точки приемлемости для бизнеса. . Обычно мы ограничены в ресурсах и не можем применять все уровни тестирования, и результаты могут быть не самыми лучшими. Поэтому нам необходимо стратегически настроить стратегию тестирования по принципу выгоды-затраты, учитывая реальную ситуацию и болевые точки проекта: например, проекты со многими трехсторонними зависимостями могут писать больше контрактных тестов, а сценарии со многими бизнес-сценарии, сложная или частая регрессия. Можно было бы написать больше сквозных тестов и т. д. Но в любом случае у вас должно быть больше низкоуровневых юнит-тестов во всей тестовой пирамиде, потому что они относительно дешевы, работают быстрее всего (обычно за миллисекунды) и имеют относительно большую ценность защиты для юнитов.
Это ответ на вопрос «Зачем нужны юнит-тесты?» В следующем разделе вы можете официально указать, как это сделать: «Как написать хороший модульный тест».
Дополнительное чтение по пирамиде тестирования:Тестовая пирамида в действии.
Как писать хорошие модульные тесты: характеристики хороших тестов
Написание юнит-тестов — это только первый шаг, далее следует более важный вопрос — как написать хорошие и простые в сопровождении юнит-тесты. Хороший тест имеет свои особенности, и хотя он не нов, его всегда нужно время от времени доставать, чтобы узнавать что-то новое. Часто студенты считают, что тесты сложно писать, сложно поддерживать, они нестабильны и малоценны, что может быть вызвано плохо написанными модульными тестами. Итак, давайте посмотрим, каким принципам должен следовать хороший модульный тест.
Во-первых, давайте рассмотрим простой пример того, как выглядит простой модульный тест JavaScript:
// production code
const computeSumFromObject = (a, b) => {
return a.value + b.value
}
// testing code
it('should return 5 when adding object a with value 2 and b with value 3', () => {
// given - 准备数据
const a = { value: 2 }
const b = { value: 3 }
// when - 调用被测函数
const result = computeSumFromObject(a, b)
// then - 断言结果
expect(result).toBe(5)
})
Вот и все для одного из простейших модульных тестов. Но хотя воробей маленький, его пять внутренних органов в основном завершены.Он раскрывает базовую структуру модульного тестирования: подготовка входных данных, вызов тестируемой функции и утверждение выходного результата. Любой модульный тест может следовать такому скелету, который часто называют триадой «дан-когда-тогда».
Почему модульное тестирование легко сказать, но нелегко сделать? В дополнение к следованию трем пунктам, очевидно, нам нужно следовать некоторым другим принципам. Как упоминалось ранее, мы возлагаем большие надежды на модульное тестирование.Давайте посмотрим, как оно может достичь желаемого эффекта, чтобы полностью изменить характеристики модульного тестирования:
- Реконструкция безопасности уже имеет код ->Должна быть одна и только одна причина неудачи,Не обращайте внимания на внутреннюю реализацию
- очень выразительный
- быстрый возврат ->быстро,стабилизировать
Давайте посмотрим, что все эти три принципа:
Есть одна и только одна причина неудачи
Есть одна и только одна причина неудачи, и что это за причина? да«Когда ввод не изменился, если и только если функция тестируемого бизнес-кода была изменена», тест должен зависнуть. Почему это поддерживает рефакторинг, потому что рефакторинг означает средство настройки внутренней реализации программного обеспечения без изменения наблюдаемого поведения вне программного обеспечения. То есть, когда ввод и вывод тестируемого кода не изменились, как бы я ни рефакторил внутреннюю реализацию кода, тест не должен провалиться. Можно сказать, что это поддерживает рефакторинг. Некоторые юнит-тесты написаны так, что как только внутренняя реализация (такая как структура данных) корректируется, тест зависает, хотя сам его бизнес не модифицировался, так как же он может поддерживать рефакторинг? Неудивительно, что бесполезно критиковать дороговизну тестирования. Обычно возникает такая ситуация, которая может быть вызвана проверкой того, что сначала пишется код, а потом дополняется, либо непонятен интерфейс и абстракция кода.
Кроме того, есть некоторые тесты (например, сага ниже, чтобы увидеть официальный рекомендуемый тест), которые необходимо протестировать для достижения порядка выполнения кода. Это своего рода «внутренне ориентированный на достижение» тест, который делает дополнение к бизнес-целям, а также «порядок выполнения» факторами, которые могут вызвать зависание теста. Этот тест также очень хрупок.
очень выразительный
Это очень выразительно, и он говорит о двух аспектах:
- Когда вы видите тест, вы знаете, какую бизнес-цель он тестирует.
- Когда тест не пройден, вы можете четко увидеть разницу между бизнесом, ожидаемыми данными и фактическим результатом.
Эти выразительные возможности отражаются во многих аспектах, таких как описание теста, наименование подготовки данных, очистка данных, не имеющих отношения к тесту, сравнения, которые могут предоставить инструменты утверждения, и т. д. Доказательств нет, пожалуйста, проверяйте их почаще, когда будете тестировать посадку после прочтения.
быстро и стабильно
Можно ли по-прежнему называть неудовлетворительный модульный тест модульным тестом? Вообще говоря, модульный тест без зависимостей и вызовов API может быть выполнен за миллисекунды. Итак, чтобы достичь цели быстро и стабильно, нам нужно:
- Изолируйте как можно больше зависимостей. Меньше зависимости, более высокая скорость и более стабильная работа.
- Поместите трудоемкие и зависимые сторонние результаты, такие как зависимости и интеграции, в тесты более высокого уровня и сделайте это стратегически.
- Не включайте логику тестового кода.不然你咋知道是实现挂了还是你的测试挂了呢?
Стратегия модульного тестирования React и реализация
Стратегии модульного тестирования для приложений React
Тестовый контент | Тестовая стратегия | объяснять | |
---|---|---|---|
Действие (создатель) слой | Объект действий создан правильно | Тестирование, как правило, не требуется, в зависимости от достоверности | Этот уровень очень прост, и, как правило, невозможно ошибиться после того, как инфраструктура настроена, наслаждаясь простотой, привносимой архитектурой. |
переходной слой | Это сделано правильно расчет | ||
Для более сложной логики селектору требуется 100% охват | |||
слой саги (побочный эффект) | Получили ли вы правильные параметры для вызова API и использовали правильные данные для доступа обратно в избыточность? | 100% покрытие рекомендуется для пяти бизнес-моментов: получены ли правильные параметры, вызывается ли правильный API, используется ли правильное возвращаемое значение для сохранения данных, логики бизнес-ветвей и ветви исключений. | Этот уровень также имеет бизнес-логику, и очень важно провести рефакторинг пяти аспектов, упомянутых выше. |
компонентный (компонентный доступ) уровень | Отображается правильный компонент | Логика рендеринга ветвей компонентов требует 100% покрытия, параметры вызова интерактивных событий обычно требуют 100% покрытия, компоненты, которые были подключены с помощью редукса, не тестируются, чистый пользовательский интерфейс не тестируется, а CSS обычно не тестируется. | Этот уровень является наиболее сложным, и стратегия тестирования по-прежнему основана на руководящем принципе «самая низкая стоимость, максимальная выгода». |
слой пользовательского интерфейса | Правильный ли стиль | Непредсказуемо на данный момент | На этом уровне, насколько я понимаю, тест сложнее стабилизировать, а стоимость выше. |
слой утилит | Различные вспомогательные функции | Те, у кого нет побочных эффектов, должны быть покрыты на 100%, те, у кого есть побочные эффекты, настраиваются в соответствии с ситуацией в проекте. |
Для этой стратегии здесь нужно сделать некоторые другие дополнения:
<Provider />
@connect
@connect
Стратегии тестирования DOM. То есть через такие инструменты, как энзим, через селектор css для проверки рендеринга DOM. Поскольку этот тип теста, естественно, должен связывать элементы DOM с помощью селектора css, в дополнение к тестируемому бизнесу, селектор css сам по себе является фактором в зависающих тестах. Тест DOM может не пройти по крайней мере по двум причинам, не соответствующим рекомендациям, упомянутым выше. Но такое тестирование иногда действительно полезно, и позже, когда мы будем говорить о компонентном тестировании, мы особо упомянем, как разработать для него подходящую стратегию.
тест действий
Этот слой слишком прост, и в принципе его нельзя тестировать, пользуясь простотой архитектуры. Конечно, если есть какие-то часто неправильные действия, то целевые дополнительные тесты для создателей этих действий.
export const saveUserComments = (comments) => ({
type: 'saveUserComments',
payload: {
comments,
},
})
import * as actions from './actions'
test('should dispatch saveUserComments action with fetched user comments', () => {
const comments = []
const expected = {
type: 'saveUserComments',
payload: {
comments: [],
},
}
expect(actions.saveUserComments(comments)).toEqual(expected)
})
тест редуктора
Существует примерно два типа редюсеров: один относительно простой и сохраняет только соответствующие срезы данных один за другим, другой более сложный, с некоторой логикой вычислений. Редуктор первого типа настолько прост в написании, что его даже не нужно покрывать тестами. Его правильность в основном гарантируется простой архитектурой и логикой. Вот пример тестирования простого редуктора:
import Immutable from 'seamless-immutable'
const initialState = Immutable.from({
isLoadingProducts: false,
})
export default createReducer((on) => {
on(actions.isLoadingProducts, (state, action) => {
return state.merge({
isLoadingProducts: action.payload.isLoadingProducts,
})
})
}, initialState)
import reducers from './reducers'
import actions from './actions'
test('should save loading start indicator when action isLoadingProducts is dispatched given isLoadingProducts is true', () => {
const state = { isLoadingProducts: false }
const expected = { isLoadingProducts: true }
const result = reducers(state, actions.isLoadingProducts(true))
expect(result).toEqual(expected)
})
Вот сложный, более сложный пример REDUCER тестового значения, которое было объединено и перегружено при сохранении данных:
import uniqBy from 'lodash/uniqBy'
export default createReducers((on) => {
on(actions.saveUserComments, (state, action) => {
return state.merge({
comments: uniqBy(
state.comments.concat(action.payload.comments),
'id',
),
})
})
})
import reducers from './reducers'
import actions from './actions'
test(`
should merge user comments and remove duplicated comments
when action saveUserComments is dispatched with new fetched comments
`, () => {
const state = {
comments: [{ id: 1, content: 'comments-1' }],
}
const comments = [
{ id: 1, content: 'comments-1' },
{ id: 2, content: 'comments-2' },
]
const expected = {
comments: [
{ id: 1, content: 'comments-1' },
{ id: 2, content: 'comments-2' },
],
}
const result = reducers(state, actions.saveUserComments(comments))
expect(result).toEqual(expected)
})
Как чистая функция, редуктор очень подходит для модульного тестирования, кроме того, в редьюсере обычно выполняется тяжелая логическая обработка, значение защиты модульного тестирования здесь также велико. Пожалуйста, обратите внимание на то, соответствует ли упомянутое выше модульное тестирование основным принципам модульного тестирования, которые мы описываем:
- Существует одна и только одна причина сбоя: когда ввод остается неизменным, тест может зависнуть только тогда, когда тестируемая бизнес-операция «слияние и дедупликация» не оправдывает ожиданий.
- Чрезвычайно выразительна: описание теста было четко написано «при использовании вновь полученных данных сообщения для распространения действия
saveUserComments
, он должен быть объединен с существующим сообщением, и дубликаты должны быть удалены ». Кроме того, тестовые данные только подготовили достаточно данных для двух идентификаторов, чтобы отразить работу« объединения », но не поместил много данных, что привело к шум;
Говорите здесь. Давайте посмотрим на тестовый пример селектора:
import { createSelector } from 'reselect'
// for performant access/filtering in React component
export const labelArrayToObjectSelector = createSelector(
[(store, ownProps) => store.products[ownProps.id].labels],
(labels) => {
return labels.reduce(
(result, { code, active }) => ({
...result,
[code]: active,
}),
{}
)
}
)
import { labelArrayToObjectSelector } from './selector'
test('should transform label array to object', () => {
const store = {
products: {
10085: {
labels: [
{ code: 'canvas', name: '帆布鞋', active: false },
{ code: 'casual', name: '休闲鞋', active: false },
{ code: 'oxford', name: '牛津鞋', active: false },
{ code: 'bullock', name: '布洛克', active: true },
{ code: 'ankle', name: '高帮鞋', active: true },
],
},
},
}
const expected = {
canvas: false,
casual: false,
oxford: false,
bullock: true,
ankle: false,
}
const productLabels = labelArrayToObjectSelector(store, { id: 10085 })
expect(productLabels).toEqual(expected)
})
тест саги
Сага — это слой, отвечающий за вызов API и обработку побочных эффектов. В реальных проектах есть другие промежуточные слои для борьбы с побочными эффектами, такие как redux-thunk, redux-promise и т.д. Суть та же, но сага лучше в тестируемости. Как протестировать этот слой побочных эффектов?Прежде всего, чтобы обеспечить скорость и стабильность модульного тестирования, мы должны имитировать неопределенные зависимости, такие как вызовы API.. После тщательного обобщения я думаю, что основное содержание этого теста слоя состоит из пяти пунктов:
- Вызывается ли правильный API с правильными параметрами (обычно из полезной нагрузки действия или избыточности)
- Для фиктивных API возвращается, сохраняются ли правильные данные (обычно сохраняются в избыточность через действия)
- Основная бизнес-логика (например, вызов API только в том случае, если пользователь удовлетворяет определенным разрешениям и т. д.)
- логика исключений
- Возникают ли другие побочные эффекты (например, события, которые иногда требуют Emit, данные, которые необходимо сохранить в IndexDB и т. д.)
Неправильная поза от чиновника
Redux-Saga официально предоставляетutil: CloneableGenerator
Помогал нам писать тесты для саги. Это первый тест, используемый в нашем проекте, тест, который, вероятно, будет написан следующим образом:
import chunk from 'lodash/chunk'
export function* onEnterProductDetailPage(action) {
yield put(actions.notImportantAction1('loading-stuff'))
yield put(actions.notImportantAction2('analytics-stuff'))
yield put(actions.notImportantAction3('http-stuff'))
yield put(actions.notImportantAction4('other-stuff'))
const recommendations = yield call(Api.get, 'products/recommended')
const MAX_RECOMMENDATIONS = 3
const [products = []] = chunk(recommendations, MAX_RECOMMENDATIONS)
yield put(actions.importantActionToSaveRecommendedProducts(products))
const { payload: { userId } } = action
const { vipList } = yield select((store) => store.credentails)
if (!vipList.includes(userId)) {
yield put(actions.importantActionToFetchAds())
}
}
import { put, call } from 'saga-effects'
import { cloneableGenerator } from 'redux-saga/utils'
import { Api } from 'src/utils/axios'
import { onEnterProductDetailPage } from './saga'
const product = (productId) => ({ productId })
test(`
should only save the three recommended products and show ads
when user enters the product detail page
given the user is not a VIP
`, () => {
const action = { payload: { userId: 233 } }
const credentials = { vipList: [2333] }
const recommendedProducts = [product(1), product(2), product(3), product(4)]
const firstThreeRecommendations = [product(1), product(2), product(3)]
const generator = cloneableGenerator(onEnterProductDetailPage)(action)
expect(generator.next().value).toEqual(
actions.notImportantAction1('loading-stuff')
)
expect(generator.next().value).toEqual(
actions.notImportantAction2('analytics-stuff')
)
expect(generator.next().value).toEqual(
actions.notImportantAction3('http-stuff')
)
expect(generator.next().value).toEqual(
actions.notImportantAction4('other-stuff')
)
expect(generator.next().value).toEqual(call(Api.get, 'products/recommended'))
expect(generator.next(recommendedProducts).value).toEqual(
firstThreeRecommendations
)
generator.next()
expect(generator.next(credentials).value).toEqual(
put(actions.importantActionToFetchAds())
)
})
Этот план был написан слишком много, и все начали чувствовать болевые точки, что явно нарушало некоторые из принципов, о которых мы упоминали ранее:
- Тест явно является копией реализации. Это нарушает вышеупомянутый принцип «есть только одна причина провалить тест», и изменение порядка реализации также приведет к провалу теста.
правильная осанка
Что касается не только болевых точек, наш идеальный тест саги должен быть таким: 1) не зависит от порядка реализации; 2) допускать только реальную заботу, ценные бизнес-тесты; 3) поддержка реконструкции без изменения деловой практики. Таким образом, тестирование эффективности и защита опыта разработчиков будут значительно улучшены.
Таким образом, мы обнаружили, что официальный представитель предоставил такой инструмент для запуска теста, который можно использовать для полного удовлетворения наших потребностей:runSaga
. Мы можем использовать его, чтобы выполнить всю сагу один раз, собрать все опубликованные действия и позволить разработчику свободно утверждать интересующее действие! Основываясь на этом выводе, мы запустили вторую версию протокола тестирования саги:runSaga
+ пользовательские расширения шуткиexpect
утверждение.最终,使用这个工具写出来的 saga 测试,几近完美:
import { put, call } from 'saga-effects'
import { Api } from 'src/utils/axios'
import { testSaga } from '../../../testing-utils'
import { onEnterProductDetailPage } from './saga'
const product = (productId) => ({ productId })
test(`
should only save the three recommended products and show ads
when user enters the product detail page
given the user is not a VIP
`, async () => {
const action = { payload: { userId: 233 } }
const store = { credentials: { vipList: [2333] } }
const recommendedProducts = [product(1), product(2), product(3), product(4)]
const firstThreeRecommendations = [product(1), product(2), product(3)]
Api.get = jest.fn().mockImplementations(() => recommendedProducts)
await testSaga(onEnterProductDetailPage, action, store)
expect(Api.get).toHaveBeenCalledWith('products/recommended')
expect(
actions.importantActionToSaveRecommendedProducts
).toHaveBeenDispatchedWith(firstThreeRecommendations)
expect(actions.importantActionToFetchAds).toHaveBeenDispatched()
})
Этот тест был намного короче, без шума посторонних утверждений и по-прежнему следует структуре «данно-когда-тогда». Кроме того, тестируются два важных бизнес-объекта: «сохранять только первые три рекомендованных продукта» и «рассылать рекламу пользователям, не являющимся VIP-пользователями», у которых есть свои краткие правила:
- Когда вход остается неизменным, независимо от того, как вы оптимизируете внутреннюю реализацию и настраиваете внутренний порядок, бизнес-сценарии, о которых заботится этот тест, не будут зависать, и он действительно выполняет роль защиты тестирования и поддержки рефакторинга.
- Вы можете утверждать только то, что вас волнует, игнорируя промежуточные процессы, которые не важны или небрежны (например, в приведенном выше примере мы не утверждаем другие
notImportant
отправлено ли действие), устраняя шум неуместных утверждений и улучшая выразительность - использовал
product
Такие испытательные данные для создания данных Suites (приспособления) упрощают данные тестовых данных, устраняют шум нерелевантных данных и улучшить выразительность - индивидуальные
expect(action).toHaveBeenDispatchedWith(payload)
сопоставители выразительны и безошибочны
Этот пользовательский сопоставитель передается через шуткуexpect.extend
Расширение реализует:
expect.extend({
toHaveBeenDispatched(action) { ... },
toHaveBeenDispatchedWith(action, payload) { ... },
})
Выше перечислены инструменты тестирования побочных эффектов, стратегии тестирования и решения для тестирования, которые мы считаем лучшими. При его использовании вы должны помнить о том, что действительно важно для бизнеса (пять пунктов, упомянутых в начале этого раздела), и всегда придерживаться трех основных принципов в более сложных модульных тестах. Только тогда модульные тесты могут действительно ускорить разработку, поддержать рефакторинг и служить документацией для бизнес-контекста.
проверка компонентов
Тестирование компонентов на самом деле является место с наибольшей практикой и большинством мнений и разногласий о тестировании практики. Актуальный компонент представляет собой очень автономную единицу. От перспективы классификации у него есть несколько категорий:
- Отображать бизнес-компоненты
- Тип бизнес-компонентов контейнера
component(props) => UI
.内容、结构和样式,比起测试,直接在页面上调试反馈效果更好。测也不是不行,但都难免有不稳定的成本在;逻辑这块,还是有一测的价值,但需要控制好依赖。综合「好的单元测试标准」作为原则进行考虑,我的建议是:两测两不测。
- Логика рендеринга должна учитывать сборку ветвления
- Вызов события и передача параметров обычно проверяются
- Чистый пользовательский интерфейс не тестируется на уровне модульного тестирования.
- Компоненты высокого порядка, связанные с избыточностью, неожиданны
- Другие общие исключения (такие как CSS, в официальных документах есть контрпримеры)
Логика ветвления компонентов часто представляет собой ветвь, имеющую бизнес-значение и ценность для бизнеса. Добавление модульных тестов может не только обеспечить реконструкцию, но и может использоваться для документирования; вызовы событий также имеют бизнес-ценность и функции документирования, а вызовы параметров вызовов событий иногда могут быть использованы, играют защитную роль в реконструкции.
Чистое интернет-интерфейс не находится в тестировании уровня тестирования единицы, он чисто потому, что это не приятно утверждать. Так называемый тест на снимок имеет смысл в два: должен быть визуальный уровень сравнения, а разработчики должны быть проверены каждый раз. Jest имеет концепцию теста на снимок, но в том, что тест пользовательского интерфейса - это сравнение уровня кода, а не Alipay визуальной сцены, и, наконец, обернуты вокруг, лучше видеть GIT Diff. Каждый раз, когда вы спрашиваете сознательного осмотра разработчика, трудно настаивать на рабочем процессе. Учитывая эти расходы, я не рекомендую тестировать тестирование типа пользовательского интерфейса на уровне теста на уровне. Для наших предыдущих средних средних проектов прибегают к обработке все еще имеет определенную управляемость.
Компоненты более высокого порядка, связанные с избыточностью, неожиданны. Причина в том,connect
Тестируемые компоненты — это не что иное, как несколько контрольных точек с точки зрения тестирования:
-
mapStateToProps
будь то изstore
-
mapDispatchToProps
actions
- карта
props
- Когда срез данных, соответствующий избыточности, будет обновлен, будет ли он использовать новый?
props
Запустить компонент для обновления
эти четыре точки,react-redux
Я уже проверил это для вас,было доказано, что он работает, Зачем повторять тест это? Конечно, это неожиданная вещь, или есть такая возможность, что вы экспортируете чистые компоненты, проверенные ранее, но на самом деле код запускает ошибку. Это может быть исчерпано несколькими основными проблемами:
- Вы
mapStateToProps
опечатка или имя переменной - вы написали
mapStateToProps
но нет подключения - Вы
mapStateToProps
Пройденный путь неверен, он был изменен в редукции
Первая и вторая возможности игнорируются. Тестирование не панацея и не может уберечь людей от ошибок.Если этот сценарий подавать маленькими шагами, он будет быстро обнаружен.Если его не подавать маленькими шагами, никакой тест вам не поможет, если логика определенного куска сбора данных повторяется во многих местах, вы можете подумать о том, чтобы извлечь эту логику в селектор и протестировать ее отдельно.
Третья возможность действительно является проблемой, но теперь кажется, что частота ее возникновения низкая. Почему же тогда, поскольку нет типа системы, которой мы не являемся и не можем произвольно изменять структуру данных, редукс, ах ... (эта инвазивная тяжелая структура йо), поэтому для небольшого количества этих сценариев происходят ненужные принимают форму полной преследует 1000 обложек. Неожиданный дефолт или часть проблемы часто могут пойти не так, как надо, и тогда стратегические тесты могут быть исправлены.
Подводить итоги,@connect
Компоненты не тестируются, т.к. фреймворк сам сделал большую часть тестов, а остальные сценарии не подвержены багам, а применение тестов удорожит (подготовка зависимостей и данных), уменьшит опыт разработки, фазз-тест сценариев, а рентабельность не высока, поэтому сильно рекомендуется сохранить это сердце. Неожиданно@connect
компоненты, по сути, такжеофициальная документацияРекомендуемая практика.
Затем, исходя из приведенных выше первых, 2 выводов, структура компонентов четырех типов сопоставляется со структурой четырех типов, мы можем получить следующую таблицу, затем находим... Каждый компонент должен быть измерен.а такжеИ тип компонента вообще не обязательно связан... Однако функциональные компоненты могут подразумевать какие-то другие модели, поэтому в общем обсуждается небольшой раздел.
Тип компонента / Тестовый контент | логика рендеринга ветвей | вызов события | @connect |
чистый интерфейс |
---|---|---|---|---|
Компоненты дисплея | ✅ | ✅ | - | ✖️ |
компонент контейнера | ✅ | ✅ | ✖️ | ✖️ |
Общие компоненты пользовательского интерфейса | ✅ | ✅ | - | ✖️ |
✅ | ✅ | ✖️ | ✖️ |
Бизнес-компоненты — рендеринг ветвей
export const CommentsSection = ({ comments }) => (
<div>
{comments.length > 0 && (
<h2>Comments</h2>
)}
{comments.map((comment) => (
<Comment content={comment} key={comment.id} />
)}
</div>
)
Ниже приведены соответствующие тесты, проверяющие различную логику рендеринга ветвей: когда комментариев нет, заголовок «Комментарии» не рендерится.
import { CommentsSection } from './index'
import { Comment } from './Comment'
test('should not render a header and any comment sections when there is no comments', () => {
const component = shallow(<CommentsSection comments={[]} />)
const header = component.find('h2')
const comments = component.find(Comment)
expect(header).toHaveLength(0)
expect(comments).toHaveLength(0)
})
test('should render a comments section and a header when there are comments', () => {
const contents = [
{ id: 1, author: '男***8', comment: '价廉物美,相信奥康旗舰店' },
{ id: 2, author: '雨***成', comment: '所以一双合脚的鞋子...' },
]
const component = shallow(<CommentsSection comments={contents} />)
const header = component.find('h2')
const comments = component.find(Comment)
expect(header.html()).toBe('Comments')
expect(comments).toHaveLength(2)
})
Бизнес-компонент — вызов события
Сценарий тестового события выглядит следующим образом: когда нажат продукт, связанная с продуктом информация должна быть отправлена в систему отслеживания для отслеживания.
export const ProductItem = ({
id,
productName,
introduction,
trackPressEvent,
}) => (
<TouchableWithoutFeedback onPress={() => trackPressEvent(id, productName)}>
<View>
<Title name={productName} />
<Introduction introduction={introduction} />
</View>
</TouchableWithoutFeedback>
)
import { ProductItem } from './index'
test(`
should send product id and name to analytics system
when user press the product item
`, () => {
const trackPressEvent = jest.fn()
const component = shallow(
<ProductItem
id={100832}
introduction="iMac Pro - Power to the pro."
trackPressEvent={trackPressEvent}
/>
)
component.find(TouchableWithoutFeedback).simulate('press')
expect(trackPressEvent).toHaveBeenCalledWith(
100832,
'iMac Pro - Power to the pro.'
)
})
Достаточно просто. Некоторые из приведенных здесь тестов не дадут сбой при изменении вещей, связанных со стилем, но если вы измените логику ветвления или содержимое вызовов функций, они не пройдут. А логика ветвления или вызовы функций — это именно то, что я считаю близким к бизнесу, поэтому они полезны для защиты логики кода и защиты рефакторинга. Конечно, они по-прежнему в некоторой степени зависят от деталей реализации внутри компонента, таких какfind(TouchableWithoutFeedback)
, или "компонент, используемый внутриTouchableWithoutFeedback
компонентов», который, вероятно, изменится. То есть, если я изменю компонент, чтобы он принимал событие щелчка, тест все равно завершится ошибкой, хотя поведение при щелчке все еще будет иметь место. Это противоречит нашей поговорке «есть только одна причина для провала теста». Это не идеально для тестирования компонентов.
Но этой проблемы не избежать. Поскольку компоненты, по сути, отображают дерево компонентов, чтобы быть связанными с деревом компонентов в тесте, необходимо передать селекторы, такие как имя компонента и идентификатор. Связь этих селекторов сама по себе является «еще одной причиной» для теста. потерпеть неудачу. Однако тестирование веток и событий компонентов имеет определенную ценность, и его нельзя избежать. Итак, я думаю, что эта часть все еще используется, но с некоторыми ограничениями, чтобы контролировать дополнительные затраты на поддержку тестов с этими предположениями:
- Не утверждайте внутренние компоненты компонента. как те
expect(component.find('div > div > p').html().toBe('Content')
действительно забыть об этом - Правильно разделите дерево компонентов. Компонент отвечает только за одну функцию, насколько это возможно, и ему не разрешается складывать слишком много функций и функций. Соблюдение принципа единой ответственности
Если каждый компонент очень понятен и интуитивно понятен, логика понятна, поэтому вышеуказанные компоненты легко измерить, за ними очень легко следить.shallow
-> find(Component)
-> Трехступенчатое утверждение, даже если вы понимаете внутренние детали некоторых компонентов, обычно находится в пределах контролируемого диапазона, а стоимость обслуживания невелика. В настоящее время это практика, которая, как мне кажется, уравновешивает выразительность, значение рефакторинга и стоимость тестирования.
Функциональные компоненты -children
компонент высокого порядка
Функциональный компонент относится к другому типу компонентов, который не имеет ничего общего с бизнесом: это функциональный компонент, больше похожий на базовый компонент, который поддерживает работу бизнес-компонентов в нижней части, таких как компоненты маршрутизации, компоненты пейджинга и т. д. Эти компоненты обычно больше ориентированы на логику и меньше на пользовательский интерфейс. Его основной метод измерения такой же, как и у бизнес-компонентов: он не заботится о конкретном отображении пользовательского интерфейса, а измеряет только отображение ветвей и вызов событий. Однако из-за частичных функциональных характеристик при разработке некоторых шаблонов проектирования часто появляются бизнес-компоненты, такие как компоненты более высокого порядка, функции в качестве подкомпонентов и т. д. Они описаны отдельно ниже.
export const FeatureToggle = ({ features, featureName, children }) => {
if (!features[featureName]) {
return null
}
return children
}
export default connect(
(store) => ({ features: store.global.features })
)(FeatureToggle)
import React from 'react'
import { shallow } from 'enzyme'
import { View } from 'react-native'
import FeatureToggles from './featureToggleStatus'
import { FeatureToggle } from './index'
const DummyComponent = () => <View />
test('should not render children component when remote toggle is empty', () => {
const component = shallow(
<FeatureToggle features={{}} featureName="promotion618">
<DummyComponent />
</FeatureToggle>
)
expect(component.find(DummyComponent)).toHaveLength(0)
})
test('should render children component when remote toggle is present and stated on', () => {
const features = {
promotion618: FeatureToggles.on,
}
const component = shallow(
<FeatureToggle features={features} featureName="promotion618">
<DummyComponent />
</FeatureToggle>
)
expect(component.find(DummyComponent)).toHaveLength(1)
})
test('should not render children component when remote toggle object is present but stated off', () => {
const features = {
promotion618: FeatureToggles.off,
}
const component = shallow(
<FeatureToggle features={features} featureName="promotion618">
<DummyComponent />
</FeatureToggle>
)
expect(component.find(DummyComponent)).toHaveLength(0)
})
утилиты тест
Каждый проект будет иметь Utils. В общем, мы ожидаем, что UTIL будет чистой функцией, то есть не полагается на внешнее состояние, не изменяет параметр и не поддерживает функции внутреннего состояния. Эффективность такого функционального теста также очень высока. Принцип тестирования ничем не отличается от предыдущего и повторно описываться не будет. Тем не менее, это стоит упомянуть, потому что функция util в основном является драйвером данных, один вход соответствует выходу и не требует подготовки каких-либо зависимостей, что делает ее очень подходящей для использования параметризованного тестирования. Этот метод тестирования может повысить эффективность подготовки данных, сохраняя при этом подробную информацию о вариантах использования, подсказках об ошибках и т. д. Jest имеет встроенную поддержку параметрического тестирования от 23, а именно:
test.each([
[['0', '99'], 0.99, '(整数部分为0时也应返回)'],
[['5', '00'], 5, '(小数部分不足时应该补0)'],
[['5', '10'], 5.1, '(小数部分不足时应该补0)'],
[['4', '38'], 4.38, '(小数部分不足时应该补0)'],
[['4', '99'], 4.994, '(超过默认2位的小数的直接截断,不四舍五入)'],
[['4', '99'], 4.995, '(超过默认2位的小数的直接截断,不四舍五入)'],
[['4', '99'], 4.996, '(超过默认2位的小数的直接截断,不四舍五入)'],
[['-0', '50'], -0.5, '(整数部分为负数时应该保留负号)'],
])(
'should return %s when number is %s (%s)',
(expected, input, description) => {
expect(truncateAndPadTrailingZeros(input)).toEqual(expected)
}
)
Суммировать
Ну вот, пока основное содержание этой статьи закончено. Подводя итог, основное содержание этой статьи выглядит следующим образом:
- Модульное тестирование является обязательным для любого проекта React (и любого другого)
- Нам нужны автоматизированные наборы тестов, и основная цель — улучшить «отзывчивость» ИТ предприятий и команд.
- Причина, по которой предпочтение отдается модульному тестированию, определяется на основе принципа соотношения затрат и результатов пирамиды тестирования.
- Есть одна и только одна причина неудачи,очень выразительный,быстро и стабильно
-
- Тяжелый логический код редуктора и селектора требует 100% охвата
- Чистые функции на уровне utils требуют 100% покрытия
- Основной тест слоя побочных эффектов:Вы получили правильные параметры?,Правильно ли называется API,Сохраняются ли правильные данные?,Бизнес-логика,логика исключенийПять уровней
- Слой компонентов имеет два теста и два исключения:Логика рендеринга ветвей должна быть протестирована,События и интерактивные вызовы должны быть протестированы; События чистого пользовательского интерфейса (включая CSS),
@connect
компоненты высокого уровня - Действующий слой выборочно покрыт: кроме как быть
- Другие дополнительные советы: пользовательские инструменты тестирования (
jest.extend
), параметризованные тесты и т. Д.
Незаконченные темы и обсуждения приветствуются
讲完 React 下的单元测试尚且已经这么花费篇幅,文章中难免还有些我十分想提又意犹未尽的地方。比如完整的测试策略、比如 TDD、比如重构、比如整洁代码设计模式等。如果读者有由此文章而生发、而疑虑、而不吐不快的种种兴趣和分享,都十分欢迎留下你的想法和指点。写文交流,乐趣如此。 благодарный.