Caixa Branca
A técnica de teste de caixa branca (white-box testing) tem como objetivo avaliar a estrutura interna dos elementos de programação (técnica estrutural) durante a etapa de Codificação nas fases de testes unitários (unit testing) - responsáveis por auxiliar e avaliar no desenvolvimento dos cenários de uma unidade de código (estrutura interna do programa) e/ou testes de integração ou componentes (integration testing or component testing) - responsáveis por avaliar a comunicação entre unidades e recursos, principalmente a camada de infraestrutura e códigos de terceiros (verificação).
Durante o desenvolvimento de software, os testes auxiliam nas técnicas de:
- Projeto de Programa Sistemático - Systematic Program Design (SPD);
- Desenvolvimento Guiado por Testes - Test Driven Development (TDD);
- Refatoração - Refactoring;
- Cobertura de Testes - Test Coverage;
- Integração Contínua - Continuous Integration (CI); e
- Testes de Regressão - Regression Testing.
Outras características apresentadas são:
- Implementação: das funcionalidades do cenário;
- FeedBack: notificação dos problemas no código;
- Bugs: previne e corrige os problemas; e
- Debugging: reduz a necessidade de uso.
O Teste
O teste utiliza as tarefas do cenário para a composição de um conjunto de casos de teste durante a etapa de Codificação, assim descrevendo o que cada estrutura de programação deve fazer para garantir a verificação da Arquitetura.
Na elaboração dos casos de teste utiliza uma identificação e um nome descritivo para cada variação de uma específica funcionalidade do cenário - formando uma execução atômica, assim definindo seus dados de testes como entrada e sua saída esperada na execução, conforme o estado inicial.
{ ID; Nome do Teste; Entrada; Saída Esperada; Estado Inicial }
Exemplo:
| ID | Nome do Teste | Entrada | Saída Esperada | Estado Inicial |
|-----+-----------------+------------+----------------+---------------------------|
| 1 | Testando o A... | "variável" | "variável" | Objeto Contador existente |
| | | 20 | 123 | |
|-----+-----------------+------------+----------------+---------------------------|
| 2 | Testando o B... | N/A | "variável" | Objeto Contador com valor |
| | | | 345 | padrão de 345 |
|-----+-----------------+------------+----------------+---------------------------|
| ... | | | | |
Os casos de teste são organizadas em quatro (4) fases/etapas, sendo:
- setup ou arrange: construção e configuração dos estados (entradas, condições e limites) requeridos pelo caso de teste;
- exercise ou act: execução das funcionalidades do código sobre teste;
- verify ou assert: avaliação dos estados e comportamentos do código, comparando os resultados obtidos com as saídas esperadas; e/ou
- teardown ou after: liberação dos recursos utilizados pelo código.
Importante o cuidado para não violar o Princípio da Responsabilidade Única - Single Responsibility Principle (SRP) durante a implementação do código sobre teste, pois deve-se testar apenas as funcionalidades expostas (Tell, Don't Ask) sem o encadeamento de mensagens (Law of Demeter), a proposta básica é que cada Classe de Equivalência deva possuir um caso de teste único e suficiente, ou seja, a inclusão de pequenos incrementos entre testes conforme suas condições limites.
O Que Testar?
As tecnologias de desenvolvimento possuem elementos de programação mínimos legíveis aos testes, sendo:
- Java: Classes e Métodos;
- Haskell: Funções;
- Lisp: Funções, Comandos e Macros; e
- C: Funções.
Documentação
Os testes formam a documentação viva do código, em que são de extrema importância seus identificadores (nomes dos testes) serem pensadas em nível sintático (relação entre os termos) e semântico (sentido de ideias) com o que será testado.
Convenções
As seguintes convenções para os identificadores são utilizados:
Casos de Testes:
- NameTest: testes unitários;
- NameIntegrationTest: testes de integração; e
- NameSystemTest: testes de sistema (ver Caixa Preta).
Testes:
- Action: utiliza uma ação avaliável do teste, considerando o resultado e/ou condição da operação, exemplo: adicionaUmUsuarioEmUmaListaVazia;
- Given-When-Then: relaciona o contexto, ação e resultado do teste, exemplo: dadoXQuandoAcontecerYEntaoFacaZ; e
- Helper Objects: nomeia os comandos em modo imperativo e as avaliações em modo indicativo, exemplo: dispareUmSinalDeFalha e mostraAOcorrenciaDaFalha.
Dicas
Os identificadores tendem a ficarem longo, assim dificultando em sua leitura - uma dica é utilizar underscore sobre o camel case:
requisitarUmaMensagemComRepositorioVazio
para requisitar_uma_mensagem_com_repositorio_vazio
Feedbacks
Os excessos no código de teste (padrões de feedback) diz o quanto estável é a unidade de programação:
- exercícios das responsabilidades: baixa coesão;
- dublês das colaboradoras: alto acoplamento; e
- avaliação em colaboradoras: encapsulamento.
Complexidade ciclomática: métrica das ramificações existentes em uma unidade de programação, quanto maior o número, mais complexo e difícil de ser testado será o software (mais informações em Medindo a complexidade do seu código).
Ferramentas
As ferramentas mais utilizadas são:
- assert
- JUnit
- Hamcrest
- Mockito
O que pensar?
AAA[A] - organização e formatação dos casos de teste:
Arrange: monta/organiza o código com todas as entradas e pré-condições necessárias ao teste;
Act: exercita/executa o código sobre teste;
Assert: avalia os resultados obtidos do código com os esperados pelo teste;
After: finaliza os recursos utilizados no teste.
FIRST - propriedades de bons testes:
Fast: rápidos em sua execução, assim mais testes serão avaliados;
Isolated: isolados dos outros códigos, assim a verificação da falha será óbvia;
Repeatable: possui repetibilidade em qualquer ordem e tempo;
Self-validating: testes autoavaliáveis, automação em sua execução e avaliação;
Timely: em momento oportuno, escreva os testes antes do código.
R->BICEP - questões sobre os testes:
Right: os resultados estão certos?;
Boundary: as condições limites estão corretas? Essa é verificada através do CORRECT;
Inverse: os relacionamentos inversos (operações inversas) estão sendo utilizadas?;
Cross-Check: uso de checagem cruzada com outros métodos de mesmo significado estão sendo utilizadas?;
Error Conditions: as condições de erros estão sendo forçadas?;
Performance: o desempenho está dentro dos limites?.
CORRECT - as condições limites estão:
Conformance: com os valores conforme o formato especificado?
Ordering: com o conjunto de valores em uma ordem determinada?
Range: com os valores dentro da faixa (mínimo e máximo) especificada?
Reference: com alguma referência externa fora do controle do código?
Existence: com valor existente (nulo, zero, presente e etc...)?
Cardinality: com a quantidade de números suficiente? Os casos interessantes são Zero, Um e Muitos através da regra 0-1-n.
Time: com as temporizações corretas (sincronização, tempo, eventos, referência e etc...)?
Boas Práticas
- Identificação: ver Documentação.
- Refatoração: importante até mesmo para o código de teste.
- Baby Steps: começando pelo cenário de teste mais simples em pequenos incrementos, assim ganhando confiança e conhecimento do sistema.
- Test Data Builders: utilização do padrão de projeto (GoF) Builder para o processo de criação dos objetos para os cenários de testes - auxilia no problema de duplicação de código.
- Adapters: utilização para testes com métodos estáticos e códigos legados.
- Test Double: isolação e simplificação das funcionalidades com dependência externa, assim avaliando as características de estado e comportamento do objeto:
- Dummy: implementa uma versão simples do código, visando a compilação e/ou execução;
- Fake: simplifica (com atalhos e simulação) a utilização dos recursos e dependências necessários para o código de produção, útil para prototyping e spikes em memória;
- Stubs: implementa uma Query - responde com valores predefinidos (hardcoded), sem o uso de dados reais e livre de efeitos colaterais;
- Saboteurs: em edição;
- Mocks: registra e avalia as expectativas do comportamento da comunicação, simulando os recursos e dependências; e
- Spy: registra a utilização da comunicação - estatístico.
- Infraestrutura: a camada de infraestrututra (DAO, Messages...) é testada com testes de integração.
- Execute Around Method: utilização para testes com avaliação de exceção.
Test Smell
Código de teste também possui bad smells, tais como:
- Unnecessary Test Code (Código de Teste Desnecessário): o caso de teste é construído com código em excesso e/ou defensivo, sem significado e valor ao teste;
- Missing Abstractions (Falta de Abstração): exagero e/ou preciosismo nos detalhes sobre a organização e formatação do teste;
- Irrelevant Information (Informação Irrelevante): utilização de dados irrelevantes ao teste, tais como literais e sentinelas;
- Bloated Construction (Construção Inchada): complexidade na montagem dos elemento no teste;
- Multiple Assertions (Múltiplas Avaliações): excesso de responsabilidades no teste;
- Irrelevant Details in Test (Detalhes Irrelevante no Teste): uso de recursos desnecessários ao teste, tais como serviços transversais de segurança, logging e etc...;
- Misleading Organization (Organização Enganosa): falta de organização no código de teste, por não responder a questão AAA;
- Implicit Meaning (Significado Implícito): excesso de dados e informações aos elementos (constantes, variáveis, dados e etc...) do teste;
Exceções
@Test:expected em edição
@Rules em edição
Try-Catch em edição
EAM em edição