Integração Contínua

Por Cauê Guerra em 04/11/08

“Integração Contínua é uma pratica de desenvolvimento de software onde os membros de um time integram seu trabalho frequentemente, geralmente cada pessoa integra pelo menos diariamente - podendo haver multiplas integrações por dia. Cada integração é verificada por um build automatizado (incluindo testes) para detectar erros de integração o mais rápido possível. Muitos times acham que essa abordagem leva a uma significante redução nos problemas de integração e permite que um time desenvolva software coeso mais rapidamente.” Martin Fowler

Integração Contínua tornou-se muito importante na comunidade de desenvolvimento de software e isso provavelmente ocorreu devido ao grande impacto causado pelas metodologias ágeis. Em equipes que adotaram tais metodologias (eXtreme Programming, Scrum, entre outras), integração contínua é um dos pilares da agilidade, garantindo que todo o sistema funcione a cada build de forma coesa, mesmo que sua equipe seja grande e diversas partes do código estejam sendo alteradas ao mesmo tempo.

Mas porque fazer Integração Contínua ? Quais os benefícios que isso pode trazer ?

Basicamente, a grande vantagem da integração contínua está no feedback instantâneo. Isso funciona da seguinte forma: a cada commit no repositório, o build é feito automáticamente, com todos os testes sendo executados de forma automática e falhas sendo detectadas. Se algum commit não compilar ou quebrar qualquer um dos testes, a equipe toma conhecimento instantâneamente (através de email, por exemplo, indicando as falhas e o commit causador das mesmas). A equipe pode então corrigir o problema o mais rápido possível, o que é fundamental para não introduzir erros ao criar novas funcionalidades, refatorar, etc. Integração contínua é mais uma forma de trazer segurança em relação a mudanças: você pode fazer modificações sem medo, pois será avisado caso algo saia do esperado.

Mas porque eu não rodo pessoalmente os testes na minha máquina e só então faço o commit?
Simples: seu projeto pode ser tão grande, que os testes (em especial os de aceite) demoram um tempo considerável para serem executados e você não vai querer esperar todo esse tempo a cada commit pra poder continuar a trabalhar. Nesse caso, o recomendado é rodar os testes que envolvem as partes que você modificou e só então commitar, deixando para o servidor de integração contínua o trabalho de realizar todos os testes do sistema e garantir que tudo esteja funcionando. Além disso, não estamos falando apenas de testes, estamos falando de builds completos: a cada commit temos uma versão que teoricamente está pronta para entrar em produção, e isso pode envolver a realização de tarefas que não faríamos se estivessemos só testando, como por exemplo gerar um arquivo .war. O projeto pode ainda ser implantado automaticamente num servidor de desenvolvimento/homologação, e então com isso a cada commit temos o projeto rodando na web instantaneamente refletindo nossas mudanças!

Um outro exemplo interessante é o processo de geração das apostilas dos nossos cursos: a cada commit feito no repositório, o servidor faz o check-out, executa o Tubaina, que transforma o fonte em latex, convertendo o latex gerado em pdf e por fim copiando esse pdf num diretório interno, pronto para impressão na gráfica. Dessa maneira, garantimos que disponibilizamos para nossos alunos o material mais atualizado disponível.
Para tudo isso funcionar, no entanto, é preciso agarrar a idéia de commits pequenos. Fica mais fácil saber onde foi introduzido um erro quando o build quebrar se houveram pequenas mudanças, do que ter de verificar as ultimas 50 classes alteradas no ultimo commit. Outro ponto importante, é garantir ao menos um build limpo, com todos os testes passando, ao final de cada dia. Assim, teremos software pronto para entrar em produção tão cedo seja necessário.

Em outras palavras, podemos descrever Integração Contínua como integração automática com processo de build automático e que roda testes de forma automática e automaticamente detecta falhas em cada pedaço.

Abaixo as ferramentas que usamos aqui na Caelum:

  • CruiseControl.rb: desenvolvida pela ThoughtWorks, é a aplicação de integração contínua. Capaz de constantemente verificar os repositórios em busca de novos commits, fazendo check-out e rodando tarefas pré-determinadas. O interessante dessa ferramenta é que ela trabalha com qualquer tipo de projeto: ruby, java, ou qualquer outro cujo build possa ser feito através da linha de comando. Além disso, tem sua interface extremamente simples e funcional :).
