Borland DevCon - USA

Report #4
PreConference Tutorials
Test-driven Development with Delphi

Criado - 6 de Novembro de 2005 – 13:00 p.m. (local time)

Atualizado - 6 de Novembro de 2005 – 13:00 p.m. (local time)

Apresentação

Hoje pela manhã tive a oportunidade de assistir a uma excelente apresentação, ministrada por Charlie Calvert. Charlie é uma celebridade reconhecida mundialmente pela comunidade Delphi, escritor de vários livros (incluindo meu preferido, Delphi Unleashed), palestrante freqüente em vários eventos Borland e Delphi.

Neste PreConference aprendemos como criar testes como parte integrante do desenvolvimento de aplicações Delphi. O objetivo de Charlie foi mostrar como criar testes para aplicações e mantê-los, de forma simplificada.

Charlie primeiramente falou dos tipos de testes: Integration – integração; Functional – funcionalidade; Stress; Acceptance – testes de aceitação, para garantir que os requisitos dos clientes foram atendidos, por exemplo; Regression – para garantir que, mesmo após a edição de uma nova feature na aplicação, tudo continua funcionando normalmente; Unit Test – testes unitários podem ajudar você a verificar que seu código funciona corretamente, este tipo de teste foi o tópico principal a palestra.

Entre os objetivos dos testes unitários, podemos citar o feedback instantâneo: você faz uma mudança no seu programa, então roda seus testes, você tem imediatamente um retorno sobre o impacto da mudança na estabilidade do projeto. Pode também ajudar a melhorar a arquitetura da sua aplicação, encorajando a criação de pequenos, robustos e reutilizáveis módulos.

Testes unitários são também frequentemente utilizados junto ao recurso de Refactoring. “Refactoring é a técnica disciplinada de reestruturar um bloco de código existente, alterando sua estrutura interna sem afetar o comportamento externo” (Martin Fowler). Cada refactoring é uma pequena transformação aplicada ao código-fonte, visando melhorar sua legibilidade, performance, organização, adequação a um padrão etc. Porém, uma seqüência muito grande de refactorings pode produzir uma reestruturação significativa. Refactorings devem garantir que a aplicação funcione da mesma forma após a reestruturação, reduzindo chances do sistema ter graves falhas após as modificações e minimizando a chance de introdução de bugs. Refactoring está intimamente ligado a testes unitários, com isso, os testes unitários é que garantirão se o código continua eficiente e funcional após os refactorings. Segundo Charlie, Refactoring é impossível sem testes unitários.

E finalmente, testes unitários garantem que sempre novas versões do seu software funcionem corretamente, provando que seu código realmente “funciona”.

Existem basicamente duas ferramentas para Delphi destinadas a criação de testes unitários:

·         DUnit – para Delphi Win32;

·         NUnit – para .NET, suportando Delphi for .NET e C#.

Se o leitor lembrar, publicamos uma matéria especial sobre DUnit na edição 52 da Revista ClubeDelphi. Na edição 57, publicamos uma matéria sobre testes unitários na IDE do Delphi 2005, confira!

Uma grande vantagem é que a IDE do Delphi suporta agora a criação integrada de testes unitários dentro do mesmo ambiente, tanto com DUnit quanto NUnit (diferente do DUnit, que já vem por padrão, para usar o NUnit você deve marcar a respectiva opção durante a instalação do Delphi). Além disso, temos suporte integrado a Refactoring na IDE, tanto para Delphi Win32 quanto para Delphi for .NET.

Test-Driven Development: Red, Green, Refactor

 “Antes de escrever uma nova classe ou método, primeiro crie uma ou mais rotinas para testá-la”.

Suponha que você queria criar um código para abrir um arquivo, processar seu conteúdo e retornar certas partes de informação sobre o arquivo. De acordo com a “radical” afirmação anterior, a primeira coisa que você deve fazer (antes mesmo de criar a funcionalidade proposta) é criar um mais testes. Ou seja, você primeiro escreve código para testar o código real, então você escreve o código real da aplicação propriamente dita e roda a suíte de testes para verificar os resultados e se funciona.

Chamamos isso de: Red, Green, Refactor. Na maioria dos front-ends para criação de testes unitários, como DUnit e NUnit, um teste que falha é representado em vermelho (Red) e se teve sucesso, em verde (Green). Primeiro escreva os testes. Então escreva código somente para que seus testes compilem, de forma que rodem mais falhem. Este é o estágio “Red”. Você apenas escreveu o código da aplicação para que compilasse, mas não implementa nenhum procedimento ou funcionalidade. O próximo estágio é corrigir a implementação para que os testes “passem”. Se necessário, faça Refactoring para tornar seu código mais simples, limpo e reutizável. Agora rode seus testes novamente.

Basicamente esse é o princípio básico para criação de testes, conforme reforçou Charlie. Lembro-me de uma frase em um artigo que publicamos na ClubeDelphi, do ilustre Adail Muniz Retamal, que coloquei aqui novamente e resume muito bem o T.D.D.:

