Лучшие практики для внешнего тестирования заказа еды

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

Об авторе: Кэ Пейлинь, инженер Meituan Dianping.

Фронтенд-тестирование по-прежнему остается горячей темой в 2019 году. Когда я присутствовал на Vueconf в мае этого года, основной докладчик по модульному тестированию Vue спросил участников, сколько команд внедрили модульное тестирование, но на удивление лишь немногие подняли руки. Хотя в то время команда автора еще не ввела тестирование переднего плана, но учитывая необходимость тестирования, и команда работала над новым проектом, она полностью интегрировала тестирование переднего плана в этот новый проект после возвращения.

В настоящее время большинство интернет-командГибкая разработкаритм. По сути, автоматизированное тестирование является основной гарантией достижения «гибкости». Быстрый запуск и быстрая проверка бизнес-стороны выдвинули повышенные требования к оперативности технической стороны:Быстрее онлайн, непрерывный онлайн. Учитывая тот факт, что поток людей и приложений постепенно увеличивается, стоимость будущих итераций будет только расти. Конечно, стоимость этой итерации проекта также связана со сложностью проекта. Например, в бизнесе заказов, где находится автор, проект имеет достаточную сложность. для новичков, которые только что присоединились к команде, они будут казаться не очень дружелюбными. Поэтому для проекта важно иметь интерфейсное тестирование, которое может эффективно гарантировать качество и стабильность бизнес-итераций.

Что такое фронтенд-тестирование?

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

  • Модульное тестирование: относится к проверке и проверке наименьшей тестируемой единицы программного обеспечения, обычно относящейся к независимому тестированию одной функции.
  • Тест пользовательского интерфейса: это тест графического интерфейса.
  • Интеграционное тестирование: оно предназначено для проверки того, как различные модули приложения интегрируются и работают вместе, что соответствует его названию.
  • Сквозной тест (e2e): это тест с точки зрения пользователя. Мы рассматриваем нашу программу как черный ящик. Я не знаю, как вы это реализуете внутри себя. Я отвечаю только за открытие браузера. и поместите тестовое содержимое на страницу. Введите его еще раз, чтобы убедиться, что это тот результат, который мне нужен.

Технический отбор

Фреймворк тестирования интерфейса можно описать как сто распустившихся цветов.

  • Модульные тесты — это Mocha, Ava, Karma, Jest, Jasmine и т. д.
  • Тесты пользовательского интерфейса включают ReactTestUtils, Test Render, Enzyme, React-Testing-Library, Vue-Test-Utils и т. д.
  • Тесты e2e включают Nightwatch, Cypress, Phantomjs, Puppeteer и т. д.

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

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

Рамка утверждение моделирование снимок Асинхронное тестирование окружающая обстановка Параллельный тест тестовое покрытие
Mocha Не поддерживается по умолчанию, можно настроить Не поддерживается по умолчанию, можно настроить Не поддерживается по умолчанию, можно настроить дружелюбный глобальная среда нет Необходимо настроить
Ava Поддержка по умолчанию Не поддерживается, требуется сторонняя конфигурация Поддержка по умолчанию дружелюбный Изолированная среда да Не поддерживается, требуется сторонняя конфигурация
Jasmine Поддержка по умолчанию Поддержка по умолчанию Поддержка по умолчанию недружелюбный глобальная среда нет Настройка не требуется
Jest Поддержка по умолчанию Поддержка по умолчанию Поддержка по умолчанию дружелюбный Изолированная среда да Настройка не требуется
Karma Не поддерживается, требуется сторонняя конфигурация Не поддерживается, требуется сторонняя конфигурация Не поддерживается, требуется сторонняя конфигурация Не поддерживается, требуется сторонняя конфигурация - - Необходимо настроить
  • Mocha — лучшая экологическая и наиболее широко используемая среда для одиночного тестирования, но для достижения высокой масштабируемости требуется дополнительная настройка.
  • Ava — это более легкий, эффективный и простой фреймворк для одиночного тестирования, но он недостаточно стабилен сам по себе и перегружает ЦП при наличии большого количества одновременно запущенных файлов.
  • Jasmine — «старичок» фреймворка одиночного тестирования, работает «из коробки», но поддержка асинхронных тестов слабая.
  • Jest основан на Jasmine, с множеством модификаций и добавлением многих фич, тоже работает «из коробки», но хорошо поддерживает асинхронное тестирование.
  • Karma можно протестировать в реальных браузерах, мощных адаптерах и настроить с помощью других одиночных сред тестирования, обычно используемых с Mocha или Jasmine.

