Mudanças entre as edições de "Framework de testes automatizados"

De PJe
Ir para: navegação, pesquisa
(Classe CenarioSelenium)
(Classe CenarioSelenium)
Linha 218: Linha 218:
 
* '''preExecucaoTeste()''': método abstrato abre oportunidade para as subclasses implementarem ações a serem executadas antes de os testes serem executados.
 
* '''preExecucaoTeste()''': método abstrato abre oportunidade para as subclasses implementarem ações a serem executadas antes de os testes serem executados.
  
* '''executarCenarioDecorator()''': método abstrato que abre oportunidade para as subclasses implementarem comportamento específico para os cenários decorados. O recurso de cenários decorados são apresentados na seção específica de apresentação da estrutura do arquivo XML de dados de cenários.
+
* '''executarCenarioDecorator()''': método abstrato que abre oportunidade para as subclasses implementarem comportamento específico para os cenários decorados.
  
 
* '''executarTeste()''': método abstrato que permite às subclasses implementarem a lógica de execução do cenário de teste. É o método mais importante desta camada do '''framework''' e obrigatoriamente deve ser implementado. É este método que contém os comandos do Selenium ou do FluentLenium para preencher os formulários HTML das páginas e executar as ações dos cenários de teste.
 
* '''executarTeste()''': método abstrato que permite às subclasses implementarem a lógica de execução do cenário de teste. É o método mais importante desta camada do '''framework''' e obrigatoriamente deve ser implementado. É este método que contém os comandos do Selenium ou do FluentLenium para preencher os formulários HTML das páginas e executar as ações dos cenários de teste.

Edição das 12h30min de 30 de abril de 2015

Conteúdo

INTRODUÇÃO

O teste funcional automatizado consiste basicamente em executar um mesmo algoritmo com diferentes massas de dados. Para cenários diferentes temos dados diferentes com resultados esperados diferentes, e inserir estes dados e resultados diretamente no código implica em replicar (tantas vezes quantos cenários diferentes existirem) código para poder atender a diferentes situações.

O objetivo de se desenvolver um framework para automação dos testes é permitir que seja criada uma camada de dados independente da camada de algoritmos que executam a automação. Esta é a tradicional divisão de software em duas camadas. Esta divisão (algoritmo versus dados) é válida tanto para testes automatizados funcionais como para testes unitários.

Este arquivo descreve a documentação técnica para manutenção do framework de testes automatizados a ser utilizado para testes unitários e testes funcionais. O documento está organizado em seções que descrevem separadamente as características de cada parte da arquitetura do framework.

VISÃO ARQUITETURAL POR SERVIÇO PRESTADO

A arquitetura do framework de testes desenvolvido pode ser vista sob o ponto de vista da funcionalidade fornecida para os desenvolvedores. A Figura abaixo ilustra a divisão por funcionalidade da arquitetura proposta.

Arquitetura funcional.png

Núcleo genérico

O núcleo genérico do framework tem a função de carregar os dados dos cenários (provenientes de arquivos XML), carregar os cenários e executá-los, independentemente se esta execução se trata de um teste funcional com Selenium ou de um teste unitário. Trata-se de um conjunto de classes, em sua maioria abstratas, que definem um conjunto de comportamentos esperados da funcionalidade de testes automatizados.

Núcleo Selenium

O núcleo Selenium é uma camada sobre o núcleo genérico, que usa os serviços deste (por herança e composição) para a execução de testes funcionais em páginas web. É um conjunto de classes em sua maioria também abstratas que, além de estender as classes do núcleo genérico, camada faz uso do Selenium WebDriver para acessar o navegador, preencher os campos de formulário e conferir os valores dos elementos HTML.

É importante observar que esta camada é independente do sistema web que será testado. Ou seja, qualquer sistema web baseado com páginas HTML pode ter seus testes funcionais automatizados com base no framework desenvolvido.

Testes do PJe

Nesta camada estão as classes que implementam de fato a automação dos casos de teste do Pje. Elas são desenvolvidas segundo o padrão Page Object e, na hierarquia do framework, estendem funcionalidades de FluentPage.

