O último D em TDD é para Arquitetura

February 3rd, 2009 § 6 comments § permalink

No meu último artigo, comentei bastante sobre a minha opinião de que testes devem ser sobre o relacionamento entre partes específicas do seu código e não sobre interfaces ou contratos. Na minha experiência, os testes mais duradouros e de maior valor são aqueles que exercem as interfaces e contratos indiretamente, através do arquitetura particular oferecida pelos mesmos.

O ressurgimento de testes como uma ferramenta ágil é uma coisa recente e, obviamente, uma excelente oportunidade para conversar sobre técnicas, filosofia e metodologia de desenvolvimento. Em especial, a comunidade Rails tem feito um trabalho excepcional de evangelização sobre o assunto, tornando testes um participante de primeira classe no discurso de desenvolvimento de software.

Entretanto, como é fácil acontecer, o próprio sucesso do assunto está se convertendo em uma fonte de perigos para desenvolvedores iniciantes ou que não tenham tanto familiaridade com TDD e BDD. A própria multiplicação dos pães, digo, dos frameworks de teste está contribuindo para isso no sentido de que, em um afã de criar mais features do que o concorrente, alguns framework estão simplesmente promovendo técnicas péssimas de testes em troca de um falso senso de segurança.

Isso volta um pouco na discussão sobre a diferença entre TDD e BDD, mas acho que o ponto merece uma ênfase. Em resumo, é imprescindível evitar substituir arquitetura, mesmo quando se está fazendo TDD, por meros testes.

Isso fica mais fácil de ser percebido com algumas ilustrações. Tomando o Shoulda como exemplo, é muito comum ver código como o seguinte:

class UserTest < ActiveRecord::TestCase
  should_belong_to :account
  should_have_many :posts
  should_have_named_scope('recent(5)').finding(:limit => 5)  
  should_have_index :age
end

Esse tipo de código não prova absolutamente nada sobre o desenho próprio de sua classe. O código acima:

  1. É redundante, porque as três primeiras cláusulas já serão testadas automaticamente em outras partes do código, especificamente em controller;

  2. É quebradiço, porque é diretamente relacionado à implementação e não ao comportamento da classe em si;

  3. É pouco mais do que um teste de sanidade para descobrir se o desenvolvedor colocou algumas poucas linhas de código em seu modelo;

  4. Expõe detalhes de implementação, como no caso do matcher para índice.

Em outras palavras, todos os testes acima são absolutamente inúteis. O teste de escopo é o único com algum valor para o teste do modelo em si, mas continua sendo redundante.

Pior ainda, existem exemplos como shouldhavebeforesavecallback, proveniente do Remarkable. Esse é o tipo de asserção que chega a ser contraproducente. É um teste que expõe a funcionalidade subjacente de um modelo, que por regras de encapsulamento deveria ser completamente isolada e invisível para as demais partes da aplicação, é um desvio completo do que TDD representa.

Testes, mais uma vez, são sobre interoperabilidade entre facetas do código. São parte de uma conversa arquitetural que procura se focar o mínimo em detalhes internos de implementação. O objetivo é escrever o menor corpo possível de testes–axiomas–que possa dar uma indicação da validade de um dado corpo de código. E como eu repito freqüentemente aqui, simplicidade é um alvo explícito de boas arquiteturas.

Testes: pragmatismo ou ideologia?

January 31st, 2009 § 6 comments § permalink

Eu gosto bastante do que Joel Spolsky e Jeff Atwood escrevem, mas a última conversa entre os dois no postcast regular que eles mantém realmente releva uma boa falta de conhecimento sobre o que testes e TDD realmente representam.

No núcleo do argumento dos dois está a idéia de que alta cobertura por meio de testes–Jeff Atwood menciona a falta de 95% ou mais–torna a manutenção dos próprios testes problemática considerando a proporção de testes que precisa ser mudada quando o código em si também é modificado. Um argumento secundário é que testes são mais indicados para código legado do que código que está sendo desenvolvido, a menos que o mesmo tenha rigidez natural como a especificação de um compilador.

A solução para o segundo argumento é simples: todo código é legado. Simples assim. Código que entrou em produção é legado por definição e o argumento de que há uma diferença entre código com “mais idade” e “menos idade” é dúbio no pior dos casos.

Lendo o diálogo entre os dois, é possível perceber que há uma confusão sobre o que testes realmente representam–especialmente quando os dois falam sobre a relação entre testes e arquitetura, algo que em um contexto ágil é normalmente referenciado por TDD e BDD.

Essa confusão–de que testes são feitos para cobrir a interface de uma classe ou método–é extremamente comum mesmo entre os próprios proponentes de testes, seja entre aqueles que propõem testes como ferramenta de design como é o caso de TDD e BDD, seja entre os que simplesmente os usam para validar o funcionamento posterior do código.

Eu posso concordar parcialmente com o argumento de que 100% de cobertura geralmente é desnecessária. De fato, 100% de cobertura nunca representa uma garantia absoluta de que seu código, e por extensão sua arquitetura, não possuem problemas.