CruiseControl.rb

CruiseControl.rb

CruiseControl.rb build report

CruiseControl.rb build report

  • CCMenu, ou CCTray: permite acompanhar o build de cada projeto sem ter de entrar no site do CruiseControl.rb. Isso é feito através de um ícone alertando quais projetos estão com o build quebrado.
  • Selenium: também desenvolvida pela ThoughWorks, provê maneira de realizar testes de integração em projetos Web, simulando a navegação do usuário pelas páginas do site, realizando diversas ações como clicar o mouse, prencher campos, etc… Usado para validar se o resultado de determinadas ações do usuario correspondem às que esperamos.
  • Xvfb: Servidor X11 que permite realizar todas as operações gráficas em memória, não exibindo nada na tela. Com essa ferramenta, é possível rodas os testes com Selenium sem ter as janelas do Firefox pulando na tela do nosso servidor a cada teste. Além disso, podemos rodar os testes mesmo em ambientes sem X instalado (como é o caso de servidores, por ex).

Ainda usamos plugins Maven e Ant para a geração de relatórios de cobertura e testes, que publicamos a cada novo build. Assim, conseguimos além de garantir que o código está todo integrado, garantir que está com uma cobertura adequada; afinal, de que adianta ter o sistema sem testes falhando, se ele possui menos testes que o necessário?

JUnit Report

JUnit Report

Cobertura Report

Cobertura Report

Por fim, pela experiência que temos no uso dessas ferramentas no desenvolvimento de aplicações internas, open source e em nossos clientes, podemos afirmar que elas de fato nos trazem uma produtividade antes inalcançável. Se você pensa em começar a utilizar alguma ferramentas de integracão contínua, vale a pena dar uma olhada nessas que indicamos. No entanto, existem várias outras disponíveis, e é questão de escolher a que melhor se adapta às suas necessidades. Não deixe de comparilhar suas experiências…

Vazamento de memória e de conexões

Por Guilherme Silveira em 02/09/08

Descrevo aqui a minha aventura junto com diversos desenvolvedores da Caelum: Cauê Guerra, Filipe Sabella, Anderson Leite, Pedro Mariano e o Pedro Matiello para resolver um grande problema que ocorria em um dos nossos projetos.

Assim como quando um avião cai, não é um bug que gera um grande problema, mas sim uma série de fatores que contribuem para a dificuldade na hora de encontrar complicações estruturais de um projeto.

Vamos as fatos, que incluem bugs e vazamentos em bibliotecas famosas, profiling e open call hierarchy de eclipse.

Sintoma: o servidor web parava de responder para qualquer página, exceto a tela de login

Nesse caso, o servidor entrava na tela de login mas ao tentar submete-la ou acessar qualquer outra funcionalidade o browser não recebia resposta alguma do servidor web.

A primeira coisa a pensar era que o processador estaria a 100%, que é o caso comum em laços infinitos ou threads gulosas além da conta. Mas um comando rápido no servidor (top) mostrava que o processador estava a 0%. Claro, se estivesse a 100%, teríamos duas opções:

  1. rodar o profiler e detectar o método que estaria consumindo 100% do cpu
  2. imprimir todas as threads do sistema (com Thread.métodos estáticos) e ver o stack trace de cada uma delas… detectaríamos o mesmo ponto.

Mas como nosso problema não era esse, uma possível causa levantada pelo Rafael Cosentino foi de que poderia haver algum deadlock, travando todas as requisições. Pensando na dificuldade de detectar tal falha, e acreditando que o problema estava relacionado com o consumo de memóriaapesar de não haver OutOfMemoryErrors), decidimos usar o JProfiler.

O primeiro passo foi rodar o profiler no Stella Boleto para verificar se ele estava com algum leak, pois sabíamos que o mesmo consome uma memória razoável durante o processo de geração do boleto, devido a imagem de background de alta resolução ser carregada num BufferedImage. Memória a qual deveria ser liberada. Ao rodar o profiler, o Caue Guerra, executou diversas threads em paralelo com invocações complexas do Stella Boletos. Durante e após as execuções, não houve memory leak algum, e o garbage collector consumia os objetos corretamente.

Sem nenhum ponto especial, teríamos que fazer o profiling da aplicação inteira.

