A Falácia do código simples – Testes são indispensáveis

Posted on 31/08/2010

9


Olá galera, tudo certin? Esse post é uma confissão.

Sim, resolvi começar esse post confessando minha arrogância – eu não escrevo testes sempre (Segundo o Giovanni Bassi, por isso, não deveria me considerar um dev sênior, e tenho coragem de dizer que sou arquiteto – que vergonha!);

Mas a questão é: Por quê? Por que eu não escrevo testes sempre? …

  • Se eu sei que testes impedem que eu libere besteira;
  • Se eu sei que testes me dão mais segurança para fazer a manutenção;
  • Se os testes permitem que eu possa ganhar tempo, não pagando o tempo de start da aplicação em debug;
  • Se eu sei que o teste é replicável;
  • Se eu experimento o benefício da automação dos testes todos os dias..

Por que eu não escrevo testes sempre? (Raiva e indignação comigo mesmo)

  • Sou ingênuo? acho que não [pelo menos, não é a palavra que melhor me descreve];
  • Gosto de perder tempo? definitivamente não;
  • Gosto de passar vergonha por liberar código bugado? Nem respondo…

Por que não escrevo testes (a questão persiste)… Resposta mais aceitável, sou arrogante e caio na falácia do código simples.

O que eu quero dizer por código simples?

Resumindo:

  • poucas linhas;
  • pouco ou nenhum desvio condicional;
  • pouco ou nenhum loop;
  • nome do método é óbvio e significado do código está claro.

O que o “bonzão” aqui pensa quando vê um código desses? Para que testar? Imagine. Não tenho como errar em um  código desses. (Eu sou fo6@!).

Qual é o impacto desse tipo de código?

Enorme!! Pelo menos no meu caso, o código “simples” é geralmente utilizado para suportar fluência e assemelhados. Ou, também, algumas funções utilitárias. Funções de conversão.. Funções de validação.. Essas coisinhas que usamos por toda a parte.

Logo, se um código desses saí com erro, o que acontece? Todo o sistema mostra comportamentos estranhos! Pior, gasta-se um tempo enorme tentando encontrar o erro nas funções cliente. Depuração passo-a-passo, obviamente com skip nas funções de código “simples” (por que erraríamos nelas?)

Se o código é tão “simples”, por que sai errado?

Duas respostas diretas:

  1. copy and paste – sim, aqui a violação de outro princípio, DRY. Mas, considere o cenário de um método com em três linhas, todas particulares do método,  mas, mesmo assim, semelhantes a outro .. a tentação de pegar outro método e só modificar o necessário é grande … e … esquecemos umas coisinhas. Pior, pegamos código bugado e replicamos o bug .. criando uma sementinha do mal;
  2. arrogância – eu confesso, não testo código que considero simples, nem em execução. Espero para fazer testes maiores (integrando). Definitivamente não considero um código simples uma SUT respeitável. E … bem .. já sabemos o final da história.

Qual é o antídoto?

Resposta direta, reconhecer a arrogância e assumir que “pequenas merd!#@$ fazem grandes cag#$as”¨. Não vejo outra saída: Escreva testes e tenha uma consciência tranqüíla.

Meu SUT de exemplo… A classe Times!

Essa classe já é nossa conhecida. Ela foi o tema central de outro post (que vergonha!! Classe bugada) aqui do blog: trata-se d


              
1 using System; 2 using EasyMock.Fluent.Interfaces; 3 4 namespace EasyMock.Fluent 5 { 6 public sealed class Times : IHideObjectMembers 7 { 8 private Times(Predicate<int> predicate) 9 { 10 this.Predicate = predicate; 11 } 12 13 public Predicate<int> Predicate { get; private set; } 14 15 public static implicit operator Times(int n) 16 { 17 return Exactly(n); 18 } 19 20 public static implicit operator Times(Predicate<int> predicate) 21 { 22 return new Times(predicate); 23 } 24 25 public static Times Once() 26 { 27 return new Times((n) => n == 1); 28 } 29 30 public static Times AtLeast(int count) 31 { 32 return new Times((n) => n > count); 33 } 34 35 public static Times AtLeastOnce() 36 { 37 return AtLeast(1); 38 } 39 40 public static Times AtMost(int count) 41 { 42 return new Times((n) => n >= 0 && n <= count); 43 } 44 45 public static Times AtMostOnce() 46 { 47 return new Times(n => n >= 0 && n <= 1); 48 } 49 50 public static Times Between(int from, int to, 51 bool exclusive = true) 52 { 53 return new Times( 54 (n) => 55 exclusive 56 ? 57 n > from && n < to 58 : 59 n >= from && n <= to 60 ); 61 } 62 63 public static Times Exactly(int count) 64 { 65 return new Times((n) => n == count); 66 } 67 68 public static Times Never() 69 { 70 return Exactly(0); 71 } 72 73 public Times And(Times t) 74 { 75 var e1 = this.Predicate; 76 var e2 = this.Predicate; 77 return new Times( 78 (n) => e1(n) && e2(n) 79 ); 80 } 81 82 public Times AndNot(Times t) 83 { 84 var e1 = this.Predicate; 85 var e2 = this.Predicate; 86 return new Times( 87 (n) => e1(n) && !e2(n) 88 ); 89 } 90 91 public static Times And(Times left, Times right) 92 { 93 return left.And(right); 94 } 95 96 public Times Or(Times t) 97 { 98 var e1 = this.Predicate; 99 var e2 = this.Predicate; 100 return new Times( 101 (n) => e1(n) || e2(n) 102 ); 103 } 104 105 public Times OrNot(Times t) 106 { 107 var e1 = this.Predicate; 108 var e2 = this.Predicate; 109 return new Times( 110 (n) => e1(n) || !e2(n) 111 ); 112 } 113 114 public static Times Or(Times left, Times right) 115 { 116 return left.Or(right); 117 } 118 119 public static Times Not(Times t) 120 { 121 var e1 = t.Predicate; 122 return new Times((n) => !e1(n)); 123 } 124 } 125 }

              