У каждого фреймворка есть свои преимущества и недостатки, нет лучшего фреймворка, есть только самый подходящий фреймворк. Средой тестирования по умолчанию для августа является Karma + Jasmine, а средой тестирования по умолчанию для React — Jest.

Jest рекомендуется и используется различными приложениями React. Он основан на Jasmine и до сих пор претерпевал значительные изменения и добавлял множество функций. Недавно созданный проект Create React App будет настроен с Jest по умолчанию, и мы можем использовать его напрямую, не внося слишком много изменений.

тестирование пользовательского интерфейса

Хотя существуют официальные тестовые фреймворки ReactTestUtils и Test Render для тестирования пользовательского интерфейса, их API более сложны, а официальная документация рекомендует использовать react-testing-library или Enzyme.

Note: We recommend using React Testing Library which is designed to enable and encourage writing tests that use your components as the end users do. Alternatively, Airbnb has released a testing utility called Enzyme, что позволяет легко утверждать, манипулировать и просматривать выходные данные ваших компонентов React.

И React Testing Library, и Enzyme основаны на ReactTestUtils и Test Render, инкапсулируя более лаконичный и простой в использовании API.

Enzyme вышел раньше, но он часто отстает от реализации React-функций (примерно пол года или около того, типа хуки не поддерживаются, я не уверен, поддерживаются ли они сейчас). Библиотека тестирования React вышла с опозданием, но, как правило, поддерживает новые функции React, что для меня является огромным преимуществом при тестировании хуков.

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

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

e2e тест

Рамка Поддержка кроссбраузерности выполнить преимущество недостаток
Nightwatch да Selenium Можно использовать с другими фреймворками. Применимо к локальным сценариям функционального тестирования Не поддерживает TypeScript, культура сообщества немного слабее, чем у некоторых других фреймворков.
Cypress нет Chrome Легко отлаживать и регистрировать, использовать Mocha в качестве тестовой структуры, если один тест использует Mocha, все тесты используют одну и ту же структуру, которая выглядит более «стандартной». Отсутствие расширенных функций
Testcafe да Testcafe Поддержка TS, можно тестировать параллельно, из коробки Расширенные функции, такие как запись экрана и моментальные снимки DOM, не поддерживаются.
Puppeteer нет Chrome Быстрая и простая отладка Безголовый Chrome не поддерживает установку расширений

Puppeteer — это библиотека от команды Google Chrome, и хотя она новее других фреймворков e2e, у нее также есть большое сообщество. Он имеет более простой и удобный в использовании API и более высокую скорость работы и постепенно стал эталоном автоматизированного тестирования в отрасли, завоевав сердца большого числа пользователей Selenium. Вы можете взглянуть на структуру тестирования e2e за последние годы.npm trends.

В заключение

После анализа производится окончательный технический выбор нашего проекта.Jest + React Testing Library + Puppeteer

Для проекта Vue, чтобы сохранить единство стека технологий, мы выбралиJest + Vue-Test-Utils + Puppeteer

