В настоящее время у нас нет хорошего способа программного запуска cucumber-js. Это нужно с двух сторон:
В настоящий момент обычно происходит создание нового экземпляра Cli
с объединенным входом argv . Очевидно, что это очень неприятно, и его также нет в общедоступном API .
Иногда (возможно, из-за предполагаемой хрупкости вышеупомянутого) фреймворки просто полагаются на CLI cucumber-js, но изо всех сил пытаются найти способы интеграции и иметь свои собственные варианты.
Класс Runtime
в настоящее время является частью общедоступного API, но он бесполезен в этих контекстах, в зависимости от кода поддержки и кода поддержки, предоставляемого вызывающей стороной.
В проекте две составляющие:
runCucumber
Новая асинхронная функция, которая выполняет тестовый запуск в процессе. Обязанности:
Это будет частью общедоступного API, и мы рекомендуем разработчикам фреймворка использовать его при «упаковке» cucumber-js. Мы также использовали бы его для нашего собственного тестирования.
Насколько это возможно, он избегал прямого взаимодействия с process
, вместо этого принимал нормализованные параметры и потоковые интерфейсы для вывода и предоставлял вызывающему абоненту право решать, как выйти, в зависимости от результата или необработанной ошибки.
Также Runtime
должен выйти из общедоступного API, поскольку это действительно внутренняя вещь.
Фактически «клиент» runCucumber
. Обязанности:
runCucumber
с решенными вариантамиЭтого по-прежнему не будет в общедоступном API. Кроме того, он будет использовать только функции / интерфейсы, которые есть в общедоступном API, так что мы могли бы легко разбить его на собственный пакет в какой-то момент, как это распространенный шаблон сейчас с такими проектами, как Jest .
Это разделение также открывает путь для некоторых интересных новых функций интерфейса командной строки, не позволяя им проникать во внутреннее устройство, например:
--gui
за --interactive
для быстрых целевых повторов при TDDМы также предоставим функции (используемые CLI и другими пользователями) для:
i18nKeywords
и i18nLanguages
Мы нацелены на это в следующем выпуске 8.0.0. Я готов приступить к этому сегодня.
Пейджинг для первоначальной обратной связи: @aslakhellesoy @charlierudolph @ aurelien- reeves @mattwynne @nicojs @ jan-molak
Обожаю это предложение! У нас уже есть API, подобный этому, в runCucumber fake-cucumber
@davidjgoss - отлично звучит!
Для справки, вот как Serenity / JS вызывает Cucumber в настоящий момент - CucumberCLIAdapter
А вот логика преобразования параметров конфигурации в argv - CucumberOptions
.
Было бы намного приятнее предоставить объект параметров вместо argv 👍🏻
Любить это!
При указании этого нового общедоступного API мы также можем рассмотреть недавнее происшествие с проблемой № 1489 и подумать о предоставлении общедоступных API для более широкого и лучшего взаимодействия с фильтрами и результирующими тестируемыми функциями.
И в дополнение к этому - давайте сделаем что-нибудь, что упростило бы и Cucumber-Electron:
Публичный API лучше, чем ничего, так что вперед 👍!
Желательно, чтобы у меня также был API для загрузки профилей с использованием тех же правил, что и cucumber-js
, чтобы я мог точно имитировать поведение обычного вызова cucumber-js.
loadProfiles(directory = process.cwd()): Record<string, Profile>
StrykerJS также будет во многом полагаться на API custom_formatters и события, публикуемые eventBroadcaster
. Можем ли мы добавить их и в общедоступный API? См. Https://github.com/stryker-mutator/stryker-js/blob/03b1f20ed933d3a50b52022cfe363c606c2b16c5/packages/cucumber-runner/src/stryker-formatter.ts#L45 -L69
Желательно, чтобы у меня также был API для загрузки профилей с использованием тех же правил, что и у cucumber-js, чтобы я мог точно имитировать поведение обычного вызова cucumber-js.
Это хороший момент. Профили (по крайней мере, в их текущей форме) фундаментально связаны с интерфейсом командной строки, поэтому кажется правильным оставить их на этой стороне границы, но мы все же можем предоставить функцию для их загрузки и создания объекта частичных параметров.
При указании этого нового общедоступного API мы также можем рассмотреть недавнее происшествие с проблемой № 1489 и подумать о предоставлении общедоступных API для более широкого и лучшего взаимодействия с фильтрами и результирующими тестируемыми функциями.
Я думаю, мы могли бы включить возможность предоставления настраиваемого фильтра рассола при вызове API (в дополнение к именам, тегам и т. Д., Которые управляют встроенной фильтрацией).
Текущий синтаксис для профилей, если он очень командной строки-y.
Я бы хотел указать профили в более общем формате, таком как JSON, JavaScript, YAML или переменные среды. В JSON это могло выглядеть так:
.cucumber.json
{
"default": {
"requireModule": ["ts-node/register"],
"require": ["support/**/*./ts"],
"worldParameters": {
"appUrl": "http://localhost:3000/",
},
"format": ["progress-bar", "html:./cucumber-report.html"]
},
"ci": {
"requireModule": ["ts-node/register"],
"require": ["support/**/*./ts"],
"worldParameters": {
"appUrl": "http://localhost:3000/",
},
"format": ["html:./cucumber-report.html"],
"publish": true
}
}
Или, используя JavaScript
.cucumber.js
const common = {
"requireModule": ["ts-node/register"],
"require": ["support/**/*./ts"],
"worldParameters": {
"appUrl": "http://localhost:3000/",
}
}
module.exports = {
default: {
...common,
"format": ["progress-bar", "html:./cucumber-report.html"]
},
ci: {
...common,
"format": ["html:./cucumber-report.html"],
"publish": true
}
}
Или даже с переменными среды (например, загруженными с помощью такого инструмента, как dotenv):
.cucumber.env
CUCUMBER_PROFILE_DEFAULT_REQUIREMODULE=ts-node/register
CUCUMBER_PROFILE_DEFAULT_REQUIRE=ts-node/register
CUCUMBER_PROFILE_DEFAULT_WORLDPARAMETERS_APPURL=http://localhost:3000/
CUCUMBER_PROFILE_DEFAULT_FORMAT=progress-bar,html:./cucumber-report.html
CUCUMBER_PROFILE_CI_REQUIREMODULE=ts-node/register
CUCUMBER_PROFILE_CI_REQUIRE=ts-node/register
CUCUMBER_PROFILE_CI_WORLDPARAMETERS_APPURL=http://localhost:3000/
CUCUMBER_PROFILE_CI_FORMAT=progress-bar,html:./cucumber-report.html
CUCUMBER_PROFILE_CI_PUBLISH=true
Фактически, конфигурационная библиотека делает именно это. Мы так и не смогли интегрировать его в Cucumber-JVM, потому что мешали другие вещи, но, может быть, мы могли бы попробовать с реализацией JavaScript?
@aslakhellesoy согласен, это было бы
Ссылка # 1004
Это хороший момент. Профили (по крайней мере, в их текущей форме) фундаментально связаны с интерфейсом командной строки, поэтому кажется правильным оставить их на этой стороне границы, но мы все же можем предоставить функцию для их загрузки и создания объекта частичных параметров.
Да, это было бы здорово и высоко ценилось бы с точки зрения создателей плагинов.
Я бы хотел указать профили в более общем формате, таком как JSON, JavaScript, YAML или переменные среды. В JSON это могло выглядеть так:
Звучит здорово! И это именно та причина, по которой я был бы признателен, если бы API загружал их так же, как это делает cucumberJS. Загрузка одного файла cucumber.js
тривиальна. Репликация алгоритма загрузки файла конфигурации, включая приоритет, формат файла и т. Д. И поддержание его - это совсем другое 😅.
В: Смогу ли я запустить runCucumber
дважды подряд _ без очистки требуемого кеша _? Это важно для сценария использования мутационного тестирования.
Мы хотим загрузить среду и запустить тесты несколько раз в быстрой последовательности, изменяя глобальную переменную, чтобы переключить активный мутант.
Прямо сейчас мы используем частный API cli
и нам нужно очищать файлы определения шагов от require.cache
между каждым запуском теста. Это не идеально для CommonJS и вообще не будет работать для esm.
Псевдокод нашего варианта использования:
const profiles = await loadProfiles();
const options = {
...profiles.default,
formatter: require.resolve('./our-awesomely-crafted-formatter'),
some: 'other options we want to override',
}
const cucumber = new Cucumber(options);
// Allow cucumber to load the step definitions once.
// This is `async`, so support for esm can be added without a breaking change
await cucumber.initialize();
// Initial test run ("dry run"), without mutants active
await cucumber.run();
collectMutantCoveragePerTestFromFormatter();
// Start mutation testing:
global.activeMutant = 1;
await cucumber.run({ features: ['features/a.feature:24']);
collectResultsFromFormatterToDetermineKilledOrSurvivedMutant()
global.activeMutant = 2;
await cucumber.run({ features: ['features/b.feature:24:25:26', 'features/c.feature:12']);
collectResultsFromFormatterToDetermineKilledOrSurvivedMutant()
// etc
@nicojs определенно согласен с тем, что нам это нужно, это уже
То, что я набросал до сих пор, было в более функциональном стиле, но с той же фундаментальной концепцией, как я думаю:
const runnerOptions = {
support: {
require: ['features/support/**/*.js']
}
}
// initial run returns support code library
const { support } = await runCucumber(runnerOptions)
// subsequent run reuses support code library
await runCucumber({
...runnerOptions,
support
})
Это работает для нас 👍
У нас тоже работает 👍🏻
Я действительно думаю, что что-то подобное было бы чрезвычайно полезно в качестве альтернативы огромному беспорядку, который у нас есть сегодня с интеграцией инструментов тестирования (Jest, Cypress), например, я обнаружил эти проблемы (в порядке важности):
cypress-cucumber-preprocessor
не поддерживает теги в примерах (https://github.com/TheBrainFamily/cypress-cucumber-preprocessor/issues/196)jest-cucumber
не поддерживает отчеты Cucumber JSON (https://github.com/bencompton/jest-cucumber/issues/27)cypress-cucumber-preprocessor
генерирует несколько отчетов Cucumber JSON без официальной поддержки агрегирования (https://github.com/TheBrainFamily/cypress-cucumber-preprocessor/issues/423)jest-cucumber
не так удобно, как jest-cucumber-fusion
cucumber-jest
...Я бы предпочел увидеть какой-нибудь минимальный связующий код между Jest / Karma / Cypress / и т. Д. и cucumber-js, поэтому мне не нужно страдать из-за всех тех недостающих функций, которые мне нужно использовать.
Отличное предложение @davidjgoss 👍
Такое разделение задач между пользовательским интерфейсом командной строки и «бизнес-логикой» анализа и выполнения сценариев в качестве тестов напоминает мне шаблон гексагональной архитектуры .
В огурце-рубине мы фактически разделили логику основного домена (или «внутренний шестиугольник») на отдельный гем-пакет, поскольку мы перестраивали его с нуля в «чистой комнате». Я понимаю, что это не контекст здесь, но, возможно, стоит черпать вдохновение из этого дизайна или использовать его инновации в Ruby API. В README гема cucumber-ruby-core есть пример того, как использовать этот API.
Хорошо, вот первый проход в сигнатуре API для бита "run". Он в значительной степени основан на объекте IConfiguration
у нас есть внутри (поэтому не должен вызывать слишком много рефакторинга ниже), но только немного менее "плоский":
export interface IRunCucumberOptions {
cwd: string
features: {
defaultDialect?: string
paths: string[]
}
filters: {
name?: string[]
tagExpression?: string
}
support:
| {
transpileWith?: string[]
paths: string[]
}
| ISupportCodeLibrary
runtime: {
dryRun?: boolean
failFast?: boolean
filterStacktraces?: boolean
parallel?: {
count: number
}
retry?: {
count: number
tagExpression?: string
}
strict: boolean
worldParameters?: any
}
formats: {
stdout: string
files: Record<string, string>
options: IParsedArgvFormatOptions
}
}
export interface IRunResult {
success: boolean
support: ISupportCodeLibrary
}
export async function runCucumber(
options: IRunCucumberOptions
): Promise<IRunResult> {
// do stuff
}
И очень надуманный пример использования:
const result = await runCucumber({
cwd: process.cwd(),
features: {
paths: ['features/**/*.feature'],
},
filters: {
name: ['Acme'],
tagExpression: '<strong i="10">@interesting</strong>',
},
support: {
transpileWith: ['ts-node'],
paths: ['features/support/**/*.ts'],
},
runtime: {
failFast: true,
retry: {
count: 1,
tagExpression: '<strong i="11">@flaky</strong>',
},
strict: true,
worldParameters: {
foo: 'bar',
},
},
formats: {
stdout: '@cucumber/pretty-formatter',
files: {
'report.html': 'html',
'TEST-cucumber.xml': 'junit',
},
options: {
printAttachments: false,
},
},
})
Обратная связь приветствуется! Обратите внимание, что это не касается загрузки профиля / конфигурации, которая была бы другой функцией.
Думаю, это хорошо выглядит. Вопрос: Как мне настроить пользовательский форматер?
@nicojs вроде как в интерфейсе командной строки
formats: {
files: {
'report.html': './my/fancy-reporter.js',
'other-report.html': '@me/reporter-package',
}
},
Приятно видеть прогресс в этом @davidjgoss!
Я не хочу замедлять прогресс в этом, но в то же время я хочу убедиться, что мы приняли формат, который может работать и для других реализаций Cucumber.
В конце концов, это будет схема JSON, но пока мы ее обсуждаем, я думаю, что типы TypeScript легче анализировать людям.
Я предлагаю создать новый выпуск о предлагаемом формате в монорепорате cucumber/common
и пригласить основную команду для обсуждения там.
@aslakhellesoy подойдет.
Что бы вы подумали о том, что программный API не привязан к общей структуре опций? Как будто мы сопоставляем это с опциями runCucumber
. Это добавляет, возможно, небольшую сложность, но привлекает из-за таких вещей, как наличие блока support
который либо загружает параметры, либо ранее загруженную библиотеку кода поддержки. Могу сделать то же самое для функций + соленья. И есть различные варианты, которые мы поддерживаем в CLI (например, --exit
), которые не подходят для программного API.
Что бы вы подумали о том, что программный API не привязан к общей структуре опций?
Я думаю, что это нормально, если мы предоставляем функцию, которая преобразует содержимое файла параметров в структуру данных, которую хочет runCucumber
.
В конце концов, это будет схема JSON, но пока мы ее обсуждаем, я думаю, что типы TypeScript легче анализировать людям.
Почему нужно выбирать? Мы используем схему JSON в StrykerJS для создания машинописного текста с использованием json-schema-to-typescript . Мы не фиксируем выходные файлы TS в системе управления версиями, вместо этого мы генерируем их на лету, используя шаг prebuild
.
Схемы JSON все еще несколько читаемы для людей IMO. У нас уже есть пиар по репо Stryker, и люди, кажется, знают, что делать 🤷♀️
вроде как на CLI
formats: { files: { 'report.html': './my/fancy-reporter.js', 'other-report.html': '@me/reporter-package', } },
Как это будет работать для репортера, которому не требуется имя выходного файла? Вот так:
formats: {
files: {
'': require.resolve('./my-awesome-stryker-formatter')
}
}
Почему нужно выбирать?
Я думаю, что мы должны использовать схему JSON как единственный источник истины для структуры конфигурации. -А затем сгенерируйте TypeScript / Java / любой код из этой схемы.
Но схему JSON сложно читать людям, поэтому, пока мы обсуждаем схему в проблеме GitHub в cucumber/common
я предлагал TypeScript для облегчения обсуждения.
Понимаете, что я имею в виду?
Схемы JSON все еще несколько удобочитаемы для людей IMO
Не для меня :-) Слишком многословно.
Как это будет работать для репортера, которому не требуется имя выходного файла?
formats: {
stdout: './my-awesome-stryker-formatter',
files: {
'report.html': './my/fancy-reporter.js',
'other-report.html': '@me/reporter-package',
}
},
(только один форматировщик может использовать поток stdout)
Самый полезный комментарий
(только один форматировщик может использовать поток stdout)