Mostrando postagens com marcador tdd. Mostrar todas as postagens
Mostrando postagens com marcador tdd. Mostrar todas as postagens

sábado, 27 de novembro de 2010

Mais sobre TDD e coding dojo

Um post sobre o uso de baby steps em dojos, escrito pelo Mauricio Aniche, tem causado discussão. Eu escrevi sobre baby steps há algum tempo e concordo em gênero, número e grau com o post, mas há um ponto que ainda não vi ser tocado na comunidade.

Eu tenho observado há alguns meses que algumas práticas do dojo andam meio que passando uma visão incorreta sobre TDD, e não é só por causa das cascatas de ifs. Nada melhor que exemplos para mostrar o que quero dizer. Em TDD/BDD/whatever no mundo real, ninguém vai escrever um teste:
it 'dois mais dois é igual a quatro' do
...
end


ou

def test_quatro_nao_e_um_numero_feliz(self):
...


Nestes códigos, o nome do método é a expectativa. Em TDD, uma expectativa, é o resultado que se espera que ocorra na execução do teste, seja uma expectativa padrão como order.clear |should| change(order.item_count).by(-3) ou uma verificação de mock como MailService.should_receive(:send).with("sos@batcave.com").

O nome do método de teste (ou a sua descrição, no caso do RSpec) não é e nem pode ser a expectativa, pelo menos a se julgar pela literatura de TDD. O nome do método de teste é o nome de uma responsabilidade da classe (o "test method names should be sentences" que é tão enfatizado em BDD).

O correto seria:

it 'soma dois números' do
...
end

ou

def test_identifica_numeros_infelizes(self):
...

Em todos os casos, o nome dos métodos é uma descrição abstrata - no sentido de não trazer exemplos concretos - e as expectativas com seus valores objetivamente verificáveis vêm no interior dos métodos de teste. Ou seja, em TDD, cada método de teste representa algo que a classe é capaz de fazer e não infinitas variações de valores passados a uma mesma funcionalidade. No fim das contas, a expectativa acaba sendo escrita duas vezes: uma vez no nome do método e outra na expectativa propriamente dita. A questão aqui não é o uso ou não de baby steps, mas uma formulação de testes que é conceitualmente diferente daquela utilizada na literatura de TDD.

Veja um exemplo, do livro "Growing Object-Oriented Software, Guided By Tests", de Steve Freeman e Nat Pryce (Addison-Wesley, 2009):

public class CatalogTest {
private final Catalog catalog = new Catalog();

@Test
public void containsAnAddedEntry() {
Entry entry = new Entry("fish", "chips");
catalog.add(entry);
assertTrue(catalog.contains(entry));
}

@Test
public void indexesEntriesByName() {
Entry entry = new Entry("fish", "chips");
catalog.add(entry);
assertEquals(entry, catalog.entryFor("fish"));
assertNull(catalog.entryFor("missing name"));
}
}

Neste código, os testes são:
- contains an added entry
- indexes entries by name


Ou seja, são responsabilidades abstratas e não exemplos concretos.

Kent Beck, em seu cĺássico "Test-Driven Development By Example" (Addison-Wesley, 2003), vai na mesma linha: seus testes na classe Money que foi citada no post se chamam "multiplication", "equality", "currency". Ou seja, nada de "twoDollarsPlusThreeDollarsEqualFiveDollars".

Outra referência de TDD que gosto bastante, "Test Driven: TDD and Acceptance TDD for Java Developers", de Lasse Koskela (Manning, 2007), também segue a mesma ideia. Este livro, por sinal, tem disponiblizado o segundo capítulo para download, contendo um excelente exemplo de baby steps que vale mesmo a pena ser lido. Neste exemplo, novamente, os testes são "unknownVariablesAreIgnored", "multipleVariables" ou "missingValueRaisesException". Novamente, nada de expectativas nos nomes dos testes.

