Testes Unitários no Visual Studio 2012

Veja neste artigo como implementar testes unitários no Visual Studio 2012, contribuindo assim para a construção de aplicações menos sujeitas a falhas e com uma melhor estruturação do código-fonte.

Por mais que a realização de testes unitários se constitua numa atividade de fundamental importância dentro do desenvolvimento de sistemas, não é um fato raro que esse tipo de prática acabe negligenciado em muitos projetos de software. Inúmeros podem ser os motivos que conduzam a problemas deste gênero, sendo que as causas mais comuns costumam girar em torno de aspectos como tempo escasso, equipes reduzidas e sobrecarregadas em suas tarefas rotineiras, a falta de hábito em se proceder com testes mais abrangentes, dentre outros fatores.

A principal meta por trás do teste de um software é garantir que o produto gerado atenda àquilo que foi especificado para o projeto em questão. Em termos práticos, isto implica verificar se a aplicação funciona de maneira correta dentro de uma série de parâmetros definidos previamente: pessoas envolvidas com essas tarefas conduzem uma série de atividades validando funcionalidades, tentando, a partir de tais ações, encontrar falhas que produzam dados inconsistentes ou, até mesmo, defeitos que comprometam a operação do sistema. Sem a realização de procedimentos deste gênero, tais problemas poderiam passar despercebidos, com consequências imprevisíveis às operações rotineiras de uma organização.

Existem diferentes maneiras de se testar uma aplicação, com cada uma destas levando em conta aspectos como quais profissionais executarão o processo de validação ou ainda, a extensão do que será verificado. Tomando por base estes critérios, os diferentes tipos de testes de software podem ser classificados em:

Este artigo discutirá a importância dos testes unitários no desenvolvimento de software dentro da plataforma .NET. Para isto será construída uma aplicação de exemplo que usará os recursos Visual Studio 2012 voltados à execução de testes de unidade automatizados.

Desenvolvimento baseado em Testes Unitários: uma visão geral

Os testes unitários são comumente empregados na checagem de métodos, classes e transições de estados dentro de sistemas orientados a objetos. O trecho de código que será testado é conhecido como “System Under Test” - SUT; é comum também o uso do termo CUT (“Class Under Test” ou “Code Under Test”).

São características comumente atribuídas aos testes unitários:

