Github Actions em um projeto iOS

Como escrever uma CI de maneira rápida e simples

Matheus de Vasconcelos
10 min readNov 6, 2020

Github

O GitHub é uma plataforma de hospedagem de código-fonte e arquivos com controle de versão através do Git.

Github Actions

Github Actions é uma API para lidar com eventos pré definidos do Github, recentemente disponibilizado para lidar com CI/CD de maneira open source.

CI

O processo de continuos Integration(CI) consiste em automatizar a execução de validação de código antes de ser mesclado na branch principal.

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

Para começar crie um projeto no Xcode e coloque ele no Github — certifique-se que o projeto tenha testes, usaremos eles mais pra frente.
Caso prefira faça um Fork deste projeto:

Adicionando uma Action

Para adicionar uma Action ao seu projeto basta criar uma pasta oculta .github e dentro dela uma outra pasta chamada workflows.

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

As actions do github são acionadas por meio de eventos pré definidos. É possível encontrá-las pela documentação de eventos.

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

Com a estrutura de uma Action apresentada é possível começar a explicar como configurar uma Action para ser uma CI de um projeto em iOS.

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 realizar o build na CI é preciso entender como fazê-lo pela linha de comando já que não temos acesso ao Xcode para rodar o comando Command + r.

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

Dando continuidade ao processo de CI uma tarefa muito importante é a de validar se os testes estão passando sem erros. Assim como o build é preciso entender como executar os testes através do terminal. Para isso utiliza-se o seguinte comando:

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

Embora esse paralelismo pareça interessante, pela possibilidade de validar dois passos ao mesmo tempo, realizar essas duas tarefas simultaneamente é problemático.

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

Embora o problema de paralelismo tenha sido resolvido, outro problema que gera um gasto computacional desnecessário acontece quando o comando de test sempre roda o comando de build mesmo que o build já tenha sido executado em um comando anterior.

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

Para que o job de testes possa utilizar o recurso criado no job anterior, é possível utilizar o recurso de artifacts do Github Actions.

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

Com o build e testes validados, uma última etapa importante para uma boa CI é a validação de métricas. Uma das métricas mais comum de ser validada é a 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

O Github Actions é uma ferramenta open source que disponibiliza uma forma simples e rápida de criar uma CI para automatizar o processo de review de um código.

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

--

--

Matheus de Vasconcelos

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