Continuando o assunto como criar meu próximo teste, irei abordar os três tópicos seguintes do segundo post da série. Recomendo que os leiam em ordem:
Para continuar, os tópicos 7 e 8 contêm muitas similaridades:
- Tenho um mock que retorna um outro objeto que precisa também de ser configurado com mock. Parece um mock-de-mock.
- Vou precisar instanciar vários objetos nesta nova feature.
Para ambos os casos, o problema parece emergir do simples fato de que seu alvo de teste (a classe e behavior testados) está com uma implementação acoplada a outras partes do sistema. Mas, o que isso quer dizer realmente?
Confira se o método testado está:
- Fazendo mais de uma coisa. Aqui, você pode ser menos flexível para melhor entendimento, por exemplo:
Você criar o seguinte caso de teste: "O usuário conseguirá logar, dado Login e Senha válidos". Daí, você tenta seguir com o seguinte código de produção:
class User
def autentica(login)
if login.username.empty? && login.password.empty?
raise InvalidArgumentError
end
# continua com o login
end
end
## nosso login ali de cima
class UserLogin
def username
end
def password
end
end
Pode parecer bobagem aqui, mas o método #autentica
está fazendo mais de uma coisa: ele está validando input e efetuando autenticação. No seu teste, você precisaria passar um stub de UserLogin
e precisaria configurar #username
e #password
somente para conseguir passar da parte de validação.
Este exemplo é minimalista justamente para evidenciar que casos mais complexos do mesmo problema fará com que você tenha que fazer mock de mock ou ficar instanciando/configurando um monte de objeto somente para fazer uma simples regra de negócio funcionar como deveria. Qual a solução? Vejamos:
Extrair a verificação de input do #autentica
seria uma ótima. Quem sabe delegar a responsabilidade da ação para o objeto que o mereça, resolva o problema, não é mesmo? Lembra do Tell don't ask ? Veja-o em prática:
class User
def autentica(login)
if login.valid?
end
end
end
class UserLogin
def username
end
def password
end
def valid?
!username.empty? && !password.empty?
end
end
Tudo que precisamos fazer agora é configurar no seu stub que o método #valid?
deve ser true. Com uma linha de configuração no teste você consegue focar no que realmente importa: fazer a regra de negócio funcionar.
Estou criando o código de produção e meu método alvo do teste está imenso ou/e cheio de chamadas à métodos privados da própria classe
"Imenso" é subjetivo. Não deve-se encanar com a quantidade de linhas de um teste, mas sim com sua anatomia. Se ele estiver sem repetição e na ordem: input de dados, executa o método em teste, analisa resultados - seu teste estará bem.
O problema com método privado é antigo. Há aqueles que odeiam método privados e do outro lado, aqueles que usam pra tudo. Ambos estão pegando pesado ao meu ver. O método não público precisa ser bem pensado. Como você viu acima, nem sempre a responsabilidade para uma dada atividade na classe pertence à classe que você imaginou. Escalando isso para um sistema, você terá sim muitos métodos privados mal planejados. Como disse, Dr. Erskine em Captain America: The First Avenger:
O soro potencializa o que há dentro da pessoa. O bom torna-se ótimo e o mal torna-se horrível.
Aplicando uma regex s/soro/teste/
e s/pessoa/classe/
, teremos uma definição hipster sobre o que é Test-Driven Development.
O TDD nestes casos, irá gritar para você de que há um problema de design ocorrendo em suas classes - e com o auxílio do teste, você consegue sair desta situação. Agora, resolver o problema é com você e seu toolbelt. Dicas:
- Tell Don't Ask. Como você viu acima.
- Extrair método privado(s) para Classe. Veja se faz sentido.
- Injeção de Dependência (via setter ou construtor).
Por fim, caso não tenha lido o post anterior da série, recomendo que o leia, pois há alguns detalhes adicionais aos tópicos discutidos aqui.
To be continued.
0sem comentários ainda