1. Testes de Unidade – intuição
Por Lucas Dias
Testes estruturais são voltados para implementação, diferentemente dos
funcionais que estão relacionados à especificação. O tipo de teste estrutural mais
popular são os testes de unidades, que são úteis para aumentar a confiança de que o
código funciona da maneira esperada. Para tal, são fornecidos dados válidos e dados
inválidos através da entrada e saída (input e output) para avaliar qual o comportamento
do código. Além disso, por unidade entendemos o menor componente testável de
uma aplicação, uma área bem pequena. Por exemplo, no caso do sistema de cadastro
do gmail, poderia ser testar se aceita cadastro com um e-mail sem @. Isso seria apenas
uma possibilidade.
Nesse contexto de possibilidades, podemos trazer à tona o conceito de
cenários, que seriam as possibilidades de testes para uma dada funcionalidade. A título
de exemplo, poderíamos investigar a tentativa de realizar um cadastro sem fornecer
um nome, e-mail, senha ou qualquer outra informação relevante e constatar como o
sistema lida com isso.
No que diz respeito os cenários, a criação de testes para ele é importante
porque permite alcançarmos uma boa cobertura, que significa a porcentagem daquele
código está sendo verificada. Quanto mais alta a cobertura, maior a nossa confiança de
que o código está adequado, muito embora seja sempre uma confiança, nunca uma
certeza, pois cobertura não garante corretude. Além desse fato, temos a existência de
variados tipos de coberturas, por exemplo: de caminhos (uma sequência de instruções
possíveis sendo executadas), instruções (passar por cada linha de código pelo menos
uma vez), gramática (uso de palavras reservadas da linguagem), entre outras. A
cobertura mais importante é a de caminhos, tendo em vista que representa um
uso do sistema mais próximo do real. Ela, por si só, já garante a cobertura de
instruções, já que se passou por todos os caminhos, passou por cada linha.
Ainda dentro de cenários, temos um conceito importante: o de
complexidade ciclomática ou complexidade de condições. Ela avalia a
quantidade de caminhos de execução a partir do código fonte, sendo que cada
condicional representa possibilidades de caminhos diferentes. Uma consequência direta
disso é que, quanto mais if-then-else, maior a complexidade ciclomática do
sistema, o que dificulta a realização do teste de todos os caminhos.
Em testes de unidade também temos a priorização, que serve para definir
quais dos cenários devem ser executados primeiro. Essa priorização pode ser em
razão do impacto do teste na funcionalidade. Por exemplo, se temos um cenário
crítico para determinada funcionalidade, certamente o impacto desse teste será
grande. Logo, ele deverá ser executado primeiro. Pode ser também em função do
tempo disponível para testes. Se tivermos pouco tempo, não haverá como testar
tudo, no caso de ter sido deixado os testes para fazer depois do código. Dessa forma,
entram em questão as funcionalidades que são base da aplicação. Nesse sentido, os
testes para essas funcionalidades seriam escolhidos num contexto em que há pouco
tempo disponível.
2. No que diz respeito aos processos ou disciplinas relativos ao
desenvolvimento envolvendo testes unitários, temos duas disciplinas centrais: TDD
(test-driven development), em que você escreve o teste primeiro (teste que falha,
faz código para o teste que falha passar e refatora... realiza esse ciclo até desenvolver
toda a aplicação) e o code first test second que, como o nome sugere, escreve-se
primeiro o código e depois os testes.