Foram várias tentativas com profiling remoto offline, o código java invocava a api de profiling para depois analisarmos os resultados em nossas máquinas, porém um bug do JProfiler com o Apache Tomcat 6.0.16 inviabilizou tal análise. Fomos então rodar a aplicação no Jetty 7 (apontando para a mesma configuração do tomcat) sem que nenhum usuário da aplicação live notasse a mudança: uma migração transparente de servlet containers executada com êxito, assim como ocorreu no GUJ.

Em paralelo, o Tomcat foi atualizado para 6.0.18, que funciona com a última versão do JProfiler corretamente e levantamos o mesmo com apenas 64 megas de ram (para estressar o garbage collector mais frequentemente). Rodando o Tomcat em outra porta, mas acessando o mesmo banco de produção, permitiu que testássemos junto ao cliente exatamente as funcionalidade que ele julgava “lentas”.

Em algum ponto durante o uso do JProfiler, o mesmo indicou que não havia nenhum deadlock de threads, bastando para ele verificar se existem threads esperando por monitores de maneira cruzadas.

Primeiro sinal de leak: Executamos algumas tarefas no sistema e vimos que o número de objetos crescia de maneira adequada, porém, ao rodar o GC, alguns objetos ficavam para trás… objetos que são entidades do projeto! É comum o número de Strings, arrays de char e de bytes crescerem, mas entidades do nosso próprio sistema? E agora? Por que isso acontece? Alguém continuou mantendo referências para objetos do projeto que não deveriam estar referenciados. Novamente o profiler vai ajudar.

Segundo sinal de leak: Cutucando mais um pouco percebemos que algumas Sessions do hibernate tambem não estavam sendo fechadas nem coletadas! Essa costuma ser uma forte indicação de vazamento de conexão. E no caso do Hibernate, isso poderia vir a gerar mais leak ainda, dado que muitas entidades ficariam presas no cache de primeiro nível, já que a sessão continuava aberta.

Objetos do hibernate apos o gc

Mas olhando a fundo, percebemos que a maior parte dessas sessões, exceto uma, estava injetada em objetos de modelo na sessao web. Isto é, são sessões usadas pelos nossos Repositories, que faz todo o sentido para o projeto e não é um memory leak, pois a sessão que era referenciada deveria estar fechada e com isso os objetos de cache de primeiro nível dele, coletados.

Sendo assim, partimos para testar funcionalidades que acreditavamos ter consumo grande de memória.

Primeiro sinal de consumo excessivo de memória: Encontramos uma funcionalidade que levantava cerca de 40 mil objetos na memória. Com isso, o consumo era de cerca de 40 megas de objetos só nesse select… mas o Tomcat estava configurado para 64 megas? O que acontecia? O consumo de memória aumentava, o garbage collector era invocado de sua maneira parcial, até que o consumo chegava ao topo, quando garbage collector full era rodado, pedindo mais memória para o sistema operacional.

Mas 64 ram não era espaço suficiente, então rodava o garbage collector full novamente, passando por todos os objetos da memória novamente, liberando mais um pouco e deixando um pouco mais de espaço, mas ainda não o suficiente. Cada vez mais, em intervalos de poucos segundos, o garbage collector era rodado e liberava menos espaço novo do que antes, tudo isso ainda na mesma requisição que usaria 40 megas… até que os espaços são tão pequenos que o processador fica a 100% de processamento, até acabar a memória e lançar o OutOfMemoryError. Nesse meio tempo, o servidor fica praticamente sem dar respostas, dado o alto uso de CPU.

Garbage collector rodando apos requisicao que criava diversos objetos

Garbage collector rodando apos requisicao que criava diversos objetos: note o garbage collector full rodado no final.

 Ok, encontramos um problema. Solução: filtrar esses 40 mil objetos para trazer menos objetos para a memória de uma vez só. No nosso caso uma simples busca por esses objetos foi o suficiente. O cliente não queria paginação e já havia recusado a paginação que havia sido feita no Sprint anterior.

Em outros casos, poderiamos usar resultados do hibernate que armazenam uma referencia somente para o objeto atual da lista, podendo garbage coletar os anteriores (uma StatelessSession, conforme discutido nos 7 hábitos dos desenvolvedores Hibernate altamente eficazes).

Esse ainda não era o grande problema, pois o nosso sintoma travava o servidordeixando o processador a 0%, e não a 100%.

Memory leak temporário encontrado: Voltando ao problema de leak anterior: o profiler mostrou que alguns objetos que estavam sobrando e não sendo garbage coletados. Com um pouco mais de procura, um heap dump mostra todas as referencias de um determinado objeto e, procurando quem referenciava aqueles que não iam para o garbage collector, encontramos que a implementação padrão do grupo Apache para a JSTL na tag c:forEach mantinha uma referencia para o último objeto iterado. Oops. Como assim?
 

c:forEach leak

c:forEach leak

A tag c:forEach, para seguir a especificação da JSTL, mantem uma referência para o último objeto iterado, mesmo após a iteração (as tags ficam em um pool do servidor de aplicação e são recicladas). Por isso, a referência era fixa e todo aquele pedaco da árvore de referÊncia dos objetos não poderia ser coletado até o proximo c:forEach usar aquela instância do pool novamente.

E agora o que isso pode causar?

Primeiro, as instâncias da tag só vão manter a referência por um tempo determinado. Quando utilizadas novamente, aquela árvore de objetos poderá ser coletada… o único problema aconteceria com um número muito grande de usuários executando uma query no servidor ao mesmo tempo. Isso não costuma acontecer em projetos de 1000 usuários, portanto não é uma preocupação no momento.

Novamente não resolvemos o problema principal, mas achamos mais um detalhe importante sobre uma biblioteca que utilizamos no dia a dia sem medo. Solução: não se preocupar pois o projeto não terá um número tão grande de usuários simultaneos para causar algum problema. Continuar usando a JSTL.

Por fim, as estatísticas do Hibernate Session Factory mostravam que raramente uma conexão se perdia. Após mais de 5000 conexões estabelecidas, três estavam abertas. Isto é, a conexão atual, e mais duas… As estatísticas do hibernate é uma das maneira mais simples de obter resultados sobre leaks de conexões, queries mal escritas ou possíveis pontos de bom uso do cache de segundo nível. Oolhando as estatísticas, confirmamos o session leak (connection leak):

c:forEach leak enquanto não apontar para outro objeto

session leak

Session leak é um dos erros mais comuns em projetos com o Hibernate. Infelizmente encontramos muitos projetos que acessam conexões em métodos estáticos, sem controle algum de quem invoca esse método e de quem libera essa sessão. Nesse projeto, o acesso a camada ORM era feita através de um Interceptador, como no Open Session in View, que é a maneira que mais gostamos de trabalhar, pois dificulta session leaks e facilita a criação de testes para seu código, já que será usado alguma maneira de inversão de controle.

Causa encontrada: Usando o Open Call Hieranchy do Eclipse, encontramos rapidamente 3 referências para abertura de sessões que não fecham ela de maneira adequada (com try/finally mal escritos)…

Solução: Abrir e fechar sessões corretamente!

Mas o que um connection leak pode causar no meu projeto? Tudo depende da configuração do seu connection pool. No caso do c3p0, o mais famoso e amplamente utilizado, existem algumas configurações que você pode fazer como, por exemplo, o número minimo de conexões no pool, c3p0.minPoolSize.

Porem, ao usar hibernate essa propriedade muda para hibernate.c3p0.min_size. Note que o hibernate
decidiu não seguir o padrão! Em uma conversa com o Diego Plentz, commiter do Hibernate e colaborador da Caelum, não encontramos rapidamente um motivo pelo qual o padrão não foi seguido. Talvez algum problema de conflito de propriedades configuradas em um mesmo arquivo, ou talvez para manter compatibilidade com o Hibernate 2.

O que acontecia nesse caso específico? A aplicação usava algumas configurações corretamente, como o minimo e máximo de conexões (nos testes de integração e de aceite, o máximo era 3). Mas uma outra configuração importantíssima estava escrita de maneira errada dentro de uma String do XML: configurar o tempo máximo que uma conexão que não está sendo usada seja eliminada, justamente para ignorar leaks. Algo como: se a conexão está idle, por, digamos, 1 minuto, considere que foi um leak, e traga ela de volta para o pool.

Causa encontrada: Como exatamente essa configuração estava sendo usada no valor errado, junto com o memory leak da implementação da JSTL, após a funcionalidade com session leak acontecer 3 vezes nos testes de integração, não existia mais nenhuma conexão disponível. O mesmo acontecia em produção com um número maior de vezes. Então as threads ficavam esperando uma conexao do connection pool.