Primeiro porque 100% de cobertura real é algo virtualmente impossível de ser conseguido para um corpo mais complexo de código (e realmente impossível se o mesmo tem dependências externas). Segundo, por que não importa a quantidade de testes que você tiver, a complexidade ciclomática sempre vai acabar com seu dia nas horas mais inapropriadas. Não importa o quantidade de white-box ou black-box testing que você possuir, cobertura é alvo exponencialmente proporcional à evolução do seu código.

Existe também um outro fator representado por uma variação casual da regra 80/20. Os maiores ganhos de testes estão nas partes mais complexas do seu código, é claro, mas os ganhos reais geralmente aparecem nos desvios pequenos que pegam você de surpresa. Nesse ponto, quando maior a cobertura, maior facilidade de introduzir novos testes.

E aqui está o verdadeiro ponto em que o argumento de Spolsky e Attwood falha: testes não são sobre interfaces, APIs ou contratos. Antes, eles são sobre o relacionamento entre as partes distintas de seu código. Nessa diferença essencial está um dos grandes debates recentes da comunidade ágil: qual é a diferença real entre TDD e BDD?

A minha resposta está centrada em uma pequena reinterpretação de TDD. Ao invés de usar o termo como Test-Driven Development, eu prefiro ver a questão toda como Test-Driven Design.

Se você está usando testes como uma ferramenta para guiar o design de sua aplicação, isso significa que você estará preocupado mais em saber com as peças se encaixam do que como elas funcionam individualmente, como mencionado acima.

Joel diz:

But the real problem with unit tests as I’ve discovered is that the type of changes that you tend to make as code evolves tend to break a constant percentage of your unit tests. Sometimes you will make a change to your code that, somehow, breaks 10% of your unit tests.

Você realmente pode fazer uma mudança que quebra 10% de seus testes, mas para que isso aconteça, o design do sistema precisa estar tão falho e seus testes tão quebradiços que os mesmos podem ser jogados fora sem remorso.

Há pouco mais do que duas semanas, eu fiz uma modificação considerável em uma biblioteca que escrevi. Em linhas gerais, significa trocar o motor de um middleware de DRb (Ruby distribuído) para JSON over HTTP. Esse código em particular está 100% coberto.

Por causa das diferenças entre os protocolos, uma quantidade de código significativa teve que ser modificada. Mas apenas três ou quatro novos testes foram escritos e nenhum dos outros foi modificado. Código foi movido, reorganizado em classes diferentes e mesmo assim, os testes resistiram bravamente.

A explicação é simples: enquanto existem testes que realmente testam como funções específicas funcionam, a vasta maioria destes testes é focado em como as várias partes da aplicação trocam informações: se os dados que saíram daqui nesse formato chegaram do outro lado do formato necessário, se essa AST foi reorganizada apropriadamente de modo que o gerador do outro lado da equação possa produzir a linguagem apropriada e assim por diante.

Jeff continua com outra asserção:

Yeah, it’s a balancing act. And I don’t want to come out and say I’m against [unit] testing, because I’m really not. Anything that improves quality is good. But there’s multiple axes you’re working on here; quality is just one axis. And I find, sadly, to be completely honest with everybody listening, quality really doesn’t matter that much, in the big scheme of things…

Isso é algo que me fez repensar todo o contexto da discussão. Eu realmente fico espantado de ver alguém que considera Peopleware e The Mythical-Man Month como referências básicas dizer isso. Ambos os livros focam em arquitetura dirigida por qualidade como um foco para produzir código mais robusto, entregue em menor tempo e que traz mais valor para o negócio e para o usuário. Os parágrafos finais da transcrição são realmente decepcionante nesse aspecto.

Para finalizar, TDD realmente não é um fim em si próprio. Mas o argumento de que usar testes é um desperdício de tempo–seja para quem está cobrindo 100% do seu código de forma viável ou para quem usa testes como validação de funcionalidade–é simplesmente falho.

Joel é conhecido bastante por seu pragmatismo em relação à correção de bugs. Testes são uma maneira bastante pragmática de garantir que aquele conjunto particular de situações de falha não vá se repetir por engano em alguma codificação posterior. Isso é business value, algo que tanto Joel como Jeff valorizam.

No final das contas, pragmatismo é o que realmente conta. E testes, quanto feitos da maneira certa, são algumas das mais pragmáticas ferramentas que um programador tem à sua disposição.

Waterfalling Agile

January 18th, 2009 § 1 comment § permalink

O Luiz Rocha escreve com razão sobre os perigos de confundir Agile com um conjunto de métodos que devem ser seguidos para que o “processo” funcione. É uma coisa natural de organizações–que por extensão são pessoas–procurarem um ponto do balanço entre necessidades discordantes e este é um dos pontos mais propícios para transformar a filosofia Agile em uma metodologia que pouco difere do tradicional Waterfall.

