Categories
Controle de qualidade Testes

Você automatiza seus testes de aceitação?

O Danilo Sato escreveu um post muito bom sobre automatização de testes de aceitação:

“Muitas equipes XP não automatizam seus testes de aceitação. Essa é uma afirmação dura, porém muito comum de acontecer. A equipe abraça TDD e testes de unidade automatizados, porém quando chega a hora dos testes de aceitação, a coisa complica. Por que isso acontece? Como melhorar essa situação?”

Testes de aceitação são essenciais e automatizá-los é extremamente desejável!

Quem acompanha meu blog já deve ter percebido que teste de software é um assunto que me interessa bastante. Ultimamente tenho trabalhado muito focado na qualidade dos softwares que entregamos na minha equipe e por isso estou constantemente explorando novas maneiras de testar ou procurando melhorar as maneiras antigas.

Nos últimos sprints temos feito da seguinte forma: o desenvolvedor que trabalhar no desenvolvimento de uma história não pode fazer testes de aceitação da mesma. Desta forma cada um sempre testa o que o outro fez (estamos usando Selenium para este tipo de teste).

Ao fim do 4o. sprint temos uma suite de testes expressiva que cobre praticamente 100% do que foi desenvolvido. Realmente percebemos que aqueles bugs extremamente simples que normalmente são encontrados em QA ou mesmo em produção foram encontrados em tempo de desenvolvimento e corrigidos com rapidez.

Rodando automaticamente estes testes são uma ferramenta poderosíssima para ajudar a garantir a qualidade da aplicação.

Recomendo fortemente a leitura do post do Danilo!

Categories
Domain-Driven Design

Em qual língua você programa?

Muitas vezes fico em dúvida sobre qual língua usar nos meus sistemas. Sempre me sentí muito mais confortável programando em inglês do que em português mas nunca achei isso muito certo já que nossa língua nativa é o português. Já houveram casos de eu trabalhar com alguém que só sabia português e apesar de achar que nessa profissão quem não sabe inglês não vai à lugar nenhum não podia simplesmente inviabilizar o trabalho da equipe.

No último projeto que participei nossa equipe usou somente português. Algumas coisas boas aconteceram como o fato de termos incorporado a linguagem do negócio ao sistema. Isso é muito bom porque a grande maioria das entidades do sistema são coisas do mundo real e as discussões são sempre sobre coisas reais e não sobre metáforas (frequentemente mal feitas).

Porém também houveram pontos negativos. Por exemplo, uma determinada classe de serviços, que em inglês seria algo como MediaServices, teve que ser chamada de ServicosDeMidia. No fim das contas acabei optando por tirar a preposição (acho que o “de” é uma preposição, certo?) ficando somente ServicosMidias. Resolví retirá-la porque inúmeras classes ficaram com “De” no nome e ficou meio esquisito. O ruim é que não é exatamente assim que a gente fala naturalmente e fica um pouco estranho…

No fim das contas acho que o ideal é utilizar a língua do negócio, seja ela português ou inglês. Desta forma quando você for conversar com os clientes (product owners) você não terá o overhead de mapear todos os conceitos reais para a linguagem ou metáforas usadas no sistema. Com isso a conversa entre os clientes e os desenvolvedores fica muito mais produtiva já que todos estão falando sobre as mesmas coisas.

Categories
Agile Engenharia de software XP

Para que você vai usar isso?

Quando você se reune com seus usuários para definir um sistema é muito comum que alguém apareça com requisitos que não fazem o menor sentido e que não agregam o menor valor para o software.

Uma vez fazendo levantamento para o desenvolvimento de um sistema alguém me pediu para mostrar em algum lugar da tela a quantidade de pessoas logadas. Tá, eu confesso que era uma coisa relativamente simples de ser feita. O que acontece é que no meio de tantas coisas realmente importantes fazer aquilo me parecia completamente estúpido e inútil.

