Working with Stubs
Olá pessoal, vou seguir com esta série sobre Testes de Software. E hoje vamos conversar um pouco sobre Stubs.
Vamos analisar o seguinte cenário: Temos o seguinte Teste.
[TestMethod] public void HasCreditInCard_ForAGivenCard_ReturnsTrue() { var analyzer = new CreditAnalyzer(); bool result = analyzer.HasCreditInCard("000.000.000"); Assert.IsTrue(result); }
Como podemos perceber desejamos testar o método HasCreditInCard da classe CreditAnalyzer.
Dada a implementação:
public class CreditAnalyzer { public bool HasCreditInCard(string cardNumber) { CreditService manager = new CreditService(); return manager.HasCredit(cardNumber); } } public class CreditService { public bool HasCredit(string cardNumber) { //Real implementation //Call WCF Service return true; } }
No entanto, notamos que este método possui uma dependência externa para um serviço externo(WCF) que está sendo utilizado por um Serviço de Crédito.
Primeiramente vamos definir o que é uma dependência externa.
Dependencia externa é um objeto no seu sistema, que sua “unidade sobre teste” interage, e sobre o qual não possui controle. Como threads, memoria, arquivos, tempo, serviços e etc.
O stub é um substituto controlável para uma dependência existente (ou um colaborador) no sistema. Usando um Stub, voce pode testar seu código sem lidar diretamente com esta dependência.
O problema aparece, uma vez que este teste depende de um serviço, então estamos na verdade utilizando um teste de integração, e como ja sabemos temos problemas associados: testes lentos para executar, precisam de configuração, testam multiplas regiões de código, etc.
Para quebrarmos esta dependencia externa podemos seguir alguns passos:
1 - Encontre a Interface que o método sobre teste opera. No nosso exemplo a Interface é o Serviço de Crédito.
2 – Se a interface estiver diretamente conectada ao método que estamos testando, e neste caso está invocando diretamente o Service, torne o código testável adicionando uma Camada de Indireção à Interface. No nosso caso podemos mover a chamada direta, para uma classe separada.
3 – Troque a implementação atual da interface por outro que você possa controlar. No nosso caso, trocaremos a instância da classe que nosso método chama, por uma classe Stub, que podemos controlar, dando ao nosso código de teste controle sobre nossas dependências externas.
Nosso substituto não irá conversar com o Serviço como a implementação original, ele quebra essa dependencia.. Nós não estamos testando a classe que fala com o Service, mas sim o código que utiliza esta classe.
Introduzindo uma camada de indireção para evitar a dependencia ao WCF Service. O código que chama este serviço esta separado na classe CreditService, que será substituida pelo Stub.
Então adicionamos uma interface. Ela irá permitir abstrair as operações realizadas pela classe CreditService.
Agora podemos dizer para nossa classe a ser testada (CreditAnalyzer), pode lidar com qualquer tipo de CreditService, sem conhecer sua implementação. Isso pode ser feito utilizando uma classe base ou uma interface.
public class CreditService : ICreditService { public bool HasCredit(string cardNumber) { //Real implementation //Do task //Do more task return true; } } public interface ICreditService { bool HasCredit(string cardNumber); } public class CreditAnalyzer { public bool HasCreditInCard(string cardNumber) { ICreditService manager = new CreditService(); return manager.HasCredit(cardNumber); } }
Simplesmente criamos uma interface com um método HasCredit e o CreditService implementa esta interface. Agora podemos substituir a implementação real do CreditService pelo nosso StubCreditService.
public class StubCreditService : ICreditService { public bool HasCreditInCard { get; set; } public bool HasCredit(string cardNumber) { return HasCreditInCard; } }
Legal mas nosso método HasCreditInCard continua chamando a implementação real diretamente. Uma solução dentre várias seria injetar o stub na classe que está sendo testada. Para este exemplo utilizarei injeção no construtor.
[TestClass] public class CreditAnalyzerTest { [TestMethod] public void HasCreditInCard_ForAGivenCard_ReturnsTrue() { var stubManager = new StubCreditService(); stubManager.HasCreditInCard = true; var analyzer = new CreditAnalyzerInjection(stubManager); bool result = analyzer.HasCreditInCard("000.000.000"); Assert.IsTrue(result); } }
Desta forma com essa implementação do analisador ficaria da seguinte forma utilizando Injeção de Dependência.
public class CreditAnalyzer { private ICreditService manager; public CreditAnalyzer(ICreditService manager) { this.manager = manager; } public bool HasCreditInCard(string cardNumber) { return manager.HasCredit(cardNumber); } }
Vimos como criar Stubs para eliminar dependências no código. usando interfaces e herança. Um Stub pode ser injetado no código de muitas maneiras. O mais importante é localizar a melhor camada de indireção a ser criada, para que voce possa injetar seu Stub. Quanto mais fundo você for nas camadas de interação mais dificil será entender e manter o teste, quanto mais proximo da superficie do objeto que está sendo testado, mais facil. Mas voce também poderá estar abrindo mão do seu poder de manipular o ambiente para testar o objeto.
Meu objetivo era explicar basicamente como funcionam Stubs. E como utiliza-los para eliminar dependências no seu código.
Espero que tenham gostado, e aguardo feedbacks.
Abraço pessoal!

















