Cucumber-js: 提案:运行cucumber-js的编程API

创建于 2021-06-28  ·  29评论  ·  资料来源: cucumber/cucumber-js

问题

目前我们没有一个好的方法来以编程方式运行cucumber-js。 需求来自两个角度:

  • 测试 Cucumber-js - 用于 CCK 和我们在项目中的验收测试
  • 测试自定义格式化程序和片段
  • 使用cucumber-js 作为框架一部分的项目(例如Serenity、Stryker)

原样

目前倾向于发生的是Cli一个新实例是用串在一起的 argv input创建的。 它显然非常笨拙,也不在公共 API 上

有时(可能是由于感知到上述的脆弱性)框架将只依赖黄瓜-js CLI,但很难找到集成的方法并有自己的选择。

Runtime目前是公共 API 的一部分,但它在这些上下文中没有用,这取决于调用者提供的泡菜和支持代码。

提议

项目中的两个组件:

runCucumber

在进程中执行测试运行的新异步函数。 职责:

  • 接受具有漂亮界面的选项对象
  • 做解决pickles、加载支持代码、运行测试用例、编排格式化程序的“工作”
  • 返回一个解析为结果的承诺

这将是公共 API 的一部分,我们鼓励框架维护者在“包装”cucumber-js 时使用它。 我们也会将它用于我们自己的测试。

尽可能避免与process直接交互,而是接受规范化的选项和流接口用于输出,并将其留给调用者根据结果或未处理的错误决定如何退出。

另外Runtime应该脱离公共 API,因为它确实是内部的事情。

命令行界面

