Github Actions em um projeto iOS

Como escrever uma CI de maneira rápida e simples

Github

Github Actions

CI

Esse processo, de modo geral, realiza as tarefas de construir a aplicação, executar os testes dela e validar métricas como a cobertura de testes.

Através dessa automação, a revisão do código pode ser feita apenas sobre o código. Sem necessidade de baixar a branch, rodar o build e os testes e validar a cobertura sempre que um novo commit for enviado para revisão.

Começando

Adicionando uma Action

Uma dica para ver as pastas ocultas no finder é utilizar o comando Command + Shift + .

Exemplo de um projeto com a pasta .github

Dentro da pasta workflows crie um arquivo CI.yml e ele será a Action. As actions podem ter qualquer nome, desde que sejam arquivos YAML.

Estrutura GitHub Actions

No caso de uma CI é interessante que o evento em que ela seja acionada seja o de pull request.

  • name: O nome da Actions criada é definida pela label name.
  • on: Para definir para qual evento a action será acionada utiliza-se a label on.

É possível customizar ainda mais os eventos que acionam a Action, por exemplo ignorando mudanças em arquivos específicos. A documentação de eventos demonstra esses casos.

Com a Action configurada para ser acionada em eventos de pull request, agora é possível realizar uma ação sempre que eles ocorrem.

  • jobs: Para executar uma tarefa cria-se primeiro a label jobs. Ela é a responsável por ter todas as tarefas que a Action criada irá executar (as tarefas rodam de maneira paralela).
  • hello: Para criar uma tarefa crie uma label para ela. No exemplo a label criada foi hello.
  • name: Após criar a label da tarefa é possível atribuir um nome para a mesma utilizando a label name.
  • runs-on: Para configurar o ambiente em que a tarefa será executada utiliza-se a label runs-on. No exemplo, como não há dependências do MacOS, pode-se utilizar o ubuntu. É possível encontrar os ambientes disponíveis na documentação de hosts.
  • steps: Assim que o ambiente é configurado podemos definir quais passos a tarefa irá realizar. Para isso utiliza-se a label steps. Cada step pode ser criado utilizando a label name para descrever seu nome e utilizar comandos como o da label run que executa uma ação no terminal.

Para validar essa Action, a crie em uma branch nova do projeto, faça o push e abra um pull request dela.
Assim que o pull request for aberto a Action irá ser executada.

Para ver os detalhes da execução clique em Details.

Com a execução, pode-se perceber que do lado esquerdo uma action com o nome “Run CI” foi executada para o evento de “pull_request”, conforme o que configuramos. Nessa action foi executado o job “Hello World”.

O job “Hello World” executou o step de “Set up job” (step padrão para todo job), em seguida o step “Say Hello” , que configuramos para escrever no console “Hello World”, e por fim o step “Complete job” (step padrão para todo job) que finaliza o job.

Build e Test

Como comentado anteriormente o ideal é que uma CI possa construir a aplicação, rodar seus testes e validar algumas métricas como, por exemplo, a cobertura.

Build

Para isso utiliza-se o xcodebuild (ferramenta de terminal disponibilizada pela Apple para lidar com aplicações iOS). Para realizar o build basta utilizar o comando build do xcodebuild com as flags do scheme que se pretende construir e em qual device isso irá ocorrer. No exemplo acima o build irá ocorrer no scheme iOSGitHubActions do simulador do iPhone 11 Pro.

Rode esse comando no seu terminal para ver o resultado. O log gerado não é nada amigável. Para contornar isso uma boa dica é utilizar a gem xcpretty.
Com essa gema instalada basta adicionar | xcpretty após as flags do xcodebuild.

Com isso pode-se criar um job de build para a Action dessa forma:

Algumas novidades apareceram. A primeira delas é para a primeira tarefa chamada de “Checkout repository”. Diferente da action anterior, essa tarefa não utiliza a label run e sim a label uses.

A label uses é utilizada para executar outras Actions que foram publicadas por outros usuários. Nesse job de build utiliza-se a Action actions/checkout@v1 e a Action maxim-lobanov/setup-xcode@v1.1.

A primeira Action é utilizada para que o projeto seja clonado na máquina que está executando o job e faz o checkout para a branch que lançou o evento da Action criada.

A segunda configura a versão do Xcode da linha de comando para a passada pelo parâmetro — através da label withxcode-version. De modo geral é possível entender como utilizar as Actions pelo README delas.

Outro detalhe é o uso do comando set -o pipefail. Sem o uso desse comando, mesmo que o comando do xcodebuild falhe, a Action não irá acusar uma falha.

As demais configurações utilizam o que já foi apresentado. Assim as tarefas que serão executadas são:

Checkout do repositório — Configuração das gemas e do Xcode — Build.

Resultado esperado.

Test

O comando é igual ao de build, com a diferença de que ao invés de utilizar o comando build, utiliza-se o comando test.

Do mesmo jeito que o comando de teste é muito semelhante ao de build, o job de teste também será semelhante ao de build.

É importante notar que essa Action ao ser executada roda ambos os jobs ao mesmo tempo.

Problema de paralelismo de Jobs

O problema é que caso o build falhe, o teste também falhará já que para executa-lo é preciso que o build da aplicação esteja funcionando. Assim, caso o build esteja quebrado, essa configuração está gastando valor computacional sem necessidade uma vez que nem seria preciso rodar o job de testes.

