Test Driven Revisited

by alisson.vale 15/3/2006 02:54:54

É impressionante como o desenvolvimento orientado a testes transforma o jeito como se escreve código. Também é impressionante como o TDD muda a concepção das coisas. Invertemos nossa forma de trabalhar - agora escrevemos testes antes do código. Nossos compiladores se transformaram de meros "Type checkers" para grandes parceiros nos frequentes processos de red-green-refactor que já estamos tão acostumados a executar. "Faça isso", "faça aquilo"...  "crie esta classe", "codifique este método", "implemente esta interface"... Outro dia, ouvindo um podcast do Kent Beck, descobri que o fato do TDD promover a qualidade do código já não é tão importante. Qualidade é um atributo momentâneo. Sobe e desce a cada release liberada.  Veja que não estou dizendo que qualidade não é importante. Muito pelo contrário, qualidade é essencial e é fato fundamental para a sobrevivência de qualquer produto. Mas qualidade não deve ser tratada como uma meta a ser alcançada a qualquer custo. Ela é uma consequência natural de um outro atributo ainda mais importante: Saúde. Como um organismo vivo, um software é saudável quando ele se defende bem de agressões externas - mudanças de requisito, novas funcionalidades, desafios tecnológicos. Software parado é software morto. Software que evolui com dificuldade é sofware convalescente.  

 "My goal is not quality software, my goal is healthy software" (K.Beck).

Mais do que promover a qualidade do software, TDD promove saúde. Great! Um ponto a mais para quem o utiliza. Mas não é só isso... como nos comerciais da Polishop, levando o TDD você ainda leva totalmente grátis a necessidade e rever todo o design de sua aplicação para que você possa se beneficiar da técnica de forma plena. Em resumo, TDD promove um melhor design. Faça a seguinte experiência: Selecione aleatoriamente uma classe qualquer do seu sistema. Você consegue testá-la em alguns minutos? Não? Então sua classe pode ter problemas de acoplamento e/ou coesão (leia mais em Michael Feathers).

 Nossa conclusão é que um teste escrito dessa forma é muito mais eficiente:

        [Test]
        public void LiquidarDocumentoComCaixaAbertoTeste()
        {

            Id idCaixa = new Id(93);
            Id idBoleto = new Id(120922);
           
Id idUsuario = new Id(2);
            const double VALOR_BOLETO = 392;

            ResultList list = new MemoryResultList();

            CaixaVO caixaVO = FactoryVO.GetCaixaVO(idCaixa, idUsuario, TestEnvironment.Hoje(), TestEnvironment.DataInDefinida());

            BoletoVO boletoVO = FactoryVO.GetBoletoVO(idBoleto, TestEnvironment.Hoje(), Boleto.STATUS_ABERTO, VALOR_BOLETO);

            Tesouraria operadorTesouraria = new Tesouraria();

            operadorTesouraria.LiquidarDocumento(caixaVO, boletoVO, TestEnvironment.Hoje(), VALOR_BOLETO, false, list);

            BoletoVO voBoletoVerificacao = (BoletoVO)list.GetObjectFromListById(typeof(BoletoVO), idBoleto);

            LiquidacaoDocumentoVO liquidacaoVO = (LiquidacaoDocumentoVO)list.GetObjectFromListByIndex(1);

            Assert.AreEqual(Boleto.STATUS_PAGO, voBoletoVerificacao.Status);
            Assert.AreEqual(idBoleto, liquidacaoVO.Boleto);

        }

Este teste é simples, mas ilustra bem como o design da aplicação pode fazer a diferença. A primeira mudança de paradigma é o uso do Dependency Injection. Push, don’t Pull! O objeto que está sendo testado recebe todos os outros objetos que colaboram com ele. Além de permitir a testabilidade da classe, essa técnica ajuda a manter o nível de acoplamento sob controle. Quanto mais objetos uma classe precisar receber, maior será a desconfiança de que algo está errado e de que as responsabilidades precisam ser redistribuídas. O objeto Tesouraria representa uma metáfora de design e sua função é selecionar e orquestrar objetos para a execução de operações co-relacionadas. Como seu estereótipo é de domínio, sua execução não depende de bancos de dados, bibliotecas de terceiros, etc. Ou seja, ela, como todas as outras desse estereótipo, é auto-testável.

