Teste de unidade - introdução

14

Estou apenas começando com o teste de unidade, mas não tenho certeza se realmente entendi o objetivo de tudo. Leio tutoriais e livros sobre tudo, mas só tenho duas perguntas rápidas:

  1. Eu pensei que o objetivo do teste de unidade é testar o código que realmente escrevemos. No entanto, para mim, parece que, para podermos executar o teste, precisamos alterar o código original; nesse momento, não estamos realmente testando o código que escrevemos, mas o código que escrevemos para o teste.

  2. A maioria dos nossos códigos depende de fontes externas. Ao refatorar nosso código, no entanto, mesmo que ele quebrasse o código original, nossos testes ainda funcionariam muito bem, pois as fontes externas são apenas estragos em nossos casos de teste. Não derrota o objetivo do teste de unidade?

Desculpe se pareço idiota aqui, mas achei que alguém poderia me esclarecer um pouco.

Desde já, obrigado.

codificador de árvore
fonte

Respostas:

7

Meus 0,02 $ ... isso é um pouco subjetivo, então leve com um pouco de sal, mas espero que isso faça você pensar e / ou acenda algum diálogo:

  1. O objetivo principal do teste de unidade para mim é garantir que o código que você escreveu cumpra os contratos e casos extremos que seu código pretende cumprir. Com os testes de unidade, você pode garantir melhor que, quando você (ou outra pessoa no futuro) refatorar seu código, qualquer consumidor externo do seu código permanecerá inalterado se você tiver uma cobertura de estado adequada. (pelo menos na medida em que você pretende que eles não sejam afetados).

    Na maioria dos casos, você deve poder escrever um código que possa ser enviado para produção e seja facilmente testável por unidade. Um bom lugar para começar pode ser procurar padrões e estruturas de injeção de dependência. (Ou outras filosofias para o seu idioma / plataforma de escolha).

  2. É correto que implementações externas possam afetar seu código. No entanto, garantir que seu código funcione corretamente como parte de um sistema maior é uma função do teste de integração . (Que também pode ser automatizado com diferentes graus de esforço).

    Idealmente, seu código deve confiar apenas nos contratos de API de qualquer componente de terceiros, o que significa que, desde que suas simulações cumpram a API correta, seus testes de unidade ainda fornecerão valor.

    Dito isto, admitirei prontamente que houve momentos em que renunciei ao teste de unidade apenas em favor do teste de integração, mas foram apenas nos casos em que meu código teve que interagir tão profusamente com componentes de terceiros com APIs mal documentadas. (ou seja, a exceção e não a regra).

Charlie
fonte
5
  1. Tente escrever seus testes primeiro. Dessa forma, você terá uma base sólida para o comportamento do seu código e seu teste se tornará um contrato para o comportamento necessário do seu código. Portanto, alterar o código para passar no teste se torna "alterar o código para cumprir o contrato proposto pelo teste" em vez de "alterar o código para passar no teste".
  2. Bem, tenha cuidado com a diferença entre tocos e zombarias. Não ser afetado por nenhuma alteração no código é um comportamento característico de stubs, mas não de zombarias. Vamos começar com a definição do mock:

    Um objeto Mock substitui um objeto real sob condições de teste e permite verificar as chamadas (interações) contra ele mesmo como parte de um sistema ou teste de unidade.

    -A arte do teste de unidade

Basicamente, suas zombarias devem verificar o comportamento necessário de suas interações. Portanto, se sua interação com o banco de dados falhar após uma refatoração, seu teste usando a simulação também falhará. É claro que isso tem limitações, mas com um planejamento cuidadoso, suas simulações farão muito mais do que apenas "ficar sentado" e não "derrotar o objetivo dos testes de unidade".

ruhsuzbaykus
fonte
1

Fazer uma boa pergunta não é burro de forma alguma.

Vou abordar suas perguntas.

  1. O objetivo do teste de unidade não é testar o código que você já escreveu . Não tem noção de tempo. Somente no TDD você deve testar primeiro, mas isso não se aplica estritamente a nenhum tipo de teste de unidade. O objetivo é poder testar seu programa de forma automática e eficiente no nível de classe. Você faz o que precisa para chegar lá, mesmo que isso signifique alterar o código. E deixe-me contar um segredo - isso geralmente significa isso.
  2. Ao escrever um teste, você tem 2 opções principais para ajudar a garantir que seu teste esteja correto:

    • Varie as entradas para cada teste
    • Escreva um caso de teste que garanta que seu programa funcione corretamente e, em seguida, escreva um caso de teste correspondente que garanta que seu programa não funcione da maneira que não deveria

    Aqui está um exemplo:

    TEST(MyTest, TwoPlusTwoIsFour) {
        ASSERT_EQ(4, 2+2);
    }
    
    TEST(MyTest, TwoPlusThreeIsntFour) {
        ASSERT_NE(4, 2+3);
    }
    

    Além disso, se você estiver testando a lógica dentro do seu código (não as bibliotecas de terceiros), é perfeitamente bom que você não se preocupe com a quebra de outro código, enquanto estiver nesse contexto. Você está essencialmente testando a maneira como sua lógica envolve e usa os utilitários de terceiros, que é um cenário de teste clássico.

Depois de concluir o teste no nível da classe, você poderá testar a integração entre suas classes (os mediadores, pelo que entendi) e as bibliotecas de terceiros. Esses testes são chamados de testes de integração e não usam zombarias, mas implementações concretas de todas as classes. Eles são um pouco mais lentos, mas ainda assim muito importantes!

Yam Marcovic
fonte
1

Parece que você tem um aplicativo monolítico que faz tudo void main(), desde o acesso ao banco de dados até a geração de saída. Existem várias etapas aqui antes de começar o teste de unidade adequado.

1) Encontre um pedaço de código que tenha sido gravado / copiado e colado mais de uma vez. Mesmo que seja apenas string fullName = firstName + " " + lastName. Divida isso em um método, como:

private static string GetFullName (firstName, lastName)
{
    return firstName + " " + lastName;
}

Agora você tem um pedaço de código testável por unidade, por mais trivial que seja. Escreva um teste de unidade para ele. Enxágue e repita esse processo. Você acabará por acabar com uma carga de métodos agrupados de maneira lógica e poderá extrair várias classes dele. A maioria dessas classes será testável.

Como bônus, depois de extrair várias classes, é possível extrair interfaces delas e atualizar seu programa para conversar com interfaces em vez dos próprios objetos. Nesse ponto, você poderá usar uma estrutura de zombaria / stubbing (ou mesmo suas próprias falsificações feitas à mão) sem alterar o programa. Isso é muito útil quando você extrai as consultas do banco de dados em uma classe (ou muitas), porque agora você pode falsificar os dados que a consulta deve retornar.

Bryan Boettcher
fonte
0

Um cara esperto disse: "Se é difícil de testar, é provavelmente um código ruim". É por isso que não é ruim reescrever o código, poder unittest-lo. O código com dependências externas é MUITO DIFÍCIL de testar, representa um risco para o código e deve ser evitado sempre que possível e concentrado em áreas específicas de integração do seu código, fx. classes do tipo fachada / gateway. Isso facilitará muito a alteração do componente externo.

Morten
fonte