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

quinta-feira, 7 de agosto de 2008

Voltas às Aulas e o Java - parte 2

Existe o clássico problema de determinar se um aluno passou ou não de ano quando são informados as notas e o número de faltas.

Vejamos:
1) O número de faltas é acima do permitido?. Aqui entra um if dos bons com uma comparação numérica, quando um número é maior ou igual a outro.
2) A média é basicamente somar as X notas e dividir por X (se for média aritmética). Somar e dividir são operações matemáticas básicas.

Uma forma de fazer é:
if( numero_de_faltas > maximo_de_faltas_toleradas) {
// reprovado por faltas
} else if ( media_do_aluno < media_para_passar ){
// reprovado por nota
}
// se nem um nem outro, esta aprovado


O ruim dessa abordagem é que o aluno estar aprovado é uma condição "que sobra". Se passarmos a focar a aprovação do mesmo ficaria assim:

if( media_do_aluno >= media_para_passar && numero_de_faltas <= maximo_de_faltas_toleradas) {
// aprovado!
} else {
// reprovado!
}


É claro que eu posso que eu posso querer saber o motivo pelo qual o aluno foi reprovado:

if( media_do_aluno >= media_para_passar && numero_de_faltas <= maximo_de_faltas_toleradas) {
// aprovado!
} else if(numero_de_faltas > maximo_de_faltas_toleradas){
// reprovado pelo número de faltas: ignoro a média.
} else {
// aqui só sobra a reprovação por nota,
// basta fazer a tabela-verdade dessas expressões
}


Se o foco do professor for o algoritmo, isso é o que deveria ser analisado. Quem implementa corretamente um método que recebe X notas e um número de faltas e consegue responder a essas perguntas consegue descrever facilmente os casos de teste para verificar se a implementação corresponde ao que se espera.

Um programa que tenha algum tipo de interação (seja Swing ou console) ajuda a fazer um macarrão de código, mesclando o algoritmo com o controle de estado dos "menuzinhos". No fim vemos laços de repetição aonde não são necessários, calculo de média errado, etc.

Ok, não acho que um marinheiro de primeira viagem devesse começar por JUnit, mas uma com boa noção dos fundamentos da linguagem e evitando a interação desnecessária com o usuario (poderia ser usado até os parâmetros do main), acredito num melhor desempenho. O risco é o aluno acreditar que java é dificil ou que tudo se resume a getters/setters e gigantescos try/catch's onde as exceptions são varridas pra debaixo do tapete.

Eu colocaria TDD num segundo momento, mas ai sem perdão, ensinando o caboclo a fazer do jeito certo o quanto antes. O que é dificil se eles conseguem passar sem saber fazer aritmética básica.

quarta-feira, 6 de agosto de 2008

Test Driven Development - parte 1.

Veja este código:

 public void testEhPar() throws Exception{
assertTrue("2 deve ser par",algoritmo.ehPar(2));
assertTrue("4 deve ser par",algoritmo.ehPar(4));
assertTrue("6 deve ser par",algoritmo.ehPar(6));

assertTrue("1 NAO deve ser par",!algoritmo.ehPar(1));
assertTrue("3 NAO deve ser par",!algoritmo.ehPar(3));
assertTrue("5 NAO deve ser par",!algoritmo.ehPar(5));
}


Dentro de uma classe de teste, usando o framework JUnit, a leitura dessas linhas é a seguinte:

Eu tenho um objeto chamado algortimo.
A chamada algoritmo.ehPar(2) deve retornar true, pois 2 é par, e este método informa se o parâmetro informado é, ou não, par.
O método assertTrue recebe dois parâmetros: uma mensagem informando o significado deste teste, e o resultado do mesmo.

Se o método estivesse com algum problema e retornasse false, isso:

assertTrue("2 deve ser par",algoritmo.ehPar(2));

seria o mesmo que

assertTrue("2 deve ser par",false);

Logo, o teste falha, pois 2 deve ser par.

Esta é uma forma programática de garantir o comportamento do código que eu desenvolvo: testando. Se o meu método só depende dos parâmetros informados, é muito simples verificar o seu funcionamento.

