Rspec-core: Reconsiderar `shared_context_metadata_behavior`

Creado en 27 dic. 2020  ·  5Comentarios  ·  Fuente: rspec/rspec-core

Fondo

Hemos tenido un problema con los metadatos definidos en grupos de ejemplo compartidos locales específicos ( shared_examples / shared_examples_for / shared_context ), lo que ha provocado que se incluyan en un grupo de ejemplo completamente no relacionado informado en [ 1 ] / [ 2 ]. Más información en https://github.com/rspec/rspec-core/issues/1762.

En https://github.com/rspec/rspec-core/issues/1790 (https://github.com/rspec/rspec-core/commit/ed2d59c8a97d32f4a20e91d0abdad87f90d2b930), shared_context_metadata_behavior con un no predeterminado :apply_to_host_groups valor. https://github.com/rspec/rspec-core/commit/3589ab577d09db88ef5d5f0d60e8c35bfc55691f lo agregó al inicializador del proyecto junto con una nota de que se convertirá en la única opción predeterminada en RSpec 4.

:trigger_inclusion siguió siendo la configuración predeterminada, sin embargo , por razones de SemVer.

En una palabra:

`:trigger_inclusion`: shared context will be implicitly included in any groups (or examples) that have matching metadata.
`:apply_to_host_groups`: the metadata will be inherited by the metadata hash of all host groups and examples.
### Uso Personalmente, nunca he visto que `: apply_to_host_groups` se use de la forma en que fue diseñado. Por otro lado, he visto que algunos proyectos usan `: trigger_inclusion` para grupos / contextos de ejemplo compartidos definidos globalmente. Pero es una muestra de uno. Echemos un vistazo. Como caja de arena para los nuevos policías `rubocop-rspec`, he reunido una lista de los proyectos Ruby más destacados que usan RSpec, [` real-world-rspec`] (https://github.com/pirj/real-world -rspec), ~ 35 proyectos en total. De esos 35 (¡también incluye repositorios RSpec!), 7 usan `: apply_to_host_groups` en sus ayudantes de especificaciones: 24pullrequests / administrate / Homebrew / camaleon-cms / canvas-lms / capistrano / capybara / cartodb / chatwoot / chef / diaspora / discurso / locomotoracms / errbit / fat_free_crm / forem / gitlabhq / sabueso / huginn / langostas / loomio / mastodon / open-source-billing / publify / puppet / radiant / refinerycms / rspec-core / rspec-generations / rspec-mocks / rspec -rails / rubocop / rubytoolbox / sharetribe / solidus / spree /
engine/spec/spec_helper.rb|48| 10:  config.shared_context_metadata_behavior = :apply_to_host_groups
rspec-rails/spec/spec_helper.rb|56| 10:  config.shared_context_metadata_behavior = :apply_to_host_groups
lobsters/spec/spec_helper.rb|45| 10:  config.shared_context_metadata_behavior = :apply_to_host_groups
rubytoolbox/spec/spec_helper.rb|49| 10:  config.shared_context_metadata_behavior = :apply_to_host_groups
forem/spec/spec_helper.rb|58| 10:  config.shared_context_metadata_behavior = :apply_to_host_groups
gitlabhq/qa/spec/spec_helper.rb|56| 10:  config.shared_context_metadata_behavior = :apply_to_host_groups
chatwoot/spec/spec_helper.rb|16| 10:  config.shared_context_metadata_behavior = :apply_to_host_groups
Y uno en `lib`:
capybara/lib/capybara/spec/spec_helper.rb|19| 16:        config.shared_context_metadata_behavior = :apply_to_host_groups
Otros, dado que no tienen esta configuración y el valor predeterminado es `: trigger_inclusion`, no usan metadatos de grupos de ejemplo compartidos o confían en activar la inclusión.
Echemos un vistazo a los usos. Abra este spoiler para ver ** todos ** 40 grupos / contextos compartidos con metadatos (de ~ 3000 grupos compartidos en total)
 rspec-core / spec / rspec / core / metadata_spec.rb | 317 | 42: RSpec.shared_examples_for ("algún comportamiento compartido",: include_it => true) hacer
 puppet / spec / shared_contexts / digests.rb | 16 | 1: shared_context ('con algoritmos de resumen compatibles',: uses_checksums => true) hacer
 puppet / spec / shared_contexts / digests.rb | 27 | 1: shared_context ("cuando digest_algorithm se establece en sha256",: digest_algorithm => 'sha256') hacer
 puppet / spec / shared_contexts / digests.rb | 42 | 1: shared_context ("cuando digest_algorithm se establece en md5",: digest_algorithm => 'md5') hacer
 puppet / spec / shared_contexts / digests.rb | 57 | 1: shared_context ("cuando digest_algorithm se establece en sha512",: digest_algorithm => 'sha512') hacer
 puppet / spec / shared_contexts / digests.rb | 72 | 1: shared_context ("cuando digest_algorithm se establece en sha384",: digest_algorithm => 'sha384') hacer
 puppet / spec / shared_contexts / digests.rb | 87 | 1: shared_context ("cuando digest_algorithm se establece en sha224",: digest_algorithm => 'sha224') hacer
 diaspora / spec / support / gon.rb | 3 | 1: shared_context: voy a hacer
 diaspora / spec / spec_helper.rb | 163 | 1: shared_context suppress_csrf_verification:: ninguno lo hace
 rubocop / lib / rubocop / rspec / shared_contexts.rb | 5 | 7: RSpec.shared_context 'entorno aislado',: entorno_aislado hacer
 rubocop / lib / rubocop / rspec / shared_contexts.rb | 43 | 7: RSpec.shared_context 'mantener registro',: restore_registry hacer
 rubocop / lib / rubocop / rspec / shared_contexts.rb | 56 | 7: RSpec.shared_context 'config',: config do # rubocop: deshabilitar Metrics / BlockLength
 rubocop / lib / rubocop / rspec / shared_contexts.rb | 114 | 7: RSpec.shared_context 'salida de consola simulada' hacer
 rubocop / lib / rubocop / rspec / shared_contexts.rb | 126 | 7: RSpec.shared_context 'ruby 2.4',: ruby24 do
 rubocop / lib / rubocop / rspec / shared_contexts.rb | 130 | 7: RSpec.shared_context 'ruby 2.5',: ruby25 do
 rubocop / lib / rubocop / rspec / shared_contexts.rb | 134 | 7: RSpec.shared_context 'ruby 2.6',: ruby26 do
 rubocop / lib / rubocop / rspec / shared_contexts.rb | 138 | 7: RSpec.shared_context 'ruby 2.7',: ruby27 do
 rubocop / lib / rubocop / rspec / shared_contexts.rb | 142 | 7: RSpec.shared_context 'ruby 3.0',: ruby30 do
 brew / Library / Homebrew / test / support / helper / spec / shared_context / homebrew_cask.rb | 34 | 7: RSpec.shared_context "Casco de cerveza casera",: needs_macos do
 chef / spec / support / shared / funcional / securable_resource.rb | 78 | 1: shared_context "usar permisos de Windows",: windows_only do
 lienzo-lms / spec / lib / turnitin / turnitin_spec_helper.rb | 22 | 7: RSpec.shared_context "shared_tii_lti",: shared_context =>: metadatos
 lienzo-lms / spec / lib / turnitin / turnitin_spec_helper.rb | 22 | 41: RSpec.shared_context "shared_tii_lti",: shared_context =>: metadatos
 lienzo-lms / spec / lti2_course_spec_helper.rb | 22 | 7: RSpec.shared_context "lti2_course_spec_helper",: shared_context =>: metadatos
 lienzo-lms / spec / lti2_course_spec_helper.rb | 22 | 50: RSpec.shared_context "lti2_course_spec_helper",: shared_context =>: metadatos
 canvas-lms / spec / plagiarism_platform_spec_helper.rb | 22 | 7: RSpec.shared_context "plagiarism_platform",: shared_context =>: metadatos
 canvas-lms / spec / plagiarism_platform_spec_helper.rb | 22 | 46: RSpec.shared_context "plagiarism_platform",: shared_context =>: metadatos
 lienzo-lms / spec / lti2_spec_helper.rb | 22 | 7: RSpec.shared_context "lti2_spec_helper",: shared_context =>: metadatos
 lienzo-lms / spec / lti2_spec_helper.rb | 22 | 43: RSpec.shared_context "lti2_spec_helper",: shared_context =>: metadatos
 canvas-lms / spec / lti_1_3_tool_configuration_spec_helper.rb | 22 | 7: RSpec.shared_context "lti_1_3_tool_configuration_spec_helper", shared_context:: metadatos
 canvas-lms / spec / lti_1_3_tool_configuration_spec_helper.rb | 22 | 64: RSpec.shared_context "lti_1_3_tool_configuration_spec_helper", shared_context:: metadatos
 lienzo-lms / spec / lti_1_3_spec_helper.rb | 23 | 7: RSpec.shared_context "lti_1_3_spec_helper", shared_context:: metadatos
 lienzo-lms / spec / lti_1_3_spec_helper.rb | 23 | 45: RSpec.shared_context "lti_1_3_spec_helper", shared_context:: metadatos
 canvas-lms / spec / apis / lti / lti2_api_spec_helper.rb | 24 | 7: RSpec.shared_context "lti2_api_spec_helper",: shared_context =>: metadatos
 canvas-lms / spec / apis / lti / lti2_api_spec_helper.rb | 24 | 47: RSpec.shared_context "lti2_api_spec_helper",: shared_context =>: metadatos
 gitlabhq / spec / lib / gitlab / git / merge_base_spec.rb | 11 | 3: shared_context 'referencias existentes con una base de fusión',: existing_refs do
 gitlabhq / spec / lib / gitlab / git / merge_base_spec.rb | 17 | 3: shared_context 'al pasar una referencia faltante',: missing_ref do
 gitlabhq / spec / lib / gitlab / git / merge_base_spec.rb | 23 | 3: shared_context 'al pasar referencias que no tienen un ancestro común',: no_common_ancestor do
 gitlabhq / spec / lib / gitlab / ci / config / entry / retry_spec.rb | 8 | 3: shared_context 'cuando el valor de reintento es numérico',: numérico do
 gitlabhq / spec / lib / gitlab / ci / config / entry / retry_spec.rb | 13 | 3: shared_context 'cuando el valor de reintento es un hash',: hash do
 rspec-expectativas / spec / spec_helper.rb | 72 | 7: RSpec.shared_context "con #should enabled",: uses_should do
 rspec-expectativas / spec / spec_helper.rb | 99 | 7: RSpec.shared_context "con #should exclusivamente habilitado",: uses_only_should do
 rspec-expectativas / spec / spec_helper.rb | 120 | 7: RSpec.shared_context "con warn_about_potential_false_positives establecido en falso",: warn_about_potential_false_positives do