Isso podia ser visto na visualização de threads do profiler: a cada nova requisição com o servidor sem responder, uma nova thread era criada pelo tomcat para atender a requisição, mas essa thread ficava em WAITING, isto é, ela estva esperando uma conexão ser liberada. Pela view de monitor era possível
verificar que ela aguardava algo do c3p0… a conexão… pronto! 

Threads aguardando monitores/sleeping etc

Threads aguardando monitores/sleeping etc

Encontramos o problema… as threads ficavam paradas (0% de processador) esperando por conexões que nunca chegariam (WAITING)! Um problema que veio por causa da configuração do c3p0 com o problema do connection leak. A solução é composta por duas tarefas: corrigir o session/connection leak e corrigir a configuração do c3p0. Encontramos facilmente o erro graças aos testes de integração.

Ufa! Prontos para o próximo desafio…

Novo Treinamento FJ-16: Laboratório Java com Swing, XML e Testes

Por Sérgio Lopes em 14/08/08

FJ-16 Laboratório SwingDepois de aprender bem o Java, uma pergunta comum de alunos e de usuários do GUJ é: como adquirir experiência e boas práticas? Com esse intuito criamos um novo treinamento, onde desenvolvemos uma aplicação desktop com Swing, que faz análise técnica da bolsa de valores, através de gráficos, usando como fonte uma base XML. É o FJ-16: Laboratório Java com Swing, XML e Testes.

FJ16

O mais interessante é que, durante o desenvolvimento da aplicação, aprendemos a aplicar diversos design patterns (como o Decorator entre indicadores), utilizamos testes unitários com JUnit, conhecemos as ferramentas Ant e Maven, aplicamos reflection e anotações, além de sempre estar refatorando o código inúmeras vezes.

Nesse treinamento, são colocados em prática também diversos conceitos da linguagem que usamos no dia-a-dia, como manipulação de datas, de XML, classes anônimas, classes internas, uso do log4J, entre outros.

Veja a ementa completa na página do treinamento. Temos turmas agendadas para final de agosto e começo de setembro. Entre em contato conosco para mais informações.

Testes unitários com JMock 2

Por Lucas Cavalcanti em 17/03/08

Podemos definir teste unitário de uma classe como um teste em que verificamos uma funcionalidade da classe em questão passando o mínimo possível por outras classes do sistema, ou que sejam dependências do sistema.

Por mais desacoplada que seja nossa classe, se ela tiver um mínimo de complexidade, ela vai precisar de funcionalidades de outras classe, ou seja, ela vai ter dependências. E essas dependências sempre nos atrapalham na hora de fazermos os testes unitários da classe.

Por exemplo, se uma das dependências da nossa classe é a interface HttpServletRequest, o que vamos passar pra ela na hora do teste? null, e levar uma NullPointerException? Desenterrar a biblioteca e passar uma implementação de verdade? Claro que não.

Em testes unitários não estamos interessados no comportamento real das dependências da classe, mas em como a classe em questão se comporta diante das possíveis respostas das dependências, ou então se a classe modificou as dependências da maneira esperada.

Então é comum passarmos implementações falsas (mock objects) das dependências da classe, retornando valores pertinentes para conseguirmos testá-la satisfatoriamente. Esses mocks costumam poluir seu código de testes com várias classes que só são usadas em poucos testes. E o problema fica pior quando a interface da dependência possui muitos métodos mas só usamos um ou dois deles (é o caso do HttpServletRequest). É nessa hora que o JMock vem para facilitar a nossa vida.

O JMock é uma biblioteca que auxilia o Test Driven Development através dos mock objects. É uma biblioteca que vai criar implementações de mentira específicas para o seu teste, de uma maneira rápida e simples, sem ter que se preocupar com os métodos que não vamos usar no teste, sem ao menos ficar criando classes “a toa”. Com o JMock podemos definir o comportamento necessário do objeto de mentira, para criarmos a situação pedida pelo teste.

Para termos uma idéia melhor vamos ver um exemplo prático de como usar o JMock. Vamos supor que temos uma lógica de login no nosso sistema e queremos testá-la. As dependências dessa lógica são recebidas no construtor:

public class LoginLogic {
    private final HttpSession session;
    private final Authenticator auth;
    public LoginLogic (HttpSession session, Authenticator auth) {
        this
.session = session;
        this.auth = auth;
    }
    public void login(User user) {
        //…
    }
}

