Caelum | Ensino e Inovação - Cursos de Java, Scrum, Ruby on Rails


Flex: Teste de unidade em Actionscript

Por David Paniz em 05/04/10

A comunidade Flex brasileira está cada vez maior, hoje temos 3 grandes grupos: Flex-Brasil, Flexdev e o fórum RIA – Flex, JavaFX e outros no GUJ. Embora essas comunidades estejam cada vez maiores e mais ativas, ainda é raro encontrar discussões sobre testes. Justamente por ser um assunto não muito discutido, e dada a extrema importância de testes em todos os nossos tiers, vale fazer uma introdução ao FlexUnit.

O Actionscript tem uma grande influência da linguagem java, e no seu framework de testes não é diferente. O FlexUnit tem muitas características vindas do JUnit na sua concepção.

Assim como no JUnit, no FlexUnit (versão 4) declaramos nossos testes através de metadados (equivalente as anotações do java). Temos o Before e o After, que serão executados antes e depois de todos os métodos de teste. Os metadados BeforeClass e AfterClass que serão executados uma única vez para todos os testes da classe, e finalmente o metadado Test que define que aquele método é um teste.

Lembrando que a utilização de metadados em Actionscript é um pouco diferente do java, sendo [Test] em vez de @Test, como vemos no trecho de código abaixo:

[Test]
public function doisMaisDoisDeveSerIgualAQuatro():void
{
  Assert.assertEquals(4, 2 + 2);
}

O FlexUnit, assim como o Cairgorm, o LiveCyle e algumas outras ferramentas para Flex, é um projeto opensource mantido pela Adobe e pode ser encontrado no Adobe Labs. Se você já está usando o Flash Builder 4, o FlexUnit já vem instalado e configurado.

Para exemplificar melhor imagine que puxamos de um serviço uma lista de Notas de uma determinada turma e queremos gerar um gráfico mostrando qual é a menor nota, a maior nota e a média de notas da turma e para agrupar esses dados criamos a classe ResultadoDeTurma:

	public class Nota
	{
		public var nota:Number;
		public var nome:String;
	}
	public class ResultadoDeTurma
	{
		public var menorNota:Number;
		public var maiorNota:Number;
		public var media:Number;
	}

A classe responsável pelo agrupamento de dados é a AgrupadorDeTurma onde temos o seguinte código:

	public class AgrupadorDeTurma
	{
		public function agrupa(notas:ArrayCollection):ResultadoDeTurma
		{
			var menor:Number = Number.MAX_VALUE;
			var maior:Number = 0;
			var total:Number = 0;
			for each(var nota:Nota in notas)
			{
				if (nota.nota < menor){
					menor = nota.nota;
				} else {
					if(nota.nota > maior){
						maior = nota.nota;
					}
				}
				total += nota.nota;
			}

			return new ResultadoDeTurma(menor, maior, (total/notas.length));
		}
	}

Agora vamos criar o nosso teste.

Criando um teste

Nesse wizard criamos a classe AgrupadorDeTurmaTeste. O Flash Builder criará 2 arquivos, a classe de teste e um outro na raiz do projeto chamado FlexUnitCompilerApplication.mxml.
Devemos executar esse arquivo para que ele crie um executor de testes como vemos na imagem abaixo.

Ao término desse passo teremos o arquivo FlexUnitApplication.mxml que é usado para executar nossos testes.
Agora que toda nossa “infra” está pronta, vamos escrever nosso primeiro método de teste. Para isso basta adicionar o método abaixo no arquivo AgrupadorDeTurmaTeste.as.

		[Test]
		public function testandoComArrayComTresElementos():void
		{
			var notas:ArrayCollection = new ArrayCollection([
				new Nota(9, "David Paniz"),
				new Nota(10, "Paulo Silveira"),
				new Nota(8, "Guilherme Silveira")
			]);

			var resultado:ResultadoDeTurma =  new AgrupadorDeTurma().agrupa(notas);
			assertEquals(10, resultado.maiorNota);
			assertEquals(8, resultado.menorNota);
			assertEquals(9, resultado.media);
		}

Para executar os testes basta rodar o arquivo FlexUnitApplication.


E no Eclipse temos o resultado:

Quando o teste passa, o código está correto. Mas será que esse código realmente está correto? Será que só este teste basta para eu ter toda essa garantia? Vejamos, vou escrever outro teste para o mesmo método:

		[Test]
		public function testandoComArrayComUmElementos():void
		{
			var notas:ArrayCollection = new ArrayCollection([
				new Nota(8, "Guilherme Silveira")
			]);

			var resultado:ResultadoDeTurma =  new AgrupadorDeTurma().agrupa(notas);
			assertEquals(8, resultado.maiorNota);
			assertEquals(8, resultado.menorNota);
			assertEquals(8, resultado.media);
		}

