Estou tentando usar o Steak e o Capybara para executar alguns cenários usando um back-end compatível com JavaScript (Culerity) e outros usando o back-end padrão rack_test.
No Cucumber isso foi feito usando tags, mas com Steak e RSpec 1.3 me disseram que você pode fazer algo assim:
scenario 'foo', :js => true do
# ... this one runs under culerity
end
scenario 'bar' do
# ... this one runs under rack_test
end
Isso é ativado por esta configuração:
Spec::Runner.configure do |config|
config.before :each do
Capybara.current_driver = :culerity if options[:js]
end
config.after :each do
Capybara.use_default_driver if options[:js]
end
end
Sob RSpec 2 este truque não funcionará; parece que "opções" não está disponível no escopo desses blocos de configuração antes/depois. Consegui desenterrar as opções por meio de uma inspeção "instance_variable_get" muito feia:
RSpec.configure do |config|
config.before :each do
if self.running_example.instance_variable_get(:@options)[:js]
Capybara.current_driver = :culerity
end
end
config.after :each do
if self.running_example.instance_variable_get(:@options)[:js]
Capybara.use_default_driver
end
end
end
Portanto, este ticket é para obter acesso aos metadados arbitrários que podem ser passados para os blocos "describe" e "it" por meio do hash de opções.
Observe que no caso de uso que estou falando acima, usar o material "filter_run" não me dará o que eu quero (isso é para selecionar um _subconjunto_ dos exemplos a serem executados, mas eu quero executar _todos_ os exemplos, mas troque os motoristas Capivara na hora).
O que você acha de fornecer acesso às opções por meio de um acessador?
Percebi que posso acessar as opções passadas para a subclasse RSpec::Core::Example; ou seja:
it "should foo", :stuff => true do
...
end
Mas não consigo entender as coisas passadas para a subclasse RSpec::Core::ExampleGroup; ou seja:
describe "foo", :thing => true do
...
end
Na prática, não me importo muito com o acesso a esses metadados, porque quero alternar os drivers por cenário (ou seja, no nível "it/scenario"). Mas se você concorda em disponibilizar os metadados nesse nível, acho que faz sentido disponibilizar os metadados no nível "descrever/recurso" também.
O que você acha?
Se eu conseguir um "sinal verde" sobre a ideia, ficarei feliz em colocar um remendo.
Saúde,
Wincent
Luz verde! Eu gostaria que ele funcionasse através da variável running_example, não apenas opções, para manter o namespace local organizado. Qualquer um destes funcionaria para mim:
RSpec.configure do |config| config.before :cada um faz Capybara.current_driver = :culerity if running_example[:js] fim fim RSpec.configure do |config| config.before :cada um faz Capybara.current_driver = :culerity if running_example.options[:js] fim fim
Separado, mas relacionado, o que você acha de renomear running_example para example?
Sim, faz sentido evitar desordenar o namespace local. Olhando as opções:
1: if example[:foo]
2: if example.options[:foo]
3: if running_example[:foo]
4: if running_example.options[:foo]
A opção "2" é mais agradável para mim porque está próxima do inglês "se a opção de exemplo foo estiver definida". Eu vou com isso por enquanto.
Vou começar a jogar e ver se consigo montar um patch antes do fim do dia.
Excelente. Obrigado!
Ok, tenha algo agora. Visto que você não pode anexar patches a tickets do Github, enviei alguns commits para forks aqui:
http://github.com/wincent/rspec-core/commits/core-ticket-42
E aqui:
http://github.com/wincent/rspec-rails/commits/core-ticket-42
Especificamente, estamos falando sobre esses commits:
http://github.com/wincent/rspec-core/commit/99f0428d53b98cee8a60250d1347224f3dbc5697
http://github.com/wincent/rspec-core/commit/2ed401684b68e404d75be051b932d592b260dc00
http://github.com/wincent/rspec-rails/commit/66c5df14c5f7e4d0f8e42f98009837077c680437
http://github.com/wincent/rspec-rails/commit/0c5801a412bfd3abaac5ed7222e4e1d34e4e78e7
As mudanças reais na base de código foram triviais (20 minutos de codificação), mas tive alguns problemas e passei várias horas tentando manter as especificações e recursos passando.
Um dos problemas é que é bastante complicado se você fizer uma alteração que exija modificações simultâneas em diferentes repositórios. Por exemplo, a renomeação de "running_example" para "example" no rspec-core requer alterações no rspec-rails porque o último usa "running_example".
Então, você faz a alteração em ambos os repositórios de uma vez, mas obtém falhas no rspec-rails porque está usando a versão do sistema do rspec-core em vez da versão local.
As falhas de especificação podem ser corrigidas ajustando o Gemfile no aplicativo Rails de exemplo gerado para que ele use a versão local do rspec-core em vez de alguma outra versão que ele possa extrair de outro lugar no sistema. (É isso que o commit 0c5801a412bfd faz.)
Dessa forma, você pode fazer a alteração no rspec-core e no rspec-rails ao mesmo tempo, e todas as especificações continuam passando.
As falhas de recursos são outra questão inteiramente. O Cucumber insiste em usar alguma outra versão do rspec-core em vez do local. Passei horas tentando descobrir o porquê, mas tenho medo de desistir.
Isso, por sua vez, significa que não estou tão confiante sobre o commit 66c5df14c5f7e. Em particular, não tenho certeza sobre a mudança para lib/rspec/rails/adapters.rb. Mudei o nome da variável de instância de @running_example para @example no método "method_name", mas nem sei o que esse método faz, e as especificações/recursos passam independentemente de como a variável de instância é chamada.
Então, dê uma olhada nos commits e talvez você possa ver algo que eu não consigo que explique como fazer com que o Cucumber use a versão correta do rspec-core.
De qualquer forma, com esses commits podemos fazer coisas como:
RSpec.configure do |config|
config.before :each do
Capybara.current_driver = :celerity if example.options[:js]
end
end
Há uma possível modificação adicional com a qual eu estava brincando, mas é um pouco mais invasiva, então ainda não cometi nada.
O problema é que em um bloco "before :all" obviamente não há "exemplo em execução" nesse ponto, portanto, se o usuário tentar fazer algo assim, ele será bombardeado porque "exemplo" é nil neste ponto:
config.before :all do
do_something if example.options[:foo]
end
Nesse ponto, estamos executando dentro do escopo de uma classe RSpec::Core::ExampleGroup, portanto, se pedirmos "example", clicaremos no acessador "example" e obteremos nil de volta.
Observação:
config.before :all do
# people could do it this way, and it works...
do_something if self.class.metadata[:foo]
# but I expect lots will try "example.options" and fail anyway...
end
A ideia com a qual eu estava brincando estava nesse contexto, retornando um objeto diferente de zero (provavelmente uma subclasse Metadata) que responde a "opções" e retornaria os metadados fornecidos pelo usuário passados no nível do bloco de descrição. ou seja:
describe Thing, :js => true do
before :all do
example.options[:js] # => true
end
before :each do
example.options[:foo] # => defined below
end
it "should behave", :foo => true do
end
end
Assim, você pode definir seus metadados em qualquer nível e acessá-los em qualquer nível.
Não tenho certeza se isso está abrindo uma lata de worms, porque então teríamos que pensar em como esses metadados devem "gotejar" quando o aninhamento está envolvido, por exemplo:
describe Outer, :foo => 1 do
describe Inner, :bar => 2 do
before :all do
# here should have access to :foo and :bar
end
before :each do
# here should have access to :foo and :bar too
# plus anything set at the "its" level
end
it "should behave", :foo => :override, :baz => 3 do
# here :foo => override, :bar => 2, :baz => 3
end
end
end
Há muito o que revisar nesse comentário e estou pressionado pelo tempo neste minuto, mas re: a mudança de nome - queremos descontinuar running_example, pois há outros consumidores desse método à solta. Então, no rspec-core, algo como:
exemplo de definição # código movido de running_example fim def running_example RSpec.deprecate("exemplo_running", "exemplo") exemplo fim
Então você não precisa se preocupar com as outras bibliotecas ainda e elas podem se mover de forma independente.
Ah, sim, boa ideia. Eu tinha visto o "RSpec.deprecate" em alguns lugares na base de código, mas não pensei em usá-lo.
Adicionado o aviso de descontinuação conforme sugerido:
http://github.com/wincent/rspec-core/commit/272c3762fa0af775f0c7d23affa3196bb7ad81c
Combinado - obrigado!!!!!
Apenas para registro, já que esse problema é o que encontrei usando a consulta do mecanismo de pesquisa "rspec metadata antes" e como não consegui encontrá-lo documentado em nenhum lugar.
Esse comportamento pode ser obtido em uma versão rspec atualizada da seguinte maneira:
before do |example|
example.metadata[:foo] # => true
end
it "should behave", :foo => true do
end
Comentários muito úteis
Apenas para registro, já que esse problema é o que encontrei usando a consulta do mecanismo de pesquisa "rspec metadata antes" e como não consegui encontrá-lo documentado em nenhum lugar.
Esse comportamento pode ser obtido em uma versão rspec atualizada da seguinte maneira: