Rua sem saída: não consigo criar meu próximo teste – Parte I

Continuando o assunto como criar meu próximo teste, irei abordar os três primeiros tópicos do primeiro post da série.

Caso não tenha visto o prelúdio que deu início a isto, recomendo que leia antes de continuar!

Não consigo imaginar em qual objeto ficará a feature – e/ou se ele precisará de outros objetos existentes para funcionar.

Antes de se preocupar com o aonde temos que nos preocupar com o o que. Explico: a vontade de sair criando coisas e ver teste passar é muito tentadora, fato. Mas antes, planejar aquela feature irá te facilitar muito o começo. Tornando em recipe:

  1. O que a feature irá fazer exatamente? Neste ponto, você já deve saber o que esta nova tarefa irá acrescentar no sistema, do começo ao fim.
  2. Será necessário criar outra classe ou é uma nova funcionalidade para uma classe existente?
  3. Essa tarefa precisará de informação de alguma classe já existente? Quais?

Carregado isto para sua memória ram (cabeça), é possível seguir em frente. O foco agora é esboçar um rascunho de como serão as coisas. Neste passo, cada um tem uma técnica diferente. Eu já fiz rascunho de UML, rasbisco em Moleskine, arquivo de texto, diagrama de sequência e arquivo de teste.

Depende do que estamos lidando, por exemplo, se você está criando uma feature que irá permitir que o sistema receba pagamentos, você provavelmente lidará com a criação de um novo módulo inteiro. Isto requer um rabisco mais arquitetural. Agora, se você está criando uma classe dentro de um módulo já existente ou apenas uma nova ação para uma classe, pode-se ficar com arquivos de teste e diagrama de sequência.

Este tipo de rascunho serve para nossa mente começar a relacionar o assunto com mais “detalhes” – este detalhe de nome, e possível relação entre objetos/pacotes facilitará pensar no assunto enquanto você está fazendo outras coisas (almoçando, por exemplo). Quem não acha que nossa mente tem background jobs? 😛

Saindo da arquitetura e partindo para o design (de código), uma coisa que me ajuda muito na hora de encontrar nomes para as classes é o Ubiquitous Language. Em miúdos, é extrair nomes/verbos das conversas com seu cliente e tentar encaixá-los dentro do software. Isso é quase uma arte – e a prática neste caso tornará você mais ágil neste passo. Se ainda assim, encontrar nome está difícil, crie a API que espera ter no código.

"Rascunho de design de código"

Durante esse processo eu acabei amadurecendo a ideia e os relacionamentos. Veja que até abortei uma ideia que ia começar. Com isso, eu tenho mais detalhes e posso sem medo criar meu arquivo de teste (RSpec, JUnit, NUnit, etc.) e começar a experimentar.

Preciso mockar um objeto do qual é criado dentro da classe que vou usar como auxiliar.

Clássico. Vou usar o esboço que fiz acima. Vamos supor que criei o código User.build_invoice(for_orders). Seguindo o que está no rabisco, sairia algo assim:

class User
  def build_invoice(for_orders)
    Checkout.new.create_for(self, for_orders)
  end
end

class UserTest < Test::Unit
  # helper method to simplify User object creation
  def subject
    User.new # Ruby doesn't require 'return statement' to return values
  end

  # collection with dummy orders just to satisfy our test
  def for_orders
    [mock('order'), mock('order')]
  end

  def test_build_invoice_with_orders
    subject.build_invoice(for_orders) # ???
  end
end

Disto eu te faço uma pergunta: como farei o teste de unidade uma vez que Checkout.new está hardcoded no método build_invoice? No exemplo acima, Checkout.new não requer nenhum objeto em seu construtor, mas provavelmente, assim como seu User ele deve instanciar outras classes internamente (o que novamente, é errado) e em algum momento seu código será intestável por causa desses objetos voodoo que aparecem do nada e você não tem como configurá-los a seu modo.

E mais, como que o User pode saber do que Checkout precisa em seu construtor? O User é bidu? Acho que não. Uma coisa interessante que existe no Ruby são os valores pré-definidos (e aceita instância de classe, viu, PHP e afins?). Eu facilmente poderia modificar meu código para:

class User
  def build_invoice(for_orders, checkout: Checkout.new)
    checkout.create_for(self, for_orders)
  end
end

Agora, User#build_invoice recebe 1 ou 2 parametros: orders e o objeto de checkout. Isto permite que você injete o Checkout na configuração que achar melhor no código. E sem mágica alguma, isto torna seu código testável novamente.

class UserTest < Test::Unit
  # helper method to simplify User object creation
  def subject
    User.new # Ruby doesn't require 'return statement' to return values
  end

  # collection with dummy orders just to satisfy our test
  def for_orders
    [mock('order'), mock('order')]
  end

  def test_build_invoice_with_orders
    checkout = mock('Checkout') # using Mocha gem to create mocks/stubs
    checkout.expects(:create_for).with(subject, for_orders).once
    subject.build_invoice(for_orders, checkout: checkout)
  end
end

Utilizei pseudo Test::Unit para ficar mais fácil para Javeiros/PHPeiros/.NETeiros, etc.

Pude criar o mock de checkout e criar uma expectativa nele que diz: espero que o User#build_invoice me chame passando os argumentos: user e orders uma vez. Para quem nunca viu, isso é um teste de expectativa sob comportamento. Tem seus usos, como já afirmou Sandi Metz.

Percebi que meu setup (before no RSpec) tem mais que 5 linhas.

Primeiramente, 5 linhas é modo de dizer. Cada projeto, cada linguagem, cada tudo, irá variar a quantidade de linhas. O ponto aqui é chamar a atenção para a complexidade de código de teste que diretamente afeta a complexidade do código de produção, que afeta o design, que afeta o bug detection, que afeta seu bug tracking, que afeta seu chefe, que te afeta e que estressa a todos. Um ciclo imenso justamente porque alguém neste meio acha que design de código não é importante e/ou que não deve ser pensado de forma contínua.

De fato a isto, a frase mostre-me seu setup e direi como tu és passa a fazer sentido. O setup é um dos caras mais evidentes nos testes. Se ele é complicado, indica que seu código é um espaguete. Apesar de gostoso na vida real, no código ele te coloca em um cenário complicadíssimo.

Soluções? Bem, refatorar ou até mesmo refazer partes/módulos do seu software. Refatorar != refazer. A conversa de não tenho tempo agora pode até ser válida em alguns casos, mas antes de afirmá-la, pense muito bem se realmente está em um momento complicado do seu negócio ou se é apenas por achar que não vale a pena.

Supondo que você pensou muito sobre o assunto e realmente não tem tempo para refazer ou refatorar, uma solução válida é criar as próximas features o mais isoladas do código macarrônico possível. Eu abordei isso no post Unit Testing em aplicações legadas e sem teste. Dá uma conferida que fará bastante sentido neste caso.

Por fim, você pode estar pecando na estrutura dos seus testes. Como disse neste post, o teste tem anatomia e você deve conhecê-la para definir se um teste está bem escrito ou não. Spoiler: não é pela quantidade de linhas que define-se isto.

To be continued.

Continuação em: Parte 2 – Tópicos 4, 5 e 6

Rua sem saída: não consigo criar meu próximo teste – Prelúdio

Você resolveu tentar a fazer test-first. Conseguiu fazer seu primeiro teste passar. As coisas estavam fluindo bem até quando você chegou em um ponto em que não consegue escrever o próximo teste. A ideia está na sua cabeça, pronta para ser jogada em execução, mas travou no processo de escrita da especificação executável (seu teste). E agora?

Neste momento, você que está ainda se adaptando a nova forma de pensar, acaba chegando em uma rua sem saída. O segredo aqui é não desistir e sair falando que o @dhh é um gênio da computação, um visionário e que TDD is dead mesmo. Calma lá, rapá!

Para lidar com este problema crítico, temos que antes analisar nosso (seu) cenário. Para tanto, vamos listar alguns sintomas que poderão nos levar a causa e depois a solução:

Ao tentar escrever o teste, eu:

  1. Não consigo imaginar em qual objeto ficará a feature – e/ou se ele precisará de outros objetos existente para funcionar.
  2. Preciso mockar um objeto do qual é criado dentro da classe que vou usar como auxiliar.
  3. Percebi que meu setup (before no RSpec) tem mais que 5 linhas.
  4. Estou querendo testar um método privado.
  5. Não consigo isolar a feature que vou criar. Os objetos que irão interagir com ela são complexos para mockar.
  6. Não consigo verificar se um evento/callback do framework que estou usando foi chamado.
  7. Tenho um mock que retorna um outro objeto que precisa também de ser configurado com mock. Parece um mock-de-mock.
  8. Vou precisar instanciar vários objetos nesta nova feature.
  9. Estou criando o código de produção e meu método alvo do teste está imenso ou/e cheio de chamadas à métodos privados da própria classe.
  10. Demorei tanto pensando numa solução que acabou desistindo e criando o código sem teste. O código de produção funciona (você o testou manualmente), porém ainda não sabe como escreverá (e se) o teste que o validará.