принципы письма

  • При тестировании кода учитывайте только тест, а не внутреннюю реализацию
  • Данные должны максимально имитировать реальность, чем ближе к реальности, тем лучше
  • Полностью учитывать граничные условия данных
  • Сосредоточьтесь на ключевом, сложном и основном коде и сосредоточьтесь на тестировании
  • использоватьAOP(beforeEach, afterEach), уменьшите количество тестового кода и избегайте бесполезных функций
  • Сочетание тестирования и функциональной разработки способствует проектированию и рефакторингу кода.

написать инструкции

Будущие проекты будут генерироваться на основе Talos.На самом деле Create-React-App используется для генерации проектов React, а Vue-CLI@3 используется для генерации проектов Vue. Он имеет конфигурацию Jest по умолчанию, и никаких серьезных изменений не требуется.

  • Папки для юнит-тестов и UI-тестов имеют одинаковые имена.tests, тестовый файл имеет суффикс .test.js
  • БудуtestsПапки размещаются на том же уровне, что и тестируемый код, поэтому относительные пути импортируются с более короткими путями.
  • Тестовая папка e2e называется e2e и помещается в корневой каталог с src.
  • И в VScode, и в WebStorm есть соответствующие Jest-плагины, после установки появляются такие функции, как автозавершение кода, отладка и автоматическая работа при написании кода.

Как писать тесты

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

Модульные тесты для служебных функций

// lib/utils.js
export function hexToRGB(hexColor) {
  const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hexColor);
  return result ? [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)] : [];
}

// lib/__tests__/utils.test.js
import { hexToRGB } from '../util';

describe('将16进制颜色转为 rgb', () => {
  it('小写', () => {
    expect(hexToRGB('#ffc150')).toEqual([255, 193, 80]);
  });
  it('大写', () => {
    expect(hexToRGB('#FFC150')).toEqual([255, 193, 80]);
  });
});

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

Модульное тестирование с Redux

тестовый редуктор

Редьюсер объединяет действие с предыдущим состоянием и возвращает новое состояние. Поскольку в проекте используется Immutable.js, требуется операция слияния.

// store/reducers/cart.js
import Immutable from 'immutable';
import { UPDATE_CART_DISH_LIST, UPDATE_CART_DISH_SORT_MAP_LIST } from '../actions/cart';

export const initialState = Immutable.Map({
  cartDishList: Immutable.Map({}),
  cartDishSortMapList: Immutable.List([]),
});

export default (state = initialState, action) => {
  switch (action.type) {
    case UPDATE_CART_DISH_LIST:
      return state.merge({
        cartDishList: action.cartDishList,
      });
    case UPDATE_CART_DISH_SORT_MAP_LIST:
      return state.merge({
        cartDishSortMapList: action.cartDishSortMapList,
      });
    default:
      return state;
  }
};

// store/reducers/__tests__/cart.js
import Immutable from 'immutable';
import cartReducer, { initialState } from '../cart';
import { UPDATE_CART_DISH_LIST, UPDATE_CART_DISH_SORT_MAP_LIST } from '../../actions/cart';

describe('cart reducer', () => {
  it('返回初始化的 state', () => {
    expect(cartReducer(undefined, {})).toEqual(initialState);
  });

  it('更新购物车', () => {
    const state = Immutable.Map({ cartDishList: null });
    const action = { type: UPDATE_CART_DISH_LIST, cartDishList: Immutable.Map({}) };
    const newState = Immutable.Map({ cartDishList: Immutable.Map({}) });
    expect(cartReducer(state, action)).toEqual(newState);
  });

  it('更新购物车菜品顺序', () => {
    const state = Immutable.Map({ cartDishSortMapList: null });
    const action = { type: UPDATE_CART_DISH_SORT_MAP_LIST, cartDishSortMapList: Immutable.List([]) };
    const newState = Immutable.Map({ cartDishSortMapList: Immutable.List([]) });
    expect(cartReducer(state, action)).toEqual(newState);
  });
});

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

Обычное действие — это функция, возвращающая обычный объект, который довольно просто проверить.

// store/actions/cart.js
export const UPDATE_SHOPID = 'UPDATE_SHOPID';