“Testar antes de codificar: essa é a lei da XP! Como? Vamos a uma metáfora: como você faria para ter certeza que acertará “na mosca” toda vez que lançar uma flecha? Fácil: lance a flecha em algum lugar (uma árvore, por exemplo) e pinte as marcas do alvo em volta do ponto onde a flecha cravou. É uma boa analogia com a Test-Driven Development (Desenvolvimento Direcionado por Testes).”

Tutorial

Charlie mostrou um exemplo prático usando o novíssimo Delphi 2006 e o DUnit. Se você está curioso de como pode criar testes unitários com o Delphi 2005/2006, fiz abaixo um pequeno tutorial semelhante ao que foi apresentado por Charlie, caso queira reproduzir em sua máquina.

No Delphi 2005, clique em File|New>VCL Forms Application – Delphi for Win32 para criar uma nova aplicação. Salve todos os arquivos do projeto, dando o nome de PrjCalc para o DPR.

Clique em File|New>Other>Delphi Projects>Delphi Files>Unit e salve o novo arquivo como “UCalculadora.pas”. Digite o código mostrado a seguir. Observe que ainda não implementamos o método Somar.

 

unit uCalculadora;

 

interface

 

type

  Calculadora = class

  public

    function Somar(x, y: Double): Double;

  end;

 

implementation

 

function Calculadora.Somar(x, y: Double): Double;

begin

 

end;

 

end.

 

No formulário principal criamos uma pequena interface para manipular a calculadora, apenas para fazer os testes iniciais.

 

DevCon2005Rp4_image001.jpg

  

procedure TForm1.Button1Click(Sender: TObject);

var

  Calc: Calculadora;

  x,y,z: double;

begin

  Calc := Calculadora.Create;

  x := StrToInt(Edit1.Text);

  y := StrToInt(Edit2.Text);

  z := Calc.Somar(x,y);

  Edit3.Text := FloatToStr(z);

end;

 

Agora criamos um projeto de teste. Na IDE do Delphi 2005, na Tool Palette, dê um duplo clique em Test Project.

 

DevCon2005Rp4_image002.jpg

 

Será aberto um assistente. ProjectName indica o nome do projeto de teste (arquivo DPR), que por padrão será o mesmo do projeto que contém as classes a serem testadas acrescido do sufixo Tests. Location é o caminho onde serão gravados os arquivos do projeto de teste. Personality indica qual a linguagem utilizada para gerar o código da classe de teste, que pode ser Delphi, DelphiDotNet ou CSharp. Add to project group indica se o projeto de teste (DPR) deverá ser incluído no grupo de projetos da IDE.

 

DevCon2005Rp4_image003.jpg

 

Clique em Next. Aqui temos duas opções, TestFramework, indicando se vamos usar o DUnit ou NUnit. Como estamos criando uma aplicação Win32, mantemos DUnit. Logo abaixo, Test Runner permite escolher a interface usada pela tela de testes do DUnit. Existem duas opções, para exibir uma tela gráfica (GUI) ou Console. Vamos manter as opções padrão. Clique em Finish.

 

DevCon2005Rp4_image004.jpg

 

O próximo passo é criar a classe de teste propriamente dita (o TestCase). Para isso, na Tool Palette dê um duplo clique em Test Case.

 

DevCon2005Rp4_image005.jpg

 

Será aberto um novo assistente, escolha a unit que queremos testar, nesse caso uCalculadora.pas. A IDE já lista que o método Somar está disponível para ser testado. Se tivéssemos mais métodos, poderíamos marcar somente aqueles que gostaríamos de testar no TestCase.

 

DevCon2005Rp4_image006.jpg

 

Clique em Next e na próxima tela mantenha as opções padrão. Aqui poderíamos escolher qual o projeto que terá a o TestCase incluído, o nome da unit a ser gerada e a classe base da nova classe de teste.

 

DevCon2005Rp4_image007.jpg

 

Clique em Finish. Salve todos os arquivos gerados, que por padrão ficarão em uma nova pasta chamada Test, criada pela IDE dentro do diretório principal da aplicação anterior. Observe que o Project Manager contém agora dois projetos, o original e o projeto de teste.

 

DevCon2005Rp4_image008.jpg

 

A IDE criou automaticamente uma nova unit (uTestCalculadora.pas) com a estrutura da classe de teste (TestCase). Para cada método a ser testado, a unit contém um método de teste. Por exemplo, para testar o método Somar, foi criado um método chamado TestSomar. Esse é um padrão, mas você pode modificar o fonte.

Para garantir que nossa calculadora está funcionando, vamos passar dois valores conhecidos (10 e 20) e testar se o retorno da função Somar é 30. Nesse caso, indicando que houve sucesso no teste. Para isso, basta modificar a implementação do método TestSomar conforme mostrado a seguir.

 

unit uTestCalculadora;

{

 

  Delphi DUnit Test Case

  ----------------------

  This unit contains a skeleton test case class generated by the Test Case Wizard.

  Modify the generated code to correctly setup and call the methods from the unit

  being tested.

 

}

 