Si hacemos una intersección con la lista anterior (rspec-rails, lobsters, rubytoolbox, forem, gitlabhq, chatwoot), resulta que ningún proyecto usa :apply_to_host_groups .
gitlabhq puede ser un poco confuso, en realidad tienen dos ayudantes de especificaciones, gitlabhq/qa/spec/spec_helper.rb y gitlabhq/spec/spec_helper.rb .
Y usan :trigger_inclusion :

  shared_context 'existing refs with a merge base', :existing_refs do
    let(:refs) do
      %w(304d257dcb821665ab5110318fc58a007bd104ed 0031876facac3f2b2702a0e53a26e89939a42209)
    end
  end

  describe '#sha' do
    context 'when the refs exist', :existing_refs do

en sus especificaciones.

Nosotros también lo usamos. rspec-expectations :

RSpec.shared_context "with warn_about_potential_false_positives set to false", :warn_about_potential_false_positives do
  original_value = RSpec::Expectations.configuration.warn_about_potential_false_positives?

  after(:context)  { RSpec::Expectations.configuration.warn_about_potential_false_positives = original_value }
end

Conclusión preliminar: esos proyectos populares de Ruby que usan RSpec que configuraron shared_context_metadata_behavior a :apply_to_host_groups hicieron esto a ciegas y nunca lo usaron.

