sexta-feira, 18 de novembro de 2011

REST - Negociação de Conteúdo HTTP / Content Negotiation

Ultimamente tenho trabalhado em um projeto com arquitetura REST e um dos requisitos, é que consigamos integrar e atender de maneira flexível o maior número possível de clients, portanto fornecemos os recursos com diferentes tipos de formatos (media types).

Basicamente o client solicita o recurso e informa em qual formato deseja receber a representação, ou seja, ele negocia o conteúdo com o servidor. Para isso o client fará uma requisição GET e colocará no request header o atributo Accept com o valor desejado: application/xml, application/json e etc...

Nosso recurso irá fornecer a representação de um funcionário e além dos formatos como xml e json, neste post também faremos uma implementação que representa o funcionário através de uma imagem.

Caso o cliente consiga trabalhar com mais de um media type, ele pode informar a ordem de prioridade, colocando o media type preferido primeiro, precedido dos menos importantes, separados por vírgula

Ex: Accept:  application/xml, application/json

Cabe esclarecer que caso o client solicite o recurso em um media type que o servidor não fornece, deverá ser retornada uma resposta com o código 406 Not Acceptable.

Para a implementação proposta, utilizaremos JAX-RS através de sua implementação padrão Jersey.

@Path("/funcionarios")
public class FuncionarioResource {
 @GET
 @Path("{id}")
 @Produces({ "application/xml", "application/json" })
 public Funcionario getFuncionario(@PathParam("id") Integer id) {
  Funcionario funcionario = repository.get(id);
  return funcionario;
 }
}
Serei breve nas explicações das anotações básicas tendo em vista que existem excelentes materiais na web e focarei no necessário para a negociação de conteúdo.

A anotação @Path("/funcionarios") informa a url da nossa RootResource.

Em seguida anotamos nosso método getFuncionario com a anotação @GET que informa que este método aceita apenas requisições feitas pelo método GET do http, isso quer dizer que nosso método é idempotente, não importa quantas vezes a requisição seja feita, ela não alterará o estado do recurso.

Também anotamos o método com @Path("{id}"), informando que devemos passar o id do funcionário na URI.  Para que consigamos acessar o recurso devemos usar o caminho funcionarios/1 por exemplo.

Como parâmetro o método espera o id do funcionário. Anotamos esse parâmetro com @PathParam("id") para que seja injetado na nossa variável id o valor passado pela URI conforme indicado na anotação @Path("{id}").

Para o assunto tratado a mais importante é a anotação @Produces({"application/xml", "application/json"}), ela informa que este método retorna o nosso recurso tanto em XML quanto JSON dependendo apenas da solicitação do cliente. A implementação JERSEY se responsabiliza em parsear o  recurso para estes formatos então precisamos apenas retornar nosso recurso:  Funcionario funcionario = repository.get(id);

Agora vamos expor nosso recurso como uma imagem. Para isso, precisamos fazer nossa própria implementação, que é transformar nosso recurso em uma imagem. Neste caso vamos usar o atributo caminhoFoto para buscar a foto e retorná-la como um array de bytes.


 @GET
 @Path("{id}")
 @Produces({ "image/jpeg", "image/jpg" })
 public byte[] getPhoto(@PathParam("id") Integer id) {
  Funcionario funcionario = repository.get(1);
  
  try {
   InputStream is = this.getClass().getResourceAsStream(funcionario.getCaminhoFoto());

   BufferedImage img = ImageIO.read(is);

   ByteArrayOutputStream bao = new ByteArrayOutputStream();

   ImageIO.write(img, "jpg", bao);

   return bao.toByteArray();

  } catch (IOException e) {
   throw new RuntimeException(e);
  }
 }

Note que são usadas as mesmas anotações no método getFuncionario e no getFoto, inclusive o @Path é o mesmo. A única diferença é que o método getFoto retorna media types do tipo "image/jpeg" ou "image/jpg".

Como o valor da anotação @Path é o mesmo, oque determina qual método atenderá a requisição é justamente o tipo de media type aceito pelo client.

