segunda-feira, 1 de novembro de 2010

Testes de Software - Inspeção de código e teste unitário

Fala pessoal, olha eu reacendendo a série sobre testes de software. No último post falei sobre testes de requisito. Agora é hora de falar do segundo lugar onde mais se encontram e corrigem defeitos: inspeção de código e testes unitários.

A inspeção de código

Este teste se caracteriza pelo fato de ser executado apenas por quem entende o código-fonte, quem vai testar precisa ser desenvolvedor também. Aqui cabem várias abordagens. Uma delas é quem fez o código é quem faz a inspeção, mas ao mesmo tempo que quem escreveu o código é a pessoa que mais o compreende, seus olhos também correm o risco de estar viciados naquilo, então uma outra abordagem é um desenvolvedor que não fez o código que o inspecione. Uma terceira é revisar em grupo. Acho esta última a mais segura.

Note que a inspeção de código é uma revisão, um highlevel checkout, você lê e tenta achar erros óbvios de lógica ou gramática/digitação. Não mais que isso. Existem outros métodos mais complexos e formais de verificação. A inspeção é importante porque é rápida em detectar erros grosseiros (ou nem tanto).

Testes unitários

Testes unitários são o teste de comportamento de uma função, de um método ou de um conjunto intimamente relacionado de funções ou métodos. Eles são a base dos testes dinâmicos (aqueles realizados executando de fato o programa), e por serem altamente específicos, aumentam a confiabilidade dos testes.

Eles geralmente são automatizados, ou seja, você escreve um programa para executá-los.

Vou me estender no assunto, porque a automação de testes é algo válido para muitos outros tipos de teste (funcionais, cobertura de código, segurança, performance...).

Vamos supor que você tenha um código como:
<php
function soma($a, $b) {
return ($a + $b);
}
?>


VocÊ pode testar esse código com uma função similar a:
<php
function testSoma() {
$source = array(
array(1,1,2),
array(0,1,1),
array(1,0,1),
array(0,-1,-1),
array(-1,1,0),
array('non', 'sense', "??")
);

$i = 1;
foreach ($source as $unit) {
if (soma($unit[0], $unit[1]) === $unit[2]) {
echo "Test $i OK
\n";
} else {
echo "Test $i FAIL. Dataset: " . print_r($unit, TRUE) . "
\n";
}
$i++;
}
}

testSoma();
?>


Não se deixe enganar pela simplicidade dos exemplos. Automatizar testes traz uma série de vantagens. Uma delas delas é livrar o código principal de echos e prints. Mantendo a integridade do código e separando os conceitos. Sempre que você sentir a necessidade de checar de alguma forma qual o estado atual do programa, escreva um teste.

Uma outra vantagem é a possibilidade de reuso. Se você alterar algo no código, poderá executar a mesma bateria de testes, sem precisar reescrever.

E uma última vantagem com o exemplo que dei, é um conceito bastante poderoso e utilizado em automação de testes: os datasources. Eles permitem que você mude o conjunto de inputs, sem alterar a função verificadora.

No exemplo utilizei um array, escrito à mão mesmo. Porém, em ambientes de produção, você pode criar seus datasources em planilhas ou bancos de dados, o que facilita tanto a criação como a manutenção.

Existem vários frameworks disponíveis para realizar testes unitários. Aqui está um ótimo tutorial sobre o uso de frameworks de teste unitário.

OK, agora vamos dissecar um pouco mais o exemplo que dei acima. Temos uma função a ser testada, uma função para testar que contém um datasource e um loop que verifica e imprime o resultado dos testes. Para ambientes de produção ou cujo volume de testes se torne alto o suficiente, podemos e devemos refatorar deixando as responsabilidades divididas. O esquema muito utilizado é o seguinte:

-> uma função para obter o datasource
-> uma função de logging, para registrar os resultados
-> uma função que faça o papel de juntar tudo (chama a função, itera sobre os dados do data source e registra o resultado).

Com essa estrutura fica fácil manter os inputs e controlar os resultados.

Voltando ao Unit Test. O Unit Test é a base dos testes dinâmicos. A maioria dos bugs é encontrada aqui, porém vale ressaltar que mesmo softwares que passam pelo unit test podem conter bugs, isto porque o unit é focado na parte e não no todo. Assim, nos testes funcionais e de integração podem aparecer bugs novos que não poderiam ter sido detectados antes. Esta questão passa muito mais por design e por arquitetura do que propriamente a funcionalidade específica de um módulo, método ou classe.

Agora um exemplo mais real e prático de Unit Test. Vamos supor que você tenha um processo de login no seu site. Um usuário fornece nome e senha, e você tem uma função que verifica a validade e loga o usuário. Para efeitos de simplicidade:

<php
/*
retorna TRUE se a combinação é válida
retorna FALSE se a combinação não é válida
*/
function isValidUser($username, $pass) {
// código que checa as condições
}
?>


O seu datasource (viu, se você tiver as ferramentas/métodos certos para reutilizar, 95% do seu trabalho vai ser focar apenas nos data sources) será mais ou menos assim:

usernamepassexpected
user1 pass1 TRUE
user1 '' FALSE
user1 inválido FALSE
'' pass1 FALSE
inválidopass1 FALSE
user1 pass2 FALSE
'' '' FALSE
inválidoinválido FALSE

Legenda:
user1 = username 1
pass1 = senha correspondente ao user1
'' = vazio
inválido = username/password não existente nos registros
pass2 = senha correspondente ao user2

Bem, espero que o conceito e a importância dos unit tests e da automação tenham ficado claros neste post. Até a próxima.
----------- keepReading

Um comentário: