Qual é a melhor maneira de testar métodos privados e protegidos no Ruby, usando a Test::Unit
estrutura padrão do Ruby ?
Tenho certeza de que alguém se manifestará e dogmaticamente afirmará que "você deve apenas testar métodos públicos por unidade; se precisar de testes unitários, não deve ser um método protegido ou privado", mas não estou realmente interessado em debater isso. Eu tenho vários métodos que são protegidos ou privados por boas e válidas razões, esses métodos privados / protegidos são moderadamente complexos e os métodos públicos da classe dependem desses métodos protegidos / privados funcionando corretamente, portanto, preciso de uma maneira de testar os métodos protegidos / privados.
Mais uma coisa ... Eu geralmente coloco todos os métodos para uma determinada classe em um arquivo e os testes de unidade para essa classe em outro arquivo. Idealmente, eu gostaria que toda a mágica implementasse essa funcionalidade de "teste de unidade de métodos protegidos e privados" no arquivo de teste de unidade, não no arquivo de origem principal, para manter o arquivo de origem principal o mais simples e direto possível.
fonte
Respostas:
Você pode ignorar o encapsulamento com o método send:
Este é um 'recurso' do Ruby. :)
Houve um debate interno durante o desenvolvimento do Ruby 1.9 que considerou
send
respeitar a privacidade esend!
ignorá-la, mas no final nada mudou no Ruby 1.9. Ignore os comentários abaixo discutindosend!
e quebrando coisas.fonte
send!
coisa, foi revogada há muito tempo,send/__send__
pode chamar métodos de toda a visibilidade - redmine.ruby-lang.org/repositories/revision/1?rev=13824public_send
(documentação aqui ) se você deseja respeitar a privacidade. Eu acho que isso é novo no Ruby 1.9.Aqui está uma maneira fácil se você usar o RSpec:
fonte
Apenas reabra a classe no seu arquivo de teste e redefina o método ou métodos como público. Você não precisa redefinir a coragem do próprio método, basta passar o símbolo para a
public
chamada.Se sua classe original é definida assim:
No arquivo de teste, faça algo assim:
Você pode passar vários símbolos para
public
se quiser expor mais métodos particulares.fonte
instance_eval()
pode ajudar:Você pode usá-lo para acessar métodos privados e variáveis de instância diretamente.
Você também pode considerar o uso
send()
, o que também lhe dará acesso a métodos privados e protegidos (como sugeriu James Baker)Como alternativa, você pode modificar a metaclasse do seu objeto de teste para tornar públicos os métodos privados / protegidos apenas para esse objeto.
Isso permitirá que você chame esses métodos sem afetar outros objetos dessa classe. Você pode reabrir a classe em seu diretório de teste e torná-la pública para todas as instâncias em seu código de teste, mas isso pode afetar seu teste da interface pública.
fonte
Uma maneira de fazer isso no passado é:
fonte
Você também pode refatorá-los em um novo objeto no qual esses métodos são públicos e delegá-los em particular na classe original. Isso permitirá que você teste os métodos sem metaruby mágico em suas especificações, mantendo-os privados.
Quais são esses motivos válidos? Outras linguagens OOP podem sair sem métodos privados (o smalltalk vem à mente - onde métodos privados existem apenas como uma convenção).
fonte
Semelhante à resposta do @ WillSargent, eis o que eu usei em um
describe
bloco para o caso especial de testar alguns validadores protegidos sem precisar passar pelo processo pesado de criar / atualizar com o FactoryGirl (e você pode usar oprivate_instance_methods
mesmo):fonte
Para tornar público todo o método protegido e privado da classe descrita, você pode adicionar o seguinte ao seu spec_helper.rb e não precisar tocar em nenhum dos seus arquivos de especificação.
fonte
Você pode "reabrir" a classe e fornecer um novo método que delega para o privado:
fonte
Eu provavelmente me inclinaria a usar instance_eval (). Antes que eu soubesse sobre instance_eval (), no entanto, criaria uma classe derivada no meu arquivo de teste de unidade. Eu definiria o (s) método (s) privado (s) para público.
No exemplo abaixo, o método build_year_range é privado na classe PublicationSearch :: ISIQuery. A obtenção de uma nova classe apenas para fins de teste permite definir um método para ser público e, portanto, diretamente testável. Da mesma forma, a classe derivada expõe uma variável de instância chamada 'resultado' que não foi exposta anteriormente.
No meu teste de unidade, tenho um caso de teste que instancia a classe MockISIQuery e testa diretamente o método build_year_range ().
fonte
Sei que estou atrasado para a festa, mas não teste métodos privados ... Não consigo pensar em um motivo para fazer isso. Um método acessível ao público está usando esse método privado em algum lugar, teste o método público e a variedade de cenários que causariam o uso desse método privado. Algo entra, sai algo. Testar métodos privados é um grande não-não, e torna muito mais difícil refatorar seu código posteriormente. Eles são particulares por um motivo.
fonte
Na estrutura Test :: Unit pode escrever,
Aqui "method_name" é um método privado.
e ao chamar esse método pode escrever,
fonte
Aqui está uma adição geral à classe que eu uso. É um pouco mais complicado do que apenas tornar público o método que você está testando, mas na maioria dos casos isso não importa e é muito mais legível.
O uso do envio para acessar métodos protegidos / privados é quebrado em 1.9, portanto, não é uma solução recomendada.
fonte
Para corrigir a resposta principal acima: no Ruby 1.9.1, é o Object # send que envia todas as mensagens e o Object # public_send que respeita a privacidade.
fonte
Em vez de obj.send, você pode usar um método singleton. São mais três linhas de código na sua classe de teste e não requer alterações no código real a ser testado.
Nos casos de teste, você usa
my_private_method_publicly
sempre que deseja testarmy_private_method
.http://mathandprogramming.blogspot.com/2010/01/ruby-testing-private-methods.html
obj.send
para métodos privados foi substituído porsend!
1.9, mas mais tardesend!
foi removido novamente. Então,obj.send
funciona perfeitamente bem.fonte
Para fazer isso:
Você pode implementar isso no seu arquivo test_helper:
fonte
Disrespect
paraPrivacyViolator
(: P) e fiz odisrespect_privacy
método editar temporariamente a ligação do bloco, de modo a lembrar o objeto de destino para o objeto do wrapper, mas apenas pela duração do bloco. Dessa forma, você não precisa usar um parâmetro de bloco, basta continuar referenciando o objeto com o mesmo nome.