segunda-feira, 26 de outubro de 2009

Testes de unidade com deployment de arquivos


Olá pessoal, vamos de Visual Studio hoje.


Imagine um cenário bem comum: Estamos desenvolvendo um projeto que manipula arquivos e, para ajudar o desenvolvimento, você cria uma classe utilitária que lê do arquivo de configuração qual é o repositório local (diretório) onde os arquivos são armazenados. Essa classe auxiliar será utilizada por toda aplicação, então não existirá somente para testes. Exemplo de código abaixo:


public class Configuração
{
private static string repositórioArquivos;

static Configuração() {
repositórioArquivos = ConfigurationManager.AppSettings["RepositórioArquivos"];
}

public static string RepositórioArquivos
{
get { return repositórioArquivos; }
}
}

public void RenomearArquivo(string nomeArquivo, string novoNomeArquivo)
{
string repositório = Configuração.RepositórioArquivos;
File.Move(Path.Combine(repositório, nomeArquivo), Path.Combine(repositório,
novoNomeArquivo));
}

Antes ou depois de codificar a classe, você desenvolve alguns testes para testar seu componente, então para tudo funcionar corretamente você adiciona ao seu projeto de testes um arquivo App.Config e colocar uma entrada no AppSettings, que será lida pelo auxiliar de configuração:

[TestMethod]
public void TesteCenárioUsoBásico() {

// Pré-condições
Assert.IsTrue(File.Exists(Path.Combine(
TestesComArquivos.Configuração.RepositórioArquivos, "arquivoqualquer.txt")));

TestesComArquivos.ClasseNegocio negócio = new TestesComArquivos.ClasseNegocio();
negócio.RenomearArquivo("arquivoqualquer.txt", "novoarquivotexto.txt");

// Pós-condições
Assert.IsTrue(File.Exists(Path.Combine(
TestesComArquivos.Configuração.RepositórioArquivos, "novoarquivotexto.txt")));
}


Execute o projeto (em anexo a este artigo) e tudo funcionará perfeitamente. Quer dizer, mais ou menos, pois aqui temos alguns problemas:

1 - Depois que você executar esse teste, o arquivo estará renomado e a próxima execução vai falhar. Você corrige isso escrevendo direito as pré-condições e setup do seu teste. Por simplicidade eu deixarei como está, ok?

Tudo isso funciona corretamente se você está desenvolvendo sozinho, mas e se houver uma equipe junto com você?

2 - Em primeiro lugar, os arquivos não serão levados juntos com o projeto, então se alguém adiciona outro arquivo para teste, o que você faz? Isso garoto, adicione os arquivos ao projeto de teste, para todos os desenvolvedores terem acesso ao distinto quando pegarem uma nova versão do projeto.

3 - (Um problema de verdade) Cada desenvolvedor pode definir um diretório local diferente para seu workspace de trabalho, então se um desenvolvedor colocar os arquivos da solução em outro diretório e mudar o App.Config, quando você pegar a última versão do projeto no TFS, bye bye testes!

Uma maneira de "resolver" isso é ficar mudando o App.Config para cada desenvolvedor ou então padronizar o diretório local para o seu projeto, mas é um gato MUITO feio. Concorda? E se houverem builds diários, você vai fazer o quê? Como resolvemos isso?


  1. Adicione ao seu projeto o diretório "Arquivos" e coloque lá dentro o famoso "arquivoqualquer.txt". Lembre de marcar a opção do arquivo "Copy to output directory" com "Copy if newer".

  2. Como a cada nova execução dos testes são gerados novos diretórios com um timestamp diferente (ex.: "C:\Projects\VisualStudio\TestesComArquivos\TestResults\luciano.moreira_DSKSRN01 2009-10-26 14_26_18"), precisamos referenciar os arquivos dentro do diretório "Out", onde estão as DLLs do projeto.

    1. Como nossa propriedade em Configuração.RepositórioArquivos é somente leitura e não queremos interferir com a interface da classe, adicionarei um método internal chamado DefineRepositórioArquivos.

      internal static void DefineRepositórioArquivos(string repositório) {
      repositórioArquivos = repositório;
      }

    2. Para que esse método seja visível no nosso projeto de testes, utilizamos um pequeno recurso do .NET, definindo que os métodos internal do assembly de negócio são visíveis somente para o projeto de testes. Recurso que somente utilizo nesse tipo de cenário.

      [assembly: InternalsVisibleTo("TestesComArquivos_TesteSuite")]

    3. A partir desse momento eu faço uma pequena alteração no setup do meu teste, para que ele defina qual o repositório de arquivos de acordo com o diretório de deployment do teste. Para isso eu utilizo a classe auxiliar TestContext, que possui a propriedade DeploymentDirectory. Se você não quiser usar essa classe auxiliar, pode partir para a ignorância com o AppDomain.CurrentDomain.BaseDirectory.

      TestesComArquivos.Configuração.DefineRepositórioArquivos(TestContext.DeploymentDirectory);

    Se você executar o seu teste nesse momento irá receber um erro! Analisando com cuidado verificará que o problema está no primeiro Assert, que pergunta pela existência do arquivo, então olhando o diretório criado pelos testes notará que o arquivo não foi colocado no Out. Huummm, mas no passo 1 você já pediu para o arquivo ser copiado para o diretório de saída, não é suficiente?


    O pior é que não! Quando você compila o projeto de teste o arquivo é colocado no "\Bin\Debug" corretamente, mas não é levado para o "\TestResult\....\Out" que é criado para o teste. Para isso é necessário editar as configurações dos testes em "Local.testsettings" (dentro de Solution Items) e no Deployment adicionar o diretório "Arquivos", conforme figura abaixo.




  3. Pronto!

    Basta executar o seu teste que tudo vai funcionar e quantas vezes forem necessárias, pois cada novo teste copia o arquivo original para um novo diretório, evitando o primeiro problema que eu citei. Agora todos os seus desenvolvedores podem trabalhar tranquilamente, adicionar novos arquivos ao diretório já criado e codificar novos testes, basta lembrar de definir o diretório correto no início dos testes.

    Bom, espero que tenham gostado. O projeto que criei está disponível juntamente com o PDF do artigo. Baixe aqui.

    []s
    Luciano Caixeta Moreira - {Luti}
    Chief Innovation Officer Sr. Nimbus Serviços em Tecnologia Ltda luciano.moreira@srnimbus.com.br
    www.twitter.com/luticm

Nenhum comentário:

Postar um comentário