Cucumber-js: ¿Cómo puedo especificar un archivo de definición de pasos para cada archivo de características?

Creado en 2 feb. 2017  ·  18Comentarios  ·  Fuente: cucumber/cucumber-js

Mi meta

Estoy tratando de crear una estructura escalable de funciones y definiciones de pasos para una aplicación grande y mi primera oportunidad fue intentar vincular archivos de definición de pasos a funciones para poder usar el mismo patrón de pasos para diferentes definiciones de pasos.

Mi código

Muestro mi ejemplo actual:

Mi estructura de carpetas:

/features/sample.feature
/features/example.feature
/features/step_definitions/sample_steps.js
/features/step_definitions/example_steps.js
/features/step_definitions/common/common_steps.js

En mi sample.feature tengo:

  Scenario: Launching Cucumber
  Given I have some step definitions
   When I check some step definition with parameter "any"
   Then I should see all green "sample"

En mi example.feature tengo:

  Scenario: Launching Cucumber
  Given I have some step definitions
   When I check some step definition with parameter "any"
   Then I should see all green "example"

Los pasos Given y When se definen en el archivo /common/common_steps.js y funcionan bien.

El paso Then se define tanto en sample_steps.js como example_steps.js pero de manera diferente.

En mi sample_steps.js tengo:

Then('I should see all green {stringInDoubleQuotes}', (arg) => {
   if (arg !== 'sample') {
     throw 'I should see all green when the argument is "sample"';
   }
   return;
 });

Y, finalmente, en mi example_steps.js tengo:

Then('I should see all green {stringInDoubleQuotes}', (arg) => {
   if (arg !== 'example') {
     throw 'I should see all green when the argument is "example"';
   }
   return;
 });

El error

Mi objetivo principal es tener todo verde aquí, pero, por supuesto, no funciona y obviamente aparece este error:

Multiple step definitions match:
   I should see all green {stringInDoubleQuotes} - features\step_definitions\example_steps.js:6
   I should see all green {stringInDoubleQuotes} - features\step_definitions\sample_steps.js:6

Pepino-JVM

Sé que en cucumber-jvm podemos especificar un atributo glue que vincula funciones y definiciones de pasos y es exactamente lo que estoy buscando, pero en cucumber-js. Ejemplo en Java:

@RunWith(Cucumber.class)
@Cucumber.Options( glue = { "com.app.stepdefinitions.common", "com.app.stepdefinitions.sample" } )
public class SampleFeature{
}

@RunWith(Cucumber.class)
@Cucumber.Options( glue = { "com.app.stepdefinitions.common", "com.app.stepdefinitions.example" } )
public class ExampleFeature{
}

Finalmente

¿Cómo puedo lograr lo mismo que cucumbr-jvm usando cucumber-js?

Comentario más útil

El alcance de la OMI definitivamente es necesario. A medida que crece su aplicación, la cantidad de características se expande y terminará con descripciones contradictorias en diferentes contextos.

En mi empresa, nuestro producto tiene cientos de funciones y QA tiene casos de prueba en un rango de 100k.
Si usamos cucumber definitivamente nos encontraremos con este problema.

Sí, creo que debería considerar el contexto en lugar del alcance.
Puede tener la mayoría de sus definiciones de pasos, si no todas, en el contexto predeterminado (o sin contexto), pero debería haber una manera de especificar el contexto sin la necesidad de un DSL personalizado.

Son el BA y el QA quienes deberían escribir estas pruebas y cualquier DSL personalizado está limitado a crear confusión y resistencia.

La Característica/Escenario ya proporciona contextos por definición, es por eso que la sintaxis de Gherkin tiene la sangría.

Agregar etiquetas y DSL personalizado es una solución alternativa a la limitación de implementación (es decir, un truco, IMO) en lugar de una solución.

¿Tal vez pueda considerar esto mientras considera https://github.com/cucumber/cucumber-js/issues/745 ?

¿Qué tal extender Scenario desde defineStep y pasar {Given, When, Then} a la devolución de llamada?

es decir:

import {defineSupportCode} from 'cucumber'

defineSupportCode(({ Scenario, Given, When, Then }) => {
  Given(...)
  When(...)
  Then(...)
  Scenario('<match scenario description>', ({ Given, When, Then}) => {
    Given('<take precedent of non-contexted>', ...)
    ...
  })
})

Todos 18 comentarios

Estoy tratando de crear una estructura escalable de funciones y definiciones de pasos para una aplicación grande y mi primera oportunidad fue intentar vincular archivos de definición de pasos a funciones para poder usar el mismo patrón de pasos para diferentes definiciones de pasos.

Muy interesante. cucumber-js no tiene nada integrado como el ejemplo de cucumber-java que diste. La idea que me gusta para este tipo de cosas es empujar este cambio de lógica a la configuración o instancia de su mundo. De cualquier manera, terminas con una sola definición de paso. Puede tener múltiples definiciones de mundo entre las que alternar al definir su código de soporte o tener una sola instancia de mundo que expone diferentes métodos basados ​​en el contexto. Actualmente no exponemos el archivo del escenario actual a los pasos en ejecución, pero podría usar etiquetas para determinar su contexto.