interface

 

uses

  TestFramework, uCalculadora;

type

  // Test methods for class Calculadora

  TestCalculadora = class(TTestCase)

  strict private

    FCalculadora: Calculadora;

  public

    procedure SetUp; override;

    procedure TearDown; override;

  published

    procedure TestSomar;

  end;

 

implementation

 

procedure TestCalculadora.SetUp;

begin

  FCalculadora := Calculadora.Create;

end;

 

procedure TestCalculadora.TearDown;

begin

  FCalculadora.Free;

  FCalculadora := nil;

end;

 

procedure TestCalculadora.TestSomar;

var

  ReturnValue: Double;

  y: Double;

  x: Double;

begin

  // TODO: Setup method call parameters

  x := 10;

  y := 20;

  ReturnValue := FCalculadora.Somar(x, y);

  // TODO: Validate method results

  CheckEquals(30,ReturnValue);

end;

 

initialization

  // Register any test cases with the test runner

  RegisterTest(TestCalculadora.Suite);

end.

 

A IDE já gera todo o código necessário para instanciar o objeto do tipo Calculadora e liberá-lo (métodos Setup e TearDown). No método TestSomar, a IDE já coloca dois TO-Dos indiciando que devemos inicializar as variáveis usadas nos parâmetros do método Somar e que devemos validar o retorno logo a seguir. No exemplo, usei a função de asserção CheckEquals do DUnit para verificar se ReturnValue é igual ao valor que estou esperando 30.

Existem outros métodos de asserção disponíveis, como:

CheckNotNull – testa e um valor é nulo;

CheckIs – testa se um objeto é de um determinado tipo;

Etc.

 

Observe que TestSomar foi declarado na seção published. Isso é necessário para que o DUnit possa usar RTTI (Runtime Type Information) para obter o nome do método, ficando disponíveis para ativação na interface principal do DUnit.

Na seção initialization da unit a IDE faz o registro do TestCase:

 

initialization

  TestFrameWork.RegisterTest(TTestCaseSoma.Suite);

 

Rode a aplicação e você verá a tela principal disponibilizada pelo DUnit. Ela possui três painéis. No primeiro painel é mostrado um TreeView dos testes, onde a raiz é o nome do executável, em seguida vem o nome da classe e posteriormente os métodos declarados na seção published da(s) classe(s) registrada(s). Cada nó contém uma descrição (nome do executável, classe ou método), um CheckBox e uma caixa para informar o resultado do teste. Você pode usar os CheckBoxes para indicar os métodos que deseja executar. A caixa de resultado do teste inicialmente possui a cor branca, após o teste ela poderá ter a cor verde (indicando sucesso), o magenta (falha, ou seja, uma asserção retornou False) ou vermelho (que indica que ocorreu uma exceção).

No segundo painel é mostrado um resumo do que ocorreu no teste, o número de métodos testados, número de falhas, número de erros e tempo de execução dos testes. Logo abaixo um painel mostra o nome do método que falhou, o tipo de falha e a mensagem (definida como parâmetro dos métodos de asserção). Ao selecionar um item nesse painel, o último painel exibirá mais informações sobre a falha ocorrida.

 

DevCon2005Rp4_image009.jpg 

Depois de rodar o teste, volte ao Delphi e implemente o método Somar da classe Calculadora, como mostrado a seguir (agora retornando o valor correto):

 

function TCalculadora.Somar(op1, op2: Double): Double;

begin

  result := op1 + op2;

end;

 

Rode a aplicação novamente para verificar se o teste foi bem sucedido.

 

DevCon2005Rp4_image010.jpg

 

Charlie também comentou alguns tópicos avançados com testes unitários. No geral, gostei muito dessa palestra e da metodologia do palestrante. Como editor da ClubeDelphi, pretendo trazer para as próximas edições da revista mais artigos sobre o tema T.D.D., pois considero de fundamental importância para todos os desenvolvedores que querem garantir a qualidade de suas aplicações.

Links

Alguns links relacionados ao assunto:

 

Site Oficial do DUnit, onde consta a documentação, exemplos, DUnit para D7 etc.

http://dunit.sourceforge.net/

 

Site do Charlie com códigos e material sobre a palestra de T.D.D:

http://www.elvenware.com/charlie/conferences/2004/talks/index.html

 

Introdução ao T.D.D.

http://www.agiledata.org/essays/tdd.html

 

Apresentação em PPT sobre T.D.D.

http://www.richplum.net/downloads/ftp/cm_20040119.ppt

 

Palestra de Gustavo Chaurais no ClubeDelphi Tech Weekend 2005, sobre T.D.D.

http://www.clubedelphi.net/tw/2005/TDD.zip

 

DevCon2005Rp4_image011.jpg

Charlie Calvert falou sobre o desenvolvimento dirigido a testes (T.D.D.) e Testes Unitários

DevCon2005Rp4_image012.jpg

PreConference Tutorials

Guinther Pauli

Editor Geral Revista ClubeDelphi