A simulação para testes de unidade é apropriada nesse cenário?

8

Eu escrevi cerca de 20 métodos em Java e todos eles chamam alguns serviços web. Nenhum desses serviços da web ainda está disponível. Para continuar com a codificação do lado do servidor, codifiquei os resultados que o serviço da Web deve fornecer.

Podemos testar esses métodos de unidade? Tanto quanto eu sei, o teste de unidade está zombando dos valores de entrada e veja como o programa responde. A zombaria dos valores de entrada e saída é significativa?

Editar:

As respostas aqui sugerem que eu deveria escrever casos de teste de unidade.

Agora, como posso escrever sem modificar o código existente?

Considere o seguinte código de exemplo (código hipotético):

    public int getAge()
    {
            Service s = locate("ageservice"); // line 1
            int age = s.execute(empId); // line 2
             return age; // line 3

 }

Agora, como zombamos da saída?

No momento, estou comentando a 'linha 1' e substituindo a linha 2 por int age= 50. Isto está certo ? Alguém pode me indicar o caminho certo para fazê-lo?

Vinoth Kumar CM
fonte

Respostas:

12

Sim.

Use zombarias e stubs para simular o comportamento esperado dos serviços da web. Valide se seu código funciona corretamente em todos os valores de limite esperados e classes de equivalência.

EDITAR:

Não, você não deve editar manualmente o teste de unidade com int age= 50. Você deve usar a injeção de dependência para poder substituí-lo facilmente Servicepor um objeto simulado.

public int getAge(Service s)
{
    int age = s.execute(empId); // line 2
    return age; // line 3
}

Seu teste de unidade deve se parecer com o seguinte pseudocódigo:

public int getAgeMinimumValueTest()
{
    ClassUnderTest uut = new ClassUnderTest();
    MockService service = new MockService();
    service.age = 10;
    int age = uut.getAge(service);
    Assert(age == 10);
}
M. Dudley
fonte
1
Se estamos zombando da entrada e da saída, qual é o sentido? Como zombamos da saída em primeiro lugar?
Vinoth Kumar CM
1
Ao testar um único método, você deve criar um teste de unidade para cada caso de uso. Ou seja, em cada teste de unidade, você especifica diferentes valores de entrada para o método e verifica se a saída é o valor correto. Se o método depender de um serviço da Web, você zomba do serviço da Web para que ele se comporte conforme o esperado nesse caso de uso. Por exemplo, você pode escrever um código simulado para o serviço da web para fornecer valores que são considerados entradas para o método.
M. Dudley
3

Sim, você deve estar zombando de seu serviço. Zombar é a prática de usar objetos simulados para substituir os objetos reais que você usaria normalmente em tempo de execução. Na maioria das vezes, você deseja usar uma estrutura de simulação para criar objetos simulados de maneira fácil e dinâmica.

Se você usar objetos simulados, provavelmente escreveria um código mais parecido com isto:

public int getAge(ServiceInterface service)
{
    return service.execute(empId);
}

onde serviceé o serviço real que você usará em tempo de execução, mas durante um teste de unidade, é um objeto simulado com comportamento simulado. Observe que o parâmetro para getAgenão é mais uma classe concreta, mas uma interface. Isso permite que você use qualquer objeto como parâmetro, desde que implemente a interface, em vez de apenas uma classe específica. Seu teste de unidade seguiria as seguintes etapas:

  1. Crie o objeto simulado
  2. Diga para retornar uma certa idade quando executefor chamado
  3. Chamar getAgecom o objeto simulado como parâmetro
  4. Verificar getAgeretorna a idade correta.

Para uma boa lista de estruturas de simulação de Java, consulte esta pergunta sobre o stackoverflow .

EDITAR

Não comente seu código e substitua-o por uma constante apenas para seus testes de unidade. O problema é que você precisará garantir que a coisa certa seja comentada ao executar testes de unidade e a coisa certa ao ser liberada para o cliente. Isso se tornaria um pesadelo para fins de manutenção a longo prazo, especialmente se você pretender escrever mais de dois testes de unidade que exijam zombarias. Além disso, seus testes devem ser executados no produto acabado ; caso contrário, isso significa que você não está testando o código real que está sendo lançado, o que nega a finalidade de realizar testes.

Phil
fonte
1

Sim, ele é. Seu trabalho é demonstrar que seu código faz a coisa certa, considerando o ambiente. Os serviços da web externos fazem parte do ambiente; não é seu trabalho testar o funcionamento adequado, é o trabalho das pessoas que escrevem os serviços. O mapeamento de entrada / saída que os testes devem verificar é a entrada / saída do seu código; se o código produzir informações para outro colaborador fazer seu trabalho, isso é estritamente acidental.

De fato, mesmo depois que os serviços da web estão disponíveis, é provavelmente uma boa idéia manter seus testes de unidade usando um ambiente simulado e não o real. Isso torna os testes mais simples, menos frágeis em relação ao contexto esperado do sistema e provavelmente mais rápido também.