Então, tentando espremer o usuário, fiz aquela pergunta clássica: “e para que você vai usar isso?”. Normalmente o cara responde alguma coisa vazia como: “eu preciso saber quantas pessoas tem logadas no sistema para ver se tem um número bom de pessoas trabalhando”. E você fica sem saber o que responder e acaba aceitando.

Segundo Scott Bellware, uma forma eficiente de resolver este problema é perguntar para o usuário da forma correta. Ele escreveu um post no Code Better sobre como utilizar uma abordagem de behaviour-driven development para discutir funcionalidades de um sistema.

Neste caso, eu poderia ter perguntado para o usuário: “você poderia me descrever uma história com este requisito?”. Desta forma eu faria com que o usuário percebesse que nenhuma situação de uso real do sistema iria requerer que tal funcionalidade existisse. Na verdade este é o benefício principal de Behaviour-Driven Development. A idéia é que pensando na forma como o software irá se comportar você consegue perceber com mais clareza qual é o comportamento realmente necessário e não o que você imagina que seja necessário.

Desenvolver um sistema a partir de um comportamento necessário faz com que você desenvolva software que atende aos usuários. Já desenvolver um sistema a partir um dado que deverá ser apresentado pode fazer com que você desenvolva software inútil sem se preocupar ou entender o que realmente importa.

Parte da nossa missão trabalhando com desenvolvimento de software é guiar o usuário e fazê-lo entender o que ele realmente precisa, não o que ele acha que quer ou acha que precisa.

Categories
Agile Scrum XP

Ambiente descontraído parte 2 – desenvolvimento ágil

O Phillip Calçado escreveu um excelente post no blog dele sobre como funciona nossa equipe de desenvolvimento ágil.

Isso é uma prova viva de que um ambiente de trabalho não precisa ser aquela chatice que todo mundo está acostumado. Você pode deitar no trabalho, pode jogar video game, pode ouvir heavy metal nas alturas e mesmo assim fazer um trabalho sério produzindo software de alta qualidade.

Nos últimos tempos temos experimentado várias práticas de desenvolvimento ágil que têm tornado nossos projetos muito mais produtivos e divertidos. O Phillip faz neste post um resumo muito bom das principais coisas que aconteceram na nossa equipe nos últimos 2 meses mostrando várias fotos e explicando alguns detalhes do processo.

Categories
Agile Scrum XP

Aumenta a adoção de metedologias ágeis em 2007

A notícia é um pouco velha mas como é uma boa notícia acho que ainda é tempo. O Scott Ambler publicou na Dr. Dobbs o resultado da sua pesquisa anual de adoção de metodologias ágeis.

A pesquisa de Scott Ambler este ano mostra que metodologias ágeis foram implantadas com sucesso na maioria das empresas. Seis anos após seus surgimento, ele acha que as metodologias ágeis finalmente cruzaram todo o “abismo de adoção de tecnologias de Moore”, que sugere que uma tecnologia passa por vários estágios de adoção, desde os inovadores e “early adopters” até os “laggards” (atrasados).

A utilização efetiva de metodologias ágeis no exterior tem crescido a passos largos, especialmente nos Estados Unidos. Isto indica que dentro de pouco tempo as metodologias ágeis também estarão muito difundidas aqui no Brasil e sendo adotadas em larga escala. É claro que muitas pessoas já conhecem, mas na prática o número de empresas que adotaram estas metodologias ainda é muito pequeno.

Na empresa onde trabalho muitos costumes já existem há vários anos e é muito complicado mudar tudo da noite para o dia. Mas lentamente várias práticas de XP e outras metodologias ágeis como o Scrum estão sendo introduzidas. Cada vez mais a alta gerência está consciente de que existem problemas no modelo atual de desenvolvimento de software e eles estão cada vez mais dispostos a tentar coisas novas.

Parece que coisas boas estão por vir.

Categories
Engenharia de software XP

Como produzir software “coxa”

Acabei de ler um post no blog do Phillip que me lembrou de uma história que eu queria escrever aqui.