São um conjunto de classes concretas responsáveis por armazenar os dados e definir o algoritmo concreto para execução. Cada caso de teste requer uma classe específica para armazenamento dos dados e outra para a execução do teste. A classe de armazenamento de dados é responsável por realizar a leitura do arquvo XML e armazenamento dos dados para execução.

VISÃO ARQUITETURAL POR CAMADAS

Sob outro ponto de vista o framework pode ser analisado de acordo com as camadas tradicionais da arquitetura em camadas, com divisão de dados e lógica de negócio. A Figura a seguir exibe a arquitetura dividida em camadas.

Erro ao criar miniatura: Arquivo aparentemente inexistente: /var/www/html/wikipje/images/5/57/Visão_em_camadas.png

Arquivo xml cenários

Este arquivo é responsável por armazenar os dados para execução dos cenários. Para cada caso de teste é necessário criar um arquivo xml com os dados para sua execução. Em um arquivo podem ser definidos vários cenários para execução, com valores diferentes entre eles.

Arquivo xml indexador

Este arquivo funciona como um conjunto de links para os cenários que serão executados, cenários estes cujos dados de execução são definidos no arquivo xml descrito na seção anterior.

De acordo com a relação entre o arquivo indexador e o arquivo de cenários ilustrada na Figura acima, um arquivo indexador fornece links para vários arquivos de cenários. Tanto arquivos xml indexadores quando arquivos de cenário devem ser criados dentro do diretório xml/ da aplicação.

Em geral deve haver poucos arquivos de indexação de cenários, pois cada um deles referencia inúmeros arquivos de cenários.

Classes de indexação de cenários

Classes de indexação são as classes chamadas pelo Junit como ponto de partida para a automação. No framework existe apenas uma classe, abstrata, chamada TesteBase, e que utiliza apenas a anotação @Test do Junit.

Para cada arquivo xml indexador descrito na seção anterior deve existir uma classe de indexação criada pelo desenvolvedor. Esta classe deve estender TesteBase, somente; nenhuma outra implementação é necessária.

O nome da classe também deve guardar relação com o nome do arquivo xml indexador. Por exemplo, se o nome do arquivo indexador for Cenarios1G.xml, o nome da classe deverá ser Cenarios1GTest. Ou seja, o nome da classe deve possuir o sufixo Test. No padrão de desenvolvimento do framework esta classe deve ser criada abaixo da estrutura src/test/java, conforme ilustrado pela Figura 3.

Relação entre os nomes das classes e arquivos XML

  • Classe indexadora de cenários: o nome da classe indexadora é livre, contudo, deve terminar com o sufixo Test. Por exemplo, para criar uma classe indexadora de cenários de teste para processos de primeiro grau o nome da classe poderia ser Cenarios1GTest.
  • Arquivo XML indexador: o nome do arquivo XML indexador deve ser idêntico ao nome da classe indexadora de cenários, porém, sem o sufixo Test. No caso do exemplo acima, o nome do arquivo XML indexador de cenários de teste para processos de primeiro grau seria Cenarios1G.xml.
  • Classes de execução de cenários: o nome da classe de execução é livre. Por exemplo, para um caso de teste de cadastro de processo o nome da classe poderia ser CadastroProcesso.
  • Classes de dados de cenários: o nome da classe de dados de cenários deve ser idêntico ao nome da classe de execução de cenários, porém, acrescido do prefixo Dados. No exemplo acima, o nome da classe de dados de cenários para um caso de teste de cadastro de processo seria DadosCadastroProcesso.
  • Arquivo XML de cenários: o nome do arquivo xml de cenários deve ser idêntico ao nome da classe de execução de cenários. No exemplo acima, o nome do arquivo XML de cenários para um caso de teste de cadastro de processo seria CadastroProcesso.xml.

Relacionamento entre as classes das camadas

O framework desenvolvido é composto por um conjunto de classes abstratas com métodos abstratos cuja implementação deve ser realizadas pelas classes concretas de execução e de dados. As classes abstratas definem também métodos template concretos que, de acordo com a definição do padrão Template Method, têm em sua implementação a chamada de métodos hook abstratos que devem ser implementados pelas subclasses.

