Testes de Software (em Perl)

Manuel Silva

Sonaecom

Com esta apresentação pretende-se transmitir uma ideia geral acerca da escrita de testes de software. Não se pretende, com esta apresentação, transmitir conhecimentos teóricos acerca da matéria.

O material que se segue demonstra práticas adoptadas com sucesso em alguns projectos realizados pela equipa de PD-ISP da Sonaecom. As mesmas práticas podem ter de ser alvo de ajustes ou não se aplicarem de todo noutros cenários.

O que são testes de software?

Os testes de software destinam-se à detecção de falhas nas aplicações e facilitar a localização dos pontos de falha existentes em qualquer peça de software.

Os testes de software manuais, normalmente, consistem no confronto dos potenciais utilizadores da aplicação com a mesma e verificando as acções destes que causaram um comportamento anómalo ou más interpretações. Problema: implica que a aplicação já é usável, o que implica já ter todo o software e módulos auxiliares funcionais. Nestes testes são, essencialmente, avaliadas as interfaces de interacção da aplicação.

Os testes automáticos são corridos por máquinas, destinam-se a verificar o comportamento de funcionalidades específicas e podem existir ainda antes de haver uma aplicação funcional. Alguns paradigmas de programação defendem a escrita total de testes antes de qualquer processo de implementação.

Testes de Software automáticos

O principal objectivo da escrita de testes de software é optimizar a qualidade do software. Este processo de optimização tem várias fases:

  1. Minimização do número de bugs na aplicação
  2. Garantir que novos desenvolvimentos não afectam comportamentos existentes
  3. Ser mais fácil localizar a origem de um problema

O que se deve testar?

Quando se fala na escrita de testes de software, a tendência de alguns programadores é a de escrever cenários que contemplaram de antemão no código que produziram. O problema está sempre nos casos que não conseguiram antever!

Isto não quer dizer que devemos ser todos paranóicos e fazer todos os testes possíveis até aos limites do hardware que dispomos... até porque vai haver sempre alguém com melhor hardware e que irá conseguir quebrar a nossa aplicação! :D

Numa função que recebe números inteiros de 0 a 20 (avaliações, por exemplo), o que é que se deve testar? Depende do código da função! Partindo do pressuposto que a função é linear e que o processamento é o mesmo independentemente do valor, basta testar 3 cenários válidos:

  • O limite inferior: 0
  • O limite superior: 20
  • Um qualquer valor do intervalo: 12 (por exemplo)
Mas também temos de ter a preocupação de testar valores inválidos:
  • A quebra do limite inferior: -1
  • A quebra do limite superior: 21
  • String em vez de Inteiro: "dezasseis" (por exemplo)
  • Real em vez de Inteiro: 10.7
  • Real E limite inferior pela esquerda: -0.01
  • Real E limite inferior pela direita: 0.01
  • Real E limite superior pela esquerda: 19.99
  • Real E limite superior pela direita: 20.01

As linguagens fortemente tipadas reduzem alguns destes cenários de erro.

Quando se devem escrever testes?

Quando se devem correr os testes?

Exercício - enunciado

Exercício - especificação

O processo de especificação consiste na descrição do comportamento do método. Depois deste processo deve ser possível escrever um protótipo do código.

Ficheiro Validator.pm:

package Validator;

use strict;
use warnings;

sub new {
    my $class = shift;
    my %args  = @_;

    return bless {
        'debug' ? ($args{'debug'} ? 1 : 0),
    }, $class;
}

sub email {
    my $self = shift;
    my %args = @_;

    my $address   = $args{'address'};
    my $mandatory = $args{'mandatory'};

    return 0;
}

1;

Exercício - escrita de teste

O nosso teste tem de começar pelo mais básico: o módulo (também conhecido como biblioteca) carrega correctamente? Quando estamos perante aplicações compiladas estaticamente (static-linked, o binário gerado contém todo o código necessário para a execução da aplicação), este problema não se põe mas nas linguagens interpretadas e nas aplicações com ligações dinâmicas (dynamic-linked, o binário referencia outros binários existentes no sistema), pode ocorrer que o módulo referenciado esteja corrompido e, portanto, nem sequer seja possível usá-lo.

Depois de termos garantias que o módulo carrega com sucesso, se estivermos a falar de programação orientada a objectos, temos de verificar que o objecto instanciado é do tipo que esperamos.

Após estas verificações iniciais, os testes que pretendemos fazer podem avançar. Devemos dividir os testes em secções, neste caso optei por dividir por casos de sucesso e casos de insucesso mas podia dividir em casos com nome da mailbox válido, nome da mailbox inválido, domínio válido, domínio inválido... Qual estrutura que faça sentido para as pessoas que vão trabalhar no projecto é válida.

Ficheiro verify_email.t:

#!/usr/bin/perl

