ti-enxame.com

Qual é o objetivo dos objetos simulados?

Eu sou novo no teste de unidade, e eu continuamente ouço as palavras 'mock objects' muito espalhadas. Em termos leigos, alguém pode explicar o que são objetos simulados e para que são normalmente usados ​​ao escrever testes de unidade?

151
agentbanks217

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

Testes Unitários

Imagine testes unitários para este sistema:

cook <- waiter <- customer

Geralmente, é fácil visualizar um componente de baixo nível como o cook:

cook <- test driver

O motorista de teste simplesmente pede pratos diferentes e verifica se o cozinheiro devolve o prato correto para cada pedido.

É mais difícil testar um componente do meio, 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 de teste pediria pratos diferentes e garantiria que o garçom devolvesse o prato correto. Infelizmente, isso significa que este teste do componente de garçom pode depender do comportamento correto do componente de cozimento. Essa dependência é ainda pior se o componente cook tiver alguma característica hostil ao 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 a equipe inteira) ou muito recursos (alguns pratos requerem ingredientes caros ou levam uma hora para cozinhar).

Como este é um teste de garçom, idealmente, queremos testar apenas o garçom, não o cozinheiro. Especificamente, queremos ter certeza de que o garçom transmite a ordem do cliente ao cozinheiro corretamente e entrega a comida do cozinheiro ao cliente corretamente.

Testes unitários significam unidades de teste independentes, então uma abordagem melhor seria isolar o componente sob teste (o garçom) usando o que Fowler chama de teste duplo (dummies, stubs, fakes, mocks) .

    -----------------------
   |                       |
   v                       |
test cook <- waiter <- test driver

Aqui, o cozinheiro de teste está "em conluio" com o motorista de teste. Idealmente, o sistema sob teste é projetado para que o cookware 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 maneiras diferentes:

  • um cozinheiro falso - alguém que finge ser um cozinheiro usando jantares congelados e um microondas,
  • um stub cook - um vendedor de cachorro-quente que sempre lhe dá cachorros-quentes, não importa o que você pedir, ou
  • um cozinheiro simulado - um policial disfarçado seguindo um roteiro fingindo ser cozinheiro em uma operação.

Veja artigo de Fowler para os mais específicos sobre fakes vs stubs vs mocks vs dummies , mas por enquanto, vamos nos concentrar em um cozinheiro simulado.

    -----------------------
   |                       |
   v                       |
mock cook <- waiter <- test driver

Uma grande parte da unidade de teste do componente garçom se concentra em como o garçom interage com o componente cook. Uma abordagem baseada em simulação concentra-se em especificar completamente o que é a interação correta e detectar quando ela dá errado.

O objecto simulado sabe antecipadamente o que é suposto acontecer durante o teste (por exemplo, qual das suas chamadas de métodos será invocada, etc.) e o objecto simulado sabe como deve reagir (por exemplo, o valor de retorno a fornecer). O mock indicará se o que realmente acontece é diferente do que deveria acontecer. Um objeto mock 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 tenta permitir que essa especificação de comportamento seja clara e facilmente indicada diretamente no caso de teste.

A conversa em torno de um teste simulado pode ser assim:

driver de teste para cozinheiro simulado : espere um cachorro-quente ordem e dar-lhe este cachorro-quente fictício em resposta

driver de teste (posando como cliente) para garçom : Eu gostaria de um cachorro-quente por favor
garçom para cozinheiro simulado : 1 cachorro-quente por favor
cozinheiro simulado para garçom : ordem acima: 1 cachorro-quente pronto (dá cachorro-quente fictício ao garçom)
garçom para driver de teste : aqui está seu cachorro-quente (dá cachorro-quente para testar o motorista)

driver de teste : TESTE SUCEDIDO!

Mas desde que o nosso garçom é novo, é isso que poderia acontecer:

driver de teste para cozinheiro simulado : espere um cachorro-quente ordem e dar-lhe este cachorro-quente fictício em resposta

driver de teste (posando como cliente) para garçom : Eu gostaria de um cachorro-quente por favor
garçom para cozinheiro simulado : 1 hamburger por favor
cozinheiro simulado interrompe o teste: Foi-me dito para esperar um pedido de cachorro-quente!

driver de teste observa o problema: TEST FAILED! - o garçom mudou a ordem

ou

driver de teste para cozinheiro simulado : espere um cachorro-quente ordem e dar-lhe este cachorro-quente fictício em resposta

driver de teste (posando como cliente) para garçom : Eu gostaria de um cachorro-quente por favor
garçom para cozinheiro simulado : 1 cachorro-quente por favor
cozinheiro simulado para garçom : ordem acima: 1 cachorro-quente pronto (dá cachorro-quente fictício ao garçom)
garçom para driver de teste : aqui estão suas batatas fritas (dá batatas fritas de algum outro pedido para testar o motorista)

driver de teste observa as batatas fritas inesperadas: TEST FAILED! o garçom devolveu o prato errado

Pode ser difícil ver claramente a diferença entre objetos simulados e stubs sem um exemplo baseado em stub contrastante para isso, mas essa resposta já é muito longa :-)

Observe também que este é um exemplo bastante simplista e que os frameworks de simulação permitem algumas especificações bastante sofisticadas do comportamento esperado dos componentes para suportar testes abrangentes. Há muito material sobre objetos falsos e estruturas de simulação para mais informações.

324
Bert F

Um Mock Object é 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 maneiras controladas.