A figura abaixo apresenta um diagrama de sequência que ilustra a dinâmica de comunicação entre as classes do framework. Trata-se apenas de uma visão geral, e não da real implementação desta lógica.

Erro ao criar miniatura: Arquivo aparentemente inexistente: /var/www/html/wikipje/images/1/14/Sequencia_carga_cenários_-_resumido.png

A sequência descrita no diagrama é executada para cada uma das classes que herdam da superclasse TesteBase. A seguir é apresentada uma breve descrição dos passos ilustrados no diagrama:

* chamada 1: para cada subclasse de TesteBase o JUnit instancia um objeto daquela classe. Esta classe possui uma lista de cenários a serem executados que é descrita no arquivo XML indexador que tem o mesmo nome da classe, sem o sufixo Test.

* chamada 1.1: para cada objeto criado são instanciados tantos objetos quantos descritos no arquivo indexador. Estes objetos são instâncias de classes que implementam a interface ICenario. São objetos que irão executar a lógica do teste.

* cahamada 1.1.1: cada objeto criado na chamada 1.1 cria seu objeto de dados. Este objeto é uma instância de uma subclasse de DadosBase. A subclasse de dados tem o mesmo nome da subclasse de execução acrescido do prefixo Dados.

* chamada 1.1.2: cada objeto criado na chamada 1.1 invoca o método carregarDados() de seu objeto de dados. Este método é abstrato e deve ser implementado pelas subclasses, com objetivo de carregar os dados do arquivo XML do cenário.

* chamada 2: para cada objeto criado na chamada 1 o JUnit invoca o método executar(). Este método é o único da superclasse TesteBase que possui a anotação @Test, necessária para o JUnit identificar os métodos que devem ser invocados.

* chamada 2.1: para cada objeto de ICenario é invocado o método executar(), que efetivamente executa o cenário de teste com os dados carregados na chamada 1.1.2.


ESTRUTURA DO NÚCLEO GENÉRICO

O núcleo genérico é composto basicamente por classes responsáveis por carregar dados de arquivos XML e executar cenários de teste. Apesar de alguns dados serem carregados automaticamente pelo framework a partir dos arquivos XML, espera-se que dados estrutados para execução dos cenários sejam carregados manualmente pelos desenvolvedores. Além disso, o framework também espera que os desenvolvedores implementem o método executar() das classes de execução. As classes pertencentes ao núcleo genérico são apresentadas nas seções seguintes. O diagrama de classes completo do núcleo genérico está disponível no ambiente de desenvolvimento do projeto, em um arquivo do Astah.

É importante observar que o framework é composto basicamente por abstrações de comportamentos esperados de classes que executam e armazenam dados para execução de cenários. É importante manter esta arquitetura de modo a evitar o acoplamento entre as classes do projeto, facilitando o reuso e a substituição destas classes caso necessário.

Classe TesteBase

É uma classe abstrata de cujas subclasses são instanciados objetos para execução do método marcado com a anotação @Test. Esta classe possui um método concreto executar(), que é o único da classe marcado com a anotação @Test.

No contexto do framework, executar() é um método projetado de acordo com o padrão Template Method. O trecho de código a seguir ilustra a implementação resumida deste método.

Executar.png

Os métodos preTeste() e posTeste() são método abstratos que devem ser implementados pelas subclasses de TesteBase. No método preTeste() devem ser implementadas ações que se espera realizar antes de ser iniciada a execução dos cenários. Um exemplo de ação a ser implementada no preTeste() seria a execução de determinados scrips para preparação do banco de dados ou a execução de logon no sistema.

Já no método posTeste() devem ser implementadas ações que devem ser executadas após a execução dos cenários. Um exemplo de ação a ser implementada no posTeste() seria a execução de determinados scripts para limpeza do banco de dados ou a execução de logoff no sistema.

Por fim, o método concreto executarTeste() implementa a chamada do objeto (da classe ControleTesteBase) que de fato executa os cenários, com interação pela lista de cenários definidos no arquivo indexador dec cenários.

Esta classe ainda possui um atributo da classe ControleTesteBase, responsável por realizar a carga do arquivo XML indexador. A seção seguinte apresenta a documentação desta classe.

Classe ControleTesteBase

