Actuellement, nous n'avons pas un bon moyen d'exécuter par programme cucumber-js. Le besoin se présente sous deux angles :
Ce qui a tendance à se produire en ce moment, c'est qu'une nouvelle instance de Cli
est créée avec une entrée argv enchaînée . C'est évidemment très peu pratique et n'est pas non plus sur l'API publique .
Parfois (peut-être en raison de la fragilité perçue de ce qui précède), les frameworks s'appuieront uniquement sur la CLI cucumber-js, mais auront du mal à trouver des moyens d'intégrer et d'avoir leurs propres options.
La classe Runtime
fait actuellement partie de l'API publique mais elle n'est pas utile dans ces contextes, selon les pickles et le code de support à fournir par l'appelant.
Deux volets dans le projet :
runCucumber
Nouvelle fonction asynchrone qui exécute un test en cours de processus. Responsabilités:
Cela ferait partie de l'API publique et nous encourageons les responsables du framework à l'utiliser lors de l'« emballage » de cucumber-js. Nous l'utiliserions également pour nos propres tests.
Autant que possible, cela éviterait une interaction directe avec process
, en acceptant plutôt des options normalisées et des interfaces de flux pour la sortie, et en laissant à l'appelant le soin de décider comment quitter en fonction du résultat ou d'une erreur non gérée.
De plus, Runtime
devrait sortir de l'API publique car c'est vraiment une chose interne.
Effectivement un "client" de runCucumber
. Responsabilités:
runCucumber
avec les options résoluesCela continuerait à ne pas être sur l'API publique. De plus, il n'utiliserait que des fonctions/interfaces qui se trouvent sur l'API publique, de sorte que nous pourrions facilement le diviser en son propre package à un moment donné, comme c'est un modèle courant maintenant avec des projets comme Jest .
Ce découplage ouvre également la voie à de nouvelles fonctionnalités intéressantes de la CLI sans qu'elles ne se répercutent sur les composants internes, par exemple :
--gui
pour les trucs concombre-électrons--interactive
pour des rediffusions ciblées rapides lors du TDDOn exposerait aussi des fonctions (consommables par la CLI et par d'autres) pour :
i18nKeywords
et i18nLanguages
Nous ciblerons cela dans la prochaine version 8.0.0. Je suis prêt à commencer aujourd'hui.
Pagination pour les premiers commentaires : @aslakhellesoy @charlierudolph @aurelien-reeves @mattwynne @nicojs @jan-molak
J'adore cette proposition ! Nous avons déjà une API comme celle-ci dans runCucumber de faux-concombre
@davidjgoss -
Pour votre information, voici comment Serenity/JS invoque le concombre en ce moment - CucumberCLIAdapter
Et voici la logique de la conversion des paramètres de configuration en argv - CucumberOptions
.
Pouvoir fournir un objet d'options au lieu de argv serait beaucoup plus agréable 👍🏻
Aimer!
Tout en spécifiant cette nouvelle API publique, nous pouvons également considérer ce qui s'est récemment passé avec le problème #1489 et penser à fournir des API publiques pour avoir une interaction plus importante et meilleure avec les filtres et les fonctionnalités résultantes en cours de test.
Et pour ajouter à cela, créons quelque chose qui faciliterait également la tâche de Cucumber-Electron :
Avoir une API publique c'est mieux que rien, alors allez-y !
De préférence, j'aurais également une API pour charger des profils en utilisant les mêmes règles que cucumber-js
, afin que je puisse imiter le comportement exact d'un appel normal de concombre-js.
loadProfiles(directory = process.cwd()): Record<string, Profile>
StrykerJS s'appuiera également fortement sur l'API custom_formatters et les événements publiés par le eventBroadcaster
. Pourrions-nous également les ajouter à l'API publique ? Voir : https://github.com/stryker-mutator/stryker-js/blob/03b1f20ed933d3a50b52022cfe363c606c2b16c5/packages/cucumber-runner/src/stryker-formatter.ts#L45 -L69
De préférence, j'aurais également une API pour charger des profils en utilisant les mêmes règles que cucumber-js, afin que je puisse imiter le comportement exact d'un appel normal de cucumber-js.
C'est un bon point. Les profils (dans leur forme actuelle du moins) sont fondamentalement couplés à la CLI, il semble donc juste de les garder de ce côté de la frontière, mais nous pourrions toujours exposer une fonction pour les charger et générer un objet d'options partiel.
Tout en spécifiant cette nouvelle API publique, nous pouvons également considérer ce qui s'est récemment passé avec le problème #1489 et penser à fournir des API publiques pour avoir une interaction plus importante et meilleure avec les filtres et les fonctionnalités résultantes en cours de test.
Je pense que nous pourrions inclure une option pour fournir un filtre pickle personnalisé lors de l'appel de l'API (en plus des noms, balises, etc. qui pilotent le filtrage intégré).
La syntaxe actuelle pour les profils est très en ligne de commande.
Je voudrais ❤️❤️❤️ pouvoir spécifier des profils dans un format plus générique tel que JSON, JavaScript, YAML ou des variables d'environnement. En JSON, cela pourrait ressembler à ceci :
.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
}
}
Ou, en utilisant 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
}
}
Ou même avec des variables d'environnement (par exemple chargées avec un outil comme 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
En fait, la bibliothèque de configuration fait exactement cela. Nous n'avons jamais fini par l'intégrer dans Cucumber-JVM parce que d'autres éléments nous gênaient, mais peut-être pourrions-nous essayer avec une implémentation JavaScript ?
@aslakhellesoy est d' accord, ce serait génial ! Je vais essayer d'obtenir un POC pour cette proposition afin que nous ayons quelque chose d'un peu plus concret à discuter, et j'adorerais faire des profils dans le cadre de celle-ci (4,5 ans et compte sur #751 😄)
Réf. #1004
C'est un bon point. Les profils (dans leur forme actuelle du moins) sont fondamentalement couplés à la CLI, il semble donc juste de les garder de ce côté de la frontière, mais nous pourrions toujours exposer une fonction pour les charger et générer un objet d'options partiel.
Oui, ce serait génial et très apprécié du point de vue des créateurs de plugins.
Je voudrais ❤️❤️❤️ pouvoir spécifier des profils dans un format plus générique tel que JSON, JavaScript, YAML ou des variables d'environnement. En JSON, cela pourrait ressembler à ceci :
Cela sonne bien ! Et c'est aussi exactement la raison pour laquelle j'apprécierais une API pour les charger de la même manière que cucumberJS. Le chargement d'un seul fichier cucumber.js
est trivial. Répliquer un algorithme de chargement de fichier de configuration, y compris la priorité, le format de fichier, etc. ET le maintenir est tout autre chose .
Q : Pourrais-je exécuter runCucumber
deux fois de suite _sans effacer le cache requis _ ? Ceci est important pour le cas d'utilisation du test de mutation.
Nous voulons charger l'environnement et exécuter les tests plusieurs fois en succession rapide tout en modifiant une variable globale afin de changer le mutant actif.
À l'heure actuelle, nous utilisons l'API privée cli
et nous devons effacer les fichiers de définition d'étape des require.cache
entre chaque exécution de test. Ce n'est pas idéal pour CommonJS et ne fonctionnera pas du tout pour esm.
Pseudo code de notre cas d'utilisation :
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 est tout à fait d'accord pour dire que nous en avons besoin, cela est déjà arrivé plusieurs fois avec, par exemple, des personnes souhaitant exécuter du concombre dans un lambda, et j'aimerais également ajouter un mode interactif qui en aurait également besoin.
Ce que j'avais esquissé jusqu'à présent était plus un style fonctionnel mais le même concept fondamental je pense :
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
})
Ça marche pour nous
Fonctionne pour nous aussi 👍🏻
Je pense vraiment que quelque chose comme ça serait extrêmement utile comme alternative au grand désordre que nous avons aujourd'hui avec les intégrations d'outils de test (Jest, Cypress), par exemple j'ai trouvé ces problèmes (par ordre d'importance) :
cypress-cucumber-preprocessor
ne prend pas en charge les balises sur les exemples (https://github.com/TheBrainFamily/cypress-cucumber-preprocessor/issues/196)jest-cucumber
ne prend pas en charge les rapports JSON sur le concombre (https://github.com/bencompton/jest-cucumber/issues/27)cypress-cucumber-preprocessor
génère plusieurs rapports JSON sur le concombre sans prise en charge officielle de l'agrégation (https://github.com/TheBrainFamily/cypress-cucumber-preprocessor/issues/423)jest-cucumber
n'est pas aussi pratique que jest-cucumber-fusion
cucumber-jest
...Je préférerais voir un code de colle minimal entre Jest/Karma/Cypress/etc. et cucumber-js pour que je n'aie pas à souffrir de toutes ces fonctionnalités manquantes que j'ai besoin d'utiliser.
Super suggestion @davidjgoss
Cette séparation des préoccupations entre l'interface utilisateur en ligne de commande et la « logique métier » d'analyse et d'exécution de scénarios sous forme de tests me rappelle le modèle d' architecture hexagonale .
Dans cucumber-ruby, nous avons en fait divisé la logique du domaine central (ou « l'hexagone intérieur ») en un package gem séparé, alors que nous le reconstruisions à partir de zéro dans une « salle blanche ». Je me rends compte que ce n'est pas le contexte ici, mais cela pourrait valoir la peine de s'inspirer ou de renvoyer les innovations de cette conception dans l'API Ruby. Il y a un exemple dans le README de la gem cucumber-ruby-core sur la façon d'utiliser cette API.
D'accord, voici un premier passage à la signature de l'API pour le bit "run". C'est fortement basé sur l'objet IConfiguration
nous avons en interne (donc ne devrait pas causer trop de refactorisation plus bas) mais juste un peu moins "plat":
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
}
Et un exemple d'utilisation très artificiel :
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,
},
},
})
Commentaires bienvenus! Notez que cela ne couvre pas le chargement de profil/configuration qui serait une autre fonction.
Je pense que cela a l'air bien. Question : Comment configurer un formateur personnalisé ?
@nicojs un peu comme sur la CLI
formats: {
files: {
'report.html': './my/fancy-reporter.js',
'other-report.html': '@me/reporter-package',
}
},
Super de voir des progrès sur ce @davidjgoss !
Je ne veux pas ralentir les progrès sur ce point, mais en même temps, je veux m'assurer que nous adoptons un format qui peut également fonctionner pour d'autres implémentations de Cucumber.
Finalement, un schéma JSON, mais pendant que nous en discutons, je pense que les types TypeScript sont plus faciles à analyser pour nous, les humains.
Je suggère que nous créions un nouveau numéro sur le format proposé dans le monorepo cucumber/common
et que nous invitions l'équipe principale à en discuter.
@aslakhellesoy fera l'affaire.
Que penseriez-vous du fait que l'API programmatique n'est pas liée à la structure d'options commune ? Comme si nous utilisions cela pour les options runCucumber
. Cela ajoute peut-être un peu de complexité, mais est attrayant en raison de choses comme avoir un bloc support
qui est soit des paramètres à charger, soit une bibliothèque de code de support précédemment chargée. Pourrait faire la même chose pour les fonctionnalités + les cornichons aussi. Et il existe diverses options que nous prendrions en charge sur la CLI (par exemple, --exit
) qui ne sont pas appropriées sur l'API de programmation.
Que penseriez-vous du fait que l'API programmatique n'est pas liée à la structure d'options commune ?
Je pense que c'est bien, tant que nous fournissons une fonction qui convertit le contenu du fichier d'options en la structure de données souhaitée par runCucumber
.
Finalement, un schéma JSON, mais pendant que nous en discutons, je pense que les types TypeScript sont plus faciles à analyser pour nous, les humains.
Pourquoi devons-nous choisir ? Nous utilisons le schéma JSON dans StrykerJS pour générer un script à l'aide de json-schema-to-typescript . Nous ne soumettons pas les fichiers de sortie TS au contrôle de source, mais nous les générons à la volée en utilisant une étape prebuild
.
Les schémas JSON sont encore quelque peu lisibles pour les humains IMO. Nous avons déjà eu des relations publiques sur le repo Stryker et les gens semblent savoir quoi faire 🤷♀️
un peu comme sur la CLI
formats: { files: { 'report.html': './my/fancy-reporter.js', 'other-report.html': '@me/reporter-package', } },
Comment cela fonctionnerait-il pour un journaliste qui n'a pas besoin d'un nom de fichier de sortie ? Ainsi:
formats: {
files: {
'': require.resolve('./my-awesome-stryker-formatter')
}
}
Pourquoi devons-nous choisir ?
Je pense que nous devrions utiliser un schéma JSON comme source unique de vérité pour la structure de la configuration. -Et puis générez du code TypeScript/Java/Whatever à partir de ce schéma.
Mais JSON Schema est un peu difficile à lire pour les humains, donc pendant que nous discutons du schéma dans un problème GitHub dans cucumber/common
je suggérais TypeScript pour faciliter la discussion.
Tu vois ce que je veux dire?
Les schémas JSON sont encore quelque peu lisibles pour les humains IMO
Pas pour moi :-) Trop verbeux.
Comment cela fonctionnerait-il pour un journaliste qui n'a pas besoin d'un nom de fichier de sortie ?
formats: {
stdout: './my-awesome-stryker-formatter',
files: {
'report.html': './my/fancy-reporter.js',
'other-report.html': '@me/reporter-package',
}
},
(un seul formateur peut utiliser le flux stdout)
Commentaire le plus utile
(un seul formateur peut utiliser le flux stdout)