Kilian Foth
fonte
"Provavelmente é uma boa ideia"? Eu diria essencial. Você não pode garantir testes de unidade confiáveis ​​além dos limites do sistema.
thehowler 17/05
1

Para adicionar à excelente resposta da emddudley, o maior ganho que você pode obter ao zombar do serviço é poder testar o que deve acontecer, o serviço não funciona corretamente. O pseudocódigo de teste pode ser algo como isto:

public int AgeMinimumValue_LogsServiceError_Test()
{
    ClassUnderTest uut = new ClassUnderTest();
    MockService service = new MockService();
    service.Throws(new TimeoutException());

    MockLogger logger = new MockLogger();

    try {
        int age = uut.getAge(service, logger);
        Assert.Fail("Exception was not raised by the class under test");
    }
    catch (TimeoutException) {
        Assert(logger.LogError().WasCalled());
    }
}

E agora sua implementação foi editada com este novo requisito

public int getAge(Service s, Logger l)
{
    try {
        int age = s.execute(empId);
        return age;
    }
    catch(Exception ex) {
        l.LogError(ex);
        throw;
    }
}

Em outros cenários, é mais provável que você precise responder a respostas mais complicadas. Se o serviço forneceu o processamento do cartão de crédito, você precisaria responder a Sucesso, Serviço indisponível, Cartão de crédito expirado, Número inválido, etc. Ao zombar do serviço, você pode garantir que responde a esses cenários da maneira adequada à sua situação. Nesse caso, você deve simular a entrada / saída do serviço e o feedback obtido ao saber que o código consumidor funcionará para todas as saídas conhecidas é realmente significativo e valioso.

EDIT: Acabei de notar que você deseja poder zombar sem modificar o método existente. Para fazer isso, o locate("ageservice");método precisaria ser alterado para suportar objetos simulados nos testes e localizar o serviço real quando estiver pronto. Essa é uma variação do padrão do localizador de serviço que abstrai a lógica para recuperar a implementação do serviço que você está usando. Uma versão rápida disso pode ser assim:

public Service locate(string serviceToLocate) {
    if(testEnvironment) // Test environment should be set externally
        return MockService(serviceToLocate);
    else
        return Service(serviceToLocate);
}

Minha recomendação, no entanto, seria mover as dependências de serviço para os Construtores:

public int AgeMinimumValue_LogsServiceError_Test()
{
    MockService service = new MockService();
    service.Throws(new TimeoutException());

    MockLogger logger = new MockLogger();

    ClassUnderTest uut = new ClassUnderTest(service, logger);

    try {
        int age = uut.getAge();
        Assert.Fail("Exception was not raised by the class under test");
    }
    catch (TimeoutException) {
        Assert(logger.LogError().WasCalled());
    }
}

Agora, o método getAge não tem mais a responsabilidade de procurar o serviço, pois foi extraído da classe deixando completamente uma implementação semelhante a esta:

public int getAge()
{
    try {
        // _service is a private field set by the constructor
        int age = _service.execute(empId); 
        return age;
    }
    catch(Exception ex) {
         // _logger is a private field set by the constructor
        _logger.LogError(ex);
        throw;
    }
}
Phil Patterson
fonte
0

A zombaria dos valores de entrada e saída é significativa?

Não, mas você não está zombando do valor de entrada. Seu teste está sendo executado e o serviço simulado verifica se a parte certa do contrato foi acessada. O fato de você retornar um valor específico é irrelevante.

Por outro lado, se o seu método faz alguma lógica, o valor de retorno é importante. Aqui você está zombando das entradas da lógica (o resultado do serviço da web) e testando a saída.

Telastyn
fonte
0

Agora, como posso escrevê-lo sem modificar o código existente?

você não pode. Mas você pode criar uma interface "IMyService" com a qual possa programar que contenha todas as assinaturas e métodos de serviço da web.

public int getAge()
{
        IMyService s = getService(); 
        int age = s.execute(empId);
         return age; // line 3

}

No modo Produção getService();, retornará uma referência a um serviço da web funcional completo e, no modo de teste, uma implementação alternativa (ou simulação) que retorna seus dados falsos.

k3b
fonte
0

Zombar não é sobre entradas ou saídas, é sobre substituir dependências externas. Portanto, sim, é apropriado escrever testes de unidade e zombar dos serviços da web externos.

Agora as más notícias: dependendo do idioma e das ferramentas disponíveis, talvez você não consiga zombar das dependências externas, a menos que o código tenha sido projetado para permitir isso. Se esse for o caso, você encontrará seus testes realmente transformados em pequenos testes de integração, em vez de testes unti puros.

No lado positivo, parece que você está autorizado a modificar o código (caso contrário, como está comentando), passe uma interface para o seu serviço para permitir que seja zombado (ou use alguma outra forma de injeção de dependência )

Por fim, o que você deseja testar parece ser um invólucro puro em torno do serviço externo; não há quase nada para testar a unidade aqui, exceto o que você chama de serviço. Como essa é basicamente uma interface, a maioria dos testes terá que ser realizada posteriormente em um nível superior (teste de integração)

jk.
fonte