Semántico

Tenemos dos formas de incluir grupos compartidos. include_context / include_examples y it_behaves_like . Este último crea un grupo anidado.

No tiene mucho sentido aplicar metadatos definidos para un grupo anidado creado implícitamente.

Por otro lado, al igual que con varios let s definidos en diferentes contextos incluidos, los metadatos, si se aplican desde diferentes contextos incluidos, tienen la posibilidad de anularse entre sí y no imprimimos una advertencia para este caso. , dejando espacio para la confusión.

Si eliminamos :trigger_inclusion , ¿qué recomendaríamos para reemplazarlo? Por ejemplo, para grupos compartidos definidos globalmente:

Para grupos compartidos definidos globalmente, esto funciona:

RSpec.shared_examples 'it is odd' do
  it { is_expected.to be_odd }
end

RSpec.configure do |config|
  config.include_context 'it is odd', :odd
end

RSpec.describe do
  context 'odd', :odd do
    subject { 1 }
  end
end

Sin embargo, ¿por qué es include_context ?
Bueno, solo tenemos include_context en nuestro objeto Configuration . No hay include_examples o it_behaves_like allí.

Uso más común y artificial:

config.include_context "example guest user", :type => :request

Pero, ¿qué prometer a cambio de grupos compartidos definidos localmente?
A veces, no es nada agradable llamar a include_context / it_behaves_like , y la inclusión implícita a través de metadatos coincidentes es útil para secar las cosas. A costa de un poco de magia. De qué se tratan los metadatos RSpec de todos modos.

Opinión impopular

Sin duda, esta opción ha resuelto el problema con la inclusión de ejemplos compartidos definidos en un alcance completamente ajeno.

Sin embargo, ¿había una buena razón para "aplicar metadatos al grupo de hosts"? ¿Se usa? ¿Es útil? ¿No es confuso?

Propuesta