Prelúdio

Para começar, precisamos carregar os pré-requisitos em nossa mente para conseguir seguir em frente. Eles são leituras importantes que contêm conceitos, dicas e definições de assuntos que precisamos lembrar de bate-pronto para lidar com o grande problema acima.

  • Three rules of TDD: Red, Green, Refactor. Sem pressa. Pensar 10 minutos pode render 1h de bate cabeça e desespero com prazos.
  • Baby steps: nos seus primeiros 6 meses, recomendo que faça sempre baby steps para evitar os problemas acima.
  • God Class ou God Method – você não está abstraindo as coisas como deveria. Você pode e deve cogitar criar POROs (Plain Old Ruby Objects) que irão auxiliar seus demais objetos dentro do pacote/domínio da aplicação.
  • Tell, Don’t Ask evite perguntar algo para o objeto colaborador para baseado no resultado, fazer algo. Se precisa fazer, solicite que o próprio faça uma vez que ele tem o valor/estado que é pré-requisito.
  • Injeção de Dependência: o Ruby inteiro é baseado nisso. É algo natural e que você deve manter em seus códigos. Poderá resolver problemas de mocking e overmocking, por exemplo.
  • Crie tipos! Seu software é financeiro? Inspire-se nos nomes reais ou que seu cliente fala. Se na reunião ele diz que o cliente irá transferir dinheiro da conta pessoal para um outro cliente, experimente criar Money, Transfer, Account, Customer. Ubiquitous Language FTW!
  • SRP. The same old Single Responsibility Principle. Ok, você já ouviu isso incontáveis vezes. Mas pare para pensar naquele objeto com um método público e 10 privados. Pensou? Agora leia a dica acima de sobre criar tipos. Entendeu o que eu quis dizer?
  • Você entendeu quais os objetivos de mockar, certo?

Continuará

Vou comentar em detalhes cada um dos 10 problemas ao criar o próximo teste nos próximos posts. Vou utilizar diagramas, exemplos de código e tudo mais que for necessário em cada caso. O prelúdio obviamente contém spoilers sobre o que utilizaremos para resolver os problemas e continuar on track no test-first. Então se você estiver disposto, pense com calma e veja se consegue sair da rua sem saída antes do post que falará sobre seu problema em detalhes.

Para começar, ouça o Ruby Rogues #158 chamado Confessions e o Thoughtbot #79: The Gentle Wise One. Se você codifica em Ruby, não deixe de ler e ver o meu post sobre Arquitetura, Rails e o ecossistema Ruby, onde contém um bom start sobre as raizes de alguns dos 10 problemas ao fazer seu próximo teste passar.

To be continued.

As demais partes estão aqui em ordem:

Test-Driven Development é desnecessário!

Há alguns dias atrás ouvi a seguinte afirmativa:

Acredito que testes de unidade são importantes; Não vejo necessidade porém, em fazer TDD.

Será?

Separando as coisas: Teste de Unidade

Teste de unidade, como comentei anteriormente, tem como objetivo garantir que uma parte da regra de negócio funcione como foi solicitada/descrita pelo interessado pelo software. O teste deve abranger apenas um cenário, seja ele feliz (do caminho feliz); ou falho. Todo e qualquer input que se faça necessário deverá ser introduzido com o auxílio de Mocks ou/e Stubs, fazendo assim com que o teste seja o mais independente possível do restante das classes que colaboram/interagem com aquele trecho na vida real (código de produção).

Uma maneira de verificar se seu código de teste está bem escrito é atentar-se para a anatomia do teste de unidade: deverá ter um trecho para input dos dados necessário ao teste; execução do código que está sob teste; e por fim, analisar os resultados obtidos.

Fazer o teste depois do código de produção poderá no início trazer confiabilidade do que se está fazendo. Ao crescer a aplicação porém, é provável que você perca o controle da suite e o resultado é preocupante: um bolo de testes acoplados e sem garantias sob o código de produção. Nessa hora você cairá um beco sem saída e culpará injustamente “essa falácia de testar software”.

Podemos afirmar também que fazer teste de unidade para mostrar para o chefe não é uma boa ideia, pois o parabéns que você obterá com ele hoje, poderá se transformar no maior vilão, quando você perder o controle estrutural do seu código.

Separando as coisas: Test-Driven Development

Tem seu cerne em: Red, Green, Refactor. Em miúdos:

  1. Escreva o teste que irá exercitar uma parte isolada do seu código (leia acima sobre isso).
  2. Escreva o código (de produção) que irá fazer o teste ficar verde (passar).
  3. Verifique se seu código de produção (e o de teste) podem ter melhora, tanto na escrita quando na qualidade e best practices da linguagem.

Repita esse processo até começar a colher os resultados do TDD. Depois que estiver mais acostumado (coisa de algumas semanas), você poderá se adaptar melhor. Lembre-se: Baby Steps são importantes nesta fase: faça uma coisa de cada vez. Não escreva 20 linhas de código sem ir verificando com seu teste o que está acontecendo. Não tenha pressa. Você está aprendendo uma nova forma de ver o mundo.

Outro dia, mexendo num código da galera aqui, encontrei um método que possuia 37 variáveis temporárias; 7 loops; 11 ifs/else; 5 comentários de uma linha.

É nítido que o programador não sabia mais onde colocar as coisas. Ele perdeu o controle do código de uma forma tão intensa, que ele precisou fazer 3 loops com a mesma Collection, para fazer coisas diferentes com ela.

Nem é preciso entrar no detalhe de Single Responsibility Principle; Dont Repeat Yourself. Ele não sabia aonde colocar cada coisa. Ponto.

É nisso que o TDD auxilia. Ele está ali como um guarda, pronto para levantar uma bandeira assim que você começa a se perder. Ele sinaliza através dos testes a necessidade de repensar a solução, reorganizar o código para a nova feature, para fazer o flush mental.

Test-Driven Development é sobre onde colocar seu código e não sobre Verificação e Validação.

Enquanto você não ter essa visão, continuará a sofrer com debugging, duplicação à n-ésima (duplicação da duplicação n vezes) e o renomado Bug Ioiô: bug que é resolvido e depois aparece novamente.

O ponto de encontro

Teste de Unidade + Design Orientado a Objetos: Test-Driven Development Design.

Concluindo

Não, você não é o Jiraya da programação. Pouco importa se você tem toda a ideia na sua cabeça, aposto com você de que, se ao invés de primeiro fazer o código de produção fizer o teste, aquela sua toda ideia inicial terá sido apenas um rascunho da versão que criará guiada pelos testes.

A dica final é: não seja afobado. Ao começar fazer testes de unidades, faça do modo clássico: Red, Green, Refactor.

Tem uma definição bacana sobre Test-Driven Development? Mande um tweet com a hashtag #deftdd ou me mencione. Vou retuitar todas as criativas 😉

Unit Testing em aplicações já existentes e sem teste

Se você achou interessante começar uma aplicação com Test-Driven Development, o que dizer sobre um dos fatores mais comuns em nossa área que é assumir um projeto que já está em produção, porém não possui testes de unidade ?

Softwares legados também possuem direito de ter seu lugar ao sol !

Cenário clássico

Você entra para uma nova equipe de desenvolvimento. Como um tester acostumado com as práticas do Test-First, você vai cegamente à procura do diretório(s) com os testes para: executá-los afim de descobrir se configurou o ambiente corretamente; ler, afinal quer entender um pouco mais do projeto; depois de vasculhar /tests, /spec, /xpto/namespace/module/tests e não encontrar nada você descobre que: o projeto não possui testes!

Normal? Infelizmente sim – Há muitos desenvolvedores que não gostam de testes. Os motivos variam e é assunto futuro -, vários projetos que vi não possuiam testes at all. Em todas às vezes porém, meus gestores eram favoráveis a adoção e receptivos a mudança de comportamento que isto causaria.

Sem testes e com carta branca para começar a mudar o cenário. O que fazer agora ?

$ ./configure

Antes de tudo, você precisa ver como o projeto está feito. Passe umas horas ou dia olhando o que tem ali e por onde poderia começar a trabalhar. Após isto, vamos listar coisas importantes sobre o código que já funciona em produção:

  • Não faça testes de unidade do que já existe;
  • Não seja convencido a fazer testes de unidade do código existente;
  • É insanidade querer testar as partes de um código que já funciona. Não faça isso;
  • Não faça teste de unidade do código existente.

Motivo para todos itens acima é o mesmo: teste de unidade é seu guia para construir o software requisitado da menor e melhor forma possível. Ele é um guia e não uma Bug Tool. Se o código já está presente, você, se cair na tentação de fazer teste daquilo, forçará seu teste a passar naquele código. Seu teste pode até pegar um bug, mas você, novato no projeto, não saberá que é um bug e acabará mudando o teste para passar naquela condição bugada. Costumo dizer que testar código já feito é perder tempo.

Vou comentar dois cenários aqui: a) você precisa criar um módulo/integração/package novo dentro desse projeto legado; b) você precisa adicionar testes ao código existente.