Ao executarmos nossos testes novamente temos o seguinte resultado:


Acabamos de encontrar uma falha no nosso teste. Dentro da iteração de notas, não havíamos previsto que e mesma nota poderia ser a maior e a menor ao mesmo tempo. Para solucionar precisamos remover o else do if:

		public function agrupa(notas:ArrayCollection):ResultadoDeTurma
		{
			var menor:Number = Number.MAX_VALUE;
			var maior:Number = 0;
			var total:Number = 0;
			for each(var nota:Nota in notas)
			{
				if (nota.nota < menor){
					menor = nota.nota;
				}
				if (nota.nota > maior){
					maior = nota.nota;
				}
				total += nota.nota;
			}

			return new ResultadoDeTurma(menor, maior, (total/notas.length));
		}

E agora ao executarmos os testes temos o seguinte resultado:

Na verdade ainda temos outros possíveis problemas nesse código que poderiam ser testados, como por exemplo receber um array vazio como parâmetro. Tanto esse quanto o erro anterior teriam sido mais facilmente detectados caso usássemos TDD, criando nossos testes antes mesmo do código.

Outras ferramentas para conhecer sobre testes em Flex são o Flexcover, mock4as,
Flex Monkey e o Flash Selenium.

  • Share/Bookmark

Integração contínua: deploys e aprovações sem dor de cabeça para o cliente

Por Guilherme Silveira em 18/01/10

Em 2008 comentamos sobre a importância de integração contínua no processo de receber feedback rápido sobre suas mudanças em um sistema e depois sobre os problemas que surgem quando um sistema possui baterias de teste muito grandes e complexas.

Um das grandes vantagens da agilidade consiste em poder efetuar mudanças sem medo e receber as respostas rapidamente em relação aos bugs que introduzimos no sistema, evitando mais erros e, para atingir esse objetivo, um servidor de integração contínua deve integrar o nosso código com aquele existente e rodar a bateria de testes automatizados a cada commit, criando um relatório do que foi feito que pode ter quebrado a aplicação.

Existem diversos níveis de adoção de um servidor de integração contínua em um projeto, indo do mais básico que envolve a simples compilação de um projeto (quando a linguagem é compilada) e a execução de testes unitários, até cenários mais complexos. Uma das principais dores de cabeça que um cliente enfrenta, mesmo em empresas que adotam metodologias ágeis como XP e Scrum, está ligada ao produto ser colocado em produção e não funcionar: a famosa frase “mas no meu computador funcionou”.

Um cenário clássico envolve o desenvolvimento de testes unitários, que são rodados na máquina do desenvolvedor e no servidor de integração contínua. O desenvolvedor chama então o cliente para testar em sua máquina, que aprova a funcionalidade e depois de duas semanas é feito o deploy da aplicação quando, para a surpresa do cliente, ela não funciona como esperado.

Acontece que a máquina de desenvolvimento em geral não possui a mesma configuração – software de banco de dados, servidor, proxies, firewall ou até mesmo os dados contidos no banco – que o sistema em produção, e mudanças do gênero podem significar resultados completamente diferentes para o cliente que, no fim da iteração, não obtém o valor que esperava.

A solução está em criar um sistema de homologação e diversas empresas adotam essa prática. A dificuldade encontrada nessa etapa é a da não automatização do processo de réplica da estrutura e dados de produção para homologação. Dados antigos ou inadequados no ambiente de aprovação facilitam um aceite errôneo por parte do cliente no momento do teste. Na Caelum e em nossos clientes que possuem sistemas que efetuam deploy diversas vezes em um ano, para garantir a competibilidade da empresa, é comum automatizar o processo de deploy ao ponto de maximizar as semelhanças entre produção e homologação, e minimizar as idas e vindas de uma funcionalidade.

Passo a passo junto com o cliente, cada fase é incluída no pipeline de integração contínua. Primeiro o projeto é compilado, depois os testes unitários são executados. Essas duas fases do pipeline são padrão para linguagens compiladas como Java. Em algumas empresas devido ao código legado ter um acoplamento muito alto, essa primeira fase já envolve mudanças na maneira de trabalhar e na qualidade do código gerado pelos desenvolvedores.

As fases seguintes variam bastante de acordo com projeto, mas para melhorar o deploy que minimize o número de tentativas de aprovação é importante adicionar três fases: testes end-to-end automatizados, deploy para homologação e para produção em um clique.

Na primeira fase, os testes executam as funcionalidades como o cliente faria e cobrem a maior quantidade de funções plausíveis de automatização. Na segunda fase, os desenvolvedores podem optar por deployar para um sistema de homologação, onde o cliente aprovará com os dados copiados de produção (alterações ligadas a sigilo de dados devem ser aplicadas).