É uma classe abstrata responsável por controlar a iteração entre os cenários que serão executados. Contudo, ela não executa diretamente os cenários. Em vez disso, esta classe mantém uma lista de blocos lógicos que contém conjuntos de cenários que devem ser executados em conjunto para completar um caso de teste. O conceito de bloco lógico é apresentado da seção seguinte. O trecho de código a seguir apresenta as principais características da classe ControleTesteBase.

ControleTesteBase.png

O método executarCenarios() é responsável por varrer a lista de blocos lógicos e, para cada um deles, invocar o método executar dos respectivos cenários de execução. Além de executar os cenários esta classe também faz a carga de fato dos cenários descritos no arquivo XML indexador.

A classe ControleTesteBase tem ainda um atributo da classe ControleBlocosLogicos, que controla os blocos lógicos de cenários. O conceito de bloco logico é apresentado na seção seguinte.

Classe BlocoLogico

Um bloco lógico é um conjunto de cenários de teste que devem ser executados atomicamente para que o caso de teste seja considerado bem sucedido. Se a execução de algum cenário do bloco lógico falhar o resultado do teste deste bloco lógico é considerado falho. Os blocos lógicos são definidos no arquivo XML indexador, conforme apresentado na seção específico sobre o arquivo indexador.

O próprio objeto de bloco lógico realiza a carga de seus cenários a partir do arquivo XML indexador, e também executa estes cenários de teste. O trecho de código seguinte apresenta as principais características da classe.

Blocologico.png

Além destas características a classe BlocoLogico mantém uma lista de cenários de execução, que são objetos de classes que implementam a interface ICenario. A função do método executarCenarios() é exatamente invocar o método executar() de cada um destes objetos.

Classe DadosBase

É uma classe abstrata que define uma lógica para carga de dados a partir de arquivos XML. Além de manipular os dados para execução esta classe é responsável por armazenar os resultados da execução do cenário bem como os resultados esperados da execução. Estes dois últimos conceitos são devidamente apresentados nas seções seguintes.

O método responsável por realizar esta carga de dados é o método inicializar(). O trecho de código a seguir ilustra a implementação resumida deste método.

Inicializar.png

O método concreto inicializar() implementa o padrão Template Method, definindo um esqueleto de algoritmo que deve ser implementado pelas subclasses.

O método concreto copiarDados() é responsável por copiar dados de outro cenário para execução. Este conceito é apresentado mais detalhadamente no capítulo de documentação dos arquivos XML. Contudo, em resumo, o framework permite que se defina um cenário (seus dados) e, em vez de se informar os dados na própria definição do cenário no arquivo XML, copia-se estes dados de outro cenário já definido, com objetivo de reutilizar estes dados.

Por sua vez, o método concreto carregarDadosAutomaticos() permite que o próprio framework preencha os atributos da classe de dados de acordo com os valores informados no arquivo XML. Isso é possível sempre que os nós de dados não tenham filhos. O trecho de código a seguir ilustra um exemplo de cenário definido num arquivo XML em que os dados são carregados automaticamente pelo framework.

CadastroAdvogado.png

Para que seja possível a carga automática dos dados a classe de dados que estende DadosBase deve possuir todos os atributos definidos no arquivo XML. Por exemplo, deve haver atributos para email, telefoneDDD, telefoneNumero e cadastrarPush, com seus métodos get e set. Todos os atributos definidos no arquivo XML devem estar implementados na classe de dados, entretanto, o contrário não é verdadeiro. Ou seja, Os atributos definidos na classe de dados não precisam ter seus valores definidos no arquivo XML. Isso porque alguns atributos podem ter seus valores setados com valores provenientes do resultado da execução de cenários anteriores. Este conceito é apresentado apropriadamente em seção específica.

Classe ControleExecucaoBase

Esta classe é responsável por encapsular a o objeto de dados da classe DadosBase. Ela cria o objeto de dados e retorna uma referência para ele, além de controlar se o cenário foi executado com sucesso ou não.

Interface IResultadoCenario