Yo sugiero:

  • mantener la opción shared_context_metadata_behavior junto con su valor predeterminado :trigger_inclusion
  • eliminar las nociones de depreciaciones

... versión 4.0

  • solucionar el problema con inclusiones incorrectas ( prueba de concepto )
  • cambie el inicializador del proyecto, elimine la nota de obsolescencia y use :trigger_inclusion como predeterminado
  • depreciar :apply_to_host_groups

Si mantenemos :trigger_inclusion predeterminado, el comportamiento no cambiará para la mayoría. Menos boletos.

Dudas

Es posible que se requiera una revisión importante para corregir la inclusión, hasta el punto en que sea apenas posible.
Todavía espero que la realidad no rompa mis sueños juveniles demasiado optimistas, y es factible.

Comentario más útil

Gracias por escribir esto @pirj y convocarme para comentar :).

Sigo pensando que la inclusión implícita de grupos de ejemplo compartidos a través de metadatos todavía tiene problemas. # 1790 describe los problemas que vi con él cuando escribí ese número. En general, el comportamiento de inclusión implícita se siente inconsistente con el resto de RSpec, IMO; Tenga en cuenta que todas las demás formas en que se aprovechan los metadatos de RSpec se configuran en un bloque RSpec.configure . Tener una función RSpec que use metadatos de manera implícita (sin requerir que los usuarios lo configuren explícitamente en RSpec.configure ) es algo que espero que confunda a los usuarios que han usado metadatos RSpec pero no son conscientes del comportamiento de inclusión implícito . Tanto rspec-rails # 1579 como rspec-rails # 1241 son ejemplos de esto: los usuarios etiquetaron sus grupos de ejemplo compartidos con metadatos, esperando que actúen como metadatos en un grupo normal de actos (por ejemplo, activando la inclusión de config.include 'd módulos, estar disponible para filtrar, etc.) y nos sorprendió ver que no se comportaba así. De hecho, quedaron tan sorprendidos que lo informaron como un error a pesar de que estaba funcionando como estaba diseñado. Eso me sugiere que, para empezar, fue un diseño deficiente: si realmente se alineara con el resto de RSpec, no llevaría a los usuarios a informarlo como un error.

Como caja de arena para nuevos policías rubocop-rspec, he reunido una lista de los proyectos Ruby más destacados que usan RSpec, real-world-rspec, ~ 35 proyectos en total. De esos 35 (¡también incluye repositorios RSpec!), 7 usan :apply_to_host_groups en sus ayudantes de especificaciones:

La semántica exacta de etiquetar un grupo de ejemplo compartido con metadatos es algo en lo que esperaría que el 99% de los usuarios de RSpec nunca piensen y la situación confusa que resuelve el comportamiento más nuevo es relativamente rara ... así que no me sorprende. en absoluto. TBH, sospecho que :apply_to_host_groups es probablemente utilizado principalmente por proyectos que generaron (o volvieron a generar) su spec_helper.rb después de que cambiamos el código de configuración spec_helper.rb generado en 3589ab577d09db88ef5d5f0d60e8c35bfc55691f.

Para los usuarios que desean activar la inclusión de grupos de ejemplo compartidos según los metadatos, se proporciona la API config.include_context que, en mi opinión, es más simple, más consistente y más explícita que hacerlo automáticamente para los usuarios porque etiquetaron un grupo compartido y un grupo normal con los mismos metadatos. AFAIK, no hay nada que :trigger_inclusion proporcione que los usuarios no puedan lograr usando config.include_context explícitamente. (Si hay algo que no se pueda lograr de esa manera, ¡hágamelo saber!). OTOH, si un usuario quiere etiquetar su grupo de ejemplo compartido con algunos metadatos y aplicarlo en todos los lugares donde se incluye el grupo compartido ... no creo que haya un mecanismo alternativo para esto. (En realidad, tal vez config.define_derived_metadata podría usarse para simular esto, pero en mi opinión eso se siente como una "solución alternativa" para una característica que falta).

Personalmente, nunca he visto que apply_to_host_groups se use de la forma en que fue diseñado.

