Eu sou novo no teste de unidade e ouço continuamente as palavras 'objetos simulados' jogados ao redor muito. Nos termos do leigo, alguém pode explicar o que são objetos falsos e para que são usados normalmente ao escrever testes de unidade?
Eu sou novo no teste de unidade e ouço continuamente as palavras 'objetos simulados' jogados ao redor muito. Nos termos do leigo, alguém pode explicar o que são objetos falsos e para que são usados normalmente ao escrever testes de unidade?
Respostas:
Como você diz que é novo no teste de unidade e pediu objetos simulados em "termos leigos", tentarei o exemplo de um leigo.
Imagine o teste de unidade para este sistema:
cook <- waiter <- customer
Geralmente, é fácil imaginar testar um componente de baixo nível como cook
:
cook <- test driver
O motorista do teste simplesmente pede pratos diferentes e verifica se o cozinheiro retorna o prato correto para cada pedido.
É mais difícil testar um componente intermediário, como o garçom, que utiliza o comportamento de outros componentes. Um testador ingênuo pode testar o componente garçom da mesma maneira que testamos o componente cook:
cook <- waiter <- test driver
O motorista do teste encomendaria pratos diferentes e garantiria que o garçom retornasse o prato correto. Infelizmente, isso significa que esse teste do componente garçom pode depender do comportamento correto do componente cook. Essa dependência é ainda pior se o componente de cozinheiro tiver características pouco amigáveis para o teste, como comportamento não determinístico (o menu inclui a surpresa do chef como prato), muitas dependências (o cozinheiro não cozinha sem toda a equipe) ou muitas recursos (alguns pratos exigem ingredientes caros ou levam uma hora para cozinhar).
Como esse é um teste de garçom, idealmente, queremos testar apenas o garçom, não o cozinheiro. Especificamente, queremos garantir que o garçom transmita o pedido do cliente ao cozinheiro corretamente e entregue a comida do cozinheiro ao cliente corretamente.
Teste de unidade significa testar as unidades de maneira independente; portanto, uma abordagem melhor seria isolar o componente em teste (o garçom) usando o que Fowler chama de teste duplo (manequins, stubs, falsificações, zombarias) .
-----------------------
| |
v |
test cook <- waiter <- test driver
Aqui, o cozinheiro de teste está "empolgado" com o driver de teste. Idealmente, o sistema em teste é projetado para que o cozinheiro de teste possa ser facilmente substituído ( injetado ) para trabalhar com o garçom sem alterar o código de produção (por exemplo, sem alterar o código do garçom).
Agora, o cozinheiro de teste (teste duplo) pode ser implementado de diferentes maneiras:
Veja o artigo de Fowler para obter mais detalhes sobre falsificações, stubs, zombarias e manequins , mas, por enquanto, vamos nos concentrar em um cozinheiro simulado.
-----------------------
| |
v |
mock cook <- waiter <- test driver
Uma grande parte do teste de unidade do componente garçom se concentra em como o garçom interage com o componente cozinheiro. Uma abordagem baseada em simulação se concentra em especificar completamente qual é a interação correta e detectar quando ela dá errado.
O objeto simulado sabe antecipadamente o que deve acontecer durante o teste (por exemplo, qual de seus métodos serão chamados, etc.) e o objeto simulado sabe como deve reagir (por exemplo, qual valor de retorno fornecer). A zombaria indicará se o que realmente acontece difere do que deveria acontecer. Um objeto de simulação personalizado pode ser criado do zero para cada caso de teste para executar o comportamento esperado para esse caso de teste, mas uma estrutura de simulação se esforça para permitir que essa especificação de comportamento seja clara e facilmente indicada diretamente no caso de teste.
A conversa em torno de um teste baseado em simulação pode ser assim:
motorista de teste para zombar do cozinheiro : espere um pedido de cachorro-quente e dê a ele esse cachorro-quente falso em resposta
motorista de teste (posando como cliente) para garçom : eu gostaria de um cachorro-quente, por favor,
garçom para zombar de cozinheiro : 1 cachorro-quente, por favor
zombe de cozinheiro para garçom : encomende: 1 cachorro-quente pronto (dá cachorro-quente manequim para garçom)
garçom para testar o motorista : aqui está o seu cachorro-quente (dá cachorro-quente manequim para testar o driver)driver de teste : TESTE SUCEDIDO!
Mas como o nosso garçom é novo, é isso que pode acontecer:
motorista de teste para zombar do cozinheiro : espere um pedido de cachorro-quente e dê a ele esse cachorro-quente falso em resposta
motorista de teste (posando como cliente) para garçom : Eu gostaria de um cachorro-quente, por favor,
garçom para zombar de cozinheiro : 1 hambúrguer, por favor,
zombe de cozinheiro para o teste: me disseram para esperar um pedido de cachorro-quente!O driver de teste observa o problema: TESTE FALHOU! - o garçom mudou a ordem
ou
motorista de teste para zombar do cozinheiro : espere um pedido de cachorro-quente e dê a ele esse cachorro-quente falso em resposta
motorista de teste (posando como cliente) para garçom : Eu gostaria de um cachorro-quente, por favor,
garçom para zombar de cozinheiro : 1 cachorro-quente, por favor,
zombe de cozinheiro para garçom : peça: 1 cachorro-quente pronto (dá cachorro-quente manequim para garçom)
garçom para testar o motorista : aqui está a sua batata frita (dá batata frita de alguma outra ordem para testar o driver)O piloto de testes observa as inesperadas batatas fritas: O teste falhou! o garçom devolveu o prato errado
Pode ser difícil ver claramente a diferença entre objetos simulados e stubs sem um exemplo contrastante baseado em stub para acompanhar isso, mas essa resposta já é muito longa :-)
Observe também que este é um exemplo bastante simplista e que as estruturas de simulação permitem algumas especificações bastante sofisticadas do comportamento esperado dos componentes para oferecer suporte a testes abrangentes. Há muito material sobre objetos simulados e estruturas de simulação para obter mais informações.
Um Objeto Simulado é um objeto que substitui um objeto real. Na programação orientada a objetos, objetos simulados são objetos simulados que imitam o comportamento de objetos reais de maneira controlada.
Um programador de computador normalmente cria um objeto simulado para testar o comportamento de outro objeto, da mesma maneira que um projetista de carros usa um manequim de teste de colisão para simular o comportamento dinâmico de um ser humano nos impactos do veículo.
http://en.wikipedia.org/wiki/Mock_object
Os objetos simulados permitem que você configure cenários de teste sem gerar recursos grandes e pesados, como bancos de dados. Em vez de chamar um banco de dados para teste, você pode simular seu banco de dados usando um objeto simulado em seus testes de unidade. Isso libera você do ônus de ter que configurar e desmontar um banco de dados real, apenas para testar um único método em sua classe.
A palavra "Mock" às vezes é erroneamente usada de forma intercambiável com "Stub". As diferenças entre as duas palavras são descritas aqui. Essencialmente, um mock é um objeto stub que também inclui as expectativas (ou seja, "asserções") para o comportamento adequado do objeto / método em teste.
Por exemplo:
class OrderInteractionTester...
public void testOrderSendsMailIfUnfilled() {
Order order = new Order(TALISKER, 51);
Mock warehouse = mock(Warehouse.class);
Mock mailer = mock(MailService.class);
order.setMailer((MailService) mailer.proxy());
mailer.expects(once()).method("send");
warehouse.expects(once()).method("hasInventory")
.withAnyArguments()
.will(returnValue(false));
order.fill((Warehouse) warehouse.proxy());
}
}
Observe que os objetos warehouse
e mailer
mock são programados com os resultados esperados.
Objetos simulados são objetos simulados que imitam o comportamento dos reais. Normalmente, você escreve um objeto simulado se:
Um objeto simulado é um tipo de teste duplo . Você está usando objetos simulados para testar e verificar o protocolo / interação da classe em teste com outras classes.
Normalmente, você terá tipo de 'programa' ou 'registro' de expectativas: chamadas de método que você espera que sua classe faça para um objeto subjacente.
Digamos, por exemplo, que estamos testando um método de serviço para atualizar um campo em um Widget. E que na sua arquitetura existe um WidgetDAO que lida com o banco de dados. Conversar com o banco de dados é lento, configurá-lo e limpá-lo depois é complicado, portanto, vamos zombar do WidgetDao.
vamos pensar no que o serviço deve fazer: ele deve obter um widget do banco de dados, fazer algo com ele e salvá-lo novamente.
Portanto, na pseudo-linguagem com uma biblioteca pseudo-simulada, teríamos algo como:
Widget sampleWidget = new Widget();
WidgetDao mock = createMock(WidgetDao.class);
WidgetService svc = new WidgetService(mock);
// record expected calls on the dao
expect(mock.getById(id)).andReturn(sampleWidget);
expect(mock.save(sampleWidget);
// turn the dao in replay mode
replay(mock);
svc.updateWidgetPrice(id,newPrice);
verify(mock); // verify the expected calls were made
assertEquals(newPrice,sampleWidget.getPrice());
Dessa maneira, podemos facilmente testar o desenvolvimento de classes que dependem de outras classes.
Eu recomendo um ótimo artigo de Martin Fowler explicando exatamente o que são zombarias e como elas diferem dos stubs.
Ao testar uma parte de um programa de computador, idealmente, você deseja testar apenas o comportamento dessa parte em particular.
Por exemplo, observe o pseudo-código abaixo de uma parte imaginária de um programa que usa outro programa para chamar algo de impressão:
If theUserIsFred then
Call Printer(HelloFred)
Else
Call Printer(YouAreNotFred)
End
Se você estava testando isso, gostaria principalmente de testar a parte que examina se o usuário é Fred ou não. Você realmente não quer testar a Printer
parte das coisas. Isso seria outro teste.
Este é o lugar onde objetos Mock entrar. Eles fingem ser outros tipos de coisas. Nesse caso, você usaria uma Mock Printer
para que funcionasse exatamente como uma impressora real, mas não faria coisas inconvenientes, como imprimir.
Existem vários outros tipos de objetos de simulação que você pode usar que não são zombarias. O principal que cria o Mocks Mocks é que eles podem ser configurados com comportamentos e expectativas.
As expectativas permitem ao seu Mock gerar um erro quando usado incorretamente. Portanto, no exemplo acima, você pode ter certeza de que a impressora é chamada com HelloFred no caso de teste "user is Fred". Se isso não acontecer, o seu Mock pode avisá-lo.
Comportamento no Mocks significa que, por exemplo, o seu código fez algo como:
If Call Printer(HelloFred) Returned SaidHello Then
Do Something
End
Agora você deseja testar o que seu código faz quando a impressora é chamada e retorna o SaidHello, para que você possa configurar o Mock para retornar o SaidHello quando for chamado com o HelloFred.
Um bom recurso para isso é Martin Fowlers postar zombarias não são stubs
Objetos de simulação e esboço são uma parte crucial dos testes de unidade. Na verdade, eles percorrem um longo caminho para garantir que você esteja testando unidades , em vez de grupos de unidades.
Em poucas palavras, você usa stubs para quebrar a dependência do SUT (Sistema em teste) de outros objetos e zombarias para fazer isso e verificar se o SUT chamou certos métodos / propriedades na dependência. Isso remonta aos princípios fundamentais do teste de unidade - que os testes devem ser facilmente legíveis, rápidos e sem exigir configuração, o que pode implicar o uso de todas as classes reais.
Geralmente, você pode ter mais de um esboço no teste, mas deve ter apenas uma simulação. Isso ocorre porque o objetivo do mock é verificar o comportamento e seu teste deve testar apenas uma coisa.
Cenário simples usando C # e Moq:
public interface IInput {
object Read();
}
public interface IOutput {
void Write(object data);
}
class SUT {
IInput input;
IOutput output;
public SUT (IInput input, IOutput output) {
this.input = input;
this.output = output;
}
void ReadAndWrite() {
var data = input.Read();
output.Write(data);
}
}
[TestMethod]
public void ReadAndWriteShouldWriteSameObjectAsRead() {
//we want to verify that SUT writes to the output interface
//input is a stub, since we don't record any expectations
Mock<IInput> input = new Mock<IInput>();
//output is a mock, because we want to verify some behavior on it.
Mock<IOutput> output = new Mock<IOutput>();
var data = new object();
input.Setup(i=>i.Read()).Returns(data);
var sut = new SUT(input.Object, output.Object);
//calling verify on a mock object makes the object a mock, with respect to method being verified.
output.Verify(o=>o.Write(data));
}
No exemplo acima, usei o Moq para demonstrar stubs e zombarias. O Moq usa a mesma classe para ambos - o Mock<T>
que o torna um pouco confuso. Independentemente, em tempo de execução, o teste falhará se output.Write
não for chamado com dados como parameter
, enquanto a falha na chamada input.Read()
não falhará.
Como outra resposta sugerida por meio de um link para " Mocks are not stubs ", as zombarias são uma forma de "teste duplo" a ser usado no lugar de um objeto real. O que os diferencia de outras formas de dobra de teste, como objetos de stub, é que outras dobras de teste oferecem verificação de estado (e opcionalmente simulação), enquanto as simulações oferecem verificação de comportamento (e opcionalmente simulação).
Com um stub, você pode chamar vários métodos no stub em qualquer ordem (ou mesmo repitidamente) e determinar o sucesso se o stub capturou um valor ou estado que você pretendia. Por outro lado, um objeto simulado espera que funções muito específicas sejam chamadas, em uma ordem específica e até um número específico de vezes. O teste com um objeto simulado será considerado "falhado" simplesmente porque os métodos foram chamados em uma sequência ou contagem diferente - mesmo que o objeto simulado tenha o estado correto quando o teste for concluído!
Dessa maneira, objetos simulados geralmente são considerados mais fortemente acoplados ao código SUT do que objetos stub. Isso pode ser uma coisa boa ou ruim, dependendo do que você está tentando verificar.
Parte do objetivo do uso de objetos simulados é que eles não precisam ser realmente implementados de acordo com as especificações. Eles podem apenas dar respostas falsas. Por exemplo, se você tiver que implementar os componentes A e B e ambos "ligar" (interagir) entre si, não poderá testar A até que B seja implementado e vice-versa. No desenvolvimento orientado a testes, isso é um problema. Então, você cria objetos simulados ("fictícios") para A e B, que são muito simples, mas eles dão algum tipo de resposta quando são interagidos. Dessa forma, você pode implementar e testar A usando um objeto simulado para B.
Para php e phpunit é bem explicado na documentação do phpunit. veja aqui a documentação do phpunit
Em palavras simples, o objeto de simulação é apenas um objeto fictício do original e retorna seu valor de retorno; esse valor de retorno pode ser usado na classe de teste
É uma das principais perspectivas dos testes de unidade. sim, você está tentando testar sua única unidade de código e seus resultados não devem ser relevantes para o comportamento de outros beans ou objetos. portanto, você deve zombar deles usando objetos Mock com alguma resposta correspondente simplificada.