De hecho, tenemos la misma necesidad de esto. Estamos usando nightwatch-pepino para ejecutar pruebas de selenio y nuestra única solución por ahora es agregar un prefijo a cada paso:

Given [comp1] I click on "Open dialog"

contra

Given [comp2] I click on "Open dialog"

Esto nos ayuda a evitar definiciones de pasos ambiguas, pero conduce a archivos de características realmente ilegibles.
Intenté jugar con la fuente cucumber.js, pero no encontré buenos consejos para agregar soporte para esta función.

¿Podría esto realizarse con algunos ganchos o mundos alternativos?

@leipert, ¿qué interfaz de usuario está imaginando? Creo que esta lógica debería vivir en el mundo o ser compatible con múltiples objetos del mundo. Luego, las definiciones de pasos solo se ocupan de analizar las coincidencias de pepinillo y pasarlas a la función mundial adecuada. Podríamos agregar una opción CLI para seleccionar qué objeto mundial usar.

Por el momento, si desea tener múltiples mundos/definiciones de pasos, puede lograrlo colocando su código en carpetas separadas y solo requiriendo uno de ellos por ejecución (usando la opción --require CLI).

Bueno, "el libro del pepino" desaconseja específicamente esta forma de diseñar pasos. El paso se comparte entre escenarios por diseño, la misma oración debe tener un significado consistente a pesar de la función que la usa. Una vez que introduce el alcance de los pasos, es realmente fácil crear una trampa de lenguaje.

Hasta ahora, solo las etiquetas están cerca de su propósito en cucuber.js. Pero solo los ganchos pueden declarar que son específicos de las etiquetas. Si está seguro de que es la forma correcta para su gente, puede inventar un DSL, tal vez tan simple como [nombre de la función] en un patrón de pasos.

Gracias, @pinxue . Respuesta muy útil. Sin embargo, no puedo entenderlo:

Bueno, "el libro del pepino" desaconseja específicamente esta forma de diseñar pasos.

Una misma frase de acción podría tener diferentes significados en diferentes contextos. Pero está bien. Es bueno saber las opciones que tengo, de hecho ya estamos caminando sobre un DSL interno para lograrlo,

Muchas gracias.

Gracias por vuestras respuestas @pinxue y @robsonrosa.
Aquí está mi razonamiento para las definiciones de pasos con alcance:

  1. _Las variables globales son malas y no escalan_:
    Solo tenemos alrededor de 15 archivos de características con 10 escenarios cada uno, y ya es difícil estructurar todas las definiciones de pasos y archivos de características. Para aplicaciones grandes (por ejemplo, 100 archivos de funciones con 10 escenarios con un promedio de 5 pasos), tener todos los pasos definidos globalmente parece una locura, ya que es realmente difícil rastrear qué archivo de funciones usa qué pasos.
  2. _Los desarrolladores no tienen control sobre la redacción de los archivos de características_:
    En nuestro caso de uso, nuestra "administración" escribe archivos de funciones, es difícil venderlos: hacemos BDD increíbles, pero debe limitarse a un DSL personalizado incómodo.
  3. _Si cucumber-jvm tiene la opción, ¿por qué no cucumber-js?_
    Este puede ser un mal argumento 💃

Veo los siguientes enfoques para resolver el problema para nosotros:

  1. Cree un DSL personalizado con prefijos en definiciones de pasos
    Desventajas: No es agradable trabajar con él.
    Pros: no necesita cambios en cucumber.js
  2. Crear glue para cucumber.js
    Contras: puede ir en contra de la idea de "El libro del pepino".
    Ventajas: Paridad de funciones con cucumber.js
  3. Cambiar la forma en que ejecutamos las pruebas
    Contras: Esta función estará disponible para menos personas.
    Pros: no necesita cambios en cucumber.js

Dicho esto, probablemente optemos por 3., si los mantenedores de cucumber.js piensan que las definiciones de pasos con alcance están fuera del alcance (juego de palabras) para este proyecto.

@robsonrosa Me interesaría su solución, una vez que tenga una.

El alcance de la OMI definitivamente es necesario. A medida que crece su aplicación, la cantidad de características se expande y terminará con descripciones contradictorias en diferentes contextos.

En mi empresa, nuestro producto tiene cientos de funciones y QA tiene casos de prueba en un rango de 100k.
Si usamos cucumber definitivamente nos encontraremos con este problema.

Sí, creo que debería considerar el contexto en lugar del alcance.
Puede tener la mayoría de sus definiciones de pasos, si no todas, en el contexto predeterminado (o sin contexto), pero debería haber una manera de especificar el contexto sin la necesidad de un DSL personalizado.

Son el BA y el QA quienes deberían escribir estas pruebas y cualquier DSL personalizado está limitado a crear confusión y resistencia.