A história que você lerá a seguir é uma dramatização do que acontece diariamente no mundo do desenvolvimento de software carinhosamente entitulada Como desenvolver software “coxa”.

Imagine uma empresa que está com necessidades de desenvolver um software. E imagine também que esta empresa (cliente) contratou uma empresa de 3 letras para fazer o trabalho.

Então, o projeto começa com a empresa contratada fazendo uma análise extensa do que será desenvolvido. Caso você não saiba, estas empresas te fazem acreditar que o RUP tem uma fase de análise antes de tudo, quando isso na verdade caracteriza um processo de desenvolvimento waterfall que é bem diferente do processo de desenvolvimento iterativo do RUP.

Depois de analisar exaustivamente tudo que precisa ser feito, eles acham que sabem tudo que o cliente precisa e com isso acham que sabem exatamente quanto tempo irá levar. Baseado nisso é estipulado um prazo de entrega do software para o cliente, assim como é estipulado um preço fixo para o trabalho.

Fechado o contrato, o projeto é iniciado. E quando começa o projeto sempre acontece a mesma coisa: a equipe de desenvolvimento começa a descobrir que certas coisas não são tão simples quanto pareciam ser. A equipe se depara com vários problemas que não haviam aparecido na fase de análise e cada vez fica mais chocada com a quantidade de coisas novas que vão surgindo. Nesse momento a equipe percebe que o projeto, que estimou-se que precisaria de 4 meses para ser desenvolvido, na verdade precisa de 8 meses para ser desenvolvido!

Então alguém surge com a brilhante idéia de contratar mais pessoas para a equipe. Afinal de contas, da mesma forma que nove grávidas conseguem parir um filho em um mês, 20 desenvolvedores conseguem fazer na metade do tempo o trabalho que 10 fariam – parece perfeito!

Mas espera aí, o preço já está combinado com o cliente desde o início. Então contratar mais pessoas é a maior furada, porque o cliente não pagará nada a mais por isso e se o custo do projeto for maior a empresa não terá lucro. Pior ainda, a empresa pode ter prejuízo! Neste momento a empresa decide comuncar ao cliente que o projeto terá que atrasar.

Quando a empresa vai dar a notícia para o cliente o cara normalmente quer matar o gerente do projeto, quer se matar, ou OS DOIS (depende do tamanho do projeto)! Afinal de contas ele pagou 50% adiantado e quer logo o retorno do seu investimento. Se o projeto ia demorar 4 meses e agora vai demorar 8, significa que todo o seu planejamento financeiro e de retorno de investimento do software foram por água a baixo. Nesse momento o cliente bate na mesa com força e diz: “se virem, eu não quero nem saber como vocês vão fazer mas eu quero o meu software na data combinada!”.

Exatamente neste momento foi parido um software “coxa”!

Vou explicar fazendo uma analogia: qualquer criança de 10 anos sabe que não existe “bom, bonito e barato”. Ou é bom, bonito e CARO; ou é RUIM, bonito e barato; ou é bom, FEIO e barato. Não tem jeito, é assim que funciona, não é possível ter tudo ao mesmo tempo.

Com software funciona exatamente da mesma forma. Só o que muda são as dimensões: qualidade, tempo e custo. Da mesma maneira que não existe nada bom, bonito e barato, não existe software “bom, desenvolvido rápido e barato”. Ou é bom, desenvolvido rápido e CARO; ou é bom, DESENVOLVIDO EM MUITO TEMPO e barato; ou é RUIM, desenvolvido rápido e barato.

No caso deste projeto repare que duas das dimensões do software não podem ser alteradas: preço (porque já foi combinado com o cliente e está em contrato) e tempo (porque a data de entrega já está estipulada). Sendo assim, como não dá para ter tudo ao mesmo tempo, a qualidade vai para o brejo. Neste momento é que vem a ordem do gerente de projeto: “gente, faz qualquer coisa aí, o importante é entregar o que foi combinado com o cliente, não importa como, faz tudo nas coxas mesmo!!”. E mais um software “coxa” será entregue.

