Cucumber-js: Предложение: программный API для запуска cucumber-js

Созданный на 28 июн. 2021  ·  29Комментарии  ·  Источник: cucumber/cucumber-js

Проблема

В настоящее время у нас нет хорошего способа программного запуска cucumber-js. Это нужно с двух сторон:

  • Тестирование cucumber-js - для CCK и наших приемочных испытаний в проекте
  • Тестирование средств пользовательского форматирования и сниппетов
  • Проекты, которые используют cucumber-js как часть фреймворка (например, Serenity, Stryker)

Как есть

В настоящий момент обычно происходит создание нового экземпляра Cli с объединенным входом argv . Очевидно, что это очень неприятно, и его также нет в общедоступном API .

Иногда (возможно, из-за предполагаемой хрупкости вышеупомянутого) фреймворки просто полагаются на CLI cucumber-js, но изо всех сил пытаются найти способы интеграции и иметь свои собственные варианты.

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

Предложение

В проекте две составляющие:

runCucumber

Новая асинхронная функция, которая выполняет тестовый запуск в процессе. Обязанности:

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

Это будет частью общедоступного API, и мы рекомендуем разработчикам фреймворка использовать его при «упаковке» cucumber-js. Мы также использовали бы его для нашего собственного тестирования.

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

Также Runtime должен выйти из общедоступного API, поскольку это действительно внутренняя вещь.

CLI

Фактически «клиент» runCucumber . Обязанности:

  • Параметры агрегирования из различных источников (argv, env vars, файлы конфигурации) (см. Комментарий )
  • Позвоните в runCucumber с решенными вариантами
  • Выйти в зависимости от результатов

Этого по-прежнему не будет в общедоступном API. Кроме того, он будет использовать только функции / интерфейсы, которые есть в общедоступном API, так что мы могли бы легко разбить его на собственный пакет в какой-то момент, как это распространенный шаблон сейчас с такими проектами, как Jest .

Это разделение также открывает путь для некоторых интересных новых функций интерфейса командной строки, не позволяя им проникать во внутреннее устройство, например:

  • --gui за
  • --interactive для быстрых целевых повторов при TDD

и т.д

Мы также предоставим функции (используемые CLI и другими пользователями) для:

  • Получение вариантов
  • Обработка i18nKeywords и i18nLanguages

Шкала времени

Мы нацелены на это в следующем выпуске 8.0.0. Я готов приступить к этому сегодня.

breaking change enhancement

Самый полезный комментарий

Как это будет работать для репортера, которому не требуется имя выходного файла?

formats: {
    stdout: './my-awesome-stryker-formatter',
    files: {
      'report.html': './my/fancy-reporter.js',
      'other-report.html': '@me/reporter-package',
    }
  },

(только один форматировщик может использовать поток stdout)

Все 29 Комментарий

Пейджинг для первоначальной обратной связи: @aslakhellesoy @charlierudolph @ aurelien- reeves @mattwynne @nicojs @ jan-molak

Обожаю это предложение! У нас уже есть API, подобный этому, в runCucumber fake-cucumber

@davidjgoss - отлично звучит!

Для справки, вот как Serenity / JS вызывает Cucumber в настоящий момент - CucumberCLIAdapter
А вот логика преобразования параметров конфигурации в argv - CucumberOptions .

Было бы намного приятнее предоставить объект параметров вместо argv 👍🏻

Любить это!

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

Публичный 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 ...
  • У Karma больше нет работающей реализации (https://github.com/cucumber/cucumber-js/issues/1095)

Я бы предпочел увидеть какой-нибудь минимальный связующий код между 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)

Была ли эта страница полезной?
0 / 5 - 0 рейтинги