Por outro lado, existe uma tentação muito grande de pensar em Agile com o oposto exato do Waterfall e transformar isso em uma desculpar para não ter nenhum processo ou método coerente de desenvolvimento. É muito comum encontrar empresas imersas na desorganização que descrevem os seus processos como ágeis.

“Não escrevemos documentação, oras. Somos Agile. Preferimos software que funciona a documentação compreensiva. Não é isso o que o manifesto diz?”

É claro que o manifesto termina com a afirmação: That is, while there is value in the items on the right, we value the items on the left more. Isso não impede que tudo seja abandonado em favor de uma pretensa agilidade.

Como o Luiz diz, não é um task board que faz uma empresa ou equipe ágil. Tampouco é a falta de planos em contraponto a uma resposta completamente desorganizada a mudanças.

Agile exige um compromisso com o detalhe:

Para valorizar interação e não processos é necessário entender que o nível de detalhe requerido do primeiro é maior que o do segundo.

Para valorizar software que funciona é preciso uma cultura de qualidade, o que implica em muito mais trabalho do que escrever documentação compreensiva–e isso vai muito além de colocar no quadro de tarefas itens para escrever testes, algo que nada mais é do que transformar o Agile em Waterfall.

Para valorizar a colaboração do cliente é preciso ouvir e raciocinar sobre o que foi ouvido, transformando isso em valor. Negociar contratos é muito mais simples.

Finalmente, para valorizar resposta rápida a mudanças é necessário uma atitude de constante atenção ao que está sendo feito e como isso vai evoluir no futuro. Novamente, seguir um plano é incomparavelmente mais fácil, mesmo que o mesmo seja completamente falho.

Agile não é, e nunca foi, uma opção para aqueles que querem tomar a rota mais fácil. Excelência técnica, cultura de qualidade, atenção ao detalhe–transformar isso em segunda natureza requer anos de prática: o mesmo tipo de esforço que um artesão passa a vida aplicando.

Se você quer algo simples, esqueça Agile. Não é para quem não tem um compromisso com o tempo necessário para que coisas permanentes floresçam.

Agile para sua diversão e lucro

December 12th, 2008 § 11 comments § permalink

Meu irmão, como já citei outras vezes aqui, tem uma pequena operação em Belo Horizonte prestando serviços em software livre para empresas no Brasil e nos Estados Unidos. Atualmente, a maior parte do trabalho é em Rails e estamos sempre conversando sobre como melhorar e aumentar a eficiência do que ele e o pessoal que trabalha lá estão fazendo.

Recentemente ele me visitou na WebCo e ficou bem curioso ao ver as dezenas de post-its colados nas várias janelas representando os diversos projetos da empresa. Expliquei um pouco de Agile–usávamos várias técnicas agéis quando eu ainda estava na empresa–mas Scrum era uma coisa que eu ainda não tinha comentado com ele.

Depois que ele voltou para Belo Horizonte, ele me ligou pedindo que eu explicasse um pouco mais sobre os tais dos sprints. Explique mais um pouco e sugeri que ele experimentasse mesmo com a equipe pequena.

Mais algum tempo passou e em mais uma de nossas conversas ele me explicou com tinha adaptado o que eu estava falando para a realidade de sua empresa:

Com R$126 ele comprou um quadro, alfinetes, post-its coloridos, canetas e marcou as tarefas de vários projetos com cores diferentes para identificar os mesmos. Mesmo tarefas administrativas da empresa foram colorizadas e colocadas no quadro. Ele também separou o progresso das mesmas em “a fazer”, “em progressos” e “feitas”, como no Scrum, mas sem a formalização de daily meetings.

Como isso, ele começou a poupar uma quantidade enorme de trabalho de gerenciamento e acompanhamento de tarefas ao mesmo tempo em que dava maior visibilidade a todos da empresa sobre o que estava acontecendo. Eventualmente, o quadro também se tornou uma reflexão do sistema Trac que ele usa para controle de tickets aumentando ainda mais a produtividade da equipe.

Para alguns puristas, a adaptação que ele fez seria considerada tão longe do Scrum quando um processo waterfall. Esse é o ponto que a maioria das pessoas perde sobre Agile:

Agilidade não tem absolutamente nada a ver com formalização. Assim como waterfall funciona para determinados tipos de processo–existem milhares de casos de extremo sucesso usando a filosofia–agilidade só funciona quando a idéia em si é comprada e adaptada à realidade específica dos processos necessários.

No caso do meu irmão, daily meetings com formalizados no Scrum não tem o menor sentido com a equipe pequena e geograficamente distribuída. Aplicar todos os parâmetros que o Scrum oferece teria o efeito contrário de reduzir sua produtividade.

Antes de tudo vem o lúdico, a diversão. E isso leva tão facilmente ao lucro que é impressionante pensar no número de pessoas enganadas quanto à história toda. Mas eu me divirto e sempre sou lembrado de pensar diferente quando vejo adaptações como a feita pelo meio irmão que funciona e resulta em efeitos permanentes positivos. Melhor do que isso é difícil.

Where Am I?

You are currently browsing the Desenvolvimento category at Superfície Reflexiva.