Vamos testar o método login, que tem duas situações possíveis: se o usuário é válido, entra na sessão, senão não entra. Para testar precisamos de um teste que vai passar um usuário válido, e garantir que o usuário entrou na sessão; e um teste que vai passar um usuário inválido e garantir que o usuário não entrou na sessão.

Para criar esses testes precisamos de uma instância da classe LoginLogic, logo precisamos nos preocupar com as dependências da classe: o HttpSession, que é uma interface do java servlet, e o Authenticator, que é uma interface do nosso projeto contendo um método chamado isValid(User) que recebe um usuário e vê se ele é válido.

Começando nosso teste:

LoginLogic logic = new LoginLogic(/*um httpSession*/, /*um authenticator*/);
logic.login(/*um user válido*/);
assertTrue(/*o usuário está na sessão*/);

Precisamos passar para nossa lógica um objeto que implementa HttpSession, e um que implementa Authenticator. Poderíamos criar implementações falsas dessas interfaces, mas não queremos poluir nosso código de testes com classes “inúteis”. Vamos, então, deixar o JMock fazer esse trabalho sujo para nós.

Para começar a criar essas implementações falsas, precisamos de uma fábrica de objetos falsos (mocks) do JMock:

Mockery mockery = new Mockery();

É comum criarmos essa fábrica no começo de cada teste, ou no setUp da sua classe de testes (se você usa o JUnit). Com a fábrica em mãos, podemos começar a mockar as nossas interfaces. É bem simples:

HttpSession session = mockery.mock(HttpSession.class);
Authenticator auth = mockery.mock(Authenticator.class);

E já podemos passar esses mocks para nosso LoginLogic. Nosso teste ficaria então:

final Mockery mockery = new Mockery();
final HttpSession session = mockery.mock(HttpSession.class);
final Authenticator auth = mockery.mock(Authenticator.class);

LoginLogic logic = new LoginLogic(session, auth);
logic.login(/*um user válido*/);
assertTrue(/*o usuário está na sessão*/);

Se fizermos simplesmente isso, o teste não vai fazer nada. Precisamos dizer para nossos mocks como vai ser o comportamento deles quando forem acessados. A maneira de fazer isso no JMock 2.4 é bem interessante, utiliza uma sintaxe um pouco incomum, mas quando você se acostuma com ela fica bastante legível:

mockery.checking(new Expectations() {{
    //comportamento dos mocks aqui
}});

Repare no truque: uma classe anônima, com um pré construtor (note os dois grupos de chaves). Esse bloco vai ser chamado pelo método checking, e vai adicionar o comportamento pedido aos mocks. Fazemos isso de uma maneira fluente bem interessante. Por exemplo, se quisermos garantir que o usuário foi colocado na sessão após o teste, fazemos:

one(session).setAttribute(“user”, with(any(User.class)));

O que essa linha quer dizer é o seguinte: eu quero que o método setAttribute seja chamado uma vez, com os parâmetros “user” e com qualquer objeto do tipo User.

Se esse método nunca for chamado com esses parâmetros ou for chamado mais de uma vez, o teste vai falhar. Na verdade, tudo que é possível acontecer com os mocks tem que estar descrito em um expectations (pode ter mais de um no teste). Se pro teste não é importante o que acontece com um dos objetos mockados, podemos ignorá-los, escrevendo:

ignoring(mockObject);

Fazendo isso, todo método chamado desse método vai retornar valores padrão (0, “”, null ou, se for possível um outro mockObject marcado como ignoring). Mas para que tudo isso aconteça, precisamos colocar no final do nosso teste a seguinte linha:

mockery.assertIsSatisfied();

Bom, ainda não terminamos o nosso teste, precisamos fazer com que o usuário seja válido, ou seja, precisamos garantir que o método isValid() do authenticator retorne true. Fazemos isso da seguinte maneira, dentro do expectations:

one(auth).isValid(with(any(User.class)));
will(returnValue(true));

Fica bastante legível: o método isValid do mock auth será chamado uma vez, com qualquer User como parâmetro, e vai retornar o valor true.

O nosso teste completo ficaria, então:

final Mockery mockery = new Mockery();
final HttpSession session = mockery.mock(HttpSession.class);
final Authenticator auth = mockery.mock(Authenticator.class)

