As zombarias violam o princípio Aberto / Fechado?

13

Há algum tempo, li em uma resposta do Stack Overflow que não consigo encontrar, uma frase que explicava que você deveria testar APIs públicas e o autor disse que deveria testar interfaces. O autor também explicou que, se uma implementação de método fosse alterada, não seria necessário modificar o caso de teste, pois isso quebraria o contrato que garantiria que o sistema em teste funcionasse. Em outras palavras, um teste deve falhar se o método não funcionar, mas não porque a implementação foi alterada.

Isso chamou minha atenção quando falamos de zombaria. Como a zombaria depende muito das chamadas de expectativa das dependências do sistema em teste, as zombarias são fortemente acopladas à implementação e não à interface.

Ao pesquisar mock versus stub , vários artigos concordam que stubs devem ser usados ​​em vez de zombarias, pois não dependem das expectativas das dependências, o que significa que o teste não precisa ter conhecimento do sistema subjacente na implementação do teste.

Minhas perguntas seriam:

  1. As zombarias violam o princípio de aberto / fechado?
  2. Há algo faltando no argumento a favor dos stubs no último parágrafo, que torna os stubs não tão bons contra os mocks?
  3. Em caso afirmativo, quando seria um bom caso de uso para zombar e quando seria um bom caso de uso para usar stubs?
Christopher Francisco
fonte
8
Since mocking relays heavily on expectation calls from system under test's dependencies...Eu acho que é aqui que você está dando errado. Um mock é uma representação artificial de um sistema externo. Ele não representa o sistema externo de forma alguma, exceto na medida em que simula o sistema externo de forma que permita a execução de testes contra códigos com dependências no referido sistema externo. Você ainda precisará de testes de integração para provar que seu código funciona com o sistema real, sem desbloqueio.
Robert Harvey
8
Em outras palavras, o mock é uma implementação substituta. É por isso que programamos para uma interface em primeiro lugar, para que pudéssemos usar zombarias como substituto da implementação real. Em outras palavras, as zombarias são dissociadas e não acopladas à implementação real.
Robert Harvey
3
"Em outras palavras, um teste deve falhar se o método não funcionar, mas não porque a implementação foi alterada", isso nem sempre é verdade. Existem muitas circunstâncias em que você deve alterar sua implementação e seus testes.
Whatsisname

Respostas:

4
  1. Não vejo por que as zombarias violariam o princípio de abrir / fechar. Se você puder nos explicar por que acha que eles podem, poderemos aliviar suas preocupações.

  2. A única desvantagem dos stubs em que consigo pensar é que eles geralmente exigem mais trabalho para escrever do que zombam, já que cada um deles é na verdade uma implementação alternativa de uma interface dependente, por isso geralmente precisa fornecer uma completa (ou convincentemente completa) implementação da interface dependente. Para dar um exemplo extremo, se o seu subsistema em teste chamar um RDBMS, uma simulação do RDBMS responderia simplesmente a consultas específicas conhecidas por serem emitidas pelo subsistema em teste, produzindo conjuntos predeterminados de dados de teste. Por outro lado, uma implementação alternativa seria um RDBMS completo na memória, possivelmente com a carga extra de ter que emular as peculiaridades do RDBMS cliente-servidor real que você está usando na produção. Felizmente, temos coisas como HSQLDB, então podemos realmente fazer isso, mas ainda assim,

  3. Os bons casos de uso para simulação são quando a interface dependente é muito complicada para escrever uma implementação alternativa para ela, ou se você tiver certeza de que apenas escreverá a simulação uma vez e nunca mais a tocará. Nesses casos, vá em frente e use uma simulação rápida e suja. Consequentemente, bons casos de uso para stubs (implementações alternativas) são praticamente tudo o resto. Especialmente se você prevê se envolver em um relacionamento de longo prazo com o subsistema em teste, definitivamente adote uma implementação alternativa que seja agradável e limpa e exigirá manutenção apenas no caso de a interface mudar, em vez de exigir manutenção sempre que a interface alterações e sempre que a implementação do subsistema em teste for alterada.

PS A pessoa a quem você está se referindo poderia ter sido eu, em uma das minhas outras respostas relacionadas a testes aqui em programmers.stackexchange.com, por exemplo, esta .