Essa classe é, por essência, código simples. Repare que todos os métodos são curtinhos… Olhe a lógica dos métodos Or/And/Not e relacionados .. tão parecidos .. tanto que eu copiei e colei .. alterei só o necessário .. uma classe assim não deve precisar de testes .. certo? ERRADO!

Vou mostrar os erros que o código tem e os testes que eu deveria ter escrito…

Método AtLeast

Esse foi minha primeira lição. Se eu tivesse escrevido esse teste:


              
1 [TestMethod] 2 public void AtLeast_EqualsToMin_ReturnsTrue() 3 { 4 // arrange 5 Times t = Times.AtLeast(5); 6 // act 7 var result = t.Predicate(5); 8 // assert 9 Assert.IsTrue(result); 10 } 11 12 [TestMethod] 13 public void AtLeast_GreaterThanMin_ReturnsTrue() 14 { 15 // arrange 16 Times t = Times.AtLeast(5); 17 // act 18 var result = t.Predicate(6); 19 // assert 20 Assert.IsTrue(result); 21 } 22 23 [TestMethod] 24 public void AtLeast_LessThanMin_ReturnsTrue() 25 { 26 // arrange 27 Times t = Times.AtLeast(5); 28 // act 29 var result = t.Predicate(4); 30 // assert 31 Assert.IsFalse(result); 32 }

              

Teria percebido que meu método está errado. AtLeast_EqualsToMin_ReturnsTrue falha vergonhosamente. O que eu errei, faltou um “igualzinho” no método … droga!


              
1 public static Times AtLeast(int count) 2 { 3 return new Times((n) => n >= count); 4 }

              

Uma linha de código e um erro! Faltou um teste!

Outra pancada da cabeça … método Or

Essa doeu mais… O método Or foi o primeiro “booleano” que escrevi .. me empolguei e escrevi os demais na base do Copy and Paste. Se eu tivesse, pelo menos, escrito o teste .. só dele .. não teria replicado um bug. Bastava esse teste para evidenciar minha distração (perdoável):


              
1 [TestMethod] 2 public void Or_TwoConstants() 3 { 4 // arrange 5 var target = Times.Exactly(5).Or(6); 6 // act 7 // assert 8 Assert.IsTrue(target.Predicate(5)); 9 Assert.IsTrue(target.Predicate(6)); 10 Assert.IsFalse(target.Predicate(4)); 11 Assert.IsFalse(target.Predicate(7)); 12 }

              

Mas não escrevi o teste (isso sim não é deplorável), e publiquei uma versão com falhas. Simplesmente, não usei o parâmetro que eu mesmo defini (variável e2) .. Aqui a versão corrigida:


              
1 public Times Or(Times t) 2 { 3 var e1 = this.Predicate; 4 var e2 = t.Predicate; 5 return new Times( 6 (n) => e1(n) || e2(n) 7 ); 8 }

              

Pior .. repliquei a falta.

Além disso…

Toda a minha classe não implementa nada de programação defensiva… que vergonha! E eu ainda falo de Contratos .. Coisa Feia! Smiley de boca aberta

Seguindo minha metodologia de testes: escrever testes para cenários válidos, outros para cenários inválidos (tipo: testar reação para parâmetros fora de range, ou nulos, teria identificado o “problema”).

Concluindo

Me permiti trair por minha própria arrogância … Liberei código bugado para a comunidade por não ter tido a humildade de reconhecer que todo código precisa ser testado. Mesmo os códigos simples precisam ser verificados .. Caí na falácia do código evidente, e tenho que reconhecer publicamente minha incompetência …

Lição de hoje: Caros, partindo de hoje, testo tudo!

Por hoje, é isso Smiley piscando

Etiquetado:, ,
Posted in: Post