Wallace ReisPublicado em 01/01/2010
Teste de software é um processo de validação e verificação dos requisitos técnicos e das regras de negócio afim de obter informações sobre a qualidade de um produto ou serviço, como também com a intenção de encontrar falhas de implementação, garantindo assim que o software funcione como esperado.
Os diversos processos de desenvolvimento discorrem diferentemente sobre quem, quando e como produzir os casos de teste, porém todos ressaltam a sua importância no desenvolvimento de software.
O CPAN contém algumas centenas de módulos, frameworks e ferramentas para serem usados no desenvolvimento de testes. Encontra-se algumas sugestões neste artigo.
Os métodos de testes são tradicionalmente divididos em testes de caixa preta e caixa branca, os quais referem-se ao ponto de visão do engenheiro quando este está desenvolvendo os casos de teste.
Testes de caixa preta compreendem o conjunto de casos onde não se tem conhecimento (ou não é tomado em consideração) os detalhes internos de design e implementação. Este método é aplicável à todos os níves de teste, porém é mais utilizado nos de integração e aceitação.
Exemplo:
use strict;
use warnings;
use Test::More;
use Test::WWW:::Mechanize;
my $foo_data = {
name => 'MyFoo',
description => 'MyFoo é uma...',
};
my $mech = Test::WWW::Mechanize->new;
$mech->get_ok('/foo');
# entrada de dados
$mech->submit_form_ok({
with_fields => $foo_data,
button => 'submit',
}, 'form submit...');
# verificação após processamento
$mech->content_contains($foo->{$_}) for qw/name description/;
# ...
done_testing;
Testes de caixa branca (ou testes de estrutura) usam a pespectiva interna do software para projetar os casos de teste baseado nas estruturas de dados e implementação. Este pode ser usado nos níveis de unidade, integração e regressão.
Neste exemplo, vamos testar o controller Foo que supostamente responderia pelo caminho acessado no exemplo anterior:
use strict;
use warnings;
use Test::More;
use MooseX::Declare;
BEGIN { use_ok 'MyApp::Controller::Foo' }
my $app_class = class {
has stash => (is => 'rw', default => sub { {} });
around stash => sub {
my $orig = shift;
my $c = shift;
my $stash = $orig->($c);
if (@_) {
my $new_stash = @_ > 1 ? {@_} : $_[0];
confess('stash takes a hash or hashref') unless ref $new_stash;
foreach my $key ( keys %$new_stash ) {
$stash->{$key} = $new_stash->{$key};
}
}
return $stash;
};
};
my $ctx = $app_class->name->new;
my $foo_controller = MyApp::Controller::Foo->COMPONENT($ctx);
$foo_controller->foo($ctx);
is($ctx->stash->{'template'}, 'foo.tt');
# ...
done_testing;
Testes são agrupados por onde e quando eles são adicionados no processo de desenvolvimento, ou pelo grau de responsabilidade. Os diversos autores sobre testes definem diferentes níveis de teste (alguns mais, outros menos), dentre eles, cito:
Testes de unidade são geralmente usados para garantir que os vários componentes que compõem o software funcionam corretamente de forma isolada uns dos outros. Estes correspondem a seções do código, geralmente funções ou classes (ou um conjunto de classes) num sistema orientado a objetos.
Técnicas muito usadas neste nível são mock object e method stub.
A primeira consiste basicamente em simular objetos que tenham a interface requerida e o comportamento desejado pelo componente testado.
Exemplo:
package MyApp::Web::Form::Login;
use HTML::FormHandler::Moose;
use namespace::autoclean;
extends 'HTML::FormHandler';
has '_authenticate' => (
isa => 'CodeRef', is => 'ro',
required => '1', init_arg => 'authenticate',
);
has_field 'username' => (
type => 'Text', required => '1', label => 'Username',
);
has_field 'password' => (
type => 'Password', required => '1', label => 'Password',
);
has 'user_agent' => (
isa => 'Str', is => 'ro', required => 1
);
around process => sub {
my $orig = shift;
my $self = shift;
if ( $self->$orig(@_) ) {
if ( my $user = $self->_authenticate->($self->values) ) {
my $account = $user->obj;
$account->log_last_access($self->user_agent);
return $user;
} else {
return;
}
}
return $self->validated(0);
};
1;
use strict;
use warnings;
use Test::More;
use MooseX::Declare;
use MyApp::Web::Form::Login;
my $user_data = {
map { $_ => $_ } qw/username password/
};
my $user_class = class {
has [qw/username password/] => (
isa => 'Str', is => 'ro', required => 1
);
method obj { return shift }
method log_last_access { return }
};
my $form = MyApp::Web::Form::Login->new(
authenticate => sub {
my $credentials = shift;
my $check_creds = $credentials->{'username'} eq $user_data->{'username'}
&& $credentials->{'password'} eq $user_data->{'password'};
return $check_creds ?
$user_class->name->new(%$user_data)
: undef;
},
user_agent => 'MyUserAgent',
);
ok($form->field($_), "has a $_ field")
for qw(username password);
my $u_field = $form->field('username');
is($u_field->label, 'Username', 'check label for username field');
my $p_field = $form->field('password');
is($p_field->label, 'Password', 'check label for password field');
ok(!$form->process(params => {}), 'authentication failed for empty dataset');
ok(!$form->validated, 'form validation failed');
ok(!$form->process(params => {
map { $_ => q{} } qw/username password/
}), 'authentication failed for empty form');
ok(!$form->validated, 'form validation failed');
ok(!$form->process(params => {
username => 'foo',
password => 'bar',
}), 'authentication failed for wrong credentials');
ok($form->validated, 'form validation pass');
ok($form->process(params => $user_data), 'authentication pass');
ok($form->validated, 'form validation pass');
done_testing;
Veja também Test::MockObject.
A segunda (também chamada de apenas stub) é um pedaço de código usado para garantir presença de alguma outra funcionalidade programada.
Exemplo:
# uma função "foo" utiliza uma outra, chamada "get_session_id"
sub foo {
# ...
my $session_id = get_session_id();
}
# podemos então definir "get_session_id" com um valor de retorno válido
# sem se preocupar agora com sua real implementação
sub get_session_id { return 'm0y1i2d3' }
O nível de integração pode ser granularizado para se adequar ao conjunto de componentes, sistemas ou subsistemas que se deseja testar, com o objetivo de validar a interface e a interação entre eles.
Algumas vezes se faz necessário obter acesso ao objeto contexto do Catalyst
- durante o desenvolvimento dos testes de aplicações que usam este web framework.
Diferente do Test::WWW::Mechanize::Catalyst, o Catalyst::Test já nos fornece
este recurso por padrão através do método ctx_request
. Então, se a aplicação
usar Class::MOP ou for desenvolvida em Catalyst a partir da versão 5.8, pode-se
contornar tal carência da seguinte forma:
use strict;
use warnings;
use Test::More;
use Test::WWW::Mechanize::Catalyst 'MyApp';
my $ctx;
my $app_meta = MyApp->meta;
my %immutable_opts = $app_meta->immutable_options;
$app_meta->make_mutable;
$app_meta->add_after_method_modifier(
dispatch => sub { $ctx = shift }
);
$app_meta->make_immutable(%immutable_opts);
# ...
done_testing;
Um teste é classificado como de regressão quando se tem por objetivo encontrar falhas após uma mudança no código que já estava funcionando ou defeitos conhecidos e resolvidos que voltaram a aparecer.
Este nível compreende o conjunto de testes produzidos para execução antes da entrega do software ao usuário final ou logo após o termino de um ciclo de desenvolvimento conhecidos como smoke tests, ou executados pelo consumidor em ambiente de laboratório então conhecidos como aceitação do usuário.
Os testes de plataforma envolvem a verificação e validação do software nos diversos tipos de hardware, sistema operacional e SGBD (Sistema Gerenciador de Banco de Dados), e geralmente são conduzidos após o nível beta.
Caso a aplicação seja desenvolvida usando o Catalyst, os casos de testes obtidos
durante o processo de desenvolvimento podem ser usados em conjunto aos novos específicos
deste nível, modificando apenas a variável de ambiente CATALYST_SERVER
:
$ CATALYST_SERVER='http://localhost:3000/' prove -lr t
ou
$ CATALYST_SERVER='http://www.myapp.com/' prove -lr t
deste modo, os testes executarão no servidor externo.
Code coverage é uma medida usada em teste de software, a qual é obtida através da analise de todas as partes do código fonte que foram percorridas durante a execução dos testes, sabendo-se quão testado está o software.
Teste de software é um assunto tão extenso quanto importante, assim não é possível abranger toda sua dimensão aqui, mas espero ter feito uma boa introdução ao tema.
Agradecimentos a Thiago Rondon, pela insistência comigo para escrever este artigo e ajuda na revisão.
Por fim, lembre-se, desenvolva testes "antes tarde do que nunca"!
Wallace Reis <wallace@reis.org.br>