Desenvolvendo os testes antes de implementar o código, tendo apenas as assinaturas dos métodos (por isso recorri a uma interface aqui, lembra?), eu posso pensar nos comportamentos esperados e, então, vou desenvolvendo até que todos os testes estejam passando.

Não é nada fácil, funciona melhor quando o projeto que estás desenvolvendo está começando, porém os resultados são excelentes: veja o quanto de tempo estás economizando! Se tu pretendes determinar se um código teu está funcionando por programas que perguntam os valores, escrever

$ ant clean test


é muito mais rápido!

O problema surge quando queremos fazer algo complexo, pois a dificuldade de testar mostra problemas na arquitetura adotada, por exemplo. Quanto temos objetos que tem objetos, herança, polimorfismo, tecnicas mais avançadas podem ser usadas como usar Mocks de objetos (e prover os mecanismos para injetar estes Mocks).

No caso desse exemplo, esta é uma solução:
public boolean ehPar(int numero){
return numero % 2 == 0;
}


Muitos alunos desconhecem as operações de divisão e módulo entre inteiros. O 1, inteiro, dividido por 2, inteiro, não é 0.5, (em java), e sim 0, pois este é o resultado da divisão inteira. O resto da divisão, simbolizado por %, retorna 1. Dessa forma, o resto da divisão de um numero par por 2 é 0, caso contrario é um número inteiro. São operações básicas que precisam ser dominadas. Repare que eu fiz um código que funcione, não tentei nada mais maquiavélico que dê 0.0001 milissegundos mais rápido. Evite a otimização precoce.

Programar profissionalmente é uma tarefa de muita responsabilidade. Desenvolver testes para garantir a qualidade provê excelentes resultados a longo prazo.

sexta-feira, 2 de maio de 2008

TDD com Perl

É possivel fazer Test Driven Development com Perl? Mas claro :)

Artigo interessante sobre Test::More:
http://www.testingreflections.com/node/view/5333

Tutorial bem sucinto:
http://www.wgz.org/chromatic/perl/IntroTestMore.pdf

Excelente apresentação:
http://wellington.pm.org/archive/200606/tdd/

segunda-feira, 3 de dezembro de 2007

Testes Unitários com VBScript

Nas ultimas semanas, tive que aprender VBScript por conta de alguns testes que estão por vir. A sintaxe é bem estranha, mas fazer o que...

Entretanto é simples o suficiente para se desenvolver pequenas rotinas e, de quebra, aprender boas praticas de programação, como TDD, no exemplo abaixo:

' Função a ser testada
' EhPrimo determina se um determinado número inteiro é primo ou não
Function EhPrimo(numero)
'
'coloque o codigo aqui :)
'
EhPrimo=false ' so pra retornar algo...
End Function

'Coloquei o nome Test para ficar mais facil :)
Sub Test( objQuery, strMessage )
If Not objQuery Then
WScript.Echo VbCrlf & "ERRO: " & strMessage
WScript.Quit 'no primeiro teste errado...
End If
End Sub

' Testando primos
Primos = array(2,3,5,7,11)
For Each Numero In Primos
Test EhPrimo(Numero),"Numero " & Numero & " deve ser primo!"
Next

' Testando _nao_ primos
NaoPrimos = array(4,6,8,9,10)
For Each Numero In NaoPrimos
Test (Not EhPrimo(Numero)),"Numero " & Numero & " NAO deve ser primo!"
Next

' Testando outras coisas
Test (Not EhPrimo( 0)),"Numero 0 NAO deve ser primo!"
Test (Not EhPrimo( 1)),"Numero 1 NAO deve ser primo!"
Test (Not EhPrimo(-1)),"Numero -1 NAO deve ser primo!"


WScript.Echo VbCrlf & "Pronto, passou tudo!"


Pronto, agora

Qual a vantagem dessa abordagem?
1) vc se obriga a testar o seu código
2) vc descobre quais parametros são uteis nas suas funções
3) vc especifica o comportamento das suas funções ANTES de implementa-la:)