Vamos lá 🙂

$ make all

Vou pegar os dois casos e comentários sem separado:

a) Você precisa criar um módulo/integração/package novo dentro desse projeto legado

Para mim, dentre os dois esse é o mais fácil. Tudo que você precisa fazer é começar o módulo novo de forma isolada. Ou seja: evite depender do código sem teste dentro do seu módulo novo. Você pode obter isso com o uso de Adapters. Crie seu módulo novo, seguindo Test-Driven Development e toda vez que precisar da colaboração de uma classe/objeto que não tem o devido teste, crie um Adaptador que isolará aquela dependência instável do seu código novo. Isso te dará maior confiabilidade no que está fazendo.

Isole toda a comunicação do seu módulo novo, com o uso de Façades.

Supondo que você está fazendo um módulo de pagamento, crie uma façade para seu código e a faça prover os recursos que o restante do software necessitar:

    class PaymentFacade {
        public ... create(...);
        public ... createRecurrent(...);
        public ... getPayments(Datetime forDate);
        public ... cancelRecurrent(Integer paymentId);
        public ... processPayment(Integer paymentId);
        ....
    }

Na sua Façade, trafegue valores escalares e não objetos do sistema. Isto tornará seu module de pagamento mais isolado e protegido do código não testado.

b) Você precisa adicionar testes ao código existente

Lembre-se: não adicione testes de unidade ao código já existente e sem teste. Partindo disto, seguimos:

Anticorruption Layer, DDD

Este é mais complicado. Costumo utilizar uma técnica do livro Domain-Driven Design de Eric Evans chamada Anti-corruption Layer.

A ideia aqui é fazer igual na figura: o “ACL” é o Anti Corruption Layer, a camada que irá defender seu código novo do código legado sem testes.

Dando um zoom nesta imagem, tudo que precisamos fazer é criar uma layer que conterá classes/objetos ou/e métodos em classes existentes que farão adapters/façades, traduzindo o que o sistema já tem para conseguirmos encaixar no que estamos fazendo.

DDD Zoom-in

Client System: seu sistema legado
Anticorruption Layer: o meio entre seu sistema legado e seu módulo novo
External: seu código novo, totalmente Test-First.

Não sinta-se intimidado em tentar fazer deploy de uma versão funcional com seus adapters, façades e anticorruption layer. Eu já mantive em produção por algumas semanas código “macabro”, cheio de “fios pendurados” e coisas em aberto, até que eu pudesse ajustá-las para em deploys futuros, pudesse removê-los tranquilamente.

# make install

Berlim Wall, the fall

Agora que consegue deployar seu código novo e testado junto do software que tinha antes sem testes de uma forma até que harmoniosa e isolada, você precisa derrubar o muro de Berlim que construiu: você precisará refatorar seu código legado e sem testes para que comece a adicionar testes nele também. Ao adicionar testes no código refeito, você poderá remover os Adapters, Façades e até a Anticorruption Layer daquele pedaço que isolava o código sem testes do código “novo”.

Isto é uma parte importante, pois você precisará fazer tudo de dentro para fora. Explico: fora é a parte mais próxima do seu cliente possível (Controller, API, etc); dentro é a parte mais próxima do código que está isolando/refatorando. Sempre venha de dentro para fora nas tuas refatorações, pela razão de que assim, você afetará a menor quantidade possível de classes, pois identificará logo de pronto até onde pretende refatorar, colocando logo na sequencia disto seus adapters/façades.

Concluindo

Agora você tem um bom motivo para utilizar adapters e façades. Esse tipo de refatoração é uma das minhas favoritas, pois envolve muito de lidar com isolamentos bem construídos e como manter isto de forma que não quebrará todo o software. O resultado final é uma aplicação funcional com um toque de código novo e testado ao mesmo tempo.

Test-First: a anatomia de um teste

Agora que as devidas apresentações foram feitas, você já deve estar inteirado O que o Test-Driven Development não é; O que são Testes de Unidade e até como conseguir criar seu primeiro teste. Assim sendo, acredito que é possível partirmos para conteúdos mais específicos dentro de cada assunto.

Para começar, vou apresentar o que acredito ser uma anatomia válida para um teste de unidade.

Ana….tomia ?

Uma pergunta até que comum que alguns co-workers têm feito é: como identificar se meu teste está bem construído ?