La Característica/Escenario ya proporciona contextos por definición, es por eso que la sintaxis de Gherkin tiene la sangría.

Agregar etiquetas y DSL personalizado es una solución alternativa a la limitación de implementación (es decir, un truco, IMO) en lugar de una solución.

¿Tal vez pueda considerar esto mientras considera https://github.com/cucumber/cucumber-js/issues/745 ?

¿Qué tal extender Scenario desde defineStep y pasar {Given, When, Then} a la devolución de llamada?

es decir:

import {defineSupportCode} from 'cucumber'

defineSupportCode(({ Scenario, Given, When, Then }) => {
  Given(...)
  When(...)
  Then(...)
  Scenario('<match scenario description>', ({ Given, When, Then}) => {
    Given('<take precedent of non-contexted>', ...)
    ...
  })
})

Aprendo BDD de http://fitnesse.org/ por lo que puedo ver BDD de manera diferente que en la comunidad de pepino.

Paginando a @richardlawrence

Esta es un área donde Cucumber es particularmente obstinado. Se basa en la creencia de que los equipos deben desarrollar un lenguaje ubicuo, donde las palabras y frases significan exactamente una cosa en el contexto de una aplicación y se usan de manera consistente. Mantener las definiciones de pasos globales mantiene una presión positiva para evitar la ambigüedad.

En los casos excepcionales en los que una aplicación tiene múltiples contextos delimitados distintos (en términos de DDD), simplemente dividiría su suite Cucumber de la misma manera que su aplicación se divide para reflejar ese límite, y las definiciones de pasos serían globales dentro de cada contexto delimitado.

Un artículo sobre trabajar con Cucumber y crear límites. Implementa algunas de las ideas de esta página, pero no presenta ninguna gran solución, ya que @richardlawrence ha mencionado que no puede configurar Cucumber para centrarse en una clase en particular para las definiciones de pasos. http://confesionsofanagilecoach.blogspot.com/2017/05/teaching-cucumbers-about-boundaries.html

Como dijo @leipert , las variables globales son malas. Creo que aquellos de nosotros en el mundo de CucumberJVM solo estamos entendiendo la mitad de la historia. Cucumber (no JVM) utiliza un concepto de mundo ordenado que es un espacio de memoria global durante la duración del escenario . Después de ejecutar el Escenario, se destruye. Esto se siente como una buena solución. Pero... No he visto una buena implementación de este patrón en CucumberJVM. Entonces, si Cucumber nos obliga a tener un espacio de nombres plano/global para las definiciones de pasos, y CucumberJVM tenía un patrón de diseño claro para implementar el patrón World, entonces estoy un poco más feliz. Hasta este punto, CucumberJVM + World pattern == soluciones demasiado complicadas. Hasta ahora, todo lo que he visto es más complicado que simplemente permitirme controlar qué funciones de paso van con qué archivo de características. Hasta ahora las alternativas que he visto no me dan nada más valioso por todo el esfuerzo/complejidad. Otros tipos de pepino tienen mejores soluciones mundiales.

En última instancia, incluso con el patrón mundial, todavía estoy descontento porque sé que habrá pérdida de información al pasar del archivo de características a los pasos. Si hago un gran esfuerzo: poner mis archivos de funciones en una buena organización, crear buenos nombres de archivos de funciones, declarar mi función de manera inteligente, nombrar mis escenarios de manera inteligente, todo el contexto se tira por la ventana y me veo obligado para trabajar con el espacio de nombres de definición de paso global.

Solo puedo pensar en mantener la relación fuera de las validaciones del sistema y simplemente agregar comodines en las rutas para las pruebas y hacer que funcionen, incluso si se necesita modificar algún marco de código abierto. podría darle una oportunidad a medida que crece nuestro proyecto.

Entiendo tu objetivo, pero no lo recomendaría.
También recomendaría tener archivos .feature explícitos, ya sea con una declaración en segundo plano o un paso dado adicional que lo haga explícito.

Alternativamente, podría crear dos archivos de configuración diferentes.
Uno para la muestra y otro para el ejemplo.
Entonces podría apuntar a diferentes carpetas de pasos.

@robsonrosa @leipert comparto tu opinión
Casi dos años después de la publicación original...
¿Obtuviste algún progreso con eso? ¿Alguna solución?

@cristianmercado19 Lo siento, no. Ya no uso pepino js.

Ups... Me gusta la forma de escribir el archivo de funciones completamente aislado de la implementación del paso.
He intentado lograr el mismo objetivo con _mocha_ pero no estoy contento.
Si tiene alguna otra alternativa que comprometa mi objetivo, más que feliz de intentarlo.
Agradezco tu ayuda @leipert .

Este hilo se ha bloqueado automáticamente ya que no ha habido ninguna actividad reciente después de que se cerró. Abra un nuevo problema para los errores relacionados.

¿Fue útil esta página
0 / 5 - 0 calificaciones