Caso ele envie uma requisição com Accept: application/xml ou application/json o método getFuncionario será invocado, ou então, caso a requisição possua Accept: image/jpeg ou image/jpg, o método getFoto será invocado.

No meu github tem a implementação completa do projeto que pode ser publicado em qualquer servlet container, desenvolvido com maven.

Espero que o post possa ser útil.

Abrasssss


quarta-feira, 28 de setembro de 2011

Como testar os Controllers do Vraptor com Mockito


Um dos frameworks que mais gosto de trabalhar, devido a sua simplicidade e produtividade é o Vraptor. Uma das suas vantagens é que ele abstrai a complexidade da API de Servlets, NÃO te obriga a trabalhar com getters/setters evitando que seu modelo se torne anêmico. Não é intrusivo, você foca na sua lógica de negócios e não em aprender sobre a arquitetura e classes do framework, ou seja, a curva de aprendizado é minima.

Trabalha de forma muito fácil com injeção de dependência. Não é necessário configurar quase nada. Tem suporte a REST e inumeros outros recursos. Mas oque chama realmente a atenção é que você consegue começar a trabalhar com ele em questão de minutos. Poderiamos ficar o dia todo discutindo sobre as vantagens de utilizar este framework. Portanto vamos ao objetivo do post: Aprender a testar unitáriamente as classes de controller do Vraptor.

Para isso utilizaremos o JUnit e o Mockito.

Desejamos testar o método salva(Aluno aluno) do controller abaixo. Este método recebe um aluno, verifica se é valido, salva e redireciona para a listagem, caso o aluno seja inconsistente ele redireciona para a página de cadastro/edição para exibir os erros.

@Resource
public class AlunoController {
private AlunoRepository alunoRepository;

 private Result result;

 private Validator validator;

 public AlunoController(AlunoRepository alunoRepository, Result result, Validator validator) {
  this.alunoRepository = alunoRepository;
  this.result = result;
  this.validator = validator;
 }

 public void add(Aluno aluno) {
  this.validator.validate(aluno);
  this.validator.onErrorForwardTo(this).cadastra();

  this.alunoRepository.add(aluno);
  this.result.redirectTo(this).lista();
 }
}
Temos como dependência a interface AlunoRepository e como o objetivo é testar o controller, devemos mockar esta dependência.

Criamos uma classe de teste comum do JUnit.

public class AlunoControllerTest {
 private AlunoController controller;

 private AlunoRepository alunoRepository;

 private MockResult result;

 private JSR303MockValidator validator;

 @Before
 public void setUp() throws Exception {
  this.alunoRepository = mock(AlunoRepository.class);

  result = spy(new MockResult());
  validator = spy(new JSR303MockValidator());
  controller = new AlunoController(alunoRepository, turmaRepository, result, validator);
 }
}

Note que os objetos result e validator usam mocks apropriados, portanto não precisamos nos preocupar com eles. É melhor utilizar estes mocks fornecidos, pois estes objetos usam Fluent Interface oque torna sua testabilidade muito difícil.

Repare também que usamos o método spy(T object) para obter as instancias do result e validator, isso é util pois conseguimos chamar os métodos reais dos objetos, então podemos verificar se realmente houveram interações com os objetos.

O teste:

@Test
public void add() {
 Aluno aluno = getAlunoConsistente();
 controller.add(aluno);
 verify(alunoRepository).add(aluno);
}
Através deste teste tentamos salvar um aluno, que é um objeto consistente (válido). Então o aluno deve ser adicionado ao repositório, portanto nos asseguramos que o método add(Aluno aluno) do repositório foi chamado, isso é feito através da linha: verify(alunoRepository).add(aluno);

Para testarmos nossas validações, desenvolvemos um teste que espera que uma ValidationException seja lançada e nos asseguramos que não houveram interações com o repositório:
@Test(expected = ValidationException.class)
public void gravaAlunoInconsistenteTest() {
 controller.grava(getAlunoInconsistente());
 verifyNoMoreInteractions(alunoRepository);
}
Esse post teve origem em uma thread do GUJ que abri e recebi orientações do Lucas Cavalcanti: http://www.guj.com.br/java/243436-vraptor-e-mockito---testes-de-controllers