Essa pergunta é um resumo das seguintes perguntas:

  • (Até) Quantas linhas deve ter um teste de unidade ?
  • Já li que é assertion por teste. Não pode ter mais por quê ?
  • Como consigo testar se um Adapter que formata dados para JSON retorna valores fidedignos, se eu tenho √784 itens no retorno ? Fiz um assertion para cada key. Não posso ficar sem testar isso. Tem ideia melhor ?

Para conseguir mostrar visualmente como isso é possível, temos que adicionar ao nosso In-Memory Testing Toolbox a técnica de como identificar se um teste tem partes faltantes (ou excedentes).

Um Teste de Unidade – teste no geral – pode ser separado em três partes: inicialização de dados; exercitar o código de produção; e, analisar resultados. Let’s turn it visible:

    public void testSomaDoisValoresDistintos() {
        int valorA = 10;
        int valorB = 20;
        float resultado = subject.somaValores(valorA, valorB);

        assertEquals(30, resultado, 0.0);
    }

Identificando as três partes:

Inicialização de dados:

int valorA = 10;
int valorB = 20;

Exercitar o Código de Produção:

float resultado = subject.somaValores(valorA, valorB);

Analisar Resultados:

assertEquals(30, resultado, 0.0);

Acredito que essa regra sirva muito bem para você se policiar sempre ao fazer seus testes de unidade. Isso quer dizer: você não precisa fazer 1 assertion/teste; Não precisa sempre criar teste com três linhas (exceto quando estiver treinando – assunto futuro(..)).

Isso quer dizer que não tem nada errado em ter aqueles √784 (28) assertEquals no meu teste do Adapter JSON, certo?

Errrr…. errado! Teoricamente falando isso é possível, porém é importante lembrar que frameworks de testes deste século utilizam técnicas de Hotspoting (assunto p/ outro post) que permitem nós definirmos nossos próprios assertions. Já visualizou como resolver o problema do JSON?

    public void testJsonAdapterReturnsEncodedJsonString() {
        # ... inicializa seu objeto para popular dados
        assertJsonAdapterWillContainKeysValues(subject.toJson());
    }

Mesmo sendo uma forma mais compacta, ainda assim temos a inicialização do teste, o exercício do código de produção a ser testado e a validação de que aquele código está de acordo com a especificação.

Mas… porque raios está errado colocar 28 assertions dentro de um teste?

Seu Teste é uma Especificação

Como disse Richard Stallman em The Code Linux, seu código é uma receita. Vamos utilizar a analogia dele em nosso favor: sendo o Teste uma receita, ele deve possuir steps que irá definir como ele chegará ao resultado final, ou seja, o Teste é uma Especificação de como aquele comportamento exercitado pelo mesmo deverá funcionar.

  • Coloque 400ml de água dentro de uma panela e leve-a ao fogo médio;
  • Espere 5 minutos para esquentar;
  • Abra o pacote do miojo e o coloque dentro da panela;
  • Após 3 (tsc, tsc) minutos, adicione o tempero e misture bem.
  • Sirva.

Se conseguir enxergar aqueles três passos da anatomia do teste como uma receita, ficará mais fácil para lembrá-los e visualizá-los nos códigos alheios.

Como uma especificação, ao invés de precisar ler 28 linhas para entender que tudo aquilo é apenas o passo de análise de resultados, não acha mais fácil criar seu assertion customizado e resolver isso com apenas 1 linha – assertJsonAdapterWillContainKeysValues(resultado) ?

A Anatomia apontará mutações genéticas

Seguindo a Anatomia do Teste, se algum dos passos aparecerem mais de uma vez por teste, é indício de que seu teste está testando mais do que uma unidade. Ao topar com isto, você deve: extrair a duplicação e criar outro teste para aquele conteúdo duplicado. Em casos extremos, a duplicação não poderá ser removida. Isso indica ao coder de que a abstração dele caiu por terra ou ainda você está testando o comportamento errado. Será necessário repensar a solução daquele pedaço.

Concluindo

Adote a Anatomia do Teste como sua métrica para um teste conciso. Leia sobre como criar Custom Matchers/Assertions em seu framework favorito de testes. Pratique isso no seu tempo livre. Avalie e peça avaliação de seus códigos de Teste e Produção. Exponha-se à críticas. Fará muito bem.

Code Coverage: uma das consequências do Test-First

Quando comecei a tentar testar meus códigos, iniciei também a busca por métricas que indicassem que eu estava no caminho correto, evitando me desvirtuar durante essa mudança de pensamento ao codificar software. Na época, o manual do PHPUnit indicava que o framework de teste possuia ferramentas para cobertura de código. Eis que fui apresentado ao Code Coverage.

“Uau! Esta é uma métrica indiscutível para mensurar a qualidade de meus testes!” – pensei na época.

