Acabei de ler um trecho do livro "Growing Object-Oriented Software", que explica algumas razões pelas quais zombar de classes concretas não é recomendado.
Aqui estão alguns exemplos de código de um teste de unidade para a classe MusicCentre:
public class MusicCentreTest {
@Test public void startsCdPlayerAtTimeRequested() {
final MutableTime scheduledTime = new MutableTime();
CdPlayer player = new CdPlayer() {
@Override
public void scheduleToStartAt(Time startTime) {
scheduledTime.set(startTime);
}
}
MusicCentre centre = new MusicCentre(player);
centre.startMediaAt(LATER);
assertEquals(LATER, scheduledTime.get());
}
}
E sua primeira explicação:
O problema dessa abordagem é que ela deixa implícita a relação entre os objetos. Espero que tenhamos deixado claro até agora que a intenção do Desenvolvimento Orientado a Testes com o Mock Objects é descobrir relacionamentos entre objetos. Se eu subclasse, não há nada no código do domínio para tornar visível esse relacionamento, apenas métodos em um objeto. Isso torna mais difícil ver se o serviço que suporta esse relacionamento pode ser relevante em outro lugar e terei que fazer a análise novamente na próxima vez que trabalhar com a classe.
Não consigo descobrir exatamente o que ele quer dizer quando diz:
Isso torna mais difícil ver se o serviço que suporta esse relacionamento pode ser relevante em outro lugar e terei que fazer a análise novamente na próxima vez que trabalhar com a classe.
Entendo que o serviço corresponde ao MusicCentre
método chamado startMediaAt
.
O que ele quer dizer com "em outro lugar"?
O trecho completo está aqui: http://www.mockobjects.com/2007/04/test-smell-mocking-concrete-classes.html
fonte
Respostas:
O autor desse post está promovendo o uso de interfaces sobre o uso de classes membros.
It turns out that my MusicCentre object only uses the starting and stopping methods on the CdPlayer, the rest are used by some other part of the system. I'm over-specifying my MediaCentre by requiring it to talk to a CdPlayer, what it actually needs is a ScheduledDevice.
O relacionamento que ele está preocupado em descobrir mais tarde é o fato de que a classe MediaCentre não precisa de todo o objeto CdPlayer. Ele afirma que, ao usar uma interface (presumivelmente limitada a apenas iniciar | parar), é mais fácil entender o que realmente é a interação.
"outro lugar" significa simplesmente que outros objetos podem ter relacionamentos igualmente limitados e o objeto de membro completo não é necessário - um subconjunto da funcionalidade agrupada por uma Interface deve ser suficiente.
A reivindicação começa a fazer mais sentido quando você explode toda a funcionalidade potencial:
Agora, sua afirmação de "Eu só preciso começar e parar" faz mais sentido. O uso do objeto membro concreto em vez de uma Interface torna menos claro para futuros desenvolvedores o que é realmente necessário. A execução de testes de unidade do MediaCentre em todas as outras funções do CdPlayer é um desperdício de esforços de teste, pois eles pertencem ao estado "não me importo". Se a
Record
função não estava funcionando nesse caso, nós realmente não nos importamos, pois ela não é necessária. Mas um futuro mantenedor não necessariamente saberia isso com base no código, como está escrito.Por fim, a premissa do autor é usar apenas o necessário e esclarecer aos futuros mantenedores o que era necessário antes. O objetivo é minimizar o retrabalho / reanalisar o módulo de código durante a manutenção subseqüente.
fonte
Depois de pensar muito sobre isso, recebo uma possível interpretação dessa citação:
O "serviço" mencionado corresponde ao "fato de agendamento". Isso pode ser expresso por uma interface bem nomeada e "focada em uma função" denominada "ScheduledDevice" ou expressa implicitamente por uma implementação concreta de método que não depende de nenhuma interface.
Na amostra acima, o agendamento é expresso por todo o objeto com todos os recursos nomeado
CDPlayer
. Assim, ainda leva a um relacionamento implícito entreMusicCentre
e "fato de agendamento".Portanto, se começarmos a injetar classes concretas e a zombar delas em objetos de alto nível; quando queremos testar esses objetos, temos que analisar cada objeto "concreto" injetado para ver se ele apresenta um relacionamento específico que TEMOS QUE MOCK porque são ocultos (implícitos). Pelo contrário, a codificação SEMPRE pela interface permite que o desenvolvedor descubra diretamente que tipo de relacionamento está prestes a ser atendido pelo objeto de alto nível e, portanto, detecte recursos que precisam ser zombados para isolar o teste de unidade.
fonte
O serviço que eu quis dizer aqui foi CDPlayer.scheduleToStartAt (). É isso que o MediaCentre chama - o colaborador que ele precisa para funcionar. O MediaCentre é o objeto em teste.
A idéia é que, se eu explicitar exatamente o que o MediaCentre depende, não uma classe de implementação, eu posso dar um nome a essa função de dependência e falar sobre isso. Tudo o que o MediaCentre precisa saber é que ele conversa com o ScheduledDevices. À medida que o resto do sistema muda, não precisarei alterar o MediaCentre, a menos que seus recursos sejam alterados.
Isso ajuda?
fonte