use strict;
use warnings;

use Test::More qw(no_plan);

# Verificar se o módulo está acessível e é carregado correctamente.
use_ok('Validator');

# Instanciação "normal" de um objecto (tal como é feita na aplicação que usa o
# módulo
my $v = Validator->new('debug' => 0);

# Verificar se o objecto criado é do tipo correcto.
is(ref($v), 'Validator', 'Objecto do tipo correcto');

note('Casos validos');

Exercício - implementação

Ficheiro Validator.pm:

package Validator;

sub new {
    my $class = shift;
    my %args = @_;

    return bless {
        'debug' => ($args{'debug'} ? 1 : 0),
    }, $class;
}

sub email {
    my $self = shift;
    my %args = @_;

    if ( defined($args{'address'}) && length($args{'address'}) ) {
       if ( $args{'address'} =~ /^[a-zA-Z0-9]\w*(?:[\.-]\w+)*\@([a-zA-Z0-9]+(?:[\.-][a-zA-Z0-9]+)*)$/ ) {
           my $domain = $1;
           if ($self->{'debug'}) {
               print STDERR "Domain: $domain\n";
           }
           return 1;
       }
    }
    elsif ( !defined($args{'mandatory'}) || !$args{'mandatory'}) {
        return 1;
    }
    return 0;
}

1;

Ficheiro verify_email.t:

#!/usr/bin/perl

use strict;
use warnings;

use Test::More qw(no_plan);

use_ok('Validator');

note('Verificar metodos existentes no modulo');
can_ok('Validator', 'email');

note verificar se objectos sao instanciados correctamente
my $v = Validator->new('debug' => 0);

isa_ok($v, 'Validator');

note('Casos validos');
is($v->email('address' => ''), 1, 'E-mail vazio mas nao obrigatorio');
is($v->email('address' => 'omeunome@omeudominio.pt'), 1, 'Endereco valido');
is($v->email('address' => 'o.meu.nome@omeudominio.pt'), 1, 'Endereco valido com pontos na mailbox');
is($v->email('address' => 'o.meu-nome@omeudominio.pt'), 1, 'Endereco valido com pontos e tracos na mailbox');
is($v->email('address' => 'o.meu-nome_1980@omeudominio.pt'), 1, 'Endereco valido com _ e algarismos');
is($v->email('address' => 'o.meu-nome_1980@sub.dominio.omeudominio.pt'), 1, 'Endereco valido com varios sub-dominios');
is($v->email('address' => 'o.meu-nome_1980@o-meu-dominio.pt'), 1, 'Endereco valido com dominio com tracos');

note('Casos invalidos');
is($v->email('address' => '', 'mandatory' => 1), 0, 'E-mail vazio mas obrigatorio');
is($v->email('address' => 'omeunome'), 0, 'Mailbox indicada mas nao tem arroba nem dominio');
is($v->email('address' => 'omeunome@'), 0, 'Mailbox indicada, arroba presente mas sem dominio');
is($v->email('address' => '@omeudominio.pt'), 0, 'Dominio valido mas sem nome de mailbox');
is($v->email('address' => 'omeunome@omeu@dominio.pt'), 0, 'Mais que 1 arroba');
is($v->email('address' => 'omeunome.@omeudominio.pt'), 0, 'Mailbox termina com ponto');
is($v->email('address' => 'omeunome-@omeudominio.pt'), 0, 'Mailbox termina com traco');
is($v->email('address' => '.omeunome@omeudominio.pt'), 0, 'Mailbox comeca com ponto');
is($v->email('address' => '-omeunome@omeudominio.pt'), 0, 'Mailbox comeca com traco');
is($v->email('address' => 'omeu.-nome@omeudominio.pt'), 0, 'Mailbox com dois caracteres non-word seguidos');
is($v->email('address' => 'omeunome@o_meu_dominio.pt'), 0, 'Dominio com _');
is($v->email('address' => '_o.meu-nome_1980@sub.dominio.omeudominio.pt'), 0, 'Mailbox a comecar por _');
is($v->email('address' => 'omeunome@omeudominio.pt.'), 0, 'Dominio termina com ponto');
is($v->email('address' => 'omeunome@omeudominio.pt-'), 0, 'Dominio termina com traco');
is($v->email('address' => 'omeunome@.omeudominio.pt'), 0, 'Dominio comeca com ponto');
is($v->email('address' => 'omeunome@-omeudominio.pt'), 0, 'Dominio comeca com traco');
is($v->email('address' => 'omeunome@omeu.-dominio.pt'), 0, 'Dominio com dois caracteres non-word seguidos');
is($v->email('address' => 'omeunome@omeu_dominio.pt'), 0, 'Dominio com _');

Agradecimentos

Testes de Software (em Perl)

Fim

Manuel Silva

msilva@portolinux.net