Depois que tomei conhecimento da análise da Cobertura de Código pelos testes, não se falava em outra coisa a não ser:

Neste projeto o objetivo é ter 90% de cobertura de código; Mas no próximo, aaaaah, no próximo nada menos do que 98% pode ser aceito.

Sério. Ficávamos numa neura assim mesmo. Não era para menos. Pense comigo: sendo como objetivo do teste, garantir que o código de produção funcione, nada mais sensato do que cobrir a maior quantidade do Código de Produção (todo código que não é de teste ou do framework no projeto, assim digamos) possível. Ou seja: a ideia é que nossos testes passe pelo maior número de linhas possíveis para garantirmos que toda linha está funcionando como deveria em seu devido fluxo. Por exemplo:

    class User {

        public void fazLalala(String comFoo) throws Exception {
            if (comFoo == null) {
                throw new Exception("Lascou!");
            }

            if (comFoo.startWith("Lalala")) {
                this.doSomething(comFoo);
            } else {
                this.doAnother(comFoo);
            }
        }
    }

No caso deveríamos exercitar os 3 possíveis casos: lançamento de exception; if == true e if != true, afinal o teste é para verificar se a exception é lançada somente quando necessário e o if/else trabalhar conforme o esperado vindo do input do método.

Porque este pensamento está incorreto

Testar as possibilidades do comportamento de um método de um objeto é fundamental, porém, no caso acima, eu estava olhando pela ótica errada.

Eu estava testando para obter Code Coverage ao invés de obter Code Coverage por estar testando.

Inversamente a Matemática, no português a inversão dos valores podem não serem equivalentes, como neste caso. Testar com o objetivo de obter Code Coverage não é um bom motivo; agora, obter um bom Code Coverage por causa do teste é algo muito bom.

Para ilustar o problema de Testar Code Coverage Driven, vamos ver o código abaixo:

    class User {

        public void addCredentials(String username, String password) {
            // do something...        
        }

        public String getName() {
            return "...";
        }

        public String getCredentials() {
            return "...";
        }

        public String getLastname() {
            return "...";
        }
    }