As principais plataformas de desenvolvimento atuais contam com diversos frameworks e funcionalidades que viabilizam a implementação e, consequentemente, a execução automatizada de construções deste tipo. No caso específico do .NET Framework, isto pode ser feito através do uso de frameworks como MS Test (parte integrante da própria plataforma .NET) e NUnit (http://nunit.org/).

Testes unitários representam inclusive a base para a forma de desenvolvimento que tem ganho bastante espaço, sobretudo com a crescente popularidade de metodologias ágeis como Scrum: trata-se do “Desenvolvimento Guiado por Testes” ou TDD.

TDD (sigla do inglês “Test Driven Development”) é um processo para desenvolvimento de software que enfatiza, através de uma série de princípios propostos por metodologias ágeis, a construção de soluções em um modo no qual as mesmas poderão ser facilmente integradas a uma ferramenta de automação de testes unitários.

A escolha do TDD como uma prática de desenvolvimento implica, obrigatoriamente, na codificação dos testes unitários antes mesmo da escrita das partes da aplicação que serão submetidas aos mesmos. Por mais que um teste possa ser formulado após a codificação de uma funcionalidade, isto não é adotado em projetos em conformidade com os princípios de TDD: se tal teste fosse elaborado após o desenvolvimento do recurso a ser verificado, o mesmo poderia ser concebido de uma maneira “viciada”, considerando apenas a possibilidade de execução com sucesso da função considerada.

A implementação de um projeto baseado em técnicas de TDD é feita seguindo um ciclo conhecido como Red-Green-Refactor, com cada um destes termos correspondendo a uma fase na construção dos testes:

Inúmeros são os benefícios decorrentes das práticas de TDD:

Diante do exposto, conclui-se que as técnicas de TDD, ao favorecer um código mais simples e de fácil manutenção, acabam contribuindo para uma melhor assimilação de boas práticas de desenvolvimento/arquitetura de software:

Utilizando Testes Unitários no Visual Studio 2012

A solução apresentada neste artigo foi criada no .NET framework 4.5, através do Visual Studio 2012 Professional. Basicamente, será implementada uma biblioteca para o cálculo de tributos federais como PIS, COFINS, ISS e IRPJ; estes estão geralmente associados a operação fiscais que envolvam a prestação de seguros.

O primeiro passo para a implementação do exemplo baseado em testes unitários consiste na criação de uma Solution chamada “TesteNF”, conforme a Figura 1.

Figura 1. Criando a Solution TesteNF.

A solução TesteNF será formada pelos seguintes projetos:

Uma vez gerado o arquivo correspondente à Solution TesteNF, deve-se prosseguir com a criação de um projeto do tipo Class Library com o nome “TesteNF.Utils” (Figura 2).

Figura 2. Criando a Class Library TesteNF.Utils.

Neste primeiro momento, será implementada no projeto TesteNF.Utils uma versão extremamente simples da classe TributacaoHelper, com todos os métodos para cálculo de impostos retornando zero como resultado de sua execução. O objetivo é justamente assegurar que a primeira bateria de testes unitários falhe, de maneira que apenas num segundo momento sejam codificados os cálculos necessários.

Na Tabela 1 estão indicados as alíquotas para cada um dos impostos calculados por meio do tipo TributacaoHelper.

ImpostoAlíquota
PIS (Programa de Integração Social)0,0065
COFINS (Contribuição para o Financiamento da Seguridade Social)0,03
IRPJ (Imposto sobre Renda de Pessoa Jurídica)0,015
CSLL (Contribuição Social sobre Lucro Líquido)0,02

Tabela 1. Alíquotas de impostos que serão utilizadas pelo tipo TributacaoHelper

A Listagem 1 tem a definição inicial para a classe TributacaoHelper. Estão declarados neste tipo os métodos estáticos CalcularPIS, CalcularCOFINS, CalcularIRPJ e CalcularCSLL, com cada uma das operações devolvendo como resultado o imposto a ser pago a partir de um valor de serviço prestado.

Listagem 1. Classe TributacaoHelper.
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TesteNF.Utils { public static class TributacaoHelper { public static double CalcularPIS(double valorBase) { return 0; } public static double CalcularCOFINS(double valorBase) { return 0; } public static double CalcularIRPJ(double valorBase) { return 0; } public static double CalcularCSLL(double valorBase) { return 0; } } }

Agora é a vez de criar o projeto de testes chamado “TesteNF.Utils.UnitTesting” (Figura 3). Assim como acontece com outros templates, esse tipo de projeto é nativo do Visual Studio e não exige qualquer configuração adicional para sua inclusão em uma Solution.

Figura 3. Criando o projeto TesteNF.Utils.UnitTesting.

Com o projeto TesteNF.Utils.UnitTesting gerado, é possível observar a existência de um arquivo chamado UnitTest1.cs, também criado automaticamente, mas pode removê-lo, já que outras classes de testes serão criadas seguindo uma nomenclatura própria para este projeto.

OBSERVAÇÃO: o projeto TesteNF.Utils.UnitTesting executa o teste de funcionalidades da classe TributacaoHelper, então será necessário adicionar ao mesmo uma referência que aponte para a Class Library TesteNF.Utils.

A primeira classe de testes a ser criada terá o nome “TributacaoHelperTeste01”. O Visual Studio também oferece um template (“Basic Unit Test”) para este tipo de construção, sendo possível selecioná-lo a partir da opção que adiciona novos tipos de arquivos a um projeto (conforme indicado na Figura 4).

Figura 4. Criando uma nova classe de teste unitário.

Os diferentes testes a serem executados através da classe TributacaoHelperTeste01 tomarão R$ 8.400,00 como valor-base para serviços prestados. Na Tabela 2 estão listados os valores esperados para cada cálculo de imposto a ser verificado.

ImpostoOperação a ser Verificada em TributacaoHelperValor Esperado
PISCalcularPIS54,6
COFINSCalcularCOFINS252
IRPJCalcularIRPJ126
CSLLCalcularCSLL84

Tabela 2. Valores esperados para os testes da classe TributacaoHelperTeste01.

Na Listagem 2 está o código corresponde à classe TributacaoHelperTeste01. Nota-se na definição deste tipo as seguintes características:

OBSERVAÇÃO: As classes TestClassAttribute, TestMethodAttribute e Assert estão situadas no namespace Microsoft.VisualStudio.TestTools.UnitTesting.

Listagem 2. Classe TributacaoHelperTeste01.
using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using TesteNF.Utils; namespace TesteNF.Utils.UnitTesting { [TestClass] public class TributacaoHelperTeste01 { [TestMethod] public void TestarCalculoPIS() { Assert.AreEqual(TributacaoHelper .CalcularPIS(8400.00), 54.60); } [TestMethod] public void TestarCalculoCOFINS() { Assert.AreEqual(TributacaoHelper .CalcularCOFINS(8400.00), 252.00); } [TestMethod] public void TestarCalculoIRPJ() { Assert.AreEqual(TributacaoHelper .CalcularIRPJ(8400.00), 126.00); } [TestMethod] public void TestarCalculoCSLL() { Assert.AreEqual(TributacaoHelper .CalcularCSLL(8400.00), 84.00); } } }

Durante a construção de testes unitários, o uso de uma nomenclatura padronizada representa uma boa prática de desenvolvimento. Nos exemplos apresentados neste artigo o nome de todos os métodos é iniciado pelo prefixo “Testar”. Já a identificação das classes de testes é formada pelo nome do tipo a ser analisado (no caso TributacaoHelper), seguidos pela palavra “Teste”, além de números identificadores (“01”, “02”).

Além de AreEqual, a classe estática Assert (namespace Microsoft.VisualStudio.TestTools.UnitTesting) também disponibiliza para validações outros métodos, constituindo exemplos disto as operações:

Uma segunda classe de testes chamada TributacaoHelperTeste02 precisará ser criada. Na Tabela 3 estão os valores que servirão de base para os testes, considerando R$ 7.728,00 como total de prestação de serviços.

ImpostoOperação a ser Verificada em TributacaoHelperValor Esperado
PISCalcularPIS50,23
COFINSCalcularCOFINS231,84
IRPJCalcularIRPJ115,92
CSLLCalcularCSLL77,28

Tabela 3. Valores esperados para os testes da classe TributacaoHelperTeste02.

Na Listagem 3 é apresentado o código que define a classe TributacaoHelperTeste02; este tipo conta com uma estrutura idêntica à da classe TributacaoHelperTeste01, diferindo apenas pelos valores analisados.

Listagem 3 Classe TributacaoHelperTeste02.
using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using TesteNF.Utils; namespace TesteNF.Utils.UnitTesting { [TestClass] public class TributacaoHelperTeste02 { [TestMethod] public void TestarCalculoPIS() { Assert.AreEqual(TributacaoHelper .CalcularPIS(7728.00), 50.23); } [TestMethod] public void TestarCalculoCOFINS() { Assert.AreEqual(TributacaoHelper .CalcularCOFINS(7728.00), 231.84); } [TestMethod] public void TestarCalculoIRPJ() { Assert.AreEqual(TributacaoHelper .CalcularIRPJ(7728.00), 115.92); } [TestMethod] public void TestarCalculoCSLL() { Assert.AreEqual(TributacaoHelper .CalcularCSLL(7728.00), 77.28); } } }

Agora processaremos os testes: as diferentes opções que permitem acionar a execução encontram-se no menu Test (Figura 5).

Figura 5. Menu Test no Visual Studio 2012.

Acionando a opção “All Tests” será ativada a janela “Test Explorer” (Figura 6) com o resultado da execução das duas classes de testes unitários. Conforme pode ser observado, todos os testes falharam.

Figura 6. Listagem dos testes que falharam.

Selecionando um dos testes que falharam serão exibidos detalhes a respeito do mesmo como, por exemplo, em que arquivo/classe está situado o método que produziu a falha (conforme indicado na Figura 7).

Figura 7. Detalhamento de um teste que falhou.

A Listagem 4 apresenta o código referente à classe TributacaoHelper, já considerando agora as instruções necessárias para o cálculo dos diversos impostos previstos por este tipo.

OBSERVAÇÃO: para a aplicação de exemplo foram utilizados valores fixos nas alíquotas de impostos. Numa situação real, o ideal seria que tais alíquotas fossem parametrizáveis, ou seja, definidas em um arquivo de configuração ou, até mesmo, numa tabela pertencente dentro de uma base de dados.

Listagem 4. Classe TributacaoHelper após a implementação dos cálculos de impostos.
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TesteNF.Utils { public static class TributacaoHelper { public static double CalcularPIS(double valorBase) { return Math.Round(valorBase * 0.65 / 100, 2); } public static double CalcularCOFINS(double valorBase) { return Math.Round(valorBase * 3 / 100, 2); } public static double CalcularIRPJ(double valorBase) { return Math.Round(valorBase * 1.5 / 100, 2); } public static double CalcularCSLL(double valorBase) { return Math.Round(valorBase * 1 / 100, 2); } } }

Uma nova execução dos testes unitários resultará em sucesso desta vez, como pode ser visualizado na Figura 8.

Figura 8: Execução com sucesso de todos os testes.

Observando o código que implementa a classe TributacaoHelper, nota-se que em todos os métodos repete-se o uso da operação Round, a qual se encontra definida na classe estática Math (namespace System). Além disso, em todas as situações a alíquota é dividida por 100, obtendo o valor decimal correspondente a uma porcentagem. Este é um bom exemplo no qual técnicas de refatoração podem ser empregadas: um método chamado CalcularImposto é implementado agrupando as instruções comuns com, o mesmo sendo acionado pelas demais operações de TributacaoHelper (Listagem 5).

Listagem 5: Classe TributacaoHelper refatorada.
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace TesteNF.Utils { public static class TributacaoHelper { private static double CalcularImposto( double valorBase, double aliquota) { return Math.Round(valorBase * aliquota / 100, 2); } public static double CalcularPIS(double valorBase) { return CalcularImposto(valorBase, 0.65); } public static double CalcularCOFINS(double valorBase) { return CalcularImposto(valorBase , 3); } public static double CalcularIRPJ(double valorBase) { return CalcularImposto(valorBase, 1.5); } public static double CalcularCSLL(double valorBase) { return CalcularImposto(valorBase, 1); } } }

Reprocessando os testes após essa etapa de refatoração, nota-se que os mesmos ainda são válidos, o que significa que não foram introduzidos erros no código-fonte (Figura 9).

Figura 9. Testes executados com sucesso após refatoração da classe TributacaoHelper.

Com isso, procurou-se demonstrar como testes unitários podem ser utilizados a partir do Visual Studio 2012. O desenvolvimento guiado por testes pode resultar não apenas na obtenção de aplicações com menor sujeição a erros, como também contribuir para uma melhor estruturação do sistema ao estimular o uso de boas práticas de arquitetura.

Contudo, é altamente recomendável não depender apenas de testes unitários para a validação de uma aplicação. As outras modalidades de testes são importantíssimas e devem ser aplicadas ao longo das diferentes fases de um projeto, garantindo um produto estável, sem falhas que impeçam sua operação normal no dia a dia.

Artigos relacionados