Para que isso seja evitado é possível utilizar a label needs. Essa label faz com que o job espere pelo sucesso de algum outro job para executar.

Retrabalho em um Job

Para resolver isso, o xcodebuild possibilita rodar o build uma vez e com esse build rodar os testes posteriormente através do uso do Derived Data.

Para isso muda-se os comandos de build e o de test e adiciona-se a flag derivedDataPath.

Após realizar essa atualização, podemos notar que o job de testes falhou. Isso ocorre pois, embora não haja mais paralelismo, cada job roda em uma instância de máquina nova, assim, o Derived Data criado não é compartilhado.

Compartilhando recursos

Os artifacts são recursos que podem ser salvos e repassados entre jobs. Assim como as demais ferramentas que foram apresentadas, essa também é uma Action.

Para salvar o Derived Data criado utiliza-se a action actions/upload-artifact@v2.

A action de upload possui dois parâmetros.

  • name: A label name é utilizada para definir o nome do artifact que será salvo. É importante utilizar um nome correto pois é com ele que será possível fazer o download e esse será o nome que o artifact terá quando o download for concluído.
  • path: A label path serve para indicar em qual path está a pasta ou arquivo que será salvo.

Para fazer o download do Derived Data criado utiliza-se a action actions/download-artifact@v1.

A action de download possui um parâmetro.

  • name: A label name é utilizada para definir o nome do artifact que será baixado.

É importante notar que para a action de download a versão utilizada é a v1. A escolha de versão se dá pois a v2 não cria uma pasta do artifact, ela apenas faz o download do conteúdo do diretório que foi salvo. Por outro lado, a v1 cria a pasta com o nome definido no upload.

Assim, com a v1 o conteúdo baixado será a pasta do Derived Data, enquanto com a v2 o conteúdo baixado será o conteúdo da pasta do Derived Data.

Cobertura de testes

Uma das formas mais fáceis de realizar essa validação é com a utilização de ferramentas de validação. Para essa publicação será utilizado o Codecov.

O Codecov é uma ferramenta de validação de cobertura de testes para CI. Para sua utilização é preciso fornecer um arquivo com o resultado dos testes em um formato XML.

Uma forma fácil de gerar esse arquivo é através da utilização da gema slather.

Para utilizar o slather é preciso criar um arquivo oculto .slather.yml.

Para isso é importante que a label coverage_service tenha o valor cobertura_xml e que as labels input_format, output_directory e build_directory tenham os valores configurados corretamente.

Além de configurar o arquivo .slather.yml corretamente é importante realizar uma alteração na execução dos jobs de build e teste.

Para o job de build é preciso realizar a execução do setup do slather pelo comando slather setup antes do build.
Para o job de teste é preciso adicionar a flag -enableCodeCoverage YES ao comando do xcodebuild para que o teste adicione um coverage report ao Derived Data.

Com o report gerado podemos adicionar o step de coverage ao job de teste.

Com esse step adicionado, o arquivo que será enviado para o Codecov foi gerado, faltando apenas de fato enviá-lo.

Para utilizar o Codecov utiliza-se a Action disponibilizada pela ferramenta.

Diferente das demais Actions utilizadas, essa possui um parâmetro diferente chamado token e seu valor é um secret.

O Github possui a opção de guardar chaves e tokens do seu repositório de maneira segura através dos secrets.

Para criar um secret é preciso clicar na aba “Settings” do repositório e depois na aba “Secrets”.

Clique em “New Secret” e preencha o nome e o valor que se deseja salvar.

Para o caso do Codecov crie uma conta na plataforma e vá para o seu perfil. Dentro do perfil clique em “Add new repository” e selecione o repositório que se deseja adicionar o suporte do Codecov.

Após adicionar o repositório, uma tela com o seu token deve aparecer. Assim, basta criar um secret com o nome CODECOV_TOKEN e o valor token que foi gerado.

Após adicionar o secret e o step de codecov à execução da Action, essa será finalizada e publicará o report como comentário do pull request.

Conclusão

Por ser open source existem diversas actions pré existentes que fazem boa parte das tarefas complexas, como criar tags e gerar um relatório de testes.

Além de já possuir uma série de Actions publicadas, a criação de uma action própria também é muito simples pois a documentação está bem escrita e com diversos exemplos já que comunidade está ativa e tirando dúvidas.

No entanto, é possível perceber que a Action criada nessa publicação não é muito performática, o que se dá pela gema do nokogiri que demora muito para ser instalada e também pelas máquinas disponibilizadas não possuirem uma grande capacidade computacional.
Para mitigar esses problemas é possível criar scripts próprios que façam o trabalho das gemas utilizadas e, até mesmo, um host próprio com maior poder computacional para executar as actions do projeto.

Portanto, é inegável que pelo tempo que o Github actions foi liberado, ele já é uma possibilidade viável para criação de uma CI dada a sua facilidade e o grande apelo da comunidade envolvida no projeto.

Referência

iOS Developer — Apple Developer Academy Alumni | Mackenzie. Studying Unit Tests.

iOS Developer — Apple Developer Academy Alumni | Mackenzie. Studying Unit Tests.