export function updateShopIdAction(shopId) {
  return {
    type: UPDATE_SHOPID,
    shopId,
  };
}

// store/actions/__tests__/cart.test.js
import { updateShopIdAction, UPDATE_SHOPID } from '../cart';

test('更新 shopId', () => {
  const expectedAction = { type: UPDATE_SHOPID, shopId: '111' };
  expect(updateShopIdAction('111')).toEqual(expectedAction);
});

Тестирование составных действий с промежуточным ПО

используется в проектеredux-thunkДля этого промежуточного программного обеспечения нам нужно использоватьredux-mock-storeприменять промежуточное ПО к моделируемомуstore.

// store/actions/cart.js
export function updateShopIdAction(shopId) {
  return {
    type: UPDATE_SHOPID,
    shopId,
  };
}
export function updateTableNumAction(tableNum) {
  return {
    type: UPDATE_TABLE_NUM,
    tableNum,
  };
}
export function updateBaseInfo(shopId, tableNum) {
  return (dispatch) => {
    dispatch(updateShopIdAction(shopId));
    dispatch(updateTableNumAction(tableNum));
  };
}

// store/actions/__tests__/cart.test.js
import configureStore from 'redux-mock-store';
import Immutable from 'immutable';
import thunk from 'redux-thunk';
import { updateBaseInfo, UPDATE_SHOPID, UPDATE_TABLE_NUM } from '../cart';

const middlewares = [thunk];
const mockStore = configureStore(middlewares);

test('updateBaseInfo', () => {
  const store = mockStore(Immutable.Map({}));
  store.dispatch(updateBaseInfo('111', '111'));
  const actions = store.getActions();
  const expectPayloads = [{ type: UPDATE_SHOPID, shopId: '111' }, { type: UPDATE_TABLE_NUM, tableNum: '111' }];
  expect(actions).toEqual(expectPayloads);
});

Тестировать асинхронные действия

Нам нужна помощьaxios-mock-adapterЭтот пакет для имитации запросов.Create-React-AppЭтот пакет установлен по умолчанию.

// store/asyncActions/shop.js
import { loadShopInfoAction } from '../actions/main';
import shop from '../../api/shop';
import Loading from '../../components/common/Loading';

export const getShopInfo = (mtShopId, tableNum) => (dispatch) => {
  Loading.show();
  return shop.getShopInfo({ mtShopId, tableNum })
    .then((res) => {
      Loading.close();
      const result = res.data;
      dispatch(loadShopInfoAction(result));
    })
    .catch((e) => {
      Loading.close();
      console.error(e);
    });
};

// store/asyncActions/__tests__/shop.test.js
import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import MockAdapter from 'axios-mock-adapter';
import instance from '@lib/axios';
import { getShopInfo } from '../main';
import { LOAD_SHOP_INFO } from '../../actions/main';

const middlewares = [thunk];
const mockStore = configureStore(middlewares);
const mockHttp = new MockAdapter(instance); // 继承了 axios.js 文件里的配置

test('getShopInfo', () => {
  const store = mockStore({});
  const expectPayloads = [{ type: LOAD_SHOP_INFO, shopInfo: { peopleCount: 0 } }];

  mockHttp.onGet('/shopInfo')
    .reply(200, {
      data: { peopleCount: 0 },
    });

  store.dispatch(getShopInfo())
    .then(() => {
      const actions = store.getActions();
      expect(actions).toEqual(expectPayloads);
    });
});

тестирование пользовательского интерфейса

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

функциональный компонент

Ниже приведен простой взгляд на тест компонентов сложения и вычитания (упрощенная часть логики).

import React from 'react';
import Immutable from 'immutable';
import './NumberCount.less';