Por fim, quando o sistema está aprovado em homologação, com um clique o desenvolvedor efetua o deploy para produção. Nesse instante, os sistemas são restartados, de preferência com o uso de load balancers que permitem o restart sem a queda da aplicação, e o cliente está pronto para usar as funcionalidades novas.

Toda essa automatização tem um custo inicial de desenvolvimento que é compensado com a minimização de erros humanos no processo de deploy e na lentidão do mesmo. Muitas vezes o processo de deploy para qualquer um dos dois ambientes não é feito tão frequentemente devido ao processo ser demasiadamente complexo para ser executado manualmente. A chance de funcionar em produção é maximizada uma vez que a mesma foi testada em homologação com uma cópia mais fidedigna do sistema de produção possível: menos reclamações e tempo perdido pelo cliente.

Note que nenhuma ferramenta específica é necessária para adotar essa prática, basta mudar o método de trabalho.

  • Share/Bookmark

Hipermídia e contratos dinâmicos: menor acoplamento

Por Guilherme Silveira em 17/12/09

Nos últimos anos você vem comprando livros em um website: você acessa o site inicial www.amazon.com, procura pelo livro que deseja comprar, adiciona-o ao seu carrinho, escolhe o método de pagamento e finaliza a compra.

Na época do Natal, o site muda: existe agora uma promoção de fim de ano e você se depara com um conteúdo inesperado: existem funcionalidades e informações novas (como um programa de desconto através de cupons). Como reage um humano ao encontrar a mudança com novas possibilidades de iteração em um site?

  • Gritar: “contrato violado! não comprarei mais nada!
  • Ignorar as novas informações e executar o processo
  • Usar o intelecto humano e se aproveitar das novas informações

Como humanos sabemos o quão natural é agir de maneira a ignorar as informações – caso elas não contribuam com meu objetivo – ou tirar proveito delas.

A opção 1 só se concretiza caso existisse um comprometimento total a maneira que o site disponibilizava suas informações e ao processo: se meu acoplamento for alto e o que eu espero seja fixo, imutável. Infelizmente robôs não são ainda capazes de raciocionar como nós e executar a última opção.

O conteúdo hipermídia permite evoluir o servidor com funcionalidades e dados sem quebrar os clientes consumidores por padrão. Ninguém deixaria de comprar pois fornecemos funcionalidades e dados novos em relação aos recursos disponibilizados.

Isso permitiu a evolução de sites por diversos anos sem que usuários enviassem emails para o responsável reclamando da nova função que foi adicionada, dizendo que não utilizarão o sistema pois existe conteúdo extra.

Hipermídia permite um baixo acoplamento entre o cliente e o servidor e pode ser levado para o mundo da automatização: a web dos sistemas. Na web humana, validamos nossos contratos com o usuário final através do uso de testes end-to-end, verificando a existência de funcionalidades como o usuário o faria.

Diversas opções de ferramentas como selenium-rc e webdriver fornecem funcionalidades para garantir que o comportamento esperado não será quebrado com novos releases.

Eles não validam tudo retornado pela requisição, dando espaço para a ::forward-compatibility::, a capacidade de evoluir nosso sistema no servidor sem quebrar o comportamento esperado. Por exemplo, adicionar novas funcionalidades ou campos não relativos ao teste não deve quebrar o mesmo.

Na web para sistemas integrados, a representação mais comum é o xml, que não suporta conteúdo hipermídia, uma vez que uris devem ser tratadas como texto (de acordo com a especificação) então acabamos criando nossos próprios media-types, como vnd/caelum+xml, onde há a definição de como elas devem ser tratadas: o nosso próprio micro formato.

Existem diversas alternativas para criar esquemas forward e backward compatíveis mas infelizmente esse não é o comportamento padrão de arquivos como o formato ::xsd:: e arquitetos não se lembram disso ao definir seus esquemas, o suporte é opcional.

Dentre essas opções, a mais fácil e possivelmente perigosa envolve permitir qualquer tipo de conteúdo em qualquer campo, enquanto outra solução envolve o uso de tipos polimórficos: um perigoso início de schema-hell controlando diversas versões para uma mesma funcionalidade.

Micro formatos como os que podemos criar permitem a definição de uma estrutura fixa e uma dinâmica: um contrato parcialmente fixo, com garantias para validação e compatibilidade, além de parcialmente dinâmico, com liberdade para evolução, diminuindo o acoplamento que seu sistema possuia ao utilizar um esquema totalmente fixo.

Mas a responsabilidade de não quebrar o contrato original fixo ainda é do servidor.

Na web humana, xhtml permite validar a estrutura (o contrato) enquanto é responsabilidade sua (seus testes) não remover o campo de busca de livro, caso contrário o processo não se completa.