A execução de cenários pode produzir resultados que são utilizados pelos cenários seguintes. Tomemos como exemplo um bloco lógico que contenha os cenários de cadastro de processo e de consulta de processo. O objetivo é cadastrar um processo, distribuí-lo e verificar se ele é apresentado corretamente na consulta processual. Neste contexto não é possível informar no arquivo XML do cenário de consulta processual o número do processo, pois ele ainda não existe, visto que o processo ainda não foi distribuído. Para resolver este problema é necessário que o cenário de cadastro de processo produza um resultado que será utilizado posteriormente pela consulta de processo. Isso é possível por meio de classes que implementam a interface IResultadoCenario.

Esta interface define um comportamento que deve ser apresentado por classes que armazenam resultados de execução de cenários. A figura a seguir apresenta o diagrama de classes desta interface.

Iresultadocenario.png

O objeto que se comporta desta maneira é semelhante a um mapa de valores, com um conjunto de pares valor e chave. O framework disponibiliza uma classe concreta que implementa esta interface, tal qual o trecho de código a seguir.

Resultadocenario.png

Apesar de o framework disponbilizar uma classe concreta simples para tratar os resultados, nada impede o desenvolvedor de criar uma nova classe estendendo ou decorando (padrão Decorator) a classe fornecida.

Resultados esperados e resultados encontrados

A execução de um cenário de teste envolve um conjunto de valores de entrada e um ou mais resultados esperados. Estes resultados podem ser uma mensagem na tela ou uma lista de objetos apresentados em uma página HTML na forma de tabela. Para tratar este último caso o framework possui o conceito de Resultados Esperados. Não se deve confundir este conceito com aquele apresentado na seção anterior.

A consulta de processo, por exemplo, é um cenário que possui resultados esperados. Ao criar um cenário de teste para consulta processual, a partir de um conjunto de parâmetros de pesquisa é possível que sejam determinados quais processos devem retornar nesta consulta.

Neste contexto, as classes GerenciadorResultadosBase, ComparadorResultadosBase e ResultadoBase (as duas últimas apresentadas nas seções seguintes) implementam a lógica de carga e comparação de resultados esperados com resultados encontrados. Resultados encontrados são aqueles apresentados após a execução de cenário. Pode ser uma página HTML ou simplesmente uma lista de objetos retornados da execução de um método.

O diagrama ilustrado na figura a seguir mostra o relacionamento entre as classes GerenciadorResultadosBase e ComparadorResultadosBase.

Comparadorresultados.png

A responsabilidade da classe GerenciadorResultadosBase é basicamente carregar os resultados esperados antes da execução dos cenários e os resultados encontrados após a execução. Importante salientar, contudo, que os resultados esperados e os resultados encontrados são objetos de classes que estendem a classe ResultadoBase.

Os resultados esperados são carregados automaticamente a partir do arquivo XML dos dados do cenário. Já os resultados encontrados devem ser carregados manualmente pelos desenvolvedores. Também é possível a construção de uma camada sobre o núcleo genérico que implemente alguma lógica de carga automática destes valores.

Após a execução dos cenários o framework tem uma lista de resultados esperados e outra lista de resultados encontrados. Com isso a classe GerenciadorResultadosBase' pode fazer uso de um objeto da classe ComparadorResultadosBase base comparar os resultados. O cenário somente passará no teste se ambas as listas contiverem os mesmos objetos, independentemente da ordem em que estiverem na lista.

Importante observar que as três classes que fazem parte desta lógica são abstratas, apesar de implementarem o comportamento descrito nesta seção. O núcleo genérico do framework não oferece para esta funcionalidade classes concretas que implementem este comportamento, sendo responsabilidade do desenvolvedor prover estas classes. A camada acima do núcleo genérico, o núcleo Selenium, provê uma classe concreta para gerenciamento dos resultados da execução de cenários de casos de teste funcionais com Selenium.

Interface ICenario

A interface ICenario descreve o comportamento esperado de classes que executam cenários de teste. Para utilizar o framework o desenvolvedor deve fornecer ao framework classes concretas que implementem este comportamento. A figura seguinte apresenta o comportamento esperado pelo framework das classes de execução fornecidas pelos desenvolvedores.

Interfaces.png

