Qual é o objetivo dos objetos simulados?

167

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?

agentbanks217
fonte
12
Eles são uma ferramenta para a engenharia maciça de coisas com flexibilidade que você não precisa para o problema em questão.
dsimcha
2
possível duplicata de O que é zombaria?
Nawfal

Respostas:

361

Como você diz que é novo no teste de unidade e pediu objetos simulados em "termos leigos", tentarei o exemplo de um leigo.

Teste de Unidade

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).

Mock Objects

Agora, o cozinheiro de teste (teste duplo) pode ser implementado de diferentes maneiras:

  • um cozinheiro falso - alguém que finge ser cozinheiro usando jantares congelados e um microondas,
  • um cozinheiro de esboço - um fornecedor de cachorro-quente que sempre oferece cachorro-quente, não importa o que você pede, ou
  • um cozinheiro falso - um policial disfarçado seguindo um script que finge ser um cozinheiro em uma operação de picada.

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.

Bert F
fonte
12
Esta é uma ótima explicação, mas você não está, de certa forma, testando a implementação do garçom? No seu caso, provavelmente está tudo bem, porque você está verificando se ele usa a API correta, mas e se houver maneiras diferentes de fazer isso, e o garçom pode escolher uma ou outra? Eu pensei que o objetivo do teste de unidade era testar a API, e não a implementação. (Esta é uma pergunta que eu sempre encontrar-me perguntando quando eu li sobre zombando.)
davidtbernal
8
Obrigado. Não sei dizer se estamos testando a "implementação" sem ver (ou definir) as especificações do garçom. Você pode presumir que o garçom tem permissão para cozinhar o prato ou preencher o pedido na rua, mas presumo que as especificações para o garçom incluam o uso do chef pretendido - afinal, o chef de produção é um chef gourmet caro e nós ' prefiro o nosso garçom para usá-lo. Sem essa especificação, acho que tenho que concluir que você está certo - o garçom pode preencher o pedido da forma que quiser para estar "correto" .OTOH, sem uma especificação, o teste não faz sentido. [Continuação ...]
Bert F
8
NUNCA, você faz um ótimo argumento que leva ao fantástico tópico de testes de unidade de caixa branca versus caixa preta. Eu não acho que exista um consenso do setor que diga que o teste de unidade deve ser caixa preta em vez de caixa branca ("teste a API, não a implementação"). Eu acho que o melhor teste de unidade provavelmente precisa ser uma combinação dos dois para equilibrar a fragilidade do teste com a cobertura do código e a integridade do caso de teste.
Bert F
1
Esta resposta não é técnica o suficiente para mim. Quero saber por que devo usar objetos simulados quando posso usar objetos reais.
Niklas R.
1
Ótima explicação !! Obrigado!! @BertF
Bharath Murali
28

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 warehousee mailermock são programados com os resultados esperados.

Robert Harvey
fonte
2
A definição que você forneceu não difere nem um pouco do "objeto stub" e, como tal, não explica o que é um objeto simulado.
Brent Arias
Outra correção "a palavra 'Mock' às vezes é erroneamente usada de forma intercambiável com 'stub'".
Brent Arias
@Myst: O uso das duas palavras não é universal; varia entre autores. Fowler diz isso, e o artigo da Wikipedia diz isso. No entanto, sinta-se à vontade para editar a alteração e remover seu voto negativo. :)
Robert Harvey
1
Concordo com Robert: o uso da palavra "mock" tende a variar em todo o setor, mas não existe uma definição definida de acordo com a minha experiência, exceto que normalmente NÃO é o objeto realmente sendo testado, mas existe para facilitar o teste em que o uso real objeto ou todas as partes seria muito inconveniente e de pouca importância.
precisa saber é o seguinte
15

Objetos simulados são objetos simulados que imitam o comportamento dos reais. Normalmente, você escreve um objeto simulado se:

  • O objeto real é muito complexo para incorporá-lo em um teste de unidade (por exemplo, uma comunicação em rede, você pode ter um objeto simulado que simula o outro ponto)
  • O resultado do seu objeto não é determinístico
  • O objeto real ainda não está disponível
Dani Cricco
fonte
12

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.

Peter Tillemans
fonte
11

Eu recomendo um ótimo artigo de Martin Fowler explicando exatamente o que são zombarias e como elas diferem dos stubs.

Adam Byrtek
fonte
10
Não é exatamente para iniciantes, não é?
Robert Harvey
@ Robert Harvey: Talvez, de qualquer forma bom para ver que ele foi útil para esclarecer a sua resposta :)
Adam Byrtek
Os artigos de Martin Fowler são escritos à maneira de RFCs: seco e frio.
revo
9

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 Printerparte 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 Printerpara 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

David Hall
fonte
7

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.Writenão for chamado com dados como parameter, enquanto a falha na chamada input.Read()não falhará.

Igor Zevaka
fonte
4

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.

Brent Arias
fonte
3

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.

LarsH
fonte
1

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

Gautam Rai
fonte
0

É 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.

Mohsen Msr
fonte