Rspec-core: Reconsiderar `shared_context_metadata_behavior`

Criado em 27 dez. 2020  ·  5Comentários  ·  Fonte: rspec/rspec-core

Fundo

Tivemos um problema com metadados definidos em grupos de exemplo compartilhados locais específicos ( shared_examples / shared_examples_for / shared_context ) fazendo com que eles sejam incluídos em um grupo de exemplo completamente não relacionado relatado em [ 1 ] / [ 2 ]. Mais algumas informações em https://github.com/rspec/rspec-core/issues/1762.

Em https://github.com/rspec/rspec-core/issues/1790 (https://github.com/rspec/rspec-core/commit/ed2d59c8a97d32f4a20e91d0abdad87f90d2b930), shared_context_metadata_behavior com um não-padrão :apply_to_host_groups valor. https://github.com/rspec/rspec-core/commit/3589ab577d09db88ef5d5f0d60e8c35bfc55691f adicionado ao inicializador de projeto junto com uma observação de que se tornará o padrão e a única opção no RSpec 4.

:trigger_inclusion permaneceu a configuração padrão, entretanto , por razões SEMPRE.

Resumindo:

`: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 Pessoalmente, nunca vi `: apply_to_host_groups` sendo usado da maneira que foi projetado. Por outro lado, eu vi alguns projetos usarem `: trigger_inclusion` para grupos / contextos de exemplo compartilhados definidos globalmente. Mas é uma amostra de um. Vamos verificar. Como uma caixa de areia para novos policiais `rubocop-rspec`, eu montei uma lista da maioria dos projetos Ruby estrelados que usam RSpec, [` real-world-rspec`] (https://github.com/pirj/real-world -rspec), ~ 35 projetos no total. Desses 35 (também inclui repositórios RSpec!), 7 usam `: apply_to_host_groups` em seus ajudantes de especificações: 24pullrequests / administrate / Homebrew / camaleon-cms / canvas-lms / capistrano / capybara / cartodb / chatwoot / chef / diaspora / discourse / locomotivecms / errbit / fat_free_crm / forem / gitlabhq / hound / huginn / lobsters / loomio / mastodon / open-source-billing / publify / puppet / radiant / refinerycms / rspec-core / rspec-responses / 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
E um em `lib`:
capybara/lib/capybara/spec/spec_helper.rb|19| 16:        config.shared_context_metadata_behavior = :apply_to_host_groups
Outros, uma vez que não têm esta configuração e o padrão é `: trigger_inclusion`, não usam metadados de grupos de exemplo compartilhados ou contam com inclusão de gatilho.
Vamos dar uma olhada nos usos. Abra este spoiler para ver ** todos ** 40 grupos / contextos compartilhados com metadados (de um total de cerca de 3.000 grupos compartilhados)
 rspec-core / spec / rspec / core / metadata_spec.rb | 317 | 42: RSpec.shared_examples_for ("algum comportamento compartilhado",: include_it => true) faça
 puppet / spec / shared_contexts / digests.rb | 16 | 1: shared_context ('com algoritmos de resumo suportados',: uses_checksums => true) faça
 puppet / spec / shared_contexts / digests.rb | 27 | 1: shared_context ("quando digest_algorithm é definido como sha256",: digest_algorithm => 'sha256') faça
 puppet / spec / shared_contexts / digests.rb | 42 | 1: shared_context ("quando digest_algorithm é definido como md5",: digest_algorithm => 'md5') faça
 puppet / spec / shared_contexts / digests.rb | 57 | 1: shared_context ("quando digest_algorithm é definido como sha512",: digest_algorithm => 'sha512') faça
 puppet / spec / shared_contexts / digests.rb | 72 | 1: shared_context ("quando digest_algorithm é definido como sha384",: digest_algorithm => 'sha384') faça
 puppet / spec / shared_contexts / digests.rb | 87 | 1: shared_context ("quando digest_algorithm é definido como sha224",: digest_algorithm => 'sha224') faça
 diáspora / spec / support / gon.rb | 3 | 1: shared_context: vai fazer
 diáspora / spec / spec_helper.rb | 163 | 1: shared_context suppress_csrf_verification:: nenhum faz
 rubocop / lib / rubocop / rspec / shared_contexts.rb | 5 | 7: RSpec.shared_context 'ambiente isolado',: isolated_environment do
 rubocop / lib / rubocop / rspec / shared_contexts.rb | 43 | 7: RSpec.shared_context 'manter registro',: restore_registry do
 rubocop / lib / rubocop / rspec / shared_contexts.rb | 56 | 7: RSpec.shared_context 'config',: config do # rubocop: desativar Metrics / BlockLength
 rubocop / lib / rubocop / rspec / shared_contexts.rb | 114 | 7: RSpec.shared_context 'simulação de saída do console' fazer
 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 "Cask Homebrew",: needs_macos do
 chef / spec / support / shared / function / securable_resource.rb | 78 | 1: shared_context "usar permissões do Windows",: windows_only faça
 canvas-lms / spec / lib / turnitin / turnitin_spec_helper.rb | 22 | 7: RSpec.shared_context "shared_tii_lti",: shared_context =>: metadados fazem
 canvas-lms / spec / lib / turnitin / turnitin_spec_helper.rb | 22 | 41: RSpec.shared_context "shared_tii_lti",: shared_context =>: metadados fazem
 canvas-lms / spec / lti2_course_spec_helper.rb | 22 | 7: RSpec.shared_context "lti2_course_spec_helper",: shared_context =>: metadados fazem
 canvas-lms / spec / lti2_course_spec_helper.rb | 22 | 50: RSpec.shared_context "lti2_course_spec_helper",: shared_context =>: metadados fazem
 canvas-lms / spec / plagiarism_platform_spec_helper.rb | 22 | 7: RSpec.shared_context "plagiarism_platform",: shared_context =>: metadados fazem
 canvas-lms / spec / plagiarism_platform_spec_helper.rb | 22 | 46: RSpec.shared_context "plagiarism_platform",: shared_context =>: metadados fazem
 canvas-lms / spec / lti2_spec_helper.rb | 22 | 7: RSpec.shared_context "lti2_spec_helper",: shared_context =>: metadados fazem
 canvas-lms / spec / lti2_spec_helper.rb | 22 | 43: RSpec.shared_context "lti2_spec_helper",: shared_context =>: metadados fazem
 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:: metadata do
 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:: metadata do
 canvas-lms / spec / lti_1_3_spec_helper.rb | 23 | 7: RSpec.shared_context "lti_1_3_spec_helper", shared_context:: metadados fazem
 canvas-lms / spec / lti_1_3_spec_helper.rb | 23 | 45: RSpec.shared_context "lti_1_3_spec_helper", shared_context:: metadados fazem
 canvas-lms / spec / apis / lti / lti2_api_spec_helper.rb | 24 | 7: RSpec.shared_context "lti2_api_spec_helper",: shared_context =>: metadados fazem
 canvas-lms / spec / apis / lti / lti2_api_spec_helper.rb | 24 | 47: RSpec.shared_context "lti2_api_spec_helper",: shared_context =>: metadados fazem
 gitlabhq / spec / lib / gitlab / git / merge_base_spec.rb | 11 | 3: shared_context 'referências existentes com uma base de fusão',: existing_refs fazem
 gitlabhq / spec / lib / gitlab / git / merge_base_spec.rb | 17 | 3: shared_context 'ao passar uma ref ausente',: missing_ref do
 gitlabhq / spec / lib / gitlab / git / merge_base_spec.rb | 23 | 3: shared_context 'ao passar refs que não têm um ancestral comum',: no_common_ancestor do
 gitlabhq / spec / lib / gitlab / ci / config / entry / retry_spec.rb | 8 | 3: shared_context 'quando o valor de nova tentativa é um numérico',: numérico faça
 gitlabhq / spec / lib / gitlab / ci / config / entry / retry_spec.rb | 13 | 3: shared_context 'quando o valor de nova tentativa é um hash',: hash do
 rspec-responses / spec / spec_helper.rb | 72 | 7: RSpec.shared_context "com #deve habilitar",: usa_deve fazer
 rspec-responses / spec / spec_helper.rb | 99 | 7: RSpec.shared_context "com #deve exclusivamente habilitado",: usa_only_deve fazer
 rspec-responses / spec / spec_helper.rb | 120 | 7: RSpec.shared_context "com warn_about_potential_false_positives definido como falso",: warn_about_potential_false_positives fazer

Se fizermos uma interseção com a lista anterior (rspec-rails, lobsters, rubytoolbox, fore, gitlabhq, chatwoot), descobrimos que nenhum projeto usa :apply_to_host_groups .
gitlabhq pode ser um pouco confuso, na verdade eles têm dois ajudantes de especificações, gitlabhq/qa/spec/spec_helper.rb e gitlabhq/spec/spec_helper.rb .
E eles usam :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

em suas especificações.

Nós também o 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

Conclusão preliminar: aqueles projetos populares de Ruby que usam RSpec que configuraram shared_context_metadata_behavior para :apply_to_host_groups fizeram isso às cegas e nunca o usaram.

Semântica

Temos duas maneiras de incluir grupos compartilhados. include_context / include_examples e it_behaves_like . O último cria um grupo aninhado.

Não faz muito sentido aplicar metadados definidos para um grupo aninhado criado implicitamente.

Por outro lado, assim como com vários let s definidos em diferentes contextos incluídos, os metadados, se aplicados a partir de diferentes contextos incluídos, têm uma chance de se sobreporem e não imprimimos um aviso para este caso , deixando espaço para confusão.

Se acontecer de removermos :trigger_inclusion , o que recomendamos para substituí-lo? Por exemplo, para grupos compartilhados definidos globalmente:

Para grupos compartilhados definidos globalmente, isso 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

No entanto, por que é include_context ?
Bem, só temos include_context em nosso objeto Configuration . Não há include_examples ou it_behaves_like lá.

Uso mais comum e artificial:

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

Mas, o que prometer em troca de grupos compartilhados definidos localmente?
Às vezes, é bastante desagradável chamar include_context / it_behaves_like , e a inclusão implícita por meio de metadados correspondentes é útil para tornar as coisas secas. Com um pouco de magia. Quais são os metadados RSpec de qualquer maneira.

Opinião impopular

Sem dúvida, essa opção resolveu o problema com a inclusão de exemplos compartilhados definidos em um escopo completamente não relacionado.

No entanto, havia um bom motivo para "aplicar metadados ao grupo de hosts"? É usado? É útil? Não é confuso?

Proposta

Eu sugiro:

  • manter a opção shared_context_metadata_behavior junto com seu valor padrão de :trigger_inclusion
  • remover noções de depreciações

... versão 4.0

  • corrigir o problema com inclusões incorretas ( prova de conceito )
  • mude o inicializador do projeto, remova a nota de depreciação e use :trigger_inclusion como o padrão
  • descontinuar :apply_to_host_groups

Se mantivermos :trigger_inclusion padrão, o comportamento não mudará para a maioria. Menos ingressos.

Dúvidas

Pode ser necessária uma revisão significativa para consertar a inclusão, até o ponto em que quase não é possível.
Ainda espero que a realidade não destrua meus sonhos excessivamente otimistas da juventude, e isso é factível.

Comentários muito úteis

Obrigado por escrever @pirj e me convocar para comentar :).

Eu ainda acho que a inclusão implícita de grupos de exemplo compartilhados por meio de metadados ainda tem problemas. # 1790 descreve os problemas que vi com ele quando escrevi essa edição. No geral, o comportamento de inclusão implícita parece inconsistente com o resto do RSpec, IMO; considere que todas as outras maneiras pelas quais os metadados RSpec são aproveitados, você os configura em um bloco RSpec.configure . Ter 1 recurso RSpec usando metadados de forma implícita (sem exigir que os usuários os configurem explicitamente em RSpec.configure ) é algo que espero confundir os usuários que usaram metadados RSpec, mas não estão cientes do comportamento de inclusão implícito . Ambos rspec-rails # 1579 e rspec-rails # 1241 são exemplos disso: os usuários marcaram seus grupos de exemplo compartilhados com metadados, esperando que eles agissem como metadados em atos de grupo normais (por exemplo, desencadeando a inclusão de config.include 'd módulos, estar disponível para filtragem, etc) e ficamos surpresos ao ver que não era assim que se comportava. Na verdade, eles ficaram tão surpresos que o relataram como um bug, embora estivesse funcionando conforme o planejado. Isso me sugere que foi um design ruim para começar: se ele realmente estivesse alinhado com o resto do RSpec, não levaria os usuários a relatá-lo como um bug.

Como uma caixa de areia para novos policiais rubocop-rspec, eu montei uma lista da maioria dos projetos Ruby estrelados que usam RSpec, real-world-rspec, cerca de 35 projetos no total. Desses 35 (também inclui repositórios RSpec!), 7 usam :apply_to_host_groups em seus auxiliares de especificação:

A semântica exata de marcar um grupo de exemplo compartilhado com metadados é algo que eu esperaria que 99% dos usuários RSpec nunca pensassem e a situação confusa que o comportamento mais recente resolve é relativamente rara ... então não estou surpreso com isso em absoluto. TBH, eu suspeito que :apply_to_host_groups é provavelmente usado principalmente por projetos que geraram (ou regeneraram) seus spec_helper.rb depois que alteramos o código de configuração spec_helper.rb gerado em 3589ab577d09db88ef5d5f0d60e8c35bfc55691f.

Para usuários que desejam acionar a inclusão de grupo de exemplo compartilhado com base em metadados, a API config.include_context é fornecida, a qual, IMO, é mais simples, mais consistente e mais explícita do que fazer isso automaticamente para usuários porque eles marcaram um grupo compartilhado e um grupo normal com os mesmos metadados. AFAIK, não há nada que :trigger_inclusion forneça que não possa ser realizado por usuários usando config.include_context explicitamente. (Se houver algo que não possa ser feito dessa forma, por favor me avise!). OTOH, se um usuário deseja marcar seu grupo de exemplo compartilhado com alguns metadados e aplicá-lo em todos os lugares em que o grupo compartilhado está incluído ... Não acho que haja um mecanismo alternativo para isso. (Na verdade, talvez config.define_derived_metadata pudesse ser usado para simular isso, mas IMO, isso parece uma "solução alternativa" para um recurso ausente).

Pessoalmente, nunca vi: apply_to_host_groups sendo usado da forma para que foi projetado.

Veja como eu usei:

  • Eu marquei temporariamente um grupo de exemplo compartilhado com :focus , :pending ou :skipped para que eu possa me concentrar em (ou filtrar) especificações que dependem de um exemplo compartilhado específico grupo. Um grupo de exemplo compartilhado geralmente significa que há alguma preocupação transversal que se aplica a todos os grupos, e ser capaz de executar apenas essas especificações (ou excluir essas especificações) pode ser bastante útil. É importante notar que este uso do recurso geralmente não aparece no código confirmado para nenhum projeto, porque :focus , :skip e :pending são geralmente mudanças temporárias que não são comprometidos (particularmente :focus ).
  • Pode ser muito útil como uma forma de expressar dependências em outro código de controle de teste. Por exemplo, considere um projeto que envolve condicionalmente exemplos em uma transação de banco de dados quando eles são marcados com :db . (Isso poderia ser realizado definindo um DB support grupo de exemplo compartilhado com um gancho around apropriado e, em seguida, usar config.include_context "DB support", :db ). Agora imagine que os desenvolvedores desejam definir um novo grupo de exemplo compartilhado denominado logged in as admin que define um gancho before que cria um usuário administrador no banco de dados e efetua login como esse usuário usando teste de rack ou qualquer outro . Como esse novo grupo de exemplo compartilhado interage com o banco de dados em um gancho before , ele depende do grupo de exemplo DB support compartilhado. IMO, a maneira mais limpa de expressar essa dependência é marcar o grupo de exemplo compartilhado logged in as admin com :db para que o suporte do DB seja automaticamente aplicado quando logged in as an admin for incluído em um grupo de host . Você poderia fazer include_context 'DB support' no grupo de exemplo logged in as an admin para aplicar as transações do banco de dados automaticamente, mas se a maneira "normal" de gerenciar isso em todos os outros lugares é por meio de uma tag :db , é estranho não ser capaz de fazer isso aqui.

Nós também o usamos. expectativas-rspec:

LOL :). Não estou muito surpreso com isso; historicamente, não atualizamos o RSpec para todas as últimas configurações "padrão", etc., principalmente se os problemas que as novas configurações abordam não acontecem nos testes do RSpec. É fácil alterar a inclusão acionada por metadados rspec-transactions para usar config.include_context , certo?

Conclusão preliminar: aqueles projetos populares de Ruby que usam RSpec que configuraram shared_context_metadata_behavior para :apply_to_host_groups fizeram isso às cegas e nunca o usaram.

Eu honestamente ficaria surpreso se houvesse mais do que um punhado de usuários que pensaram sobre como exatamente eles desejam que os metadados de grupos de exemplo compartilhados se comportem, para então ir e definir a opção de configuração para o comportamento que desejam. Imagino que quase todos os usos de :apply_to_host_groups na configuração se devam à geração dele por meio de rspec --init .

Não acho que isso seja necessariamente um argumento contra :apply_to_host_groups ; a meu ver, metade do benefício de :apply_to_host_groups é que ele desativa o comportamento de auto-inclusão legado implícito e contra-intuitivo. A outra metade do benefício é que permite um novo comportamento que está mais de acordo (IMO, YMMV, é claro) com o design geral do RSpec. Um projeto que configura :apply_to_host_groups e nunca marca grupos compartilhados com metadados ainda pode se beneficiar com a opção; ainda há um comportamento potencialmente confuso que eles estão evitando. Por exemplo, se um desenvolvedor marcar temporariamente um grupo compartilhado com :focus , com a intenção de focalizá-lo ... é benéfico que RSpec não inclua o grupo compartilhado em outros grupos que estão sendo :focus ed em.

Não faz muito sentido aplicar metadados definidos para um grupo aninhado criado implicitamente.

Acho que :apply_to_host_groups ainda fornece um comportamento benéfico para este caso, mesmo que você esteja certo de que aplicar os metadados ao grupo de hosts não se aplica per se aqui. Considere o caso de um grupo de exemplos compartilhados em que todos dependem do banco de dados (em um projeto que marca tais exemplos com :db para envolvê-los em transações de banco de dados). É útil para os usuários poderem marcar seu grupo de exemplo compartilhado com :db para expressar a dependência no banco de dados (e para envolver automaticamente todos os exemplos contidos em transações de banco de dados). Embora it_behaves_like não mescle os metadados :db nos metadados de um grupo de host, ainda é bom ser capaz de marcar o grupo compartilhado com :db e aplicá-lo ao exemplos nele. O comportamento legado de :trigger_inclusion exclui essa possibilidade porque quando você faz shared_examples_for "common metadata support", :db , a tag :db é usada para incluir automaticamente este grupo compartilhado em outras tags :db grupos.

Este é basicamente o ponto crucial de porque eu acho que :apply_to_host_groups é preferível: temos uma maneira de incluir grupos de exemplo compartilhados com base em metadados usando config.include_context (que espelha config.include para módulos. ..), mas se usarmos implicitamente metadados de grupos compartilhados para acionar a inclusão, isso impede que os usuários possam marcar seus exemplos compartilhados com metadados comuns no nível do grupo. E se usarmos metadados de grupos compartilhados dessa maneira, então IMO, a extensão natural disso para include_context / include_examples (quando a inclusão não está aninhada) é mesclar os metadados com o host metadados do grupo. (Mas YMMV; esse é apenas meu modelo mental de como os metadados RSpec funcionam).

No entanto, por que é include_context? Bem, só temos include_context em nosso objeto Configuration. Não há include_examples ou it_behaves_like lá.

Isso é intencional. IMO, seria surpreendente (e estranho) incluir automaticamente alguns exemplos compartilhados em muitos grupos de exemplo com base em contextos. Um usuário poderia olhar para o arquivo de especificações, ver 4 blocos it (sugerindo que a execução do arquivo executará 4 exemplos), executá-lo e ficar surpreso ao ver 20 exemplos executados (por exemplo, devido a 16 exemplos compartilhados incluídos) . IMO, os exemplos são uma coisa primária que deve ser vista e compreendida para entender como o conjunto de testes está estruturado e como funciona. O código de teste compartilhado (por exemplo, métodos auxiliares, let s ou um gancho around que gerencia uma transação de banco de dados ...) não tem a mesma necessidade de estar visível. Na verdade, eu tendo a usar grupos de exemplos compartilhados (e include_context ) especificamente para ocultar detalhes sem importância da adição de ruído a toneladas de especificações. Por exemplo, para uma preocupação transversal como captura de log ou transações de banco de dados, não quero que cada teste que tenha essas preocupações tenha o ruído do código visível para gerenciar essas coisas. Ser capaz de marcar o grupo com :db ou :capture_logs e deixar config.include_context aplicar o código compartilhado para mim, resulta em especificações com maior relação sinal-ruído. Não é o mesmo com include_examples / it_behaves_like , que não se destinam a ser usados ​​para os mesmos tipos de interesses transversais comuns que são fáceis de lidar automaticamente.

De qualquer forma, são meus dois centavos :). Como não estou mais envolvido na manutenção do RSpec, não me sinta obrigado a dar peso indevido à minha opinião.

Todos 5 comentários

Eu adoraria invocar @myronmarston como a força motriz por trás de :apply_to_host_groups .

Obrigado por escrever @pirj e me convocar para comentar :).

Eu ainda acho que a inclusão implícita de grupos de exemplo compartilhados por meio de metadados ainda tem problemas. # 1790 descreve os problemas que vi com ele quando escrevi essa edição. No geral, o comportamento de inclusão implícita parece inconsistente com o resto do RSpec, IMO; considere que todas as outras maneiras pelas quais os metadados RSpec são aproveitados, você os configura em um bloco RSpec.configure . Ter 1 recurso RSpec usando metadados de forma implícita (sem exigir que os usuários os configurem explicitamente em RSpec.configure ) é algo que espero confundir os usuários que usaram metadados RSpec, mas não estão cientes do comportamento de inclusão implícito . Ambos rspec-rails # 1579 e rspec-rails # 1241 são exemplos disso: os usuários marcaram seus grupos de exemplo compartilhados com metadados, esperando que eles agissem como metadados em atos de grupo normais (por exemplo, desencadeando a inclusão de config.include 'd módulos, estar disponível para filtragem, etc) e ficamos surpresos ao ver que não era assim que se comportava. Na verdade, eles ficaram tão surpresos que o relataram como um bug, embora estivesse funcionando conforme o planejado. Isso me sugere que foi um design ruim para começar: se ele realmente estivesse alinhado com o resto do RSpec, não levaria os usuários a relatá-lo como um bug.

Como uma caixa de areia para novos policiais rubocop-rspec, eu montei uma lista da maioria dos projetos Ruby estrelados que usam RSpec, real-world-rspec, cerca de 35 projetos no total. Desses 35 (também inclui repositórios RSpec!), 7 usam :apply_to_host_groups em seus auxiliares de especificação:

A semântica exata de marcar um grupo de exemplo compartilhado com metadados é algo que eu esperaria que 99% dos usuários RSpec nunca pensassem e a situação confusa que o comportamento mais recente resolve é relativamente rara ... então não estou surpreso com isso em absoluto. TBH, eu suspeito que :apply_to_host_groups é provavelmente usado principalmente por projetos que geraram (ou regeneraram) seus spec_helper.rb depois que alteramos o código de configuração spec_helper.rb gerado em 3589ab577d09db88ef5d5f0d60e8c35bfc55691f.

Para usuários que desejam acionar a inclusão de grupo de exemplo compartilhado com base em metadados, a API config.include_context é fornecida, a qual, IMO, é mais simples, mais consistente e mais explícita do que fazer isso automaticamente para usuários porque eles marcaram um grupo compartilhado e um grupo normal com os mesmos metadados. AFAIK, não há nada que :trigger_inclusion forneça que não possa ser realizado por usuários usando config.include_context explicitamente. (Se houver algo que não possa ser feito dessa forma, por favor me avise!). OTOH, se um usuário deseja marcar seu grupo de exemplo compartilhado com alguns metadados e aplicá-lo em todos os lugares em que o grupo compartilhado está incluído ... Não acho que haja um mecanismo alternativo para isso. (Na verdade, talvez config.define_derived_metadata pudesse ser usado para simular isso, mas IMO, isso parece uma "solução alternativa" para um recurso ausente).

Pessoalmente, nunca vi: apply_to_host_groups sendo usado da forma para que foi projetado.

Veja como eu usei:

  • Eu marquei temporariamente um grupo de exemplo compartilhado com :focus , :pending ou :skipped para que eu possa me concentrar em (ou filtrar) especificações que dependem de um exemplo compartilhado específico grupo. Um grupo de exemplo compartilhado geralmente significa que há alguma preocupação transversal que se aplica a todos os grupos, e ser capaz de executar apenas essas especificações (ou excluir essas especificações) pode ser bastante útil. É importante notar que este uso do recurso geralmente não aparece no código confirmado para nenhum projeto, porque :focus , :skip e :pending são geralmente mudanças temporárias que não são comprometidos (particularmente :focus ).
  • Pode ser muito útil como uma forma de expressar dependências em outro código de controle de teste. Por exemplo, considere um projeto que envolve condicionalmente exemplos em uma transação de banco de dados quando eles são marcados com :db . (Isso poderia ser realizado definindo um DB support grupo de exemplo compartilhado com um gancho around apropriado e, em seguida, usar config.include_context "DB support", :db ). Agora imagine que os desenvolvedores desejam definir um novo grupo de exemplo compartilhado denominado logged in as admin que define um gancho before que cria um usuário administrador no banco de dados e efetua login como esse usuário usando teste de rack ou qualquer outro . Como esse novo grupo de exemplo compartilhado interage com o banco de dados em um gancho before , ele depende do grupo de exemplo DB support compartilhado. IMO, a maneira mais limpa de expressar essa dependência é marcar o grupo de exemplo compartilhado logged in as admin com :db para que o suporte do DB seja automaticamente aplicado quando logged in as an admin for incluído em um grupo de host . Você poderia fazer include_context 'DB support' no grupo de exemplo logged in as an admin para aplicar as transações do banco de dados automaticamente, mas se a maneira "normal" de gerenciar isso em todos os outros lugares é por meio de uma tag :db , é estranho não ser capaz de fazer isso aqui.

Nós também o usamos. expectativas-rspec:

LOL :). Não estou muito surpreso com isso; historicamente, não atualizamos o RSpec para todas as últimas configurações "padrão", etc., principalmente se os problemas que as novas configurações abordam não acontecem nos testes do RSpec. É fácil alterar a inclusão acionada por metadados rspec-transactions para usar config.include_context , certo?

Conclusão preliminar: aqueles projetos populares de Ruby que usam RSpec que configuraram shared_context_metadata_behavior para :apply_to_host_groups fizeram isso às cegas e nunca o usaram.

Eu honestamente ficaria surpreso se houvesse mais do que um punhado de usuários que pensaram sobre como exatamente eles desejam que os metadados de grupos de exemplo compartilhados se comportem, para então ir e definir a opção de configuração para o comportamento que desejam. Imagino que quase todos os usos de :apply_to_host_groups na configuração se devam à geração dele por meio de rspec --init .

Não acho que isso seja necessariamente um argumento contra :apply_to_host_groups ; a meu ver, metade do benefício de :apply_to_host_groups é que ele desativa o comportamento de auto-inclusão legado implícito e contra-intuitivo. A outra metade do benefício é que permite um novo comportamento que está mais de acordo (IMO, YMMV, é claro) com o design geral do RSpec. Um projeto que configura :apply_to_host_groups e nunca marca grupos compartilhados com metadados ainda pode se beneficiar com a opção; ainda há um comportamento potencialmente confuso que eles estão evitando. Por exemplo, se um desenvolvedor marcar temporariamente um grupo compartilhado com :focus , com a intenção de focalizá-lo ... é benéfico que RSpec não inclua o grupo compartilhado em outros grupos que estão sendo :focus ed em.

Não faz muito sentido aplicar metadados definidos para um grupo aninhado criado implicitamente.

Acho que :apply_to_host_groups ainda fornece um comportamento benéfico para este caso, mesmo que você esteja certo de que aplicar os metadados ao grupo de hosts não se aplica per se aqui. Considere o caso de um grupo de exemplos compartilhados em que todos dependem do banco de dados (em um projeto que marca tais exemplos com :db para envolvê-los em transações de banco de dados). É útil para os usuários poderem marcar seu grupo de exemplo compartilhado com :db para expressar a dependência no banco de dados (e para envolver automaticamente todos os exemplos contidos em transações de banco de dados). Embora it_behaves_like não mescle os metadados :db nos metadados de um grupo de host, ainda é bom ser capaz de marcar o grupo compartilhado com :db e aplicá-lo ao exemplos nele. O comportamento legado de :trigger_inclusion exclui essa possibilidade porque quando você faz shared_examples_for "common metadata support", :db , a tag :db é usada para incluir automaticamente este grupo compartilhado em outras tags :db grupos.

Este é basicamente o ponto crucial de porque eu acho que :apply_to_host_groups é preferível: temos uma maneira de incluir grupos de exemplo compartilhados com base em metadados usando config.include_context (que espelha config.include para módulos. ..), mas se usarmos implicitamente metadados de grupos compartilhados para acionar a inclusão, isso impede que os usuários possam marcar seus exemplos compartilhados com metadados comuns no nível do grupo. E se usarmos metadados de grupos compartilhados dessa maneira, então IMO, a extensão natural disso para include_context / include_examples (quando a inclusão não está aninhada) é mesclar os metadados com o host metadados do grupo. (Mas YMMV; esse é apenas meu modelo mental de como os metadados RSpec funcionam).

No entanto, por que é include_context? Bem, só temos include_context em nosso objeto Configuration. Não há include_examples ou it_behaves_like lá.

Isso é intencional. IMO, seria surpreendente (e estranho) incluir automaticamente alguns exemplos compartilhados em muitos grupos de exemplo com base em contextos. Um usuário poderia olhar para o arquivo de especificações, ver 4 blocos it (sugerindo que a execução do arquivo executará 4 exemplos), executá-lo e ficar surpreso ao ver 20 exemplos executados (por exemplo, devido a 16 exemplos compartilhados incluídos) . IMO, os exemplos são uma coisa primária que deve ser vista e compreendida para entender como o conjunto de testes está estruturado e como funciona. O código de teste compartilhado (por exemplo, métodos auxiliares, let s ou um gancho around que gerencia uma transação de banco de dados ...) não tem a mesma necessidade de estar visível. Na verdade, eu tendo a usar grupos de exemplos compartilhados (e include_context ) especificamente para ocultar detalhes sem importância da adição de ruído a toneladas de especificações. Por exemplo, para uma preocupação transversal como captura de log ou transações de banco de dados, não quero que cada teste que tenha essas preocupações tenha o ruído do código visível para gerenciar essas coisas. Ser capaz de marcar o grupo com :db ou :capture_logs e deixar config.include_context aplicar o código compartilhado para mim, resulta em especificações com maior relação sinal-ruído. Não é o mesmo com include_examples / it_behaves_like , que não se destinam a ser usados ​​para os mesmos tipos de interesses transversais comuns que são fáceis de lidar automaticamente.

De qualquer forma, são meus dois centavos :). Como não estou mais envolvido na manutenção do RSpec, não me sinta obrigado a dar peso indevido à minha opinião.

Muito obrigado por uma resposta tão detalhada e esclarecedora, @myronmarston!

é benéfico que RSpec não inclua, em vez disso, o grupo compartilhado em outros grupos que estão sendo :focused

Isso realmente seria uma apoteose de confusão.

Minhas dúvidas foram dissipadas, continuarei tornando :apply_to_host_groups a única opção padrão.
Vou manter o tíquete aberto, vou vincular os PRs a ele.

Minhas dúvidas foram dissipadas, continuarei tornando :apply_to_host_groups a única opção padrão.

Pode valer a pena remover totalmente a opção de configuração, visto que o plano é não oferecer suporte a nenhum outro comportamento. Ou talvez para compatibilidade com versões anteriores ele pudesse ser definido, mas ignorado (embora isso possa ser confuso se o usuário configurou o comportamento legado ...). Não sei se vocês estão planejando lançar um RSpec 3.99 (como fizemos com 2.99) para fornecer avisos de atualização, mas se vocês seguirem esse caminho, 3.99 poderia reter a opção e avisar apropriadamente e 4.0 não poderia ter a opção em tudo talvez.

Obrigado pela resposta detalhada @myronmarston : joy: Estou encerrando isso porque concordo e, a julgar pelos RPs de Phil, ele também foi convencido.

Esta página foi útil?
0 / 5 - 0 avaliações