Así es como lo he usado:

  • He etiquetado temporalmente un grupo de ejemplo compartido con :focus , :pending o :skipped para poder centrarme en (o filtrar) especificaciones que dependen de un ejemplo compartido específico grupo. Un grupo de ejemplo compartido generalmente significa que existe una preocupación transversal que se aplica a todos, incluidos los grupos, y poder ejecutar solo esas especificaciones (o excluir esas especificaciones) puede ser bastante útil. Vale la pena señalar que este uso de la función generalmente no se mostrará en el código comprometido para ningún proyecto, porque :focus , :skip y :pending generalmente son cambios temporales que no son comprometido (particularmente :focus ).
  • Puede ser bastante útil como una forma de expresar dependencias en otro código de arnés de prueba. Por ejemplo, considere un proyecto que envuelve condicionalmente ejemplos en una transacción de base de datos cuando están etiquetados con :db . (Esto se puede lograr definiendo un grupo de ejemplo compartido DB support con un gancho around apropiado, y luego usar config.include_context "DB support", :db ). Ahora imagina que los desarrolladores quieren definir un nuevo grupo de ejemplo compartido llamado logged in as admin que define un gancho before que crea un usuario administrador en la base de datos e inicia sesión como ese usuario usando rack-test o lo que sea . Dado que este nuevo grupo de ejemplo compartido interactúa con la base de datos en un gancho before , tiene una dependencia del grupo de ejemplo compartido DB support . En mi opinión, la forma más limpia de expresar esta dependencia es etiquetar el grupo de ejemplo compartido logged in as admin con :db para que el soporte de DB se aplique automáticamente cuando logged in as an admin se incluya en un grupo anfitrión . Puede hacer include_context 'DB support' en el grupo de ejemplo logged in as an admin para aplicar las transacciones DB automáticamente, pero si la forma "normal" de administrar eso en cualquier otro lugar es a través de una etiqueta :db , es extraño no poder hacer eso aquí.

Nosotros también lo usamos. rspec-expectativas:

JAJAJA :). No estoy muy sorprendido por eso; históricamente no hemos actualizado RSpec a todas las configuraciones de configuración "estándar" más recientes, etc., particularmente si los problemas que aborda la nueva configuración de configuración no ocurren dentro de las pruebas de RSpec. Es bastante fácil cambiar la inclusión activada por metadatos de las expectativas de rspec para usar config.include_context , ¿verdad?

Conclusión preliminar: esos proyectos populares de Ruby que usan RSpec que configuraron shared_context_metadata_behavior a :apply_to_host_groups hicieron esto a ciegas y nunca lo usaron.

Honestamente, me sorprendería si hay más de un puñado de usuarios que han pensado en cómo exactamente quieren que se comporte el metadato de grupo de ejemplo compartido, y luego vayan y establezcan la opción de configuración en el comportamiento que desean. Me imagino que casi todos los usos de :apply_to_host_groups en la configuración se deben a su generación a través de rspec --init .

Sin embargo, no creo que sea necesariamente un argumento en contra de :apply_to_host_groups ; como yo lo veo, la mitad del beneficio de :apply_to_host_groups es que deshabilita el comportamiento de auto-inclusión heredado implícito y contrario a la intuición. La otra mitad del beneficio es que permite un nuevo comportamiento que está más en consonancia (IMO, YMMV, por supuesto) con el diseño general de RSpec. Un proyecto que configura :apply_to_host_groups y luego nunca etiqueta grupos compartidos con metadatos aún puede beneficiarse de la opción; todavía hay un comportamiento potencialmente confuso que están evitando. Por ejemplo, si un desarrollador etiqueta temporalmente un grupo compartido con :focus , con la intención de enfocarlo ... es beneficioso que RSpec no incluya el grupo compartido en otros grupos que están siendo :focus ed.

No tiene mucho sentido aplicar metadatos definidos para un grupo anidado creado implícitamente.

Creo que :apply_to_host_groups todavía proporciona un comportamiento beneficioso para este caso, aunque tiene razón en que aplicar los metadatos al grupo anfitrión no se aplica per se aquí. Considere el caso de un grupo de ejemplos compartidos que dependen todos de la base de datos (en un proyecto que etiqueta tales ejemplos con :db para envolverlos en transacciones DB). Es útil para los usuarios poder etiquetar su grupo de ejemplo compartido con :db para expresar la dependencia en la base de datos (y para envolver automáticamente todos los ejemplos contenidos en transacciones DB). Si bien it_behaves_like no fusionará los metadatos :db en los metadatos de un grupo anfitrión, es bueno poder etiquetar el grupo compartido con :db y aplicarlo al ejemplos en él. El comportamiento heredado :trigger_inclusion excluye esta posibilidad porque cuando lo hace shared_examples_for "common metadata support", :db , la etiqueta :db se usa para incluir automáticamente este grupo compartido en otros :db etiquetados grupos.

Este es básicamente el quid de por qué creo que :apply_to_host_groups es preferible: tenemos una forma de incluir grupos de ejemplo compartidos basados ​​en metadatos usando config.include_context (que refleja muy bien config.include para los módulos. ..), pero si usamos implícitamente metadatos de grupo compartidos para activar la inclusión, evita que los usuarios puedan etiquetar sus ejemplos compartidos con metadatos comunes a nivel de grupo. Y si usamos metadatos grupales compartidos de esa manera, entonces, en mi opinión, la extensión natural de eso para include_context / include_examples (cuando la inclusión no está anidada) es fusionar los metadatos con el host metadatos del grupo. (Pero YMMV; ese es solo mi modelo mental de cómo funcionan los metadatos RSpec).