Certamente, não há "culpados" por isto, e também é provável que muitos não vejam assim tanto problema no que expus. Coding dojos são dinâmicos e não hierárquicos por definição, portanto evoluem e se alteram com o tempo e com o contato de diferentes pessoas. Assim, o "desvio" de TDD pode até não ser um problema em si, afinal o coding dojo é um jogo, mas temos que perceber que estamos praticando sob o nome de TDD uma coisa que não é exatamente TDD. E, na verdade, estamos praticando e aprendendo, uma vez que o coding dojo, pelo menos aqui no RJ, tem sido visto como ferramenta de aprendizado de programação. Quando o desenvolvedor está consciente que nos dojos é praticada uma variação do TDD, tudo bem. Porém, no caso de um iniciante, nós temos, sim, um problema, pois as pessoas vão aprender como TDD algo que poderia até mesmo ser considerado um antipattern.

Talvez pelos problemas utilizados em dojos serem bastante simples, muitas vezes com apenas uma feature, a única forma de ter mais de um método de teste acaba sendo a fatoração das classes de teste por expectativas e não por features. Na verdade, não sei nem se isto é um problema para todo mundo. Pessoalmente, é algo que não gosto e que me incomoda, me soa como se o código estivesse pulando e gritando "ei, eu estou errado, você não está vendo?". Mas é o meu ponto de vista. E aí, qual é o seu?

quinta-feira, 2 de outubro de 2008

Passos de bebê

Na lista de discussão do núcleo de pesquisa de que faço parte, surgiu uma polêmica a respeito do uso da técnica de passos de bebê. Resumindo a história, alguém estranhou os passos minúsculos preconizados pela literatura de TDD. Por exemplo, em se tendo apenas o teste (em Java):

Template template = new Template("#{tecnica-agil} é legal!");
template.set("tecnica-agil", "TDD");
assertEquals("TDD é legal!", template.parse());


a implementação seria necessariamente

...
public String parse()
{
return "TDD é legal!";
}
...


Bom, vamos à minha visão da coisa toda: a técnica de "passos de bebê" serve para evitar complexidade desnecessária, como generalização prematura e overengineering. O tamanho do passo - o que parece ser o centro da polêmica - deve ser baseado no bom senso e no quanto é confortável para o desenvolvedor. Se um desenvolvedor ou par se sente confortável em utilizar a técnica de modo mais ortodoxo, ótimo. Se não, é uma questão de ajuste fino. Eu, por exemplo, normalmente utilizo passos um pouco (mas não muito) maiores, exceto quando o domínio é pouco conhecido.

Os passos de bebê estão diretamente ligados a Test-Driven Development. Em TDD, a implementação deve ser unicamente aquela suficiente para passar nos testes, e nada mais. Deste modo, os passos de bebê não estão na implementação, mas na elaboração dos testes. Caso se queira que os passos sejam maiores, basta aumentar a granularidade dos testes unitários. Escrever um teste apenas para, por exemplo, uma entrada "N" e implementar uma solução genérica para o alfabeto inteiro é algo que contraria diretamente os princípios do agilismo (ou seja, da engenharia de software moderna), é implementar a solução para um problema ainda não formulado. Do ponto de vista da programação, os testes unitários são a especificação de requisitos. Assim, uma solução geral para o alfabeto inteiro com um teste só para "N" recairia em uma das duas situações: (1) ou há implementação desnecessária, ou seja, desperdício e complexidade inútil ou (2) o conjunto de testes é deficiente, o que mostra requisitos mal formulados e pode ter consequências ruins como bugs de regressão.

A questão é que a implementação tem que ser a coisa mais simples (sem ser simplória, já dizia Einstein) que possa passar nos testes (e uma cascata de if-elses é tosqueira, não simplicidade). Se você consegue ter uma função que passa nos testes com um mero return "n", o problema está em um conjunto fraco de testes. A implementação está correta, pois cumpre os requisitos (testes unitários) de modo claro, simples, rápido e manutenível.

Toda e qualquer prática ágil deve estar ancorada em princípios e valores. No caso em questão, o valor é a já citada simplicidade, traduzida nos lemas KISS e YAGNI. Os passos de bebê asseguram que o software terá apenas a complexidade necessária e nada mais.