Conforme prometi vamos continuar a falar sobre Decorators do DUnit e aprender como utiliza-los.
Bom para começar vamos criar uma nova aplicação VCL Forms e adicionar uma nova unit onde criaremos uma interface para implementar algumas classes de manipulação de texto. Nesta unit declararemos nossa interface da seguinte forma:
IManipuladorArquivos = interface
['{95CB70E7-34C9-449B-8D4A-C2224DCD4A3A}']
function Ler: string;
procedure Escrever(texto: string);
function GetNomeArquivo: string;
procedure SetNomeArquivo(valor: string);
property NomeArquivo: string read GetNomeArquivo write SetNomeArquivo;
end;
Adicione na uses de sua unit as seguintes units Classes, SysUtils, XMLDoc, XMLIntf, Variants.
Abaixo de nossa interface vamos criar duas classes que implementarão esta interface, não vou detalhar muito a implementação destas classes pois o foco deste post é explicar o teste unitário.
//Classe que implementa a interface de manipulação de arquivos para arquivos XML
TManipuladorXML = class(TInterfacedObject, IManipuladorArquivos)
private
FArquivo: IXMLDocument;
FNomeArquivo: string;
function GetNomeArquivo: string;
procedure SetNomeArquivo(valor: string);
public
constructor Create(nomeArquivo: string = '');
destructor Destroy; override;
function Ler: string;
procedure Escrever(texto: string);
property NomeArquivo: string read GetNomeArquivo write SetNomeArquivo;
end;
//Classe que implementa a interface de manipulação de arquivos para arquivos TXT
TManipuladorTxt = class(TInterfacedObject, IManipuladorArquivos)
private
FArquivo: TStringList;
FNomeArquivo: string;
function GetNomeArquivo: string;
procedure SetNomeArquivo(valor: string);
public
constructor Create(nomeArquivo: string = '');
destructor Destroy; override;
function Ler: string;
procedure Escrever(texto: string);
property NomeArquivo: string read GetNomeArquivo write SetNomeArquivo;
end;
Feito isso pressione CTRL + SHIFT + C para completar as declarações dos métodos, funções, construtores e destrutores das classes.
Em seguida complete a implementação das declarações da classe de manipulação de arquivos xml conforme abaixo:
{ TManipuladorXML }
constructor TManipuladorXML.Create(nomeArquivo: string);
begin
FNomeArquivo := nomeArquivo;
FArquivo := TXMLDocument.Create(nil);
end;
destructor TManipuladorXML.Destroy;
begin
FArquivo.Active := False;
FArquivo := nil;
inherited;
end;
function TManipuladorXML.Ler: string;
begin
if FileExists(nomeArquivo) then
begin
FArquivo.Active := False;
FArquivo.LoadFromFile(FNomeArquivo);
FArquivo.Active := True;
Result := Trim(VarToStr( FArquivo.ChildNodes.Nodes[0].ChildValues['texto'] ));
end;
end;
procedure TManipuladorXML.SetNomeArquivo(valor: string);
begin
FNomeArquivo := valor;
end;
procedure TManipuladorXML.Escrever(texto: string);
begin
FArquivo.Active := False;
FArquivo.XML.Add('');
FArquivo.XML.Add('');
FArquivo.XML.Add(texto);
FArquivo.XML.Add('');
FArquivo.XML.Add('');
FArquivo.Active := True;
FArquivo.SaveToFile(FNomeArquivo);
end;
function TManipuladorXML.GetNomeArquivo: string;
begin
Result := FNomeArquivo;
end;
Agora complete a implementação das declarações da classe de manipulação de arquivos txt conforme abaixo:
{ TManipuladorTxt }
constructor TManipuladorTxt.Create(nomeArquivo: string);
begin
FNomeArquivo := nomeArquivo;
FArquivo := TStringList.Create;
end;
destructor TManipuladorTxt.Destroy;
begin
FArquivo.Free;
inherited;
end;
function TManipuladorTxt.Ler: string;
begin
if FileExists(FNomeArquivo) then
begin
FArquivo.LoadFromFile(nomeArquivo);
Result := Trim(FArquivo.Text);
end;
end;
procedure TManipuladorTxt.SetNomeArquivo(valor: string);
begin
FNomeArquivo := valor;
end;
procedure TManipuladorTxt.Escrever(texto: string);
begin
FArquivo.Clear;
FArquivo.Text := texto;
FArquivo.SaveToFile(FNomeArquivo);
end;
function TManipuladorTxt.GetNomeArquivo: string;
begin
Result := FNomeArquivo;
end;
Feito isso volte para o form principal de sua aplicação e crie o visual como na imagem abaixo:
Agora vá para o código do seu form e adicione na uses a unit criada anteriormente, em seguida declare na seção private da classe do seu form duas variáveis como abaixo:
TuiPrincipal = class(TForm)
mmArquivo: TMemo;
Panel1: TPanel;
btAbrir: TButton;
odArquivo: TOpenDialog;
btSalvar: TButton;
sdArquivo: TSaveDialog;
procedure btAbrirClick(Sender: TObject);
procedure btSalvarClick(Sender: TObject);
private
FNomeArquivo: string; //Variável utilizada para armazenar o nome do arquivo atual.
FManipulador: IManipuladorArquivos; //Variável utilizada para armazenar o tipo de manipulador de arquivos utilizado.
public
end;
Implemente os eventos OnClick dos botões como abaixo:
procedure TuiPrincipal.btAbrirClick(Sender: TObject);
var
tipoArquivo: string;
begin
if odArquivo.Execute then
begin
if Assigned(FManipulador) then
FManipulador := nil;
FNomeArquivo := odArquivo.FileName;
tipoArquivo := ExtractFileExt(FNomeArquivo);
if tipoArquivo = '.xml' then
FManipulador := TManipuladorXML.Create(FNomeArquivo);
if tipoArquivo = '.txt' then
FManipulador := TManipuladorTxt.Create(FNomeArquivo);
if Assigned(FManipulador) then
begin
mmArquivo.Text := FManipulador.Ler;
end
else
Application.MessageBox('Tipo de arquivo desconhecido!','Erro!', MB_OK+MB_ICONERROR);
end;
end;
procedure TuiPrincipal.btSalvarClick(Sender: TObject);
var
tipoArquivo: string;
begin
if FNomeArquivo = '' then
begin
if sdArquivo.Execute then
FNomeArquivo := sdArquivo.FileName;
end;
if FNomeArquivo <> '' then
begin
if Assigned(FManipulador) then
FManipulador := nil;
tipoArquivo := ExtractFileExt(FNomeArquivo);
if tipoArquivo = '.xml' then
FManipulador := TManipuladorXML.Create(FNomeArquivo);
if tipoArquivo = '.txt' then
FManipulador := TManipuladorTxt.Create(FNomeArquivo);
if Assigned(FManipulador) then
begin
FManipulador.Escrever(mmArquivo.Text);
end
else
Application.MessageBox('Tipo de arquivo desconhecido!','Erro!', MB_OK+MB_ICONERROR);
end;
end;
Pronto agora que temos nosso projeto pronto, vamos criar um novo projeto do DUnit e adicionar um TestCase para nossa primeira unit, que contem nossa interface para manipulação de arquivos. Nesta etapa é importante selecionar apenas a interface e não as classes, deixe seu test case como abaixo:
Vamos então escrever um código simples para testar nosso manipulador.
procedure TTestTManipulador.TestLer;
begin
FManipulador.Escrever('teste');
CheckEqualsString('teste', FManipulador.Ler, 'Arquivo não foi lido corretamente!');
end;
procedure TTestTManipulador.TestEscrever;
begin
FManipulador.Escrever('teste');
CheckTrue(FileExists(FManipulador.NomeArquivo),'Arquivo não foi salvo!');
CheckEqualsString('teste', FManipulador.Ler, 'Arquivo não foi escrito corretamente!');
end;
Note que não implementamos o código de setup e teardown pois faremos a implementação em nosso Decorator.
Mas antes precisamos agora criar uma interface que será utilizada para compartilhar dados entre nosso Teste e nosso Decorator declare e altere sua classe de testes para implementar a interface como abaixo:
//Interface para compartilhamento de dados entre decorador e a classe de testes
ITestDataSharer = interface
['{8B3239CE-D7CF-4695-9BA3-BBC52D764174}']
procedure OneTimeSetup(AObject: TObject); //Será disparado apenas uma vez por projeto de teste unitário
function OneTimeTearDown: TObject; //Será disparado apenas uma vez por projeto de teste unitário
end;
// Métodos de testes da classe TManipulador
TTestTManipulador = class(TTestCase, ITestDataSharer)
strict private
FManipulador: IManipuladorArquivos;
public
procedure OneTimeSetup(AObject: TObject);
function OneTimeTearDown: TObject;
procedure SetUp; override;
procedure TearDown; override;
published
procedure TestLer;
procedure TestEscrever;
end;
Em seguida implemente os métodos OneTimeSetup e OneTimeTearDown como abaixo:
procedure TTestTManipulador.OneTimeSetup(AObject: TObject);
begin
//Como estamos utilizando uma interface apenas pegamos a referencia dela e armazenamos na variável FManipulador.
Supports(AObject, IManipuladorArquivos, FManipulador);
//Caso estejamos utilizando uma classe base por exemplo devemos implementar como abaixo
//FManipulador := AObject as TManipuladorArquivos;
end;
function TTestTManipulador.OneTimeTearDown: TObject;
begin
Result := FManipulador as TObject;
end;
Repare que no OneTimeSetup apenas configuramos nossa variável interna para utilizar a instância passada pelo parâmetro AObject, já o OneTimeTearDown devolve a variável interna utilizada para ser removida.
Isso é necessário devido ao fato do dunit por padrão disparar o Setup e TearDown uma vez para cada método e não por Teste que é o que precisamos. Para mais informações do comportamento interno veja o post anterior.
Agora remova o código do initialization de sua unit de testes que registra seu teste, pois registraremos nosso Decorador invés do teste.
Em seguida crie uma nova unit em seu projeto de testes e salve com o nome registerTests.pas ela conterá nossos decoradores e seus respectivos registros.
Adicione na uses os seguintes arquivos
uses
TestFramework, TestExtensions, sua_unit_de_testes_do_manipulador_de_arquivos_criada_automaticamente_pelo_wizard_do_delphi ,
Forms, SysUtils, sua_unit_do_manipulador_de_arquivos_a_primeira_que_fizemos;
Repare que como disse no post anterior adicionamos na uses a unit TestExtensions que possui a classe base TTestDecorator.
Vamos criar nossa classe base para decoradores de manipuladores de arquivos, faça como abaixo:
//Decorador Base de Manipuladores
TTestManipuladorDecorator = class (TTestDecorator)
protected
function GetSetupObject: TObject; virtual; abstract;
procedure TearDownObject(var AObject: TObject); virtual; abstract;
procedure DoOneTimeSetup;
procedure DoOneTimeTearDown;
procedure RunTest(ATestResult: TTestResult); override;
public
function GetName: string; override;
end;
Adicione abaixo da seção implementation a constante utilizada para apresentar os nomes dos testes na tela de execução de testes do DUnit.
const
CustomManipuladorDecoratorName: string = 'Teste Manipulador de: %s';
Em seguida implementaremos os códigos dos métodos DoOneTimeSetup, DoOneTimeTearDown, RunTest e GetName como abaixo:
{ TTestManipuladorDecorator }
procedure TTestManipuladorDecorator.DoOneTimeSetup;
var
I: Integer;
itfc: ITestDataSharer;
begin
//Aqui percorreremos todos os testes disponíveis em nosso projeto através da variável interna da classe TTestDecorator
//e dispararemos o método OneTimeSetup caso o teste implemente nossa interface de compartilhamento de dados.
//Note que passamos para o OneTimeSetup o resultado da função GetSetupObject que esta declarada como virtual e abstract
//ou seja será implementada pela nossa classe filha que permitirá sua personalização de inicialização.
for I := 0 to FTest.Tests.Count - 1 do
begin
if FTest.Tests[I].QueryInterface(ITestDataSharer,itfc) = 0 then
itfc.OneTimeSetup(GetSetupObject);
end;
end;
procedure TTestManipuladorDecorator.DoOneTimeTearDown;
var
I: Integer;
itfc: ITestDataSharer;
AObject: TObject;
begin
//Aqui percorreremos todos os testes disponíveis em nosso projeto através da variável interna da classe TTestDecorator
//armazenamos em uma variável o objeto adquirido pela função OneTimeTearDown e disparamos o método TearDownObject
//que esta declarado como virtual e abstract e será implementada pela nossa classe filha para remover o objeto criado anteriormente
//pelo OneTimeTearDown.
for I := 0 to FTest.Tests.Count - 1 do
begin
if FTest.Tests[I].QueryInterface(ITestDataSharer,itfc) = 0 then
begin
AObject := itfc.OneTimeTearDown();
TearDownObject( AObject );
end;
end;
end;
procedure TTestManipuladorDecorator.RunTest(ATestResult: TTestResult);
begin
//Disparamos o DoOneTimeSetup que configurara nosso teste unitário e agora sim é disparado apenas uma vez
DoOneTimeSetup;
try
//Este RunTest executa todos os testes de nossa classe de métodos,
//podemos executar um teste quantas vezes quisermos de acordo com o necessário.
inherited RunTest( ATestResult ) ;
//Se precisar disparar o teste mais de uma vez basta fazer o seguinte
//for i:= 0 to 10 do
// inherited RunTest( ATestResult );
finally
//Disparamos o DoOneTimeTearDown que limpara os dados criados pelo DoOneTimeSetup e também será disparado apenas uma vez
DoOneTimeTearDown;
end;
end;
function TTestManipuladorDecorator.GetName: string;
begin
//Devolvemos o nome padrão que será exibido na tela do teste unitário
Result := Format(CustomManipuladorDecoratorName,['Base']);
end;
Vamos agora criar nossas classes decoradoras de arquivos TXT e XML, nelas personalizaremos a criação e destruição de seus testes.
Abaixo da declaração da classe base de decoradores declararemos as duas classes como abaixo:
//Decorador para testar Manipuladores de arquivos XML
TTestManipuladorXMLDecorator = class (TTestManipuladorDecorator)
protected
function GetSetupObject: TObject; override;
procedure TearDownObject(var AObject: TObject); override;
public
function GetName: string; override;
end;
//Decorador para testar Manipuladores de arquivos TXT
TTestManipuladorTXTDecorator = class (TTestManipuladorDecorator)
protected
function GetSetupObject: TObject; override;
procedure TearDownObject(var AObject: TObject); override;
public
function GetName: string; override;
end;
Note que nessas duas classes precisamos apenas implementar 3 métodos sendo o GetName opcional mas recomendado para que o nome apareça personalizado e facilite o entendimento do que esta sendo testado.
Implemente agora como abaixo:
{ TTestManipuladorXMLDecorator }
function TTestManipuladorXMLDecorator.GetName: string;
begin
//Devolvemos o nome a ser exibido na tela de execução dos testes
Result := Format(CustomManipuladorDecoratorName,['XML']);
end;
function TTestManipuladorXMLDecorator.GetSetupObject: TObject;
var
nomeArquivo: string;
begin
//Especificamos o caminho do arquivo a ser utilizado durante os testes e criamos a instância da classe correta a ser realizado os testes
nomeArquivo := IncludeTrailingPathDelimiter( ExtractFilePath(Application.ExeName) );
nomeArquivo := nomeArquivo + 'teste.xml';
Result := TManipuladorXML.Create(nomeArquivo);
end;
procedure TTestManipuladorXMLDecorator.TearDownObject(var AObject: TObject);
begin
//Como estamos compartilhando uma interface e não um objeto apenas atribuimos nil e não disparamos
//o free do objeto, casos vc esteja utilizando uma classe descomente a linha abaixo
//AObject.Free;
AObject := nil;
end;
{ TTestManipuladorTXTDecorator }
function TTestManipuladorTXTDecorator.GetName: string;
begin
//Devolvemos o nome a ser exibido na tela de execução dos testes
Result := Format(CustomManipuladorDecoratorName,['TXT']);
end;
function TTestManipuladorTXTDecorator.GetSetupObject: TObject;
var
nomeArquivo: string;
begin
//Especificamos o caminho do arquivo a ser utilizado durante os testes e criamos a instância da classe correta a ser realizado os testes
nomeArquivo := IncludeTrailingPathDelimiter( ExtractFilePath(Application.ExeName) );
nomeArquivo := nomeArquivo + 'teste.txt';
Result := TManipuladorTxt.Create(nomeArquivo);
end;
procedure TTestManipuladorTXTDecorator.TearDownObject(var AObject: TObject);
begin
//Como estamos compartilhando uma interface e não um objeto apenas atribuimos nil e não disparamos
//o free do objeto, casos vc esteja utilizando uma classe descomente a linha abaixo
// AObject.Free;
AObject := nil;
end;
Finalmente no initialization registraremos nossos decoradores de testes da seguinte forma:
initialization
// Registra o Decorator utilizando arquivos XML
RegisterTest(TTestManipuladorXMLDecorator.Create( TTestTManipulador.Suite ));
// Registra o Decorator utilizando arquivos Txt
RegisterTest(TTestManipuladorTXTDecorator.Create( TTestTManipulador.Suite ));
Execute seu teste unitário e note que os testes estão disponíveis e caso você precisa criar mais manipuladores de arquivos basta agora implementar a ultima parte de nosso código invés de criar um novo test case apenas para ele duplicando o código.
Esta é a nossa tela de testes com decoradores:
Bom é isso pessoal espero que vocês tenham gostado e acho que pelo tamanho que ficou este post de para entender o porque da demora ;)
Aqui está o link para download deste exemplo.
Grande abraço,
Diego Garcia.
Nenhum comentário:
Postar um comentário