E finalmente chegamos ao nosso último artigo sobre automação de testes funcionais para Android e iOS utilizando o Calabash. Quem acompanhou os últimos textos de automação para Android e para iOS provavelmente se perguntou se não existe uma maneira de reaproveitar algumas estruturas, visto que as especificações são idênticas para ambas as plataformas e as definições de passos muito parecidas.
Para resolver esse problema, surgiu uma estrutura baseada na arquitetura de Page Object Layer, que tem a seguinte aparência:
Como podemos ver, são três camadas. De cima para baixo: as especificações (Feature), a definição dos passos (Steps) e os objetos de tela, tanto de Android quanto de iOS (Android Screen e iOS Screen). Reaproveitamos dessa estrutura todas as features e todos os steps, que podem ser utilizados em ambas as plataformas. Todas as particularidades de automação dependentes de plataforma são implementadas somente na camada de telas.
Depois de utilizarmos essa estrutura em diversos projetos, percebemos que ela não funciona perfeitamente em um ambiente real. Por isso, criamos a seguinte estrutura:
Apesar de atender à grande maioria dos casos, a primeira estrutura não é suficiente para todos. Ainda existem alguns momentos nos quais alguma funcionalidade é específica para determinada plataforma. Para esses casos, existem os membros específicos, com os quais podemos ter especificações e definições de passos que somente fazem sentido no contexto de Android ou iOS, sendo ignorados quando as especificações da outra plataforma são executadas.
Como criar os testes nessa estrutura? Primeiro, precisamos criar essas oito pastas. E para criar uma feature que serve tanto para Android quanto iOS, quantos arquivos precisamos criar? Precisamos de um arquivo de especificação, um de definição de passos e dois de telas, ou seja, quatro arquivos.
Com isso, a complexidade para iniciar um projeto ficou muito maior, uma vez que agora não podemos mais nos apoiar nos geradores das gems do Calabash, pois eles geram estruturas dependentes de plataformas. Para facilitar essa atividade, desenvolvi uma gem que é capaz de simplificar todas essas atividades. Para instalá-la, execute o seguinte comando no terminal:
gem install cs-bdd
Com a gem instalada, podemos criar o nosso projeto para as especificações. Vale lembrar que, como as especificações serão tanto para iOS quanto Android, não faz mais sentido armazená-las no mesmo repositório fonte dos aplicativos. Sugiro a criação de um novo repositório, chamado de “specs”. Com isso, vamos executar no terminal o seguinte comando:
cs-bdd new specs
Esse comando criará uma pasta chamada specs no diretório corrente e colocará todos os arquivos do projeto dentro dessa pasta. O projeto terá a estrutura de pastas apresentadas na segunda imagem e diversos arquivos para auxiliar na criação e manutenção de suas especificações. Esses arquivos são scripts para execução em ambientes de Integração Contínua e diversos passos base que percebi que se repetiam em diversos projetos. O objetivo é facilitar o início da escrita de suas especificações.
Um ponto a ser destacado é que essa gem possui internacionalização. Com a execução do comando acima iremos criar um projeto cuja língua padrão é o inglês. Se desejar criar um projeto em português, execute:
cs-bdd new specs --lang=pt
Você pode trocar o ‘pt’ por qualquer uma das mais de 40 linguagens aceitas pelo Gherkin. Isso permitirá que você crie especificações na linguagem natural que desejar. O problema será com os passos bases, que só estão disponíveis em português e inglês. Se você conhece outra língua e deseja ajudar, pode realizar a tradução e inserir na gem, acessando seu repositório.
Se tentar executar novamente o comando, para gerar um projeto em português, a gem reconhecerá que a pasta specs já existe e irá avaliar, um por um, todos os arquivos, para identificar quais são idênticos e quais sofreram alteração. Quando identificar algum arquivo alterado, a gem irá perguntar qual ação deve ser realizada. Neste momento, você poderá ver a diferença entre os arquivos e solicitar ou não a substituição do arquivo atual. Assim, quando sair uma nova versão da gem, você pode realizar a atualização gerando um novo projeto em cima do seu atual e escolhendo os arquivos que deseja atualizar.
Finalizada a teoria, vamos começar a prática. Assim como fizemos nos dois últimos artigos, vamos utilizar o aplicativo de consulta CEP para criação de um exemplo. Se você ainda não tem os projetos em seu PC, o Android pode ser encontrado no repositório do Victor Nascimento e o do iOS no repositório do Alexandre Garrefa. É importante lembrar que o projeto iOS precisa ser configurado com a criação de um target para o Calabash. Qualquer dúvida, consulte o artigo sobre automatização no iOS.
Agora que já temos nossos aplicativos Android e iOS, vamos criar nossa especificação de consulta de CEP. Relembrando: para isso, será necessário criar quatro arquivos, um para a especificação, um para a definição dos passos e dois de telas. Também utilize a gem cs-bdd, que possui diversos geradores que permitem a criação dos quatro arquivos ou dos arquivos individuais. Para ver todos os geradores digite um dos comandos abaixo:
cs-bdd g cs-bdd generate
Ao analisar a listagem dos métodos, perceba que os geradores podem criar features, passos e telas para ambos os sistemas, ou para as plataformas específicas. Como a nossa especificação de consultar CEP é comum para ambas as plataformas, vamos criar nossos arquivos utilizando o comando:
cs-bdd g feature consultar --lang=pt
Vale destacar que aqui utilizamos novamente a linguagem pt, e esse comando sempre deverá ser executado da pasta raiz das especificações. Um erro será exibido sempre que você executar esse comando de qualquer outra pasta.
Com isso temos nossos quatro arquivos gerados, como exemplificado na imagem abaixo:
Agora, abra o arquivo consultar.feature em seu editor preferido e insira as seguintes linhas:
# language: pt Funcionalidade: Consultar CEP Cenário: Posso consultar um CEP Dado que estou na tela inicial Quando digitar um CEP E clicar no botão consultar Então devo ver o nome da rua na tela
Execute o Calabash para que o Cucumber possa identificar as definições dos passos e nos sugerir as implementações. Aqui temos uma pequena diferença. Para realizar a separação entre Android e iOS, usamos as configurações de profile do Cucumber. Existem dois profiles, um chamado “android” e outro chamado “ios”. Todo profile deve ser informado como valor do parâmetro “-p”. Assim, o comando de execução fica:
calabash-android run path_do_apk -p android
Após a execução, copie as sugestões de definição de passos para o arquivo consultar_steps.rb. Agora, vamos iniciar a implementação dos nossos passos. Lembrando que as definições de passos devem ser genéricas, para que possamos utilizá-las em ambas as plataformas. Para implementar o primeiro passo, utilize:
Dado(/^que estou na tela inicial$/) do @page = page(ConsultarScreen).await(timeout: 5) end
Esse passo cria uma variável de instância chamada “@page” que irá receber um objeto do tipo ConsultarScreen. O método “page” serve para verificar se no emulador, simulador ou device aparece realmente a tela ConsultarScreen. Esse método usa um atributo “trait” (identificador), que deve ser configurado no arquivo “features/android/screens/consultar_screen.rb”. Esse trait pode ser o id de um elemento da tela ou um id do layout dessa tela. Nesse exemplo, usaremos o id do campo de inserção do valor do CEP. Nossa ConsultarScreen ficará assim:
# coding: utf-8 class ConsultarScreen < AndroidScreenBase # Identificador da tela trait(:trait) { "* id:'#{layout_name}'" } # Declare todos os elementos da tela element(:layout_name) { campo_cep } element(:campo_cep) { 'input_cep' } # Declare todas as ações da tela # action(:touch_button) { # touch("* id:'#{button}'") # } end
Como podemos ver, o trait é representado por uma query que recebe, por interpolação de strings, o valor do atributo layout_name. Esse valor, por sua vez, é o mesmo que o atributo campo_cep, que representa o campo de input de CEP. Todos os elementos da nossa tela que vamos usar devem ser declarados por meio da função “element”, assim melhoramos a modularização da nossa Page Object e facilitamos futuros processos de refatoração.
Vamos agora para o segundo passo, uma implementação séria:
Quando(/^digitar um CEP$/) do @page.digitar_cep end
Por que não chamamos diretamente o comando que irá digitar o CEP em vez de chamarmos um método do objeto @page que ainda nem foi implementado? Porque precisamos que a definição do passo seja genérica e, como os identificadores dos elementos muito provavelmente vão variar entre as plataformas, devemos deixar essa informação para a nossa ConsultarScreen e, portanto, abstraí-la em um método. A implementação seria assim:
# Declare todas as ações da tela action(:digitar_cep) do enter '04565001', campo_cep end
Acabamos de definir uma action, que nada mais é que uma nova forma de definir uma função. Costumo utilizar essa forma por ser mais semântica, quando a ação não recebe parâmetro. Se a função receber algum parâmetro, costumo utilizar a forma padrão de declaração:
# Declare todas as ações da tela def digitar_cep enter '04565001', campo_cep end
Aqui, utilizamos uma função da classe AndroidScreenBase. Como comentei, a cs-bdd gera diversos passos bases e facilitadores que identifiquei nos diversos projetos que trabalhei. Os facilitadores ficam dentro do arquivo “features/android/android_screen_base.rb”, que é a classe pai de todas as classes de tela Android, e do arquivo “features/ios/ios_screen_base.rb”, no caso do iOS. A função “enter” digita um determinado texto em um campo a partir de seu id, ambos informados como parâmetros dessa função.
Para o próximo passo, temos:
Quando(/^clicar no botão consultar$/) do @page.tocar_botao_consultar end
E a implementação do método tocar_botao_consultar seria:
element(:botao_consultar) { 'botao_consulta' } action(:tocar_botao_consultar) do touch_screen_element botao_consultar end
Primeiro, é necessário declarar o elemento com o id do botão consultar e, somente depois, implementar a nossa action. A função touch_screen_element também pertence às classes base e, além de realizar um touch em um elemento a partir de seu id, espera esse elemento por um tempo, tornando assim as execuções mais robustas, pois um tempo maior para carregamento da tela não irá afetar a execução da especificação.
Para finalizar, implementamos o último passo:
Então(/^devo ver o nome da rua na tela$/) do fail "Nome da rua não encontrado!" unless @page.contem_nome_rua? end
Esse método irá forçar a falha da execução se o método contem_nome_rua? retornar falso. O método contem_nome_rua? possui a seguinte implementação:
def contem_nome_rua? begin wait_for(timeout: 5) { element_exists "* {text CONTAINS1 'Rua Flórida'}" } rescue false end return true end
Se você executar essa especificação, verá que todos os passos passam.
Como faremos agora para o iOS? Como nossa especificação e nossas definições de passos podem ser reaproveitadas, somente precisamos implementar a classe ConsultarScreen. Essa classe está presente no arquivo “features/ios/screens/consultar_screen.rb” e deve conter exatamente a mesma interface da classe ConsultarScreen do Android. Uma possível implementação dessa classe seria:
class ConsultarScreen < IOSScreenBase # Identificador da tela trait(:trait) { "* marked:'#{layout_name}'" } # Declare todos os elementos da tela element(:layout_name) { 'CEP_SCREEN' } element(:campo_cep) { 'CAMPO_TEXTO_CEP' } element(:botao_consultar) { 'Buscar' } # Declare todas as ações da tela def digitar_cep enter '04565001', campo_cep end action(:tocar_botao_consultar) do touch_screen_element botao_consultar end def contem_nome_rua? begin wait_for(timeout: 5) { element_exists "* {text CONTAINS1 'Rua Flórida'}" } rescue false end return true end end
Antes de executar as especificações, precisamos saber onde o APP foi gerado pelo Xcode, pois, como agora estamos em outro repositório, o Calabash não consegue mais encontrar essa informação. A maneira mais fácil de encontrar o APP é executar o script de build que existe na pasta config/scripts/ios/build_app.rb. Esse script irá realizar o build do APP e irá, ao final do build, indicar a pasta onde o APP foi gerado. Para executar esse script, primeiro você precisa acertar algumas configurações no arquivo config/scripts/ios/build_app.yml. Configure o path do xcworkspace, o target do Calabash e path onde será gerado o APP. Depois disso, execute o comando:
ruby config/scripts/ios/build_app.rb dev simulator
O primeiro parâmetro é o nome do ambiente de configuração que você criou no arquivo build_app.yml. Por padrão, ele vem com os ambientes dev e jenkins. Você pode criar quantos desejar. O segundo parâmetro é se você deseja realizar um Build para simulador (parâmetro simulator) ou para um dispositivo (parâmetro device).
Ao executar as especificações por meio de um projeto normal do Calabash iOS, você precisa instalar o APP no device por meio do Xcode. Porém, a funcionalidade que permite a instalação automática do APP nos simuladores e devices é incluída nos arquivos de configuração gerados pela gem CS-BDD, assim como já acontece no Calabash Android. Para realizar a instalação em devices, você precisará instalar o ios-deploy da PhoneGap. Para permitir que o APP seja desinstalado corretamente, você precisa também configurar o Bundle ID do seu aplicativo no arquivo features/ios/support/01_launch.rb.
Para executar as especificações em um simulador, rode o seguinte comando no terminal:
APP_BUNDLE_PATH=Path_do_APP DEVICE_TARGET=Device_UUID cucumber -p ios
Se desejar executar os testes em um device, inclua também o parâmetro DEVICE_ENDPOINT.
Com isso, fechamos a nossa automatização de especificações, tanto em iOS e Android, reaproveitando boa parte da estrutura gerada com o auxílio da gem CS-BDD. No próximo artigo, falarei sobre o ambiente de Validação Contínua, como configurá-lo e como a CS-BDD nos auxilia também nesse processo.
Ficou alguma dúvida ou tem alguma sugestão para melhorar este artigo? Deixe nos comentários. Até a próxima!