terça-feira, 22 de junho de 2010

Exceções em testes de unidade para banco de dados

Como todo bom geek que se preza, estou tentando usar os diversos recursos que temos disponíveis nas nossas ferramentas e que muitas vezes ficam em segundo plano, pois acabamos viciados no que nos é comum.

Com um extremo cuidado estou tentando criar testes para meus projetos e recursos auxiliares, para garantir um kit profissional, com garantia da qualidade e confiabilidade, então nada mais natural que recorrer aos testes de unidade.
 Ao invés de trabalhar com o nosso tradicional UnitTest.cs e codificar todos os setups e testes em C#, resolvi usar o velho e bom DataDude (AKA. Visual Studio Database Professional), que na versão do 2010 me parece somente estar incluso nas versões Premium e Ultimate (http://www.microsoft.com/visualstudio/en-us/products).

Usando meu projeto de teste eu adicionei o item "Database Unit Test" e codifiquei para a procedure chamada proc_CriaSnapshot, uma série de testes (bem como o setup destes) utilizando o Transact-SQL. Durante a codificação eu achei relativamente simples a criação dos testes, mas não gostei da maneira que foi fornecida para tratar os erros gerados pelo teste.

Segundo a documentação (com escassas referências na web - nenhuma que me ajudou) a maneira que temos para verificar se uma exceção foi gerada, é a mesma que utilizamos para os testes de unidade tradicionais, isto é, utilizando atributos sobre a declaração do método. Então temos que colocar a chamada do procedimento sem nenhuma validação (figura 01) e depois, diretamente no código fonte decorar o método com o atributo ExpectedSqlException, conforme script 01.


(Figura 01)

(Script 01)
[TestMethod(), ExpectedSqlException(MatchFirstError=true, MessageNumber=50000, Severity=16, State=1)]
public void TesteException() { ... }

Executei o teste e funcionou tudo beleza, mas não me agradou por dois motivos.
  1. Por ser um procedimento que vou utilizar em vários ambientes eu optei por não atribuir um número específico aos erros gerados dentro do procedimento, isso significa que todos os meus erros terão o 50000 como número.
    1. Então o meu teste unitário pode passar como certo (capturando a exceção), mesmo se a exceção gerada for relativa a um outro problema diferente do que eu espero. Esse não determinismo no teste não é nada interessante, concordam.
    2. Se eu forçar um número (suponha 50001), caso chegue em um ambiente que já exista esse número registrado, terei que alterar o procedimento e os meus testes, o que também não me agrada.
  2. Até esse momento todos os testes estavam com asserções definidas através da interface, e não no código fonte por debaixo dos panos. Alguém desavisado poderia olhar para o teste sem nenhuma condição de teste e apagá-lo.
    1. Ok, eu posso colocar um comentário direto no T-SQL dizendo: "-- Esse teste possui no code-behind um ExpectedSqlException...". Mas não gostei nem um pouco disso.
Alternativa ao ExpectedSqlException

Como alternativa para o problema eu pensei inicialmente em estender o Visual Studio criando meu próprio TestCondition, que é detalhado na documentação do produto: Define Custom Conditions for Database Unit Tests (http://msdn.microsoft.com/en-us/library/dd193282.aspx).

Além de ser uma abordagem mais trabalhosa (digna da metáfora canhão vs. mosca) analisando os passos que eles mostram no walkthrough, é utilizado como ponto de verificação o recebimento de uma conexão e resultado através do método Assert. Mas se uma exceção for gerada pelo código, possivelmente o Assert nunca será invocado, o que me pareceu claramente ser a razão de não existir já disponível no Visual Studio 2010 uma test condition para exceções. Acho que vale a pena depois aprofundar um pouco mais no assunto, mas não terei tempo para isso agora.

Por fim resolvi utilizar o seguinte código (script 02) na chamada ao procedimento:

(Script 02)
BEGIN TRY
        EXEC CleansingKit.dbo.proc_CriaSnapshot 'TesteSnapshot', 'C:\DiretorioInexistente\'
END TRY
BEGIN CATCH
        SELECT 'Exceção correta foi gerada.'
        WHERE ERROR_MESSAGE() LIKE 'Directory lookup for the file % failed with the operating system error 2(The system cannot find the file specified.).'
END CATCH

Com essa abordagem eu consigo verificar exatamente a mensagem do erro (poderia também testar o ERROR_NUMBER) e mostrar a string "Exceção correta foi gerada.". Então com uma simples condição para verificação de um valor escalar (figura 02), que deve ser a string retornada pelo SELECT, conseguimos verificar se a exceção correta foi recebida.


(Figura 02)
Nessa abordagem fica claro o que estamos tentando fazer com o teste, podemos verificar exatamente qual é a mensagem de erro (inclusive usando wildcards como o "%" para informações que variam) e caso nenhuma exceção seja gerada o catch não é invocado e o teste não passa na asserção feita pelo Visual Studio.

O que você acha dessa abordagem? Como faz seus testes unitários para procedures?
[]s
Luciano Caixeta Moreira - {Luti}
Sr. Nimbus Serviços em Tecnologia Ltda
luciano.moreira@srnimbus.com.br
www.twitter.com/luticm

Nenhum comentário:

Postar um comentário