mockery.checking(new Expectations() {{
    one(session).setAttribute(“user”, with(any(User.class)));
    one(auth).isValid(with(any(User.class)));
    will(returnValue(true));
}});

LoginLogic logic = new LoginLogic(session, auth);
logic.login(new User());

mockery.assertIsSatisfied();

Note que poderíamos garantir que o user passado para os métodos foi o mesmo que passamos para a lógica, apenas colocando ele como parâmetro para o método. E para fazer o teste do usuário inválido só precisamos mudar o conteúdo do expectations, substituindo por:

never(session).setAttribute(“user”, with(any(User.class)));
one(auth).isValid(with(any(User.class)));
will(returnValue(false));

Ou seja, o isValid vai retornar false, e o método setAttibute nunca vai ser chamado. Se isso acontecer, o teste passa.

O roteiro do expectations não precisa estar na ordem em que ele vai acontecer no seu método (por exemplo o isValid provavelmente vai ser chamado antes do setAttribute), mas tem um jeito de você garantir a ordem dos comandos.

Por padrão o JMock só é capaz de mockar interfaces, mas você pode configurá-lo para mockar classes.

Você pode, ainda, usar o JMock para fazer testes de integração. Como o roteiro do teste pode ficar bastante complexo, você pode utilizar uma máquina de estados para adicionar um pouco mais de poder ao roteiro (por exemplo garantir que uma chamada de método sempre ocorra depois de uma outra).

Uma chamada genérica num expectations, então, seria:

invocation-count (mock-object).method(argument-constraints);
inSequence(sequence-name);
when(state-machine.is(state-name));
will(action);
then(state-machine.is(new-state-name));

Se você quiser usar todo o poder do JMock, dê uma olhada na sua documentação. E, claro, não abuse de mocks colocando-os em todas as situações, senão seu teste unitário pode perder o valor de testar uma pequena unidade.

Novo treinamento: PM-51, Programação Extrema (XP) com Java

Por Sérgio Lopes em 21/10/07

Você já desenvolveu software usando o modelo waterfall? Brigou com o cliente para discutir se aquele requisito fazia ou não parte do contrato? Se perdeu com tantas obrigações do processo unificado? Cansou de escrever todo UML antes de escrever uma única linha de código? E aí alterar todo o UML pois ele não representava aquilo que o cliente queria? Só faz uma release estável de ano em ano?

O mercado está mudando. O gerenciamento de software está mudando. Precisamos de releases  rápidos, código testado, responder rapidamente aos novos requisitos do cliente, não se perder em milhares de páginas de contratos e requisitos. Agilidade.

Curso de XPA Caelum acaba de anunciar a criação de um novo treinamento focado em desenvolvimento ágil: PM-51, Programação Extrema (XP) com Java. A elaboração do treinamento contou com a consultoria de Danilo Sato, mestre em Ciência da Computação pela USP em metodologias ágeis.

As metodologias “tradicionais” de desenvolvimento de software têm causado cada vez mais complicações para as empresas. A abordagem das metodologias ágeis (como Scrum e XP) procura resolver esses problemas focando no resultado final do produto, na rápida resposta às mudanças e na satisfação do cliente. Grandes nomes do desenvolvimento de software e grandes empresas pelo mundo todo (como Google, Yahoo e Microsoft) têm apostado nas metodologias ágeis e têm tido ótimos resultados. A Caelum também abraçou a causa, e utiliza metodologias ágeis em seus projetos e clientes.

Iniciamos o enfoque em metodologias ágeis com o treinamento de Scrum no início desse semestre, com Alexandre Magno. Agora completamos a grade com o treinamento de Extreme Programming (XP), metodologia que é inclusive muito utilizada em conjunto com Scrum.

Neste treinamento de XP abordaremos as práticas da metodologia e as práticas de programação que trazem agilidade ao desenvolvimento de software. Desde tópicos técnicos como testes automatizados, design patterns, refatoração e o processo de build até a integração contínua, programação pareada e o uso de controle de versão. Além disso, falaremos dos valores ágeis, das reuniões diárias, jogo do planejamento, métricas de qualidade de código e tracking para controle geral do processo de desenvolvimento. Diferentemente do treinamento de Scrum, a parte prática deste treinamento possui bastante código e desenvolvimento.

Entre em contato conosco para saber mais ou fazer sua reserva.