UPDATE:
Muitas vezes desejamos verificar se o nosso método fez um redirecionamento para uma view ou lógica específica. Então como o Lucas citou, podemos fazer:


AlunoController spy = Mockito.spy(alunoController); 
when(result.redirectTo(alunoController)).thenReturn(spy); 
this.alunoController.grava(aluno); 
verify(spy).lista();

Assim estamos nos assegurando que o método lista() foi chamado, ou seja, foi feito o redirecionamento.

Abraços

quarta-feira, 24 de agosto de 2011

Automatizando testes de aceitação com Selenium

Testar a interface (camada de visualização) de uma aplicação web é uma tarefa trabalhosa, repetitiva e que requer atenção. Na maioria dos projetos a única técnica utilizada é o teste manual: o desenvolvedor abre o navegador, preenche todos os campos manualmente, envia o formulário e então certifica-se que o resultado renderizado é o resultado esperado.

Estes testes devem ser utilizados em todas as interfaces do sistema, porém muitas equipes os ignoram. Em interfaces com poucos campos a serem preenchidos tais testes passam a impressão de que são desnecessários, pois, pode-se testá-las mais rapidamente de forma manual do que escrevendo o teste automatizado.

Entretanto quando o sistema começa crescer e a complexidade das telas aumenta, os testes automatizados pagam seu custo inicial, além de garantir a qualidade do sistema.

O Selenium WebDriver é uma ótima ferramenta para execução de testes de aceitação e será abordado para o desenvolvimento destes testes, neste em outros posts.

Primeiro passo:

Adicione o Selenium em seu classpath. Com o maven basta adicioná-lo como dependência:

<dependency>
	<groupId>org.seleniumhq.selenium</groupId>
	<artifactId>selenium-firefox-driver</artifactId>
	<version>2.4.0</version>
</dependency>

O próximo passo é instanciar um WebDriver:

WebDriver driver = new FirefoxDriver();

WebDriver é uma interface que simula o browser em seu teste. Neste caso instanciamos o FirefoxDriver que permite que o teste seja visualizado.

Existem outras implementações da interface WebDriver, como o HtmlUnit Driver, que permite que o teste seja executado mais rapidamente, porém não é possível visualizar o teste em tela.

Aqui podem ser encontradas as vantagens e desvantagens entre as implementações.

Depois é necessário acessar a url:
driver.get("http://www.meudominio.com.br/aluno/cadastro");

O método get requisita a página e espera que seja totalmente carregada para o
início dos testes.

Com a interface WebElement é possível manipular os valores dos campos, para isso, é necessária uma referência do campo.

Supondo que os campos do cadastro sejam:
<input type="text" name="aluno.nome" />
<input type="text" name="aluno.email" />

A referência pode ser obtida através de uma pesquisa do nome do campo:
WebElement nome = driver.findElement(By.name("aluno.nome"));
WebElement email = driver.findElement(By.name("aluno.email"));

Agora com referências aos elementos, pode-se enviar algum valor para eles:
nome.sendKeys("Renan");
email.sendKeys("renan@renan.com");

Então submetemos o formulário:
email.submit();

Note que chamamos o método submit da instância email. Apesar de email não ser um formulário, o Selenium submete o formulário a que o elemento pertence.

Agora que submetemos o formulário podemos averiguar se o sistema se comportou da forma que esperávamos.

Supomos que o comportamento esperado é exibir a mensagem: "Aluno Renan Cadastrado com sucesso.", dentro de um elemento com o atributo id preenchido como "result".

<div id="result">Aluno Renan Cadastrado com sucesso.</div>

Podemos fazer as asserções para verificar o resultado. Segue um exemplo utilizando JUnit:
Assert.assertEquals("Aluno Renan Cadastrado com sucesso.", result.getText());

Grande abraço.