Sin embargo, ¿por qué es include_context? Bueno, solo tenemos include_context en nuestro objeto de configuración. No hay include_examples o it_behaves_like allí.

Eso es por diseño. En mi opinión, sería sorprendente (y extraño) incluir automáticamente algunos ejemplos compartidos en muchos grupos de ejemplo basados ​​en contextos. Un usuario podría mirar el archivo de especificaciones, ver 4 it bloques (lo que sugiere que la ejecución del archivo ejecutará 4 ejemplos), ejecutarlo y sorprenderse al ver que se ejecutan 20 ejemplos (por ejemplo, debido a que se incluyen 16 ejemplos compartidos) . En mi opinión, los ejemplos son algo primordial que se debe ver y comprender para comprender cómo está estructurado el conjunto de pruebas y cómo funciona. El código de arnés de prueba compartido (por ejemplo, métodos auxiliares, let s, o un gancho around que gestiona una transacción de base de datos ...) no tiene la misma necesidad de ser visible. De hecho, tiendo a usar grupos de ejemplo compartidos (y include_context ) específicamente para ocultar detalles sin importancia de agregar ruido a toneladas de especificaciones. Por ejemplo, para una inquietud transversal como la captura de registros o las transacciones de base de datos, no quiero que cada prueba que tenga esas inquietudes tenga el ruido del código visible para administrar esas cosas. Ser capaz de etiquetar el grupo con :db o :capture_logs y dejar que config.include_context aplique el código compartido por mí, da como resultado especificaciones con una relación señal-ruido más alta. No es lo mismo con include_examples / it_behaves_like , que no están destinados a ser utilizados para el mismo tipo de preocupaciones comunes y transversales que son agradables de manejar automáticamente.

De todos modos, esos son mis dos centavos :). Dado que ya no estoy involucrado en el mantenimiento de RSpec, no me siento obligado a dar mi opinión de manera indebida.

Todos 5 comentarios

Me encantaría convocar a @myronmarston como la fuerza impulsora detrás de :apply_to_host_groups .

Gracias por escribir esto @pirj y convocarme para comentar :).

Sigo pensando que la inclusión implícita de grupos de ejemplo compartidos a través de metadatos todavía tiene problemas. # 1790 describe los problemas que vi con él cuando escribí ese número. En general, el comportamiento de inclusión implícita se siente inconsistente con el resto de RSpec, IMO; Tenga en cuenta que todas las demás formas en que se aprovechan los metadatos de RSpec se configuran en un bloque RSpec.configure . Tener una función RSpec que use metadatos de manera implícita (sin requerir que los usuarios lo configuren explícitamente en RSpec.configure ) es algo que espero que confunda a los usuarios que han usado metadatos RSpec pero no son conscientes del comportamiento de inclusión implícito . Tanto rspec-rails # 1579 como rspec-rails # 1241 son ejemplos de esto: los usuarios etiquetaron sus grupos de ejemplo compartidos con metadatos, esperando que actúen como metadatos en un grupo normal de actos (por ejemplo, activando la inclusión de config.include 'd módulos, estar disponible para filtrar, etc.) y nos sorprendió ver que no se comportaba así. De hecho, quedaron tan sorprendidos que lo informaron como un error a pesar de que estaba funcionando como estaba diseñado. Eso me sugiere que, para empezar, fue un diseño deficiente: si realmente se alineara con el resto de RSpec, no llevaría a los usuarios a informarlo como un error.

Como caja de arena para nuevos policías rubocop-rspec, he reunido una lista de los proyectos Ruby más destacados que usan RSpec, real-world-rspec, ~ 35 proyectos en total. De esos 35 (¡también incluye repositorios RSpec!), 7 usan :apply_to_host_groups en sus ayudantes de especificaciones:

La semántica exacta de etiquetar un grupo de ejemplo compartido con metadatos es algo en lo que esperaría que el 99% de los usuarios de RSpec nunca piensen y la situación confusa que resuelve el comportamiento más nuevo es relativamente rara ... así que no me sorprende. en absoluto. TBH, sospecho que :apply_to_host_groups es probablemente utilizado principalmente por proyectos que generaron (o volvieron a generar) su spec_helper.rb después de que cambiamos el código de configuración spec_helper.rb generado en 3589ab577d09db88ef5d5f0d60e8c35bfc55691f.