Enquanto esquemas permitem a validação de dados, os testes permitem a validação dos processos. Ambos devem ser escritos de maneira a permitir a evolução desacoplada do servidor e do cliente. E quais seriam então as partes dinâmicas do meu contrato?

Os possíveis estados de seu recurso podem variar com o tempo: uma aplicação para empréstimo pode ser só aprovada ou recusada, mas com o passar do tempo a empresa pode decidir a existência de um novo estado: “prolongado”.

As relações entre seu recurso e outros recursos também variam: um cliente pode ter uma lista de serviços contratados atualmente, acessando a sua representação via links. É natural imaginar que surjam novos serviços e que o cliente mude suas contratações.

As transições e operações disponíveis para seus recursos também são dinâmicas: suportando um método HTTP novo ou um novo link não quebra a existência de clientes que consomem as transições e operações existentes até então.

Todo esse dinamismo é guiado através de hiperlinks e conteúdo hipermídia. Como os clientes terão certeza que não quebramos o contrato dinâmico?

Da mesma maneira que implementamos testes para garantir o comportamento esperado, precisamos deles para garantir que o processo não é alterado no servidor.

Os testes end-to-end são a única garantia de que não quebramos os processos junto ao cliente, seja ele humano ou outro serviço.

Esquemas xml podem ser usados de maneira a garantir flexibilidade e compatibilidade, mas não é o comportamento padrão de tal ferramenta: depende muito mais do usuário conhecer e fazer o uso adequado dela.

ATOM é um exemplo que suporta por padrão contratos dinâmicos: ao seguir o Must Ignore, ganhamos forward e backward compatibility. Contratos dinâmicos fornecem dicas para os frameworks, permitindo ao servidor guiar o cliente naquilo que pode executar ou acessar.

A consequência principal de contratos dinâmicos é o baixo acoplamento.

O Restfulie foca no poder do hipermídia como facilitador na evolução a médio e longo prazo: não são URIs elegantes ou a adoção do protocolo HTTP sozinhos que criam sistemas de baixo acoplamento.

  • Share/Bookmark

Screencast Ruby on Rails: Introdução a RSpec e Cucumber

Por Anderson Leite em 15/10/09

screencast Ruby on Rails: Introdução a RSpec e Cucumber

TDD e BDD são assuntos amplamente difundidos entre a comunidade ágil, especialmente entre quem usa Ruby on Rails. Aproveitando o fim do Rails Summit, gravamos este screencast sobre o assunto que é amplamente discutido no nosso novo curso RR-75 sobre Rails avançado, junto com buscas textuais, integração com Web Services, escalabilidade, etc.

Esse vídeo introdutório sobre RSpec e Cucumber é voltado para quem já conhece Ruby on Rails e quer evoluir seus conhecimentos em direção a estas boas práticas do mercado.

Outros eventos sobre Ruby on Rails vem por aí: em Novembro a Caelum realizará o CaelumDay no Rio de Janeiro com palestra sobre o assunto e também está apoiando o Ceará on Rails, dia 7 de novembro.

O screencast tem 12 minutos de duração e você pode assiti-lo aqui ou no vimeo. Recomenda-se colocar no fullscreen, pois há código. Também é possível fazer o download do vídeo (32 mb) para uma visualização em alta resolução.

  • Share/Bookmark

VRaptor 3 disponível para download

Por Guilherme Silveira em 21/08/09

A versão beta do VRaptor 3 já se encontra disponível para download, juntamente com sua documentação:

vraptor

O VRaptor é um framework web, que faz o papel de controlador MVC, e nasceu dentro da universidade de São Paulo há 5 anos e hoje em dia é mantido pela Caelum, com comitters de diversas empresas. A melhor maneira de você conhecer os recursos e objetivos do VRaptor 3 é através da palestra que ocorreu no Falando em Java deste ano, apresentada por mim e pelo Filipe Sabella:

O VRaptor 3 traz um modelo simples de injeção de dependências, facilidade para se trabalhar com AJAX e Rest, conversores robustos e uma forma de trabalhar com componentes que facilita muito os seus testes unitários.

Para tirar dúvidas sobre o VRaptor agora você pode usar o fórum do GUJ sobre frameworks brasileiros. Contamos com o seu feedback e dos antigos usuários!

  • Share/Bookmark



Caelum | Ensino e Inovação
São Paulo: Rua Vergueiro, 3185, cj. 87, próximo ao Metrô Vila Mariana   |   Tel. (11) 5571-2751
Rio de Janeiro: Rua Senador Dantas, 80, cj. 307/308 - Centro   |   Tel. (21) 2220-4156 ou 2297-0033
Brasília: SCS Qd. 8 Bl. B-50, Sala 521 - Ed. Venâncio 2000   |   Tel. (61) 3039-4222