E quanto ao acesso ao banco? É difícil, hoje em dia,  imaginar uma aplicação que não dependa de um banco de dados ou de um meio de armazenamento qualquer. Fazer acesso a banco em objetos de domínio não é uma boa opção. Em nossa arquitetura, o acesso a banco é feito apenas para alimentar a camada de domínio, mas não faz parte dela. Uma camada de Serviços recupera informações persistidas de uma camada DAO. Para testar a camada de serviços podemos usar Mock Objects ou utilizar um framework de cenários que criamos para gerar um cenário no banco de dados compatível com o teste que se deseja realizar. Veja o código que testa uma classe da camada de serviço com o apoio de um framework de cenários:

        [TestFixtureSetUp]
        public void CriarCenario()
        {
            ScenarioManager.RunScenario("001");
        }

        [Test]
        public void ObterBoletoTeste()
        {
            string sqlPrimeiroBoletoAberto = "SELECT TOP 1 * FROM " + BoletoVO.TABLE_NAME;
            sqlPrimeiroBoletoAberto += " WHERE " + BoletoVO.COL_STATUS + " = 'A'";

            DataRow dadosBoleto = ScenarioManager.GetInfoInTheFirstRow(sqlPrimeiroBoletoAberto);

            Assert.AreEqual(false, dadosBoleto == null, "Deveria existir um boleto no Banco de Dados neste ponto.");

           int idBoleto = Convert.ToInt32(dadosBoleto[BoletoVO.PK_NAME]);

           ServicosBoleto service = new ServicosBoleto();
           BoletoVO boletoVO = service.ObterBoleto(idBoleto);

           Assert.AreEqual(boletoVO.Id, new Id(idBoleto));
          
Assert.AreEqual(boletoVO.DataVencimento, Convert.ToDateTime(dadosBoleto[BoletoVO.COL_DATA_VENCIMENTO]));
           Assert.AreEqual(boletoVO.Status, Convert.ToString(dadosBoleto[BoletoVO.COL_STATUS]));
           Assert.AreEqual(boletoVO.ValorDocumento, Convert.ToDouble(dadosBoleto[BoletoVO.COL_VALOR_DOCUMENTO]));
       
}

        [TestFixtureTearDown]
        public void TearDown()
        {
            ScenarioManager.CleanUp();
        }

O Framework de cenários é configurado em um arquivo Xml, algo como o mostrado a seguir:

<?xml version="1.0" encoding="utf-8" ?>

<Cenarios RootScriptFolder=" \Phidelis.Src\Kernel\Financeiro\ TestesFuncionais\Cenarios\Scripts">

    <Script Id="CleanUp_01" Type="CleanUp" Description="Exclusão de todos os registros" File="CleanUp.sql" />
   
<
Script Id="001" Type="Cenario" Description="Ficha Financeira com um boleto de rematrícula aberto" />

</
Cenarios>

Este tipo de teste é categorizado como um “Teste Funcional” e ainda é um teste de desenvolvedor e não um teste de Quality Assurance. No entanto, só é executado sobre a camada de serviços, que é a camada consumida pela Interface de Usuário.

Depois de 2 anos trabalhando com TDD e mais de 1000 testes sendo executados a cada build, chegamos a importantes conclusões com relação ao nosso processo de escrever testes. Apenas com uma mudança arquitetural, poderíamos ter um aumento fenomenal na quantidade e na qualidade dos testes unitários, além de podermos executá-los de forma mais rápida e com mais frequência. As técnicas de Design realmente fazem a diferença, e são os testes unitários que nos têm levado a descobri-las e nos motivado a incorporá-las em nossos sistemas.


Tags:

Coding

Posts relacionados

Comentar


(Vai mostrar seu Gravatar)  

  Country flag

[b][/b] - [i][/i] - [u][/u]- [quote][/quote]



Pré-visualização

22/11/2008 02:15:35

Sobre o Autor

Alisson Vale Alisson Vale
Líder de Projeto da Phidelis Tecnologia.


E-mail me Send mail
      Sign in

Últimos posts

Últimos comentários

Termo de Responsabilidade

Este site apresenta apenas opiniões pessoais. Não necessariamente representa as opiniões ou práticas da Phidelis Tecnologia.

© 2008