Um programador de computador normalmente cria um objeto simulado para testar o comportamento de algum outro objeto, quase da mesma maneira que um projetista de automóveis usa um boneco de testes de colisão para simular o comportamento dinâmico de um ser humano em impactos de veículo.

http://en.wikipedia.org/wiki/Mock_object

Os objetos de simulação permitem que você configure cenários de teste sem usar recursos grandes e difíceis, 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 fardo de ter que configurar e desmontar um banco de dados real, apenas para testar um único método em sua classe.

A palavra "Mock" é algumas 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 de simulação warehouse e mailer são programados com os resultados esperados.

27
Robert Harvey

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

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

Um objeto Mock é um tipo de Duplo teste . Você está usando mockobjects para testar e verificar o protocolo/interação da classe em teste com outras classes.

Normalmente, você vai programar as expectativas de 'programar' ou 'gravar': chamadas de método que você espera que sua classe faça em um objeto subjacente.

Digamos que, por exemplo, estamos testando um método de serviço para atualizar um campo em um widget. E que na sua arquitetura há um WidgetDAO que lida com o banco de dados. Falar com o banco de dados é lento e configurá-lo e, depois, a limpeza é complicada, então vamos ridicularizar o WidgetDao.

vamos pensar no que o serviço deve fazer: ele deve pegar um Widget do banco de dados, fazer algo com ele e salvá-lo novamente.

Então, em 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());

Desta forma, podemos facilmente testar o desenvolvimento de drives de classes que dependem de outras classes.

12
Peter Tillemans

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

11
Adam Byrtek

Quando testar uma parte de um programa de computador, você deseja testar apenas o comportamento dessa parte específica.

Por exemplo, observe o pseudo-código abaixo de uma parte imaginária de um programa que usa outro programa para chamar print something:

If theUserIsFred then
    Call Printer(HelloFred)
Else
   Call Printer(YouAreNotFred)
End

Se você estava testando isso, você quereria testar principalmente a parte que olha se o usuário é Fred ou não. Você não quer realmente testar a parte Printer das coisas. Isso seria outro teste.

Este é onde entram os objetos Mock. Eles fingem ser outros tipos de coisas. Nesse caso, você usaria um Mock Printer para que ele funcionasse como uma impressora real, mas não faria coisas inconvenientes como imprimir.


Existem vários outros tipos de objetos que você pode usar e que não são Mocks. A principal coisa que faz Mocks Mocks é que eles podem ser configurados com comportamentos e expectativas.

Expectativas permitem que o Mock aponte um erro quando é usado incorretamente. Portanto, no exemplo acima, convém ter certeza de que a impressora é chamada com HelloFred no caso de teste "usuário é Fred". Se isso não acontecer, seu Mock pode avisá-lo.

Comportamento em Mocks significa que, por exemplo, o seu código fez algo como:

If Call Printer(HelloFred) Returned SaidHello Then
    Do Something
End

Agora você quer 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 ele for chamado com o HelloFred.

Um bom recurso em torno disso é Martin Fowlers post Mocks não são stubs

9
David Hall

Objetos mock e stub são uma parte crucial do teste de unidade. Na verdade, eles percorrem um longo caminho para garantir que você está testando nidades, em vez de grupos de unidades.

Em suma, você usa stubs para quebrar a dependência do SUT (System Under Test) em outros objetos e mocks 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 não exigem configuração, o que pode implicar o uso de todas as classes reais.

Geralmente, você pode ter mais de um stub no seu teste, mas você deve ter apenas um mock. 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 mocks. Moq usa a mesma classe para ambos - Mock<T>, o que torna um pouco confuso. Independentemente disso, em tempo de execução, o teste falhará se output.Write não for chamado com dados como parameter, enquanto falha ao chamar input.Read() não falhará.

7
Igor Zevaka

Como outra resposta sugerida através de um link para " Mocks não são Stubs ", mocks são uma forma de "teste duplo" para usar no lugar de um objeto real. O que os torna diferentes de outras formas de teste de duplas, como objetos stub, é que outros testes duplos oferecem verificação de estado (e, opcionalmente, simulação), enquanto os simulados 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 repetidamente) e determinar o sucesso se o stub tiver capturado um valor ou estado que você pretendia. Em contraste, um objeto simulado espera que funções muito específicas sejam chamadas, em uma ordem específica, e até mesmo um número específico de vezes. O teste com um objeto simulado será considerado "com falha" simplesmente porque os métodos foram invocados em uma sequência ou contagem diferente - mesmo que o objeto fictício tenha o estado correto quando o teste foi concluído!

Dessa maneira, os objetos simulados são geralmente considerados mais fortemente acoplados ao código SUT do que objetos stub. Isso pode ser bom ou ruim, dependendo do que você está tentando verificar.

4
Brent Arias

Parte do objetivo de usar objetos simulados é que eles não precisam ser realmente implementados de acordo com as especificações. Eles podem apenas dar respostas fictícias. Por exemplo. se você tiver que implementar os componentes A e B, e ambos "chamarem" (interagirem) uns com os outros, então você não pode testar A até que B seja implementado, e vice-versa. No desenvolvimento orientado a testes, isso é um problema. Então você cria objetos mock ("dummy") para A e B, que são muito simples, mas eles dão alguns tipo de resposta quando eles são interagidos. Dessa forma, você pode implementar e testar A usando um objeto simulado para B.

3
LarsH

Para php e phpunit é bem explicado no phpunit documentaion. veja aqui documentação phpunit

No objeto Word mocking simples é apenas objeto fictício do seu original e é retornar seu valor de retorno, esse valor de retorno pode ser usado na classe de teste

1
Gautam Rai