Para los usuarios que desean activar la inclusión de grupos de ejemplo compartidos según los metadatos, se proporciona la API config.include_context que, en mi opinión, es más simple, más consistente y más explícita que hacerlo automáticamente para los usuarios porque etiquetaron un grupo compartido y un grupo normal con los mismos metadatos. AFAIK, no hay nada que :trigger_inclusion proporcione que los usuarios no puedan lograr usando config.include_context explícitamente. (Si hay algo que no se pueda lograr de esa manera, ¡hágamelo saber!). OTOH, si un usuario quiere etiquetar su grupo de ejemplo compartido con algunos metadatos y aplicarlo en todos los lugares donde se incluye el grupo compartido ... no creo que haya un mecanismo alternativo para esto. (En realidad, tal vez config.define_derived_metadata podría usarse para simular esto, pero en mi opinión eso se siente como una "solución alternativa" para una característica que falta).

Personalmente, nunca he visto que apply_to_host_groups se use de la forma en que fue diseñado.

Así es como lo he usado:

  • He etiquetado temporalmente un grupo de ejemplo compartido con :focus , :pending o :skipped para poder centrarme en (o filtrar) especificaciones que dependen de un ejemplo compartido específico grupo. Un grupo de ejemplo compartido generalmente significa que existe una preocupación transversal que se aplica a todos, incluidos los grupos, y poder ejecutar solo esas especificaciones (o excluir esas especificaciones) puede ser bastante útil. Vale la pena señalar que este uso de la función generalmente no se mostrará en el código comprometido para ningún proyecto, porque :focus , :skip y :pending generalmente son cambios temporales que no son comprometido (particularmente :focus ).
  • Puede ser bastante útil como una forma de expresar dependencias en otro código de arnés de prueba. Por ejemplo, considere un proyecto que envuelve condicionalmente ejemplos en una transacción de base de datos cuando están etiquetados con :db . (Esto se puede lograr definiendo un grupo de ejemplo compartido DB support con un gancho around apropiado, y luego usar config.include_context "DB support", :db ). Ahora imagina que los desarrolladores quieren definir un nuevo grupo de ejemplo compartido llamado logged in as admin que define un gancho before que crea un usuario administrador en la base de datos e inicia sesión como ese usuario usando rack-test o lo que sea . Dado que este nuevo grupo de ejemplo compartido interactúa con la base de datos en un gancho before , tiene una dependencia del grupo de ejemplo compartido DB support . En mi opinión, la forma más limpia de expresar esta dependencia es etiquetar el grupo de ejemplo compartido logged in as admin con :db para que el soporte de DB se aplique automáticamente cuando logged in as an admin se incluya en un grupo anfitrión . Puede hacer include_context 'DB support' en el grupo de ejemplo logged in as an admin para aplicar las transacciones DB automáticamente, pero si la forma "normal" de administrar eso en cualquier otro lugar es a través de una etiqueta :db , es extraño no poder hacer eso aquí.

Nosotros también lo usamos. rspec-expectativas:

JAJAJA :). No estoy muy sorprendido por eso; históricamente no hemos actualizado RSpec a todas las configuraciones de configuración "estándar" más recientes, etc., particularmente si los problemas que aborda la nueva configuración de configuración no ocurren dentro de las pruebas de RSpec. Es bastante fácil cambiar la inclusión activada por metadatos de las expectativas de rspec para usar config.include_context , ¿verdad?

Conclusión preliminar: esos proyectos populares de Ruby que usan RSpec que configuraron shared_context_metadata_behavior a :apply_to_host_groups hicieron esto a ciegas y nunca lo usaron.

Honestamente, me sorprendería si hay más de un puñado de usuarios que han pensado en cómo exactamente quieren que se comporte el metadato de grupo de ejemplo compartido, y luego vayan y establezcan la opción de configuración en el comportamiento que desean. Me imagino que casi todos los usos de :apply_to_host_groups en la configuración se deben a su generación a través de rspec --init .

Sin embargo, no creo que sea necesariamente un argumento en contra de :apply_to_host_groups ; como yo lo veo, la mitad del beneficio de :apply_to_host_groups es que deshabilita el comportamiento de auto-inclusión heredado implícito y contrario a la intuición. La otra mitad del beneficio es que permite un nuevo comportamiento que está más en consonancia (IMO, YMMV, por supuesto) con el diseño general de RSpec. Un proyecto que configura :apply_to_host_groups y luego nunca etiqueta grupos compartidos con metadatos aún puede beneficiarse de la opción; todavía hay un comportamiento potencialmente confuso que están evitando. Por ejemplo, si un desarrollador etiqueta temporalmente un grupo compartido con :focus , con la intención de enfocarlo ... es beneficioso que RSpec no incluya el grupo compartido en otros grupos que están siendo :focus ed.

No tiene mucho sentido aplicar metadatos definidos para un grupo anidado creado implícitamente.

