Об авторе
wr_zhang25, старший инженер по интерфейсной разработке в Ctrip, специализируется на покрытии внешнего кода и направлении открытого исходного кода JavaScript.
Лян, старший менеджер по исследованиям и разработкам Ctrip и эксперт по качеству, специализируется на области разработки качества.
1. Предыстория
istanbuljs Это отличный инструмент покрытия кода JavaScript, который в основном используется для обнаружения покрытия кода модульного тестирования и создания локальных отчетов о покрытии. Однако с развитием современных интерфейсных технологий и автоматизированного тестирования пользовательского интерфейса потребность в обнаружении покрытия кода для сквозного тестирования постепенно увеличивается, а функции, предоставляемые istanbuljs, кажутся расширенными.
JavaScript внутри покрытия Ctrip код использует встроенную отчетность о покрытии gitlab,Он также поддерживает только сбор покрытия модульных тестов и отображение обзорных данных. Поскольку интерфейсная технология Ctrip становится все более сложной,,У нас есть собственная платформа для регистрации трафика.,И развернули довольно масштабный кластер симуляторов дляUIавтоматизация(flybirds)Воспроизведение。В этом сценарии,Требуется сквозное тестированиепокрытие код собирайте и отображайте, чтобы студенты-разработчики могли лучше понять свои собственные кода。
Функции, предоставляемые традиционными Стамбулами, больше не могут удовлетворить наши потребности. Нам необходимо обрабатывать отчеты о покрытии с высокой степенью параллелизма из внешнего интерфейса во время процесса автоматизации пользовательского интерфейса, агрегирования покрытия в реальном времени и совокупного отображения данных о покрытии. Поэтому мы разработали Canyon на основе Istanbuljs, чтобы решить проблему сложного сбора сквозного тестового покрытия.
В настоящее время несколько отделов Ctrip начали использовать Canyon, вставлять тестовый код на этапе построения конвейера непрерывной интеграции, а также собирать и сообщать данные о покрытии на этапе автоматического тестирования пользовательского интерфейса. Сервер генерирует подробные отчеты о покрытии в режиме реального времени, предоставляя полные индикаторы данных о покрытии для тестовых сценариев автоматизации пользовательского интерфейса.
2. Введение
Canyon может обеспечить инструментирование кода, создание отчетов о покрытии и создание отчетов в реальном времени с помощью простой настройки подключаемого модуля Babel. Его технологический стек полностью основан на JavaScript и для запуска требуется только среда Node.js. Он прост в развертывании и подходит для развертывания в облачных средах (таких как Docker и Kubernetes).
Архитектурный дизайн приложения подходит для обработки высокочастотных и крупномасштабных отчетов по данным покрытия, а также может обрабатывать различные сценарии автоматического тестирования пользовательского интерфейса. В то же время Canyon легко интегрируется с существующими инструментами CI/CD (такими как GitLab CI, Jenkins), что позволяет пользователям легко использовать его в конвейерах непрерывной интеграции.
Схема архитектуры выглядит следующим образом:
Основные функции Canyon будут представлены в следующих частях:
3. Покрытие кода
По мере того, как вы будете писать больше сквозных тестовых примеров, вы обнаружите, что возникают некоторые вопросы: нужно ли мне писать больше тестовых примеров? Какие еще коды не проверялись? Будут ли повторяться варианты использования? В этот момент на помощь приходит покрытие кода. Его принцип заключается в вставке зонда кода в исходный код перед выполнением кода (фактически это контекст плюс счетчик), чтобы счетчик мог срабатывать каждый раз, когда возникнет случай. выполняется.
Этап внедрения зондов кода в код называется инструментированием. Код перед инструментированием:
// add.js
function add(a, b) {
return a + b
}
module.exports = { add }
Инструментирование — это процесс анализа кода для поиска всех функций, операторов и ветвей, а затем вставки счетчиков в код. Для приведенного выше кода после завершения инструментирования:
// Этот объект используется для подсчета количества выполнения каждой функции и каждого оператора.
const c = (window.__coverage__ = {
// "f" Запишите количество вызовов каждой функции.
f: [0],
// "s" Запишите, сколько раз вызывается каждый оператор.
// У нас есть 3 утверждения, и все они начинаются с 0.
s: [0, 0, 0],
})
// Первый оператор определяет функцию
c.s[0]++
function add(a, b) {
// После вызова функции второй оператор
c.f[0]++
c.s[1]++
return a + b
}
// Третье утверждение вот-вот будет называться
c.s[2]++
module.exports = { add }
Мы хотим убедиться, что каждый оператор и функция в файле add.js были выполнены нашими тестами хотя бы один раз. Итак, пишем тест:
// add.cy.js
const { add } = require('./add')
it('adds numbers', () => {
expect(add(2, 3)).to.equal(5)
})
Когда тест вызывает add(2, 3), счетчик внутри функции add увеличивается, и объект покрытия становится:
{
f: [1],
s: [1, 1, 1]
}
Покрытие этого тестового примера достигает 100%, и каждая функция и каждый оператор выполняется хотя бы один раз. Но в реальных приложениях для достижения 100% покрытия кода требуется несколько тестов.
Это базовое введение в освещение. Благодаря этим необходимым знаниям каждому будет легче понять следующее содержание.
4. Инструментирование кода (инструментирование-код)
Наиболее важной частью покрытия кода является инструментирование кода.
istanbuljs — это проверенный в боях золотой стандарт инструментирования кода JS. Canyon в основном предоставляет решения для сквозного тестирования. После большого количества экспериментов было проверено, что инструменты покрытия современного интерфейсного проектирования должны компилироваться во время компиляции. Конкретная причина заключается в том, что инструментарий nyc, предоставляемый istanbuljs, может инструментировать только собственный js. Однако существует бесконечное количество синтаксисов шаблонов внешнего интерфейса, таких как ts, tsx и vue. Хотя nyc также можно инструментировать, структурная практика доказала это. Эффект покрытия от прямого инструментирования Неудовлетворительный, невозможно точно определить функции, операторы и ветви, которые следует инструментировать.
К счастью, после исследования,мы узналиbabel-plugin-istanbul、vite-plugin-istanbul(experimental)、swc-плагин-покрытие-инструмент (экспериментальный). Свайные решения для других типов проектов. Все без исключения эти методы представляют собой инструментированные вызовы методов в соответствующий момент на этапе компиляции интерфейса, когда код анализируется в абстрактном синтаксическом дереве ast, и инструментированные функции более точно、заявление、ветвь.
Применимые типы проектов:
Тип проекта | план |
---|---|
vanilla javascript | nyc |
babel | babel-plugin-istanbul |
vite | vite-plugin-istanbul (experimental) |
swc | swc-plugin-coverage-instrument (experimental) |
Пользователи могут настроить проекта Выберите подходящий план инструментирования, вам нужно только установить в проект соответствующий плагин, и тогда инструментирование будет выполнено автоматически при компиляции.
Возьмем в качестве примера Babel.config.js:
module.exports = {
plugins: [
[
'babel-plugin-istanbul',
{
exclude: ['**/*.spec.js', '**/*.spec.ts', '**/*.spec.tsx', '**/*.spec.jsx'],
},
],
],
};
После завершения инструментирования в код будут вставлены некоторые зонды кода. Эти зонды кода будут собирать данные о покрытии во время выполнения, а затем передавать их на сервер Canyon.
Чтобы проверить успешность инструментирования, вы можете выполнить поиск по запросу __coverage__ в скомпилированном продукте. Если он существует, значит, инструментирование прошло успешно.
Чтобы тесно связать исходный код кода инструментирования, мы адаптировали различных провайдеров, отправили переменные среды на сервер Canyon и преобразовали их в reportID, чтобы облегчить связанное отображение исходных файлов покрытия после завершения расчета агрегации данных покрытия. .
Мы также предоставляем плагин Babel Babel-plugin-canyon, который может считывать переменные среды (branch, sha) в различных конвейерах (aws, gitlab ci), чтобы последующие данные о покрытии можно было связать с соответствующим исходным кодом gitlab.
babel.config.js
module.exports = {
plugins: [
[
'babel-plugin-canyon',
{
provider: 'gitlab',
branch: process.env.CI_COMMIT_REF_NAME,
sha: process.env.CI_COMMIT_SHA,
},
],
],
};
Поддерживаемые провайдеры:
Важно отметить, что инструментирование проб кода добавит пробы кода в контекст продукта сборки, что увеличит общий объем кода на 30%. Не рекомендуется использовать его в производственной среде.
5. Тестирование и отчетность
Когда инструментарий будет завершен и выпущен в тестовую среду, мы сможем его протестировать. Возьмем, к примеру, драматург. Для успешно оснащенного сайта клиентского приложения к объекту окна будут подключены объекты __coverage__ и __canyon__. Нам необходимо собирать и передавать эти данные на сервер canyon во время процесса тестирования драматурга.
пример драматурга:
const {chromium} = require('playwright');
const main = async () => {
const browser = await chromium.launch()
const page = await browser.newPage();
// Введите тестируемую страницу
await page.goto('http://test.com')
// Выполнение тестовых случаев
// Вариант использования 1
await page.click('button')
// Вариант использования 2
await page.fill('input', 'test')
// Вариант использования 3
await page.click('text=submit')
const coverage = await page.evaluate(`window.__coverage__`)
// Соберите отчетность
upload(coverage)
browser.close()
}
main()
У Ctrip есть собственная платформа автоматизации пользовательского интерфейса Flybirds, и мы интегрировали сбор и отчетность данных о покрытии Canyon в Flybirds. Сценарий сбора покрытия при реальном автоматизированном тестировании пользовательского интерфейса браузера относительно сложен, что в основном отражается в неопределенности времени сбора покрытия для многостраничных тестов (MPA).
Одностраничный (SPA) и многостраничный (MPA)
После выполнения тестового примера для одностраничных приложений (SPA) или многостраничных приложений этап создания отчета заключается в сообщении объекта __coverage__ в объекте окна страницы на сервер Canyon. Для одностраничных приложений, условно говоря, это так. относительно просто. Когда весь тестовый контент находится в одностраничном приложении, данные о покрытии будут постоянно находиться в объекте окна. Для многостраничных приложений переходы при маршрутизации приведут к копированию объекта окна и потере объекта. Таким образом, этот момент имеет решающее значение. После долгих практических проверок мы нашли метод onvisiblechange браузера.
Сообщайте данные о покрытии при изменении видимости в браузере. Стоит отметить, что изменение видимости может привести к повторному предоставлению данных, но для статистики покрытия невыполняемый код не будет объединяться несколько раз. Статистика по конкретным показателям, влияющим на покрытие.
Chrome активно внедряет революционный API JavaScript — fetchLater(). Этот новый API призван полностью упростить процесс отправки данных при закрытии страницы, гарантируя, что даже после закрытия страницы или ухода пользователя запрос может быть выполнен безопасно и надежно в какой-то момент в будущем.
Запуск этого API является захватывающим. Он может решить сложную проблему многостраничного сбора данных (MPA), и его необходимо собирать только при закрытии браузера.
Примечание. fetchLater() уже доступен в Chrome для тестирования на реальных пользователях в оригинальной пробной версии, начиная с версии 121 (выпущенной в январе 2024 г.) и продолжаясь до Chrome 126 (июль 2024 г.).
6. Агрегация
Источником данных о покрытии является та же версия кода. Данные о покрытии могут быть агрегированы внутри компании Canyon, используя reportID для связывания тестовых примеров и измерений агрегирования сегментов. Это позволяет агрегировать массивные данные о охвате в ограниченное число, то есть количество случаев.
/**
* Объединяет экземпляры объекта покрытия файлов двух идентичных файлов, чтобы обеспечить правильность счетчика выполнения.
*
* @method mergeFileCoverage
* @static
* @param {Object} first Первый объект переопределения файла для данного файла.
* @param {Object} second Второй файл перезаписывает объект того же файла
* @return {Object} Объединенный объект результата. Обратите внимание, что входные объекты не изменяются.
*/
function mergeFileCoverage(first, second) {
const ret = JSON.parse(JSON.stringify(first));
delete ret.l; // Удалить производную информацию
Object.keys(second.s).forEach(function (k) {
ret.s[k] += second.s[k];
});
Object.keys(second.f).forEach(function (k) {
ret.f[k] += second.f[k];
});
Object.keys(second.b).forEach(function (k) {
const retArray = ret.b[k];
const secondArray = second.b[k];
for (let i = 0; i < retArray.length; i += 1) {
retArray[i] += secondArray[i];
}
});
return ret;
}
Одной из характеристик данных о покрытии сквозным тестированием является большой размер отдельного объема данных, который эквивалентен 30 % общего объема исходного кода при инструментировании всего проекта. Количество отчетов по автоматизации пользовательского интерфейса на странице бронирования сайта рейсов Ctrip Trip.com может достигать 2000 раз каждый раз, каждый раз с 10 млн данных. Такой объем данных является огромной проблемой для сервера Canyon.
Для сценариев отчетности о данных, где один фрагмент данных большой и частый, трудно выполнить вычисления агрегирования данных в реальном времени. Canyon использует данные в виде очередей сообщений и спроектирован как служба без отслеживания состояния. Он подходит для контейнерного развертывания в эпоху облачных вычислений. Он может использовать эластичное масштабирование HPA для применения отчетов о тестовом покрытии в различных сценариях.
7. Отчет
Для отображения отчета о покрытии мы использовали стиль интерфейса istanbul-report. Однако, поскольку istanbul-report обеспечивает генерацию только статических html-файлов, по этой причине он не подходит для современного внешнего режима генерации данных гидратации. , мы ссылались на его исходный код, чтобы отметить покрытие исходного кода.
const decorations = useMemo(() => {
if (data) {
const annotateFunctionsList = annotateFunctions(data.coverage, data.sourcecode);
const annotateStatementsList = annotateStatements(data.coverage);
return [...annotateStatementsList, ...annotateFunctionsList].map((i) => {
return {
inlineClassName: 'content-class-found',
startLine: i.startLine,
startCol: i.startCol,
endLine: i.endLine,
endCol: i.endCol,
};
});
} else {
return [];
}
}, [data]);
Эффект после окрашивания:
8. Изменение покрытия кода
Для измененного покрытия кода мы рассчитываем формулу: новые покрытые строки кода/все новые строки кода.
Укажите цель сравнения, настроив CompareTarget, а затем объедините ее с интерфейсом git diff gitlab, чтобы получить измененные строки кода и вычислить данные покрытия.
/**
* returns computed line coverage from statement coverage.
* This is a map of hits keyed by line number in the source.
*/
function getLineCoverage(statementMap:{ [key: string]: Range },s:{ [key: string]: number }) {
const statements = s;
const lineMap = Object.create(null);
Object.entries(statements).forEach(([st, count]) => {
if (!statementMap[st]) {
return;
}
const { line } = statementMap[st].start;
const prevVal = lineMap[line];
if (prevVal === undefined || prevVal < count) {
lineMap[line] = count;
}
});
return lineMap;
}
9. реагировать native сбор покрытияплан
Стек мобильных технологий Ctrip в основном основан на реагировании Родной, хорошая новость в том, что он применим и к нашему плану приборостроения, ведь все они собраны на базе Babel. И это эффективно для реагирования внутри компании. Собственная структура проекта унифицирована. Мы реализуем инструменты времени компиляции в конвейере и упаковываем «обычный пакет» и «пакет инструментов» соответственно в конвейер. Таким образом, в сочетании с автоматизацией пользовательского интерфейса, создается полный набор записи и воспроизведения. может быть сформирована система сбора индикаторов покрытия.
Используйте веб-сокет, чтобы предоставить данные о покрытии в симуляторе:
// Создать соединение WebSocket
const socket = new WebSocket('ws://localhost:8080');
// Запускается при открытии соединения WebSocket
socket.onopen = () => {
console.log('Connected to coverage WebSocket server');
};
// Срабатывает при получении сообщения WebSocket
socket.onmessage = event => {
try {
if (JSON.parse(event.data).type === 'getcoverage') {
// Отправить данные о покрытии
socket.send(JSON.stringify(payload));
}
} catch (e) {
console.log(e);
}
};
// Запускается, когда соединение WebSocket закрывается
socket.onclose = () => {
console.log('Disconnected from coverage WebSocket server');
};
В настоящее время модули APP отдела авиабилетов Ctrip подключены к Canyon. После практики Стамбул может очень хорошо выполнять сбор данных о покрытии и инструментах. Команда тестировщиков будет измерять это с помощью показателей покрытия Canyon перед каждым выпуском продукции. этот выпуск.
10. Список приоритетов улучшения покрытия
Когда пользователи впервые подключатся к системе Canyon, они столкнутся с проблемой: без большого количества автоматизированных тестов пользовательского интерфейса покрытие кода больших приложений будет особенно низким. Вначале простое предоставление отчета о покрытии кода в Стамбуле не помогало команде эффективно улучшить покрытие, что приводило всех в замешательство и растерянность.
Чтобы решить эту проблему, мы провели углубленное исследование и обнаружили, что в компании уже есть развитая система сбора данных о покрытии кода производственной среды. Основываясь на этом выводе, мы решили объединить данные из этой системы с нашими собственными данными о покрытии, чтобы создать «список приоритетов улучшения покрытия». Цель этого списка — предоставить командам разработчиков четкие рекомендации о том, где следует уделять приоритетное внимание улучшению покрытия кода.
Чтобы сделать это руководство более научным и практичным, мы сформулировали формулу веса покрытия:
Охват производственной среды × 100 × 0,3 + (1 – тестовое покрытие) × 100 × 0,3 + количество функций × 0,2
С помощью этой формулы мы можем расставить приоритеты для файлов кода с высокой степенью использования, большим количеством строк и низким покрытием тестирования в производственной среде, тем самым предоставляя целевые предложения по улучшению для команды разработчиков. Такой подход не только улучшает качество кода, но и усиливает контроль над общим покрытием.
11. Продвижение сообщества
На момент публикации этой статьи мы официально откроем исходный код Canyon.JavaScriptСегодня это самый популярный язык программирования.,Однако поле сбора сквозного тестового покрытия осталось пустым.,Наша разработка кода основана на istanbuljs,monaco Редактор и другие отличные проекты с открытым исходным кодом, мы уверены, что запуск Canyon с открытым исходным кодом может вызвать отклик сообщества, и в нем сможет принять участие большое количество разработчиков JavaScript.
У Canyon еще есть много возможностей для развития в будущем. Например, набор инструментов производственной среды еще не проверен и не опробован, и нет глубокой связи с инструментами автоматического тестирования, такими как драматург, кукольник и кипарис. было запланировано в дальнейших планах развития. Я надеюсь, что больше людей в Ctrip и сообществе смогут принять участие в строительстве Каньона в будущем.
Справочная ссылка
Проект с открытым исходным кодом Canyon:
https://github.com/canyon-project/canyon
Инструменты покрытия JavaScript:
https://github.com/istanbuljs/istanbuljs
Редактор кода на основе браузера:
https://github.com/microsoft/monaco-editor
Различия в тексте JavaScript:
https://github.com/kpdecker/jsdiff
"An O(ND) Difference Algorithm and its Variations" (Myers, 1986).
http://www.xmailserver.org/diff2.pdf
Делитесь, общайтесь, развивайтесь