Testes Unitários

O que é um Teste Unitário?

Testes Unitários são programas de software escritos para exercitar outros programas de software (chamado Código em Teste ou Código de Produção) com condições prévias específicas e verificando o comportamento esperado.

Os testes unitários geralmente são escritos na mesma linguagem de programação que o seu código em teste.

Cada teste unitário deve ser pequeno e testar apenas um código limitado de uma funcionalidade. Os casos de teste geralmente são agrupados em Grupos de Testes ou Provas de Testes. Existem muitos frameworks de testes unitários open source. Os populares geralmente seguem um padrão xUnit inventado por [Kent Beck] (http://c2.com/cgi/wiki?KentBeck “Kent Beck”), por exemplo, JUnit para Java e CppUTest para C/C ++ .

Testes unitários também devem ser executados muito rápido. Normalmente, esperamos executar centenas de casos de testes unitários dentro de alguns segundos.

Por que Testes Unitários?

O objetivo do teste unitário não é encontrar erros. É uma especificação para os comportamentos esperados do código em teste. O código em teste é a implementação para os comportamentos esperados. Portanto, o teste unitário e o código em teste são usados ​​para verificar a exatidão uns dos outros e se protegerem. Mais tarde, quando alguém alterou o código em teste e mudou o comportamento esperado pelo autor original, o teste falhará. Se o código estiver coberto por um teste unitário razoável, você pode manter o código sem quebrar a funcionalidade existente. É por isso que Michael Feathers define código legado como código sem teste unitário.

O objetivo do teste unitário é proteger o que implementamos mais que encontrar defeitos, como as âncoras fincadas por um alpinista ao longo da rocha. Essas âncoras o ajudam a proteger o que ele conseguiu.

Propósito de um Teste Unitário

O objetivo do teste de unidade pode ser resumido como:

  • Facilita mudanças
    • Protege os comportamentos decididos pelos programadores anteriores. Então as pessoas podem mudar o código sem quebrar as funcionalidades existentes.
  • Simplifica a integração
    • O teste unitário testa as unidades básicas do programa, as funções e as classes. Isso garante que as unidades básicas funcionem como esperado. Quando essas unidades são integradas juntas, podemos separar os problemas de integração (os problemas de acoplamento) dos problemas internos da unidade (os problemas de coesão).
  • Documentação
    • Teste unitário bem escrito pode ser usado como documentação para a funcionalidade do código em teste. O teste unitário contém informações normalmente que você não consegue encontrar a partir do código em teste, por exemplo, o propósito de design da programação original de quem escreveu o código e como o código deve ser usado. Teste unitário como documentação, ao contrário de outras documentações tradicionais, não “mente”. Porque se mentir, o teste falharia. E isso indica que o teste ou o código estão errados.
  • Ferramenta de design
    • Teste unitário também é uma ferramenta de design importante. O teste unitário exige a compatibilidade com o código. Fácil de testar geralmente significa fácil de usar. Assim, o teste unitário poderia ser usado para garantir que o design considere a perspectiva de uso, em vez de apenas na perspectiva da implementação. O código testável precisa de uma melhor modularidade e menos dependências. Então o teste unitário pode facilmente tomar uma pequena parte do código em teste (uma “unidade”) sem cuidar da quantidade esmagadora de dependências. Assim, o teste unitário pode ser usado para garantir que o design tenha “alta coesão, baixo acoplamento”.

Por que Nível de “Unidade”?

“Sim, é importante usar testes automatizados para proteger as funcionalidades existentes. Mas por que precisa estar no nível de unidade?”

Você pode se perguntar. Por que não usamos testes completos de sistema operacional ou de sistema para proteger o programa?

Custo total de propriedade - O teste unitário é baseado no nível de abstração da linguagem de programação. É apenas um código que usa outro código. Não precisa ser executado no mesmo ambiente que a produção. Para a linguagem de programação compilada, nem precisa usar o mesmo compilador que a produção. O custo de criação e execução do teste de unidade é muito baixo. Se projetado adequadamente, o custo de manutenção também é muito baixo. Você não pode obter o mesmo nível de confiança de um caso de teste unitário bem sucedido, como você pode obter de um teste funcional. Você precisará de muitos casos de teste unitários pequenos para obter aproximadamente o mesmo nível de confiança. Mas o custo desses casos de teste unitários pequenos ainda é muito menor do que possuir alguns casos de teste funcionais.

Se uma base de código de software não obteve qualquer teste unitário nos últimos 2 anos, haverá um custo extra para a aplicação de teste unitário para esta base de código. O custo vem principalmente de duas fontes:

  1. O custo da aplicação de um framework de teste para o projeto de código. Isto é relativamente mais fácil para linguagens de programação dinâmicas, como Python, Ruby ou Javascript. Geralmente, também é trivial para o projeto Java e C #. Pode ser bastante complicado para o projeto C / C ++. Não importa se é fácil ou difícil, este é apenas um investimento único.
  2. A base de código existente não é testável. O código foi projetado sem considerar a testabilidade. A aplicação do teste unitário para este tipo de base de código geralmente envolve a melhoria do projeto atual. Fazer isso não só aumenta o custo de criação de teste, mas também tem custo potencial de introdução de novos erros, alterando o design. Então, aplicar o teste unitário à base de código existente deve ser combinado com outros trabalhos que precisam da mudança do código em teste - quando você precisa alterar essa parte do código independentemente.

Qualidade Interna Versus Qualidade Externa - Teste automatizado de alto nível, como teste funcional e teste do sistema, verifica a qualidade externa do software. A qualidade externa significa o bom funcionamento do software de acordo com o requisito. Teste unitário não é tão efetivo como o teste funcional na proteção da qualidade externa. Por outro lado, o teste unitário garante algumas das qualidades internas do software. Qualidade interna aqui significa testabilidade do código e quão bem o código está protegido. Um design testável é, em geral, um bom design. Outros níveis de teste automatizado não podem servir este propósito, tão bem como teste unitário.

Qualidade do feedback Quando você passou por um teste funcional, você poderia estar muito confiante sobre a funcionalidade que você acabou de testar. Mas quando você achou que ele falhou, geralmente você precisa fazer alguma depuração para ver o que está errado. O teste unitário pode fornecer informações mais precisas sobre o que está funcionando e o que está quebrado.

Teste unitário está no nível de abstração da linguagem de programação. Ao executar o teste unitário, tudo deve acontecer apenas dentro da CPU e da memória. E cada caso de teste deve ser pequeno. Portanto, ele deve rodar muito rápido. Normalmente, você pode executar centenas de testes unitários em alguns segundos. Incluindo a compilação ou outro tempo de preparação, todo o processo de execução de testes unitários deve demorar menos de 1 minuto.

Teste de unidade também deve ser repetitivo. Se nada mudar, o teste de unidade deve sempre retornar o mesmo resultado.

Se o teste da unidade for muito rápido e repetível, os programadores podem executá-lo sempre que quiserem, por exemplo a cada poucos minutos. O teste unitário fornecerá continuamente feedback de qualidade ao programador. Então o programador pode acompanhar um progresso constante e se concentrar em coisas mais importantes em vez de gastar muita energia em questões triviais.

Uma estrutura de teste automatizada razoável deve ser como uma pirâmide. Na parte inferior estão muitos casos de testes unitários. No meio, há muito menos casos de teste de nível de integração. No topo, há ainda menos testes de nível funcional/sistema.

Equívocos Comuns dos Testes Unitários

Teste unitário não é tão importante quanto o código de produção

É verdade que, no final, é o código de produção que faz o produto. Mas a maioria dos produtos de software tem ciclos de vida evolutivos. O código não é estático. Isso muda ao longo do tempo. O código sem teste unitário não possui a proteção necessária ao ser alterado. Testes unitários também contém informações importantes que não estão incluídas no código de produção.

Portanto, o teste unitário é tão importante quanto o código de produção. Eles deveriam estar no mesmo repositório SCM. Eles devem seguir o mesmo padrão de codificação que o código de produção.

Teste unitário é feito somente por engenheiros de teste

O objetivo do teste unitário não é encontrar erros. Tecnicamente, ele checa em vez de testar se o código em teste implementou o comportamento pretendido pelo programador que o projetou. Portanto, a escolha razoável é apenas deixar que o mesmo programador escreva o teste e o código em teste.

Também é encorajado a ter duas ou mais pessoas para fazerem a programação juntas. Elas escrevem o teste unitário e o código em teste juntos. Existem muitas maneiras divertidas de programação em par. Você pode encontrar mais informações na seção Test-Driven Development.

Você pode escrever teste unitário sem alterar o código em teste

Isso muitas vezes não é verdade. Se o código não tiver uma boa testabilidade, você ainda pode escrever o teste de unidade para isso tecnicamente. Mas o teste unitário escrito para código não testável geralmente é muito difícil de manter e entender. Portanto, não faz muito sentido ter isso.

O segredo do teste unitário não é sobre escrever testes, mas escrever código testável sob teste. Queremos um código testável e um teste fácil, que é um ganha-ganha. Nós não queremos código não testável e código difícil de manter, o que é um perde-perde.

Posso adicionar teste de unidade mais tarde

Bem, tente perguntar aos escaladores de rocha para colocar suas âncoras mais tarde.

Bons padrões de testes unitários

Nenhuma notícia é boa notícia

Se o teste passar, ele deve apenas sinalizar um OK (e talvez alguns pontos para mostrar o progresso). Nenhuma outra informação.

Regra geral:

Nenhuma intervenção humana deve ser necessária para se preparar para o teste, executar os casos de teste ou verificar o resultado.

E quando ele falhar, ele deve fornecer informações precisas. O objetivo é limitar a quantidade de tempo gasto na depuração quando o teste falhar.

Organizar, Agir, Afirmar

Um bom padrão a seguir em um teste unitário é “OAA”: Organizar, Agir e Afirmar.

Se você pode facilmente encontrar esse padrão em cada um de seus casos de teste, seus testes devem ser fáceis de entender, e eles devem ser bastante específicos e direto ao ponto. Um caso de teste unitário deve testar apenas uma coisa. Portanto, deve haver apenas um conjunto de OAA em um caso de teste. Um caso de teste não deve ser muito longo (mais de 10 linhas de código) se seguir o padrão OAA.

import unittest
class TestGroupForTextWrapping(unittest.TestCase):
    
    def test_should_have_no_wrapping_when_string_length_is_5_and_line_width_is_10(self):
        # Arrange:  Arrange all necessary preconditions and inputs. 
        wrapper = TextWrapper(width=10)
        
        # Act:  Act on the object or method under test. 
        wrapped = wrapper.wrap("a" * 5)
        
        # Assert:  Assert that the expected results have occurred. 
        self.assertEqual(["a" * 5], wrapped)

Behaviour Driven Development (BDD)

Similar ao padrão OAA, o BDD usa três outras palavras-chave para especificar cada caso de teste: Dado, Quando e Então. (Você também pode usar e como outra palavra chave.)

Dado a largura do seletor de texto definida como 10
E usando '-' como conector de palavra
Quando o tamanho do texto selecionado for inferior a 10
Então o texto não deve ser selecionado

Como você pode ver, “dado-quando-então” mapeia para “organizar-agir-afirmar” muito bem. Ambos simplesmente definem um estado de transição de uma máquina de estado finito (FSM). Você pode encontrar mais sobre isso no artigo do Uncle Bob’s. Algumas diferencás:

  • BDD é mais “fora-de-entrada”, o que significa que enfatiza mais o comportamento externo
  • Com BDD, você precisa definir um linguagem específica do domínio para escrever suas especificações de teste. Por isso, geralmente você precisará de uma estrutura diferente. Um exemplo para Python é behave.

A Regra de Ouro de um Teste Unitário

Em geral, uma boa regra para o caso de teste unitário é:

Cada caso de teste unitário deve ter um alcance muito limitado.

De modo a:

  • Quando o teste falhar, nenhuma depuração é necessária para localizar o problema.
  • Os testes são estáveis ​​porque as dependências são simples.
  • Menos duplicação, mais fáceis de manter.

Não há segredo para escrever um bom teste unitário. Para escrever um bom teste unitário, você precisa criar um design fácil de testar.