Se eu contar os softwares “coxa” que eu já ví por aí ninguém acredita. O mais bizarro deles e que não poderia deixar de ser citado tinha o seguinte código na tela de autenticação (PHP):

if (($_REQUEST["login"]  == "admin") && ($_REQUEST["senha"]  == "1234")) {
    header("Location: index_autenticado.php");
} else {
    header("Location: index.php?mensagem=Login%20invalido");
}

No final das contas, quando o projeto é entregue, o cliente vê as telinhas e se as telinhas estiverem aparecendo e estiverem no mínimo bonitinhas ele vai dar pulos de alegria! Eventualmente até irá contratar a empresa para outro projeto ou indicar para amigos e outros projetos dentro da sua empresa. Por baixo dos panos existe esse monte de lixo, mais ou menos como um vulcão que pode entrar em erupção a qualquer momento causando efeitos devatadores! Deixa só alguém pedir para trocar a senha do sistema para ver o que vai acontecer…

Para mim é claro como água: não adianta querer prever tudo que acontecerá no projeto e pré-fixar datas e valores. É receita certa para fazer lixo. Já está mais do que provado que não funciona, porque insistir no mesmo erro?

Se você que está lendo se identificou com a história (o que não é nem um pouco difícil), sugiro fortemente que você leia sobre um modelo de contrato de desenvolvimento de software proposto pelo XP que muito me agrada: o Contrato de Escopo Negociável. O artigo é bem grande mas vale apena ler até o final! Você vai perceber que as coisas não precisam ser assim tão tristes nos projetos de desenvolvimento do software.

Categories
Java TDD

Test Driven Development in a nutshell

Semana passada tivemos uma reunião na empresa em que trabalho sobre os procedimentos de controle de qualidade dos softwares que produzimos. Antes de fazer o release de um software é necessário que sejam tomadas algumas precauções básicas para garantir que ele funcione satisfatoriamente em produção e estávamos apresentando para os nossos gerentes e equipe de controle de qualidade da empresa as ferramentas e procedimentos adotados pela nossa equipe para isto.

Das várias medidas que estamos tomando, talvez a principal por ser a base para todas as outras direta ou indiretamente é o “desenvolvimento guiado pelos testes” ou “test driven development” (TDD para os mais íntimos).

No TDD você desenvolve os testes do software antes mesmo de desenvolver o software. A cada peça da aplicação que é construída uma série de testes são escritos ANTES do desenvolvimento para garantir que a aplicação funciona como deveria funcionar.

O conceito de TDD é bem simples de ser aplicado mas para quem não está acostumado com testes unitários e práticas de desenvolvimento ágil pode parecer meio estranho e ser um pouco mais difícil de entender.

Inspirado nesse cenário vou tentar explicar rapidamente como funciona o TDD com um exemplo prático e bem simples. Vou explicar seguindo a linha de raciocínio que normalmente se tem em tempo de programação pois a maneira de pensar no processo de desenvolvimento também faz parte do TDD.

Imagine que você está desenvolvendo um sistema no qual um usuário deve cadastrar seu endereço. O CEP digitado precisa ser validado para que tenha o formato 00000-000 e para isso será necessário desenvolver uma classe que faça a validação dos dados. Vamos definir a interface para esta classe de validação de dados:

public interface ValidadorDeDados {
    boolean isCepValido(String cep);
}

A partir deste ponto já temos o comportamento do nosso validador claramente definido: dada uma String contendo o valor de um CEP ele retornará verdadeiro se o CEP for válido e falso caso contrário. Por exemplo, se o validador receber uma String “teste” ou “” obviamente deverá retornar falso. Por outro lado se ele receber uma String “00000-000” ou “12345-123” deverá retornar verdadeiro.

Então já podemos desenvolver uma classe de teste que faça estas verificações e mais algumas outras pertinentes. Vou utilizar para os testes o framework JUnit que é o framework de testes unitários Java mais popular do mercado.