const NumberCount = (props) => {
  const { dish, count, addToCart, minusDish, minusPoint,} = props;

  return (
    <div className="number-count">
      {
        count > 0
          ? (
            <>
              <div className="minus">
                <span className="minus-icon" />
                <span className="minus-trigger" onClick={() => minusDishToCart(dish)} />
              </div>
              <div className="num">{count}</div>
            </>
          )
          : null
      }
      <div className="plus">
        <span className="plus-icon" />
        <span className="plus-trigger" onClick={() => addToCart(dish)} />
      </div>
    </div>
  );
};

export default NumberCount;

Для этого компонента логика относительно проста. Наши тестовые точки - правильно ли запускаются события кнопок добавления и вычитания.Когда количество равно 0, отображается кнопка минус и количество, а когда количество не равно 0, правильно ли отображается.

import React from 'react';
import { render, cleanup, fireEvent } from '@testing-library/react';
import Immutable from 'immutable';
import NumberCount from '../NumberCount';

afterEach(cleanup);

test('点击加菜按钮', () => {
  const props = {
    dish: Immutable.fromJS({ spuId: 111 }),
    count: 0,
    addToCart: jest.fn(),
  };
  const { container } = render(<NumberCount {...props} />);
  const addButton = container.querySelector('.plus-trigger');
  const minusButton = container.querySelector('.minus-trigger');
  const numDiv = container.querySelector('.num');
  expect(minusButton).toBeNull();
  expect(numDiv).toBeNull();
  fireEvent.click(addButton);
  expect(props.addToCart).toBeCalledWith(props.dish);
});

test('点击减菜按钮', () => {
  const props = {
    dish: Immutable.fromJS({ spuId: 111 }),
    count: 1,
    addToCart: jest.fn(),
    minusDish: jest.fn(),
  };
  const { container } = render(<NumberCount {...props} />);
  const minusButton = container.querySelector('.minus-trigger');
  const numDiv = container.querySelector('.num');
  expect(numDiv.innerHTML).toBe('1');
  fireEvent.click(minusButton);
  expect(props.minusDish).toBeCalledWith(props.dish);
});

Компоненты более высокого порядка, обернутые с подключением

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

In order to be able to test the App component itself without having to deal with the decorator, we recommend you to also export the undecorated component.

Вот пример.

import React from 'react';
import { connect } from 'react-redux';
import Immutable from 'immutable';
import ImmutableBaseComponent from './ImmutableBaseComponent';
import { selectSkuDish, toggleMultiPanelAction } from '../../store/actions/cart';
import { computeCount } from '@modules/cartHelper';
import './SelectDish.less';

// 需要将这个未被 connect 的组件 export, 用于测试
export class SelectDish extends ImmutableBaseComponent {
  togglePanel = (e, spuDish) => {
    e.stopPropagation();
    this.props.selectSkuDish(spuDish);
    this.props.toggleMultiPanelAction(true);
  }

  render() {
    const { spuDish, cartDishList } = this.props;
    const count = computeCount(spuDish, cartDishList);
    return (
      <div className="select-dish" onClick={e => this.togglePanel(e, spuDish)}>
        选择
        { count > 0 && <span>{count}</span> }
      </div>
    );
  }
}

const mapStateToProps = state => ({
  cartDishList: state.getIn(['cart', 'cartDishList']),
});

const mapDispatchToProps = dispatch => ({
  selectSkuDish: spuDish => dispatch(selectSkuDish(spuDish)),
  toggleMultiPanelAction: show => dispatch(toggleMultiPanelAction(show)),
});

export default connect(mapStateToProps, mapDispatchToProps)(SelectDish);

Вы можете видеть, что SelectDish до того, как пакет экспортируется в код, так что можно выполнить следующие тесты:

import React from 'react';
import { render, cleanup, fireEvent } from '@testing-library/react';
import Immutable from 'immutable';
import { SelectDish } from '../SelectDish';

afterEach(cleanup);