Mike Nakis
fonte
an alternative implementation would be a full-blown in-memory RDBMS- Você não precisa necessariamente ir tão longe com um esboço.
Robert Harvey
@RobertHarvey bem, com HSQLDB e H2, não é tão difícil ir tão longe. Provavelmente é mais difícil fazer algo pela metade para não ir tão longe. Mas se você decidir fazer isso por conta própria, terá que começar escrevendo um analisador de SQL. Claro, você pode cortar alguns cantos, mas há muito trabalho . De qualquer forma, como eu disse acima, este é apenas um exemplo extremo.
Mike Nakis
9
  1. O princípio Aberto / Fechado é principalmente sobre ser capaz de alterar o comportamento de uma classe sem modificá-lo. Portanto, a injeção de uma dependência de componente simulada dentro de uma classe em teste não a viola.

  2. O problema com as duplas de teste (mock / stub) é que você basicamente faz suposições arbitrárias sobre como a classe sob teste interage com seu ambiente. Se essas expectativas estiverem erradas, é provável que você tenha alguns problemas assim que o código for implantado. Se você puder pagar, teste seu código com as mesmas restrições que as que delimitam seu ambiente de produção. Se não puder, faça o mínimo de suposições possíveis e simule / stub apenas os periféricos do seu sistema (banco de dados, serviço de autenticação, cliente HTTP, etc ...).

A única razão válida pela qual IMHO, um duplo deve ser usado, é quando você precisa registrar suas interações com a classe em teste ou quando você precisa fornecer dados falsos (o que ambas as técnicas podem fazer). Tenha cuidado, no entanto, abusar dele reflete um design ruim ou um teste que depende muito da API em implementação de teste.

Francis Toth
fonte
6

Nota: Estou assumindo que você está definindo Mock como "uma classe sem implementação, apenas algo que você pode monitorar" e o Stub como "simulação parcial, ou seja, usa parte do comportamento real da classe implementada", conforme esta pilha Pergunta de estouro .

Não sei por que você acha que o consenso é usar stubs, por exemplo, é exatamente o oposto na documentação do Mockito

Como de costume, você lerá o aviso parcial de simulação: A programação orientada a objetos é mais menos complexa, dividindo a complexidade em objetos SRPy específicos e separados. Como o mock parcial se encaixa nesse paradigma? Bem, simplesmente não ... Simulação parcial geralmente significa que a complexidade foi movida para um método diferente no mesmo objeto. Na maioria dos casos, não é dessa maneira que você deseja projetar seu aplicativo.

No entanto, existem casos raros em que zombarias parciais são úteis: lidar com código que você não pode alterar facilmente (interfaces de terceiros, refatoração provisória de código herdado etc.) No entanto, eu não usaria zombarias parciais para novos, orientados a testes e bem- código projetado.

Essa documentação diz que é melhor do que eu. O uso de zombarias permite apenas testar essa classe em particular e nada mais; se você precisar de zombarias parciais para alcançar o comportamento que está procurando, provavelmente fez algo errado, está violando o SRP e assim por diante, e seu código pode ser um refator. As zombarias não violam o princípio de aberto-fechado, porque só são usadas em testes de qualquer maneira, não são mudanças reais nesse código. Geralmente eles são gerados em tempo real de qualquer maneira por uma biblioteca como o cglib.

durron597
fonte
2
Da mesma pergunta SO fornecida (resposta aceita), esta é a definição de Mock / Stub que eu estava me referindo também: Objetos de mock são usados ​​para definir expectativas, ou seja: Nesse cenário, espero que o método A () seja chamado com esses e esses parâmetros. Zomba de registro e verificação de tais expectativas. Os stubs, por outro lado, têm um objetivo diferente: eles não registram ou verificam as expectativas, mas permitem "substituir" o comportamento, o estado do objeto "falso" para utilizar um cenário de teste ...
Christopher Francisco
2

Eu acho que a questão pode surgir da suposição de que os únicos testes válidos são aqueles que atendem ao teste aberto / fechado.

É fácil ver que o único teste que importa é o que testa a interface. No entanto, na realidade, geralmente é mais eficaz testar essa interface testando o funcionamento interno.

Por exemplo, é quase impossível testar qualquer requisito negativo, como "a implementação não deve gerar exceções". Considere uma interface de mapa sendo implementada com um hashmap. Você quer ter certeza de que o hashmap atenda à interface do mapa, sem jogar, mesmo quando precisar refazer a tarefa (o que pode ser arriscado). Você pode testar todas as combinações de entradas para garantir que elas atendam aos requisitos da interface, mas isso pode levar mais tempo do que a morte por calor do universo. Em vez disso, você quebra um pouco o encapsulamento e desenvolve simulações que interagem mais fortemente, forçando o hashmap a fazer exatamente o rehash necessário para garantir que o algoritmo de rehashing não seja lançado.

Tl / Dr: fazê-lo "de acordo com as instruções" é bom, mas quando o assunto é empurrar, ter um produto na mesa de seu chefe até sexta-feira é mais útil do que uma suíte de testes que leva até a morte pelo calor. universo para confirmar a conformidade.

Cort Ammon
fonte