public class ValidadorDeDadosTest extends TestCase {
    private ValidadorDeDados validador = null; // instância de validador de dados para o teste
 
    public void testIsCepValido() {
        assertFalse("retorno deve ser FALSE", validador.isCepValido(null));
        assertFalse("retorno deve ser FALSE", validador.isCepValido(""));
        assertFalse("retorno deve ser FALSE", validador.isCepValido("iodfjodfd"));
        assertFalse("retorno deve ser FALSE", validador.isCepValido("03490340"));
        assertTrue("retorno deve ser TRUE", validador.isCepValido("20202-020"));
        assertTrue("retorno deve ser TRUE", validador.isCepValido("00000-000"));
        assertTrue("retorno deve ser TRUE", validador.isCepValido("99999-999"));
    }
}

Para quem não conhece o JUnit, o método assertFalse checa se o retorno da execução retornou falso e o método assertTrue checa se o retorno da execução retornou verdadeiro. Em todos os casos que o assertTrue foi utilizado o validador deverá retornar verdadeiro e o oposto deverá acontecer para os assertFalse. Se isto não acontecer, significa que tem alguma coisa errada.

Além disso colocamos uma pequena mensagem explicando o que esperamos que aconteça na execução do método. Isso é util para que outros desenvolvedores da equipe possam entender com facilidade o que você programou e está esperando que aconteça nos seus testes, bem como para deixar as mensagens de erro mais explicativas.

Só para não deixar dúvida nenhuma, vamos ler uma das linhas de teste:

assertFalse("retorno deve ser FALSE", validador.isCepValido(""));

Que é: Ao executar a validação do CEP “”, certifique-se que ele retornará FALSO.

Então vamos executar o teste para ver o que vai acontecer:

TDD 1

Ele falhou com NullPointerException porque não há uma implementação de validador, desenvolvemos somente a interface. No teste unitário o validador está setado como null.

Sendo assim, vamos desenvolver a primeira implementação de validador:

public class ValidadorDeDadosImpl implements ValidadorDeDados {
    public boolean isCepValido(String cep) {
        return false;
    }
}

Além disso é necessário alterar a classe de teste para utilizar esta implementação de validador de dados que desenvolvemos.

public class ValidadorDeDadosTest extends TestCase {
    private ValidadorDeDados validador = new ValidadorDeDadosImpl(); // agora o validador não é mais null
 
    public void testIsCepValido() {
        assertFalse("retorno deve ser FALSE", validador.isCepValido(null));
        assertFalse("retorno deve ser FALSE", validador.isCepValido(""));
        assertFalse("retorno deve ser FALSE", validador.isCepValido("iodfjodfd"));
        assertFalse("retorno deve ser FALSE", validador.isCepValido("03490340"));
        assertTrue("retorno deve ser TRUE", validador.isCepValido("20202-020"));
        assertTrue("retorno deve ser TRUE", validador.isCepValido("00000-000"));
        assertTrue("retorno deve ser TRUE", validador.isCepValido("99999-999"));
    }
}

Agora o teste já está rodando, o resultado foi bem diferente. Antes estava sendo lançada uma exception porque a implementação do validador de dados sequer existia. Agora o que acontece é que temos um problema na implementação do validador e o teste acusou isso:

TDD 2

Veja a implementação do validador de dador e repare que independente da String passada para o método ele retorna falso, ou seja, está errado. Vamos corrigir essa implementação:

public class ValidadorDeDadosImpl implements ValidadorDeDados {
    public boolean isCepValido(String cep) {
        if ((cep == null) || (cep.length() != 9) || cep.charAt(5) != '-') {
                return false;
        }
        return true;
    }
}

E executando novamente os testes podemos verificar que agora todos estão passando:

TDD 3

Desenvolvimento finalizado? Nada disso. Navegando pelo sistema e fazendo outros testes manuais descobrimos que é possível entrar com um CEP com letras tipo ABCDE-FGH. Se analisarmos a implementação do método veremos que isso realmente é possível. Não há nenhuma verificação que impeça isto.