test('选择多规格菜品', () => {
  const props = {
    spuDish: Immutable.fromJS({ spuId: '111' }),
    cartDishList: Immutable.fromJS({}),
    selectSkuDish: jest.fn(),
    toggleMultiPanelAction: jest.fn(),
  };
  const { container } = render(<SelectDish {...props} />);
  const selectButton = container.querySelector('.select-dish');
  fireEvent.click(selectButton);
  expect(props.selectSkuDish).toBeCalledWith(props.spuDish);
  expect(props.toggleMultiPanelAction).toBeCalledWith(true);
});

Советы по написанию тестов

При написании некоторых модулей или UI-тестов каждый может столкнуться с некоторыми трудностями, такими как localStorage или какие-то триггеры функции задержки. Давайте посмотрим, как вести себя в таких ситуациях.

LocalStorage

Поскольку среда Jest основана на jsdom, нам нужно эмулировать поведение localstorage. Изучите метод обнаружения данных в Vue2.0. Создайте новый файл и добавьте следующий код

// config/jest/browserMocks.js
const localStorageMock = (function () {
  let store = {};
  return {
    getItem(key) {
      return store[key] || null;
    },
    setItem(key, value) {
      store[key] = value.toString();
    },
    removeItem(key) {
      delete store[key];
    },
    clear() {
      store = {};
    },
  };
}());

Object.defineProperty(window, 'localStorage', {
  value: localStorageMock,
});

Затем вам нужно настроить этот файл как файл запуска в конфигурации Jest.

setupFiles: [
  '<rootDir>/config/jest/browserMocks.js',
]

Функция задержки

Используйте API, предоставляемые Jest, такие как jest.useFakeTimers(), jest.runAllTimers(), jest.useRealTimers(), чтобы завершить тест. Вы можете увидеть тест пользовательского интерфейса компонента Toast ниже.

import React from 'react';
import './Toast.less';

class Toast extends React.Component {
  static close() {
    Toast.beforeClose && Toast.beforeClose();
    if (Toast.timer) {
      clearTimeout(Toast.timer);
      Toast.timer = null;
    }
    if (Toast.toastWrap) {
      document.body.removeChild(Toast.toastWrap);
      Toast.toastWrap = null;
    }
  }

  componentDidMount() {
    const { duration, beforeClose } = this.props;

    Toast.beforeClose = beforeClose;
    if (duration > 0) {
      Toast.timer = setTimeout(() => {
        Toast.close();
      }, duration);
    }
  }

  render() {
    if (Toast.toastWrap) Toast.close();
    const { content, hasMask } = this.props;

    return (
      <div className="toast-wrap">
        { hasMask && <div className="toast-mask" /> }
        <div className="toast-box">
          {content}
        </div>
      </div>
    );
  }
}

export default Toast;

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

import React from 'react';
import { render, cleanup } from '@testing-library/react';
import Toast from '../Toast';

afterEach(cleanup);

test('正常弹窗', () => {
  jest.useFakeTimers();
  const props = {
    duration: 2000,
    content: 'hello world',
    beforeClose: jest.fn(),
  };
  const { container } = render(<Toast {...props} />);
  const toastDiv = container.querySelector('.toast-box');
  expect(toastDiv.innerHTML).toBe('hello world');
  expect(props.beforeClose).not.toBeCalled();
  jest.runAllTimers();
  expect(props.beforeClose).toBeCalled();
  jest.useRealTimers();
});

e2e тест

Для e2e-тестирования нам не нужно писать слишком много кода, в конце концов, у всех нас есть профессиональные однокурсники по тестированию. Я думаю, что необходимо просто осветить основной процесс, такой как наш бизнес-заказ, войти на страницу меню со страницы исходного номера выбора, добавить и вычесть блюда, а затем войти на страницу заказа, чтобы разместить заказ и т. д. e2e также требует небольшой настройки для Jest. Создайте новый файл jest-e2e.config.js, который не конфликтует с конфигурацией модульного теста.

