segunda-feira, 1 de agosto de 2011

Usando DUnit para implementar testes unitários - Parte 2

No último post fiz uma introdução sobre a criação de testes unitários com o DUnit. Hoje, vou explorar mais alguns recursos desta poderosa ferramenta, continuando com o exemplo da Calculadora, para isso, a calculadora vai ganhar um método para fazer divisão. Lembram do ritual? Red - Green - Refactor! Vejam a classe de teste, garanta que o teste falhe e então faça-o passar.
procedure TTestCalculadora.DivisaoDe6Por2DeveSer3;
var
 vCalculadora: TCalculadora;
begin
 vCalculadora := TCalculadora.Create;
 try
   CheckEquals(3, vCalculadora.Soma(6).DividePor(2).GetTotal, 'Divisão inválida');
 finally
   vCalculadora.Free;
 end;
end;
Fail!
function TCalculadora.DividePor(const AValor: Double): TCalculadora;
begin
  FValor := FValor / AValor;
  Result := Self;
end;
Success!



E o que acontece se eu tentar uma divisão por zero?
procedure TTestCalculadora.DivisaoDe6PorZero;
var
 vCalculadora: TCalculadora;
begin
 vCalculadora := TCalculadora.Create;
 try
   vCalculadora.Dividir(6, 0);
 finally
   vCalculadora.Free;
 end;
end;
Isso não parece bom. Rode o teste e veja que será lançada uma exceção do tipo EZeroDivide - se fosse um Integer lançaria uma EDivByZero - e o teste falhará.

Para esse tipo de teste, o DUnit oferece um recurso bastante útil, o ExpectedException, onde declaro que estou esperando uma exceção de determinado tipo. Se a exceção não for lançada, o teste falhará. Então vamos alterar o código e agora o mesmo deve passar.
procedure TTestCalculadora.DivisaoDe6PorZero;
var
 vCalculadora: TCalculadora;
begin 
 ExpectedException := EZeroDivide; 
 vCalculadora := TCalculadora.Create;
 try
   vCalculadora.Dividir(6, 0);
 finally
   vCalculadora.Free;
 end;
end;

Esses testes nos trouxeram a tona um problema de design em nossa classe de teste, a duplicação, não estamos sendo DRY. Em cada teste implementado estamos instanciando e destruindo o objeto Calculadora. Vamos extrair isso para outros métodos.

A classe TTestCase da qual estamos herdando possue dois métodos adequados para o que precisamos: SetUp e TearDown. O SetUp é onde preparamento o teste - instanciar a calculadora. O TearDown é onde limpamos a sujeira - destruir a calculadora. É importante ressaltar que esses métodos são disparados para cada teste da suíte.

Refatorei a classe de teste, e ela ficou assim. Rode os testes e veja se continua tudo funcionando.
type
  TTestCalculadora = class(TTestCase)
  private
    FCalculadora: TCalculadora;
  protected
    procedure SetUp; override;
    procedure TearDown; override;
  published
    procedure SomaDe2Mais3DeveSer5;
    procedure DeveResetarOValor;

    procedure DivisaoDe6Por2DeveSer3;
    procedure DivisaoDe6PorZero;
  end;

implementation

uses SysUtils;

{ TTestCalculadora }

procedure TTestCalculadora.SetUp;
begin
  inherited;
  FCalculadora := TCalculadora.Create;
end;

procedure TTestCalculadora.TearDown;
begin
  FCalculadora.Free;
  inherited;
end;

procedure TTestCalculadora.DeveResetarOValor;
begin
  FCalculadora.Soma(1).Reset;
  CheckEquals(0, FCalculadora.GetTotal, 'Não resetou o valor');
end;

procedure TTestCalculadora.DivisaoDe6Por2DeveSer3;
begin
  CheckEquals(3, FCalculadora.Soma(6).DividePor(2).GetTotal, 'Divisão inválida');
end;

procedure TTestCalculadora.DivisaoDe6PorZero;
begin
  ExpectedException := EZeroDivide;
  FCalculadora.Soma(6).DividePor(0);
end;

procedure TTestCalculadora.SomaDe2Mais3DeveSer5;
begin
  CheckEquals(5, FCalculadora.Soma(2).Soma(3).GetTotal);
end;

Existem ainda varias outras opções de métodos de validação além do CheckEquals, alguns dos mais usados são
procedure CheckTrue(condition: Boolean; msg: string = ''); 
    procedure CheckFalse(condition: Boolean; msg: string = ''); 
    procedure CheckNotEquals(expected, actual: integer; msg: string = ''); 
    procedure CheckNotNull(obj: TObject; msg: string = ''); 
    procedure CheckNull(obj: TObject; msg: string = ''); 
    procedure Fail(msg: sTring; ErrorAddrs: Pointer = nil); 

O código fonte dos exemplos está disponível aqui. Até o próximo post, abraços e bons testes.

3 comentários:

  1. Parabéns pela colaboração, Fabricio. Sua contribuição é de grande importância a todos da comunidade Delphi.
    Até mais!

    ResponderExcluir
  2. Obrigado Paulo, fico feliz que tenha gostado. Fique ligado que em breve terá mais conteúdo novo.
    Abraços.

    ResponderExcluir
  3. Olá,
    Em uma plicação real e de grande porte, que foi implementada sem o uso adequado de OO qual seria a melhor forma para adicionar testes automatizados?
    Obrigado.

    ResponderExcluir