Creo que :apply_to_host_groups todavía proporciona un comportamiento beneficioso para este caso, aunque tiene razón en que aplicar los metadatos al grupo anfitrión no se aplica per se aquí. Considere el caso de un grupo de ejemplos compartidos que dependen todos de la base de datos (en un proyecto que etiqueta tales ejemplos con :db para envolverlos en transacciones DB). Es útil para los usuarios poder etiquetar su grupo de ejemplo compartido con :db para expresar la dependencia en la base de datos (y para envolver automáticamente todos los ejemplos contenidos en transacciones DB). Si bien it_behaves_like no fusionará los metadatos :db en los metadatos de un grupo anfitrión, es bueno poder etiquetar el grupo compartido con :db y aplicarlo al ejemplos en él. El comportamiento heredado :trigger_inclusion excluye esta posibilidad porque cuando lo hace shared_examples_for "common metadata support", :db , la etiqueta :db se usa para incluir automáticamente este grupo compartido en otros :db etiquetados grupos.

Este es básicamente el quid de por qué creo que :apply_to_host_groups es preferible: tenemos una forma de incluir grupos de ejemplo compartidos basados ​​en metadatos usando config.include_context (que refleja muy bien config.include para los módulos. ..), pero si usamos implícitamente metadatos de grupo compartidos para activar la inclusión, evita que los usuarios puedan etiquetar sus ejemplos compartidos con metadatos comunes a nivel de grupo. Y si usamos metadatos grupales compartidos de esa manera, entonces, en mi opinión, la extensión natural de eso para include_context / include_examples (cuando la inclusión no está anidada) es fusionar los metadatos con el host metadatos del grupo. (Pero YMMV; ese es solo mi modelo mental de cómo funcionan los metadatos RSpec).

Sin embargo, ¿por qué es include_context? Bueno, solo tenemos include_context en nuestro objeto de configuración. No hay include_examples o it_behaves_like allí.

Eso es por diseño. En mi opinión, sería sorprendente (y extraño) incluir automáticamente algunos ejemplos compartidos en muchos grupos de ejemplo basados ​​en contextos. Un usuario podría mirar el archivo de especificaciones, ver 4 it bloques (lo que sugiere que la ejecución del archivo ejecutará 4 ejemplos), ejecutarlo y sorprenderse al ver que se ejecutan 20 ejemplos (por ejemplo, debido a que se incluyen 16 ejemplos compartidos) . En mi opinión, los ejemplos son algo primordial que se debe ver y comprender para comprender cómo está estructurado el conjunto de pruebas y cómo funciona. El código de arnés de prueba compartido (por ejemplo, métodos auxiliares, let s, o un gancho around que gestiona una transacción de base de datos ...) no tiene la misma necesidad de ser visible. De hecho, tiendo a usar grupos de ejemplo compartidos (y include_context ) específicamente para ocultar detalles sin importancia de agregar ruido a toneladas de especificaciones. Por ejemplo, para una inquietud transversal como la captura de registros o las transacciones de base de datos, no quiero que cada prueba que tenga esas inquietudes tenga el ruido del código visible para administrar esas cosas. Ser capaz de etiquetar el grupo con :db o :capture_logs y dejar que config.include_context aplique el código compartido por mí, da como resultado especificaciones con una relación señal-ruido más alta. No es lo mismo con include_examples / it_behaves_like , que no están destinados a ser utilizados para el mismo tipo de preocupaciones comunes y transversales que son agradables de manejar automáticamente.

De todos modos, esos son mis dos centavos :). Dado que ya no estoy involucrado en el mantenimiento de RSpec, no me siento obligado a dar mi opinión de manera indebida.

¡Muchas gracias por una respuesta tan detallada y perspicaz, @myronmarston!

es beneficioso que RSpec no incluya, en cambio, el grupo compartido en otros grupos que están :focused en

Eso ciertamente sería una apoteosis de la confusión.

Mis dudas se disiparon, voy a proceder a hacer de :apply_to_host_groups la única opción predeterminada.
Mantendré el boleto abierto, lo vincularé a los RP.

Mis dudas se disiparon, voy a proceder a hacer de :apply_to_host_groups la única opción predeterminada.

Podría valer la pena eliminar la opción de configuración por completo, dado que el plan es no admitir ningún otro comportamiento. O tal vez, por compatibilidad con versiones anteriores, podría definirse pero ignorarse (aunque eso podría ser confuso si el usuario configuró el comportamiento heredado ...). No sé si todos están planeando lanzar un RSpec 3.99 (como hicimos con 2.99) para proporcionar advertencias de actualización, pero si sigue esa ruta 3.99 podría retener la opción y advertir adecuadamente y 4.0 no podría tener la opción en todos quizás.

Gracias por la respuesta detallada @myronmarston : alegría: cierro esto porque estoy de acuerdo y, a juzgar por las relaciones públicas de Phil, él también está convencido.

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