实际上是runCucumber的“客户”。 职责:

  • 来自各种来源(argv、env vars、配置文件)的聚合选项(见评论
  • 使用已解决的选项调用runCucumber
  • 根据结果​​酌情退出

这将继续不在公共 API 上。 此外,它只会用功能/界面,对公共API,这样,我们可以很容易地打破它变成自己的包在某些时候,因为是一种常见的模式现在喜欢的项目玩笑

这种解耦也为一些有趣的新 CLI 功能铺平了道路,而不会让它们渗入内部,例如:

  • --gui用于黄瓜电子的东西
  • --interactive用于在 TDD'ing 时快速有针对性地重新运行

等等

我们还将公开以下功能(由 CLI 和其他人使用):

  • 获取选项
  • 处理i18nKeywordsi18nLanguages

时间表

我们将在即将发布的 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

我喜欢这个提议! 我们已经在 fake-cucumber 的runCucumber 中提供了这样的 API

@davidjgoss - 听起来很棒!

供您参考,以下是目前 Serenity/JS 调用 Cucumber 的方式 - CucumberCLIAdapter
这是将配置参数转换为 argv - CucumberOptions的逻辑。

能够提供一个选项对象而不是 argv 会更好👍🏻

爱它!

在指定新的公共 API 的同时,我们还可以考虑最近发生的问题 #1489 并考虑提供公共 API 以与过滤器和测试中的结果功能进行更多更好的交互

拥有公共 API 总比没有好,所以继续吧👍!

最好我也有一个 API 来使用与cucumber-js相同的规则加载配置文件,这样我就可以模拟普通黄瓜 js 调用的确切行为。

loadProfiles(directory = process.cwd()): Record<string, Profile>

StrykerJS 还将严重依赖custom_formatters API 和eventBroadcaster发布的事件。 我们也可以将它们添加到公共 API 中吗? 参见: https :

我最好也有一个 API 来使用与 Cucumber-js 相同的规则来加载配置文件,这样我就可以模拟正常的 Cucumber-js 调用的确切行为。

这是个好的观点。 配置文件(至少以其当前形式)从根本上与 CLI 耦合,因此将它们保留在边界的那一侧感觉是正确的,但我们仍然可以公开一个函数来加载它们并生成部分选项对象。

在指定新的公共 API 的同时,我们还可以考虑最近发生的问题 #1489,并考虑提供公共 API 以便与过滤器和测试中的结果功能进行更多更好的交互。

我认为我们可以在调用 API 时包含一个提供自定义 pickle 过滤器的选项(除了驱动内置过滤的名称、标签等)。

如果非常命令行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同意那会很棒! 我会尝试为这个提案建立一个 POC,所以我们有一些更具体的东西可以讨论,并且很乐意将个人资料作为其中的一部分(4.5 年,指望 #751 😄)

参考文献第1004章

这是个好的观点。 配置文件(至少以其当前形式)从根本上与 CLI 耦合,因此将它们保留在边界的那一侧感觉是正确的,但我们仍然可以公开一个函数来加载它们并生成部分选项对象。

是的,从插件创建者的角度来看,这将是非常棒的并且非常感谢。

我希望 ❤️❤️❤️ 能够以更通用的格式指定配置文件,例如 JSON、JavaScript、YAML 或环境变量。 在 JSON 中,它可能如下所示:

听起来不错! 这也正是我喜欢 API 以与 CucumberJS 相同的方式加载它们的原因。 加载单个cucumber.js文件是微不足道的。 复制配置文件加载算法,包括优先级,文件格式等并维护它完全是另一回事😅。

问:我是否可以连续运行runCucumber两次_而不清除require 缓存_? 这对于突变测试用例很重要。

我们希望加载环境并快速连续多次运行测试,同时更改全局变量以切换活动突变体。

现在,我们正在使用cli私有 API ,我们需要在每次测试运行之间清除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绝对同意我们需要这个,之前出现过几次,例如人们想要在 lambda 中运行黄瓜,我还想添加一个也需要它的交互模式。

到目前为止,我所勾画的更多是一种功能风格,但我认为是相同的基本概念:

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/etc 之间看到一些最小的胶水代码。 和黄瓜-js,所以我不必为我需要使用的

很好的建议@ davidjgoss👍

命令行用户界面与作为测试解析和执行场景的“业务逻辑”之间的关注点分离让我想起了六边形架构模式。

在 Cucumber-ruby 中,我们实际上将核心域逻辑(或“内六边形”)拆分为一个单独的 gem 包,因为我们是在“洁净室”中从头开始重建它。 我意识到这不是这里的上下文,但可能值得从中汲取一些灵感,或者将此设计的创新反馈到 Ruby API 中。 在 Cucumber-ruby-core gem 的 README 中有一个关于如何使用该 API的示例

好的,这是“运行”位的 API 签名的第一遍。它很大程度上基于我们内部拥有的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有点像 CLI

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

很高兴看到这个@davidjgoss 的进展!

我不想放慢这方面的进展,但同时我想确保我们采用的格式也适用于其他 Cucumber 实现。

最终是一个 JSON 模式,但在我们讨论它时,我认为 TypeScript 类型对我们人类来说更容易解析。

我建议我们在cucumber/common monorepo 中创建一个关于提议格式的新问题,并邀请核心团队在那里讨论。

@aslakhellesoy会做的。

您如何看待编程 API 不绑定到通用选项结构? 就像我们从那个映射到runCucumber选项一样。 它可能会增加一些复杂性,但很有吸引力,因为它有一个support块,它可以是要加载的参数,也可以是先前加载的支持代码库。 也可以对功能+泡菜做类似的事情。 我们会在 CLI 上支持各种不适合编程 API 的选项(例如--exit )。

您如何看待编程 API 不绑定到通用选项结构?

我认为这很好,只要我们提供一个函数,将选项文件内容转换为runCucumber想要的数据结构。

最终是一个 JSON 模式,但在我们讨论它时,我认为 TypeScript 类型对我们人类来说更容易解析。

为什么我们需要选择? 我们在 StrykerJS 中使用 JSON 模式使用json-schema-to- typescript 生成prebuild步骤即时生成它们。

JSON 模式对于人类 IMO 来说仍然具有一定的可读性。 我们已经在 Stryker 仓库上发布了 PR,人们似乎知道该怎么做 🤷‍♀️

有点像 CLI

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

对于不需要输出文件名的记者来说,这将如何工作? 像这样:

formats: {
  files: {
    '': require.resolve('./my-awesome-stryker-formatter')
  }
}

为什么我们需要选择?

我认为我们应该使用 JSON Schema 作为配置结构的单一事实来源。 - 然后从该模式生成 TypeScript/Java/Whatever 代码。

但是 JSON Schema 对于人类来说有点难以阅读,因此当我们在cucumber/common的 GitHub 问题中讨论模式时,我建议使用 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 等级