Mas caramba, os testes não passaram? É verdade, os testes ainda estão passando. Acabamos de descobrir um bug.

Quando um bug é descoberto ou é reportado para a equipe de desenvolvimento, a primeira coisa a se fazer é escrever um teste para comprovar a existência do bug. Vamos então complementar a nossa classe de teste:

public class ValidadorDeDadosTest extends TestCase {
    private ValidadorDeDados validador = new ValidadorDeDadosImpl();
 
    public void testIsCepValido() {
        assertFalse("retorno deve ser FALSE", validador.isCepValido(null));
        assertFalse("retorno deve ser FALSE", validador.isCepValido(""));
        assertFalse("retorno deve ser FALSE", validador.isCepValido("iodfjodfd"));
        assertFalse("retorno deve ser FALSE", validador.isCepValido("03490340"));
        assertTrue("retorno deve ser TRUE", validador.isCepValido("20202-020"));
        assertTrue("retorno deve ser TRUE", validador.isCepValido("00000-000"));
        assertTrue("retorno deve ser TRUE", validador.isCepValido("99999-999"));
    }
 
    public void testIsCepValidoComLetras() {
        assertFalse("retorno deve ser FALSE", validador.isCepValido("AAAAA-AAA"));
        assertFalse("retorno deve ser FALSE", validador.isCepValido("A2AA1-333"));
        assertFalse("retorno deve ser FALSE", validador.isCepValido("x2334-567"));
    }
}

Executando os testes novamente vemos que o teste que acabamos de escrever falhou. Ao entrar com este CEP o validador deveria retornar falso pois é um CEP inválido mas ele está retornando verdadeiro. Muito bem, o bug está comprovado:

TDD 4

Repare que somente o teste novo está falhando, os testes antigos estão passando normalmente (em verde).

Com os testes prontos já podemos fazer a correção na implementação:

public class ValidadorDeDadosImpl implements ValidadorDeDados {
    public boolean isCepValido(String cep) {
        if ((cep == null) || (cep.length() != 9) || cep.charAt(5) != '-') {
            return false;
        }
        for (int i = 0; i < cep.length(); i++) {
            if (i != 5) {
                char posicao = cep.charAt(i);
                if (!Character.isDigit(posicao)) {
                    return false;
                }
            }
        }
        return true;
    }
}

E corrigida a implementação, vamos executar os testes novamente para verificar se o bug foi corrigido:

TDD 5

Agora sim tudo funcionando.

Podemos perceber como é fácil implementar funcionalidades e corrigir bugs com TDD. Alguns pontos fortes que merecem destaque:

1) Qualquer tipo de implementação por mais complexa que seja será suportada pelos testes e com isso você programa com mais confiança. Dado um comportamento do método que será definido antes do desenvolvimento você pode executar os testes inúmeras vezes até que eles passem. E quando eles passam você tem certeza absoluta de que o que você fez está efetivamente funcionando.

2) TDD facilita o refactoring: depois de cada reescrita de código ou qualquer tipo de alteração, especialmente em códigos que você não conhece bem porque foram feitos por outros membros da equipe, você pode rodar os testes da aplicação inteira afim de garantir que você não está quebrando nenhuma funcionalidade. Alguns sistemas são tão podres que dependendo do lugar que você mexe quebra tudo. Os testes te ajudam a não fazer isso.

3) Mesmo que seja um pouco mais demorado escrever testes ao desenvolver, com esta prática você praticamente não encontra bugs em produção e quando encontra eles podem ser corrigidos rapidamente e com confiança. Então no final das contas você GANHA tempo. E o melhor de tudo é que você programador não precisa ficar pisando em ovos e sem dormir porque mexeu na aplicação. Algumas aplicações são tão difĩceis de serem alteradas (porque são mal programadas) que você vai para casa e dorme com o celular do lado porque tem certeza que ele vai tocar porque deu pau no sistema!

Então, preserve seus cabelos e sua saúde: programe com qualidade! 😉

Download do código fonte.