Ao Testar focado em obter Code Coverage, algo assim poderá ser encontrado na classe de teste do User:

    class UserTest {

        @test
        public void testGetName() {
            user = new User();
            user.setName("name");

            assertEquals("name", user.getName());
        }

        @test
        public void testGetLastname() {
            user = new User();
            user.setLastname("last name");

            assertEquals("last name", user.getLastname());
        }

        @test
        public void testAddCredentials() {
            user = new User();
            user.addCredentials("username", "senha_marota");

            assertEquals([["username", Digest::MD5.hexdigest("senha_marota")]], user.getCredentials());
        }

É possível observar dois problemas aqui:

  1. Getter/Setter foi testado isoladamente, como se fosse uma regra de domínio.
  2. O teste do addCredentials teve o mesmo tratamento do que os Getters/Setters.

Não me entenda errado: não existe nada que diga para não testarmos getters/setters. O problema é: testar coisas que serão testadas em métodos futuros por consequência. Por consequência que digo seria:

    @test
    public void testCredentialUsernameShouldNotEqualsToName() {
        String name = "Mesmo nome";
        user = new User();
        user.setName(name)
        user.addCredentials(name, "senha_marota");

        assertEmpty(user.getCredentials());
    }

Assumindo que uma das regras da Credencial é que o username não seja igual ao nome do usuário, um teste deste tipo já nos garante que o setName() e o getName() funcionam como esperamos que funcionará, pois o addCredentials irá chamar o getName para achar o nome do usuário:

    public void addCredentials(String username, String password) {
        if (getName() == username) {
            // do nothing / throws exception
        }
    }

Com isto, o teste testGetName() pode ser dispensado tranquilamente, pois ele foi testado em consequência ao teste de negócio testCredentialUsernameShouldNotEqualsToName.

O segundo problema é ainda mais grave: por testar getter/setter, você poderá cair em copy/paste nos testes pela sua similaridade e talvez isso o fará cair na armadilha de esquecer de testar o que realmente precisa de atenção por conter Regras de Negócio (Domínio) envolvida, como o addCredentials tem. Testes válidos para a classe proposta seriam: testAddNewCredential, testCredentialUsernameShouldNotEqualsToName e talvez até testDuplicatedCredentialShouldRaiseError.

Porque este pensamento está correto

A corretude de Testar Orientado a Cobertura de Código está em se e somente se o Teste fosse para garantir que o código funciona o que não é o objetivo do teste de unidade. Por não ser objetivo principal, testar para satisfazer a cobertura de código não trará garantia alguma, além de que o software não têm erros de sintaxe ou fluxo. É alguma coisa? Sim, claro! Mas estará longe da grande vantagem que Test-First traz: montar sua aplicação da forma mais orquestrada possível.

Concluindo

Foque nos testes que agregam valor ao negócio(domínio) do software que os métodos auxiliares serão avaliados em consequência, trazendo uma cobertura altíssima e mais: confiável. Exercite seu addCredentials criando um teste para cada regra de negócio que lhe foi solicitada, dando segurança real no seu Código de Produção.

Mock elevado à enésima potência

itsatrap

Mocks: mock é um assunto polêmico em Software Testing nos dias atuais. Quase todos defendem o uso, Robert Martin que o diga. Logo, você começa a ler, um passo depois está fazendo seus próprios mocks e gosta do que vê. Num piscar de olhos, está Mockando tudo que pode e fica feliz com isto.

Ver um teste com 3, 4, 5 até 6 mocks pode no começo parecer algo sensacional, mas uma coisa que é necessário aprender sobre Mocks é que assim como seu teste, ele fala. Não uma mutação do say do OS X, mas com sua própria e simples linguagem: em excesso torno-me algo extramemente ruim. É exatamente isso que ele diz aos berros quando ele é super utilizado em um mesmo Test Case.

Pecando pelo excesso

Ao Mockar, esqueça essa frase popular de que é melhor pecar pelo excesso. Overmocking é tão ruim quanto não mockar at all. Se eu tivesse que escolher entre Mockar excessivamente ou não mockar nada, eu optaria pela segunda opção mantendo todas minhas dependências levemente soltas. (assunto para posts futuros!)

Quando você estiver forçado a stubar/mockar muita coisa para que um teste seu possa ficar isolado o suficiente para então testar a funcionabilidade (que você isolou), isso quer dizer simplesmente o seguinte: seu design ficou acoplado e como tal, precisa ser repensado para algo menos atrelado. Deixando suas dependências (outras classes) mais independentes, a quantidade de mocks por teste cairá e seu problema sucumbirá.

O acoplamento é aterrorizante para alguns ou/e em algumas situações, mas eles acontecem quando se perde o controle do que está criando ou quando se modifica o comportamento sem modificar como as coisas se falam. Sabe aquela história de: “ah, modifica isso aqui rapidinho só para X fazer Y.” – quando você muda, 99.9% de chances de criar acoplamento e a consequência disso reflete diretamente sobre a quantidade de itens mockados por teste.

Mocks por Teste

Matematicamente dizendo, não há um número exato para mocks por teste (mocks/teste), mas eu tenho meu limite pessoal baseado em nada erro e acerto: 3 mocks/teste para mim é o limite aceitável. Ao ultrapassar isto, automaticamente aquele meu código que o teste exercita cairá na minha Lista de Redesign. Isto forcará a repensar se é caso de refazer ou se é uma exceção. Tudo questão de análise caso a caso. Caso você tenha uma abordagem diferente com seu limite por teste, comente ao final 😉

Mockar apenas o que te pertence

Um ponto defendido por alguns é o de only mock what you own, em outras palavras seria: não mock seu framework; não mock sua API; Mock apenas o que você criou. Aquilo que faz parte do seu domínio. Seu framework é um domínio transversal, ou seja, é um domínio que auxilia você a criar seu próprio domínio. Lembre-se disso!

Com o Rails em particular, eu acabei por várias vezes não seguindo isto. Na verdade, eu não sou muito a favor disto pode me julgar, xingar, etc.. O motivo é simples: o Active Record se mistura de uma forma tão intríseca ao seu domínio, que em alguns momentos é difícil dizer aonde está a linha que separa o framework do seu código. Por este motivo, eu já fiz mock dos finders do ActiveRecord.

O problema de mockar o framework é que você ao fazer esse tipo de maluquisse precisa estar atento é de que ao mocka-lo, você presume como ele irá se comportar dado um certo input porém se por algum motivo sua intuição/palpite estiver errado, seu teste te dará um resultado errado. Com o resultado errado, seu teste passará, mas na hora de rodar o código de produção sem os mocks, o comportamento esperado não acontecerá. O Teste te deixará na mão, por causa do mock e por falha sua.

Mockar o framework é muito perigoso. O teste poderá tornar-se arisco. O true poderá ser false e você precisará lidar com isto.

Concluindo

Mock é um aliado importantíssimo em Test-First. Sabê-lo utilizá-lo é ainda mais importante.