Concrete Logo
Hamburger button

Automatizando testes funcionais no Android e no iOS com Calabash

  • Blog
  • 29 de Maio de 2015

E finalmente chegamos ao nosso último post sobre automação de testes funcionais para Android e iOS utilizando o Calabash. Quem acompanhou os últimos posts 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:

1

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 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:

2

 

Apesar de atender a 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 existe 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 dependente 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:

[sourcecode language=”text”]
gem install cs-bdd
[/sourcecode]

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:

[sourcecode language=”text”]
cs-bdd new specs
[/sourcecode]

Esse comando criará uma pasta chamada specs no diretório corrente e colocará todos os arquivos do projeto dentro desta 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:

[sourcecode language=”text”]
cs-bdd new specs –lang=pt
[/sourcecode]

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 lingua 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 posts, 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 post 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. Para isso 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:

[sourcecode language=”text”]
cs-bdd g
cs-bdd generate
[/sourcecode]

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:

[sourcecode language=”text”]
cs-bdd g feature consultar –lang=pt
[/sourcecode]

Vale destacar que aqui utilizamos novamente a linguagem pt, e esse comando sempre deverá ser executado da pasta raíz 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:

3

Agora, abra o arquivo consultar.feature em seu editor preferido e insira as seguintes linhas:

[sourcecode language=”text”]
# 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
[/sourcecode]

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:

[sourcecode language=”text”]
calabash-android run path_do_apk -p android
[/sourcecode]

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:

[sourcecode language=”text”]
Dado(/^que estou na tela inicial$/) do
@page = page(ConsultarScreen).await(timeout: 5)
end
[/sourcecode]

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:

[sourcecode language=”text”]
# 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
[/sourcecode]

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:

[sourcecode language=”text”]
Quando(/^digitar um CEP$/) do
@page.digitar_cep
end
[/sourcecode]

Por que não chamamos diretamente o comando que irá digitar o CEP ao invés 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:

[sourcecode language=”text”]
# Declare todas as ações da tela
action(:digitar_cep) do
enter ‘04565001’, campo_cep
end
[/sourcecode]

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:

[sourcecode language=”text”]
# Declare todas as ações da tela
def digitar_cep
enter ‘04565001’, campo_cep
end
[/sourcecode]

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:

[sourcecode language=”text”]
Quando(/^clicar no botão consultar$/) do
@page.tocar_botao_consultar
end
[/sourcecode]

E a implementação do método tocar_botao_consultar seria:

[sourcecode language=”text”]
element(:botao_consultar) { ‘botao_consulta’ }
action(:tocar_botao_consultar) do
touch_screen_element botao_consultar
end
[/sourcecode]

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:

[sourcecode language=”text”]
Então(/^devo ver o nome da rua na tela$/) do
fail "Nome da rua não encontrado!" unless @page.contem_nome_rua?
end
[/sourcecode]

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:

[sourcecode language=”text”]
def contem_nome_rua?
begin
wait_for(timeout: 5) { element_exists "* {text CONTAINS[c] ‘Rua Flórida’}" }
rescue
false
end
return true
end
[/sourcecode]

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:

[sourcecode language=”text”]
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 CONTAINS[c] ‘Rua Flórida’}" }
rescue
false
end
return true
end
end
[/sourcecode]

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:

[sourcecode language=”text”]
ruby config/scripts/ios/build_app.rb dev simulator
[/sourcecode]

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:

[sourcecode language=”text”]
APP_BUNDLE_PATH=Path_do_APP DEVICE_TARGET=Device_UUID cucumber -p ios
[/sourcecode]

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 post, 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 esse post? Deixe nos comentários. Até a próxima!