module.exports = {
  preset: 'jest-puppeteer',
  testRegex: 'e2e/.*\\.test\\.js$',
};

Добавьте строку в package.json

"scripts": {
  "test:e2e": "jest -c jest-e2e.config.js --detectOpenHandles"
}

Вот простой пост для кода e2e.

// e2e/regression.test.js
const puppeteer = require('puppeteer');

const TARGET_URL = 'XXX';
const width = 375;
const height = 667;

test('主流程', async () => {
  const browser = await puppeteer.launch({ headless: false });
  const context = await browser.createIncognitoBrowserContext();
  const page = await context.newPage();
  await page.goto(TARGET_URL, {
    waitUntil: 'networkidle2', // 等到空闲
  });
  await page.setViewport({ width, height });
  await page.waitForSelector('.people-count');
  await page.screenshot({ path: 'e2e/screenshots/main.png' });
  await page.click('.people-count:nth-child(1)');
  await page.click('.start-btn');
  await page.waitFor(3000);
  await page.screenshot({ path: 'e2e/screenshots/menu.png' });

  // 添加普通菜
  await page.click('.meal-list-style > ul > li:nth-child(8) .plus-trigger');

  // 展开购物车
  await page.click('#cart-chef');
  await page.screenshot({ path: 'e2e/screenshots/cart.png' });
  await page.click('#cart-chef');

  // 进入下单页
  await page.click('.cart-bar > .btn.highlight');
  await page.waitFor(3000);
  await page.screenshot({ path: 'e2e/screenshots/order-confirm.png' });

  await browser.close();
}, 20000);

тестовое покрытие

Вы можете просмотреть тестовое покрытие проекта, добавив в проект одну строку команды.

"scripts": {
  "cov": "node scripts/test.js --coverage"
}

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

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

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

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

Измените объем тестовой статистики с помощью конфигурации collectCoverageFrom в Jest.Требования к тестовому покрытию финального проекта: Заявление 60%, Филиалы 60%, Функции 60%, Линии 60%. Соответствующая конфигурация выглядит следующим образом:

{
  collectCoverageFrom: [
    'src/components/common/**/*.{js,jsx,ts,tsx}',
    'src/modules/**/*.{js,jsx,ts,tsx}',
    'src/lib/**/*.{js,jsx,ts,tsx}',
    'src/store/**/*.{js,jsx,ts,tsx}',
    'src/constants/**/*.{js,jsx,ts,tsx}',
    'src/api/**/*.{js,jsx,ts,tsx}',
  ],
  coverageThreshold: {
    global: {
      statements: 60,
      branches: 60,
      functions: 60,
      lines: 60,
    },
  },
}

Вот тестовое покрытие в папке store/actions в нашем проекте.

Вы можете увидеть общее тестовое покрытие всей папки вверху и конкретное покрытие каждого файла ниже. Нажав на файл, можно также просмотреть покрытие определенного кода.

Суммировать

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

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

  • Во-первых, уменьшить количество ошибок в тестовой среде и обнаружить некоторые логические ошибки, запустив один тест.
  • Во-вторых, он охватывает граничные условия, которые не охвачены многими QA-студентами (автор исправил несколько проблем при дополнении теста позже 😁), потому что наш принцип написания теста заключается в том, чтобы полностью учитывать граничные условия данных.
  • Может быть легко рефакторинг.
  • Когда к исходной логике добавляются новые функции, качество и стабильность итераций можно значительно улучшить, запустив предыдущие тесты.

В этой статье в основном обобщается опыт и осадки автора в написании тестов в проекте React, но для проекта Vue пока нет глубокого изучения. Но у Vue есть особенность.В основном, важные библиотеки, такие как Vue-Router и Vuex, официально поддерживаются.Тот же Vue Test Utils также является официальной библиотекой инструментов модульного тестирования Vue.js.ДокументацияОн достаточно подробный и на него можно ссылаться при написании тестов для проектов Vue.