Pex no Visual Studio 2012: Code Digger e TDD com teorias
Uma ferramenta que eu tenho acompanhado a evolução é o Pex (http://research.microsoft.com/en-us/projects/pex/). Trata-se de um projeto conduzido pela Microsoft Research desde os tempos de Visual Studio 2010. Como um subproduto deste projeto nasceu o Moles, um framework de isolamento de dependências, que na versão 2012 do Visual Studio tornou-se o Fakes.
Cheguei a publicar artigos e posts referente a essas ferramentas (Introdução ao framework de testes Microsoft Moles - Artigo Revista . Net Magazine 87, Geração automática de testes numa abordagem TDD - Revista .Net Magazine 90 e "Novidades" no Visual Studio 11 Beta - Microsoft Fakes). Confesso que a falta de novidades no Pex me deixou um pouco desanimado desde então, mas recentemente foi lançada uma primeira extensão dela voltada para o Visual Studio 2012: Code Digger!
Mas o que faz o Pex? Bom, ele é uma ferramenta de análise de código que busca encontrar todos os caminhos lógicos de forma a identificar valores que, quando passados para seus métodos, acabem gerando erros. Para fazer isso, ele analisa todas as possibilidades de instruções condicionais (if/else, while, etc.) dentro do seu código, sugerindo valores de parâmetros que sejam "interessantes". Por esta característica, podemos classificar o Pex como uma ferramenta de testes de caixa branca, ou seja, onde o teste é executado sobre uma implementação conhecida (diferente de testes de caixa preta, onde parte-se da premissa que não se conhece o que tem dentro de um objeto que será testado).
O Code Digger é uma extensão do Visual Studio 2012 que pode ser baixada em http://visualstudiogallery.msdn.microsoft.com/fb5badda-4ea3-4314-a723-a1975cbdabb4. Ele não tem todas as funcionalidades que tínhamos na versão 2010 do Pex, mas já é um começo. Após instalado, ele permite gerar uma tabela contendo os valores de parâmetros e respetivos resultados de determinado método para casos interessantes. Para isso, basta clicar com o botão direito sobre a função que queremos analisar, e selecionar a opção "Generate Inputs / Outputs Table".
Para explicar, nada melhor que um exemplo. Imagine que estamos construindo um novo algoritmo de criptografia simétrico (que usa uma mesma chave para tanto cifrar quanto decifrar valores). A ideia desse algoritmo é transformar cada letra em um número, e separá-los pelo caractere de pipe ("|"). Ou seja, se tivermos o valor "AB" para criptografar, ele deve ser transformado em "14560|14850". Para não ficar tão fácil de descobrir o texto criptografado, nosso algoritmo permite que seja passada uma chave ("salt") para influenciar no valor final. Por exemplo, se o "salt" for 123, o resultado da criptografia de um caractere qualquer seria "12345"; se o "salt" for 456 o resultado final seria "7894", e assim por diante. Obviamente, se utilizarmos o "salt" errado na hora de descriptografar, não teremos o resultado correto.
A imagem abaixo descreve um pouco melhor esse algoritmo. Supondo o valor "AB" e um "salt" com o valor 224. Multiplicando o valor do caractere A na sua representação decimal (65), temos o valor 14560; somamos então +1 para o "salt", dando 225; multiplicamos o valor de B (66) por 225, dando 14850; somamos então +1 para o "salt"; multiplicamos o próximo caractere por 226, e assim até terminar.
Para descriptografar, fazemos o processo ao contrário: 14560 dividido por 224 para dar 65; 14850 dividido por 225 para dar 66, etc.
Observação: apesar do cuidado de se utilizar uma chave "salt" para se ter resultados diferentes, a ideia desse algoritmo proposto poderia ser quebrada com um pouco de esforço. É claro que algoritmos simétricos da vida real possuem lógicas mais complexas, mas para o nosso exemplo esta lógica já basta.
Trabalhando numa abordagem TDD, podemos criar os seguintes métodos para começarmos a desenvolver nossa classe, baseado nos cenários que descrevemos:
using Microsoft.VisualStudio.TestTools.UnitTesting;
using PexCodeDiggerExample;
namespace PexCodeDiggerExampleTest
{
[TestClass]
public class MySymmetricKeyAlgorithmTest
{
[TestMethod]
public void ShouldCryptText()
{
var target = new MySymmetricKeyAlgorithm();
var result = target.Crypt("AB", 224);
Assert.AreEqual("14560|14850", result);
}
[TestMethod]
public void ShouldDecryptText()
{
var target = new MySymmetricKeyAlgorithm();
var result = target.Decrypt("14560|14850", 224);
Assert.AreEqual("AB", result);
}
}
}
O primeiro método, ShouldCryptText, verifica se a lógica de criptografar que descrevemos está correto. O segundo, ShouldDecryptText, valida se o texto criptografado consegue retornar ao seu valor original após efetuarmos a descriptografia.
O código que desenvolvemos até agora está abaixo, lembrando que TDD é uma técnica iterativa, onde para cada novo teste temos uma versão diferente do código (até termos a versão final).
using System;
using System.Linq;
namespace PexCodeDiggerExample
{
public class MySymmetricKeyAlgorithm
{
public string Crypt(string plainText, int salt)
{
char[] chars = plainText.ToCharArray();
long[] values = new long[chars.Length];
for (int i = 0; i < chars.Length; i++)
{
values[i] = chars[i] * salt;
salt+= 1;
}
return string.Join("|", values);
}
public string Decrypt(string cipherText, int salt)
{
string[] values = cipherText.Split("|".ToCharArray(), StringSplitOptions.None);
char[] chars = new char[values.Length];
for (int i = 0; i < chars.Length; i++)
{
chars[i] = (char)(int.Parse(values[i]) / salt);
salt+= 1;
}
return new string(chars);
}
}
}
Até aqui não vimos nada de novo. Vamos utilizar a ferramenta Code Digger em cima de um desses métodos, para vermos quais são os erros que ela retorna para nós. Podemos ver o resultado da análise do método Crypt na imagem abaixo.
Apenas um erro encontrado, onde o problema é simplesmente uma exceção de referência de objeto nulo. Aqui cabem algumas críticas que muitos fazem a respeito desse tipo de ferramenta, quando aplicado em desenvolvimento orientado a testes:
"Ferramentas como o Pex / Code Digger não são de muita serventia, pois basicamente permitem que seja obtida 100% de cobertura de testes dos códigos. As situações de erro não são muito significativas da parte do negócio, limitando-se basicamente a testar valores limites dos parâmetros."
Eu concordo com esta crítica. Mas o problema dela é que considera um uso incorreto deste tipo de ferramenta. Realmente não nos é dada nenhuma vantagem nos testes do ponto de vista de negócio, pois os erros que são pegos se resumem a codificação incorreta, como a do exemplo anterior, que poderia ser tratada com uma verificação de objeto nulo. Mas fica somente nisso.
Uma abordagem melhor para tratarmos o uso desse tipo de ferramenta é criar testes de unidade que ajam como teorias. Partimos então de uma afirmação na qual o Pex / Code Digger deve encontrar um caso negativo para invalidá-la. No momento que ele encontra esta situação, sabemos que nosso código possui um erro. Vejamos o exemplo abaixo
using System.Diagnostics.Contracts;
namespace PexCodeDiggerExample
{
public class MySymmetricKeyAlgorithmTestTheory
{
public void EncipherAndDecipherATextWillResultInTheSameText(string plainText, int salt)
{
Contract.Assume(!string.IsNullOrEmpty(plainText));
var target = new MySymmetricKeyAlgorithm();
Contract.Assume(salt > 0);
var cipherText = target.Crypt(plainText, salt);
var plainTextAgain = target.Decrypt(cipherText, salt);
Contract.Assert(plainTextAgain == plainText);
}
}
}
Nossa teoria aqui é que todo texto que é criptografado com uma determinada chave "salt", ao ser submetido ao processo de decriptação, deve sempre resultar no texto original. Ou seja, se criptografando "XYZ" resultar em "123|456|789", fazer a descriptografia de "123|456|789" deve resultar em "XYZ". Essa é a nossa teoria.
Note que estamos utilizando o Code Contracts (http://research.microsoft.com/en-us/projects/contracts/) para definir quais são as premissas da nossa teoria (texto original não nulo e a chave "salt" maior que zero) e também a forma como confirmamos a nossa teoria (valor descriptografado sendo igual ao original). Aqui vale citar que o Pex / Code Digger é um projeto bem "intímo" do Code Contracts, o que nos permite utilizá-lo para controlar a forma com que a análise de código leva o nosso código em consideração.
O resultado da execução do Code Digger, na imagem abaixo, mostra alguns erros que foram identificados no nosso código, considerando a nossa teoria. Podemos então concluir que nosso código ainda não está finalizado. Então, devemos corrigir o código, até erros não sejam mais identificados.
Por fim, gostaria de frisar que o uso do Pex / Code Digger não substituí os testes de unidade "manuais" que costumamos fazer. O que essa abordagem faz é complementar o TDD, nos dando mais uma opção para podermos identificar problemas no nosso código, que é basicamente a função de um teste.
[]'s
ATUALIZAÇÃO: Agora não existe mais a limitação de efetuar a exploração de código apenas em projetos do tipo Portable Class Library. Agora podemos colocar as teorias nos projetos de testes, que é o lugar mais apropriado.
Para isto, basta colocar a opção DisableCodeDiggerPortableClassLibraryRestriction como true, nas opções do Pex no Visual Studio.
Muito Bom!
ResponderExcluir