O núcleo genérico do framework não fornece qualquer classe que implemente a interface ICenario. Recomenda-se, desta forma, tal qual apresentado no núcleo Selenium, que seja criada uma camada sobre o núcleo genérico que forneça uma classe (possivelmente abstrata) que implemente o comportamento padrão exigido pela interface, preferencialmente fazendo uso do padrão Template Method para estruturação de um algoritmo genérico.

ESTRUTURA DO NÚCLEO SELENIUM

O núcleo Selenium é uma camada desenvolvida sobre o núcle genérico. Ele é composto basicamente por classes responsáveis por executar cenários de testes funcionais em páginas HTML. É importante observar que este núcleo Selenium é independente do sistema que é testado. A arquitetura foi concebida para que qualquer sistema acessível pelo navegador e baseado em páginas HTML possa ser testado com base neste núcleo. As classes pertencentes ao núcleo Selenium são apresentadas nas seções seguintes. O diagrama de classes completo deste núcleo está disponível no ambiente de desenvolvimento do projeto, em um arquivo do Astah.

Classe BaseExecucaoSelenium

Trata-se de uma classe abstrata que estende funcionalidades da classe FluentPage, do framework FluentLenium. Ela fornece basicamente métodos para encontrar elementos em páginas HTML. Funciona como uma fachada para a superclasse FluentPage.

Esta classe mantém referência para um objeto da classe ControleExecucaoBase, que é a classe responsável por encapsular o objeto que mantém os dados para execução dos cenários. Além disso ela faz a inicialização do driver do navegador que é usado na automação dos testes.

Classe CenarioSelenium

Esta classe é a que de fato implementa fluxo de execução do teste automatizado funcional em páginas HTML. O trecho de código seguinte apresenta o algoritmo de execução do teste implementado pela classe. Trata-se de uma implementação do padrão Template Method, que define um esqueleto de algoritmo que pode ser complementado pelas subclasses.

ExecutarSelenium.png

A seguir são apresentadas descrições dos métodos hook invocados no método executar():

  • preTeste(): método concreto que executa ações que devem ser realizadas antes de se iniciar a execução do teste propriamente dita. São ações como abertura do navegador, maximização da janela, acesso ao perfil específico e acesso ao menu específico para execução do cenário.
  • preExecucaoTeste(): método abstrato abre oportunidade para as subclasses implementarem ações a serem executadas antes de os testes serem executados.
  • executarCenarioDecorator(): método abstrato que abre oportunidade para as subclasses implementarem comportamento específico para os cenários decorados.
  • executarTeste(): método abstrato que permite às subclasses implementarem a lógica de execução do cenário de teste. É o método mais importante desta camada do framework e obrigatoriamente deve ser implementado. É este método que contém os comandos do Selenium ou do FluentLenium para preencher os formulários HTML das páginas e executar as ações dos cenários de teste.
  • posExecucaoTeste(): método abstrato abre oportunidade para as subclasses implementarem ações a serem executadas após de os testes serem executados.
  • conferirResultados(): método abstrato que permite às subclasses implementarem alguma lógica de conferência dos resultados esperados com os resultados encontrados.
  • exibirMensagemPosExecucao(): método abstrato que permite às subclasses implementarem alguma lógica de exibição de mensagens de acordo com o resultado da execução do cenário de teste.
  • posTeste(): método concreto que executa ações que devem ser realizadas após a execução do teste propriamente dita. São ações como raelizaao de logoff na aplicação e fechamento do navegador.

REFERÊNCIAS

Frameworks: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.29.6157&rep=rep1&type=pdf

Selenium Webdriver: http://www.seleniumhq.org/projects/webdriver

Fluentlenium: https://github.com/FluentLenium/FluentLenium

Design Pattern Page Object: https://code.google.com/p/selenium/wiki/PageObjects

Design Pattern Decorator: http://www.dofactory.com/net/decorator-design-pattern

Design Pattern Template Method: http://www.dofactory.com/net/template-method-design-pattern

Design Patterm Singleton: http://www.dofactory.com/net/singleton-design-pattern

Ferramentas pessoais
Espaços nominais

Variantes
Ações
Navegação
Informações Gerais
Aplicativos PJe
Manuais
Suporte
Ferramentas