Alura > Cursos de Mobile > Cursos de Flutter > Conteúdos de Flutter > Primeiras aulas do curso CI/CD Mobile: automação para aplicativos Android e iOS

CI/CD Mobile: automação para aplicativos Android e iOS

CI/CD, Testes e Análise de Código - Introdução

Apresentando os instrutores e objetivos do curso

Gostaríamos de dar as boas-vindas a mais um curso da Alura. Somos William Bezerra e seremos seus instrutores ao longo de toda essa jornada.

Audiodescrição: William é um homem de cabelos pretos e está utilizando óculos.

Ao longo deste curso, nós iremos juntos construir uma pipeline completa de CI e CD utilizando GitHub, Actions e Fastlane. Vamos trabalhar desde a primeira etapa, onde abrimos um PR com código novo e avaliamos se aquele código não quebrou um teste unitário ou se não há nada quebrando no lint. Após essa avaliação, executaremos um merge, por exemplo, e geraremos uma versão para alguém do seu time testar.

Implementando a integração com Firebase App Distribution

Utilizaremos o Firebase App Distribution integrado com GitHub Actions para fazer esse deploy e liberar automaticamente, sem que seja necessário apertar nada. Nosso trabalho será apenas abrir os PRs, e automaticamente ele já será direcionado para as etapas corretas.

Na segunda etapa, construiremos essa integração com o Firebase App Distribution para distribuir o aplicativo. Avançando ainda mais na pipeline, construiremos uma etapa para rodar os testes de integração, os end-to-ends dentro da aplicação, em uma máquina virtual utilizando o Firebase Test Lab. Com isso, conseguiremos testar em diversos dispositivos ao mesmo tempo, validar uma série de aspectos e receber feedback com vídeos, desempenho e gráficos, para entender como o aplicativo tem se comportado e funcionado ao longo do tempo.

Configurando o deploy automático para as lojas

Por fim, e não menos importante, vamos configurar o deploy automático do aplicativo para as lojas, tanto para a Apple Store quanto para o Google Play. Faremos isso integrando com as actions do GitHub e utilizando o Fastlane para garantir que todas as credenciais e o funcionamento do aplicativo estejam corretos. Assim, conseguiremos criar uma pipeline, uma esteira de desenvolvimento adequada para cada contexto.

Estruturando workflows e requisitos para o curso

Utilizaremos o GitHub para criar workflows e estruturar uma pipeline similar à que está sendo apresentada, mas adaptável ao contexto de cada um. Esse conteúdo é muito importante, pois pode ser aplicado no dia a dia, permitindo trabalhar com ele na publicação dos aplicativos, adaptando, criando workflows e estruturando todo o processo.

É essencial que, para realizar este curso, já tenhamos pelo menos algum aplicativo publicado, seja na Apple Store ou na Google Play, pois realizaremos essas etapas diretamente na loja. Caso não tenhamos uma conta ou acesso para fazer essa configuração, será mais complicado avançar ao longo do curso. No entanto, isso não impede de realizar as primeiras etapas.

Também é importante já ter um conhecimento básico de como funciona o processo de publicação, como gerar uma versão e como subir uma versão na loja manualmente, para entender as etapas que abordaremos e, consequentemente, publicar.

Concluindo a introdução do curso

Nos encontramos na próxima aula, pois este curso será muito interessante e aprenderemos bastante. Até mais!

CI/CD, Testes e Análise de Código - CI-CD

Introduzindo o conceito de CI/CD

Antes de iniciarmos qualquer escrita de automação ou implementação de Fastlane e GitHub Actions, precisamos entender o que é o CI/CD e como a pipeline de CI/CD que vamos construir ao longo deste curso funcionará. Antes de compreendermos a pipeline, é necessário entender o conceito de CI/CD. Vamos seguir um caminho: primeiro, entender o que é CI/CD, depois o que é uma pipeline de CI/CD, e então construir essa pipeline.

O que é CI/CD? Provavelmente, já ouvimos falar bastante sobre esse conceito no contexto de programação, especialmente quando trabalhamos com programação mobile ou até mesmo front-end. O CI/CD refere-se a duas siglas: CI e CD. O CI é o Continuous Integration (Integração Contínua), que basicamente monitora o projeto com alguns gatilhos e garante a execução de testes automatizados, a qualidade do código, evita regressões, executa testes unitários, entre outros. Essa estrutura de CI assegura que o que estamos desenvolvendo e entregando ao usuário seja bem feito, seguindo as regras definidas no projeto. Se fizermos alguma alteração e o fluxo de CI detectar um teste quebrado, não podemos prosseguir com a implementação; precisamos corrigir, rodar novamente e integrar.

Explicando a importância do CI e do CD

Embora possamos rodar isso em nossa máquina, não seria contínuo, pois estaríamos testando localmente. A ideia é que as regras sejam automatizadas para evitar falhas, garantindo que sempre passemos pelo CI antes de chegar ao produto final. Essa é a importância de ter um CI aplicado, pois evita que alguém, mesmo sem intenção, cometa erros, especialmente em situações de pressa, como ao subir um hotfix ou bug fix.

O CD, por sua vez, é o Continuous Delivery (Entrega Contínua). Enquanto o CI garante a qualidade, o CD é responsável pela entrega do que foi desenvolvido ao usuário. A ideia é automatizar a entrega do aplicativo, site, etc., seja para uma versão de QA, onde um membro do time testa, ou para um Product Manager. Antes de subir a versão para a loja, podemos implementar um processo que passe pelo QA, garantindo que tudo esteja correto antes da entrega final.

Descrevendo a pipeline de CI/CD

Quando falamos de Pipeline de CI/CD, estamos nos referindo à aplicação desses dois conceitos em um workflow que funciona automaticamente, conforme a dinâmica da equipe. Não existe uma pipeline perfeita de CI/CD; ela deve se adequar ao contexto específico. Por exemplo, podemos ter uma pipeline estruturada para um time menor, enquanto em outros casos, com três ambientes diferentes (desenvolvimento, homologação e produção), teremos mais gatilhos e estruturas. A ideia é que a equipe, incluindo desenvolvedores e outras partes interessadas, trabalhe em conjunto para estruturar uma pipeline que funcione de forma automática e se adapte ao projeto.

No nosso projeto, a pipeline funciona da seguinte maneira: temos duas branches no nosso GitHub, a branch de desenvolvimento e a branch de produção.

Detalhando o fluxo de trabalho na pipeline

Quando uma pessoa desenvolvedora escreve um código, ela abre um Pull Request (PR) não para a main, mas sim para a branch de desenvolvimento. Ao abrirmos esse PR para a branch de desenvolvimento, automaticamente, teremos um fluxo de Integração Contínua (CI) que analisará o código escrito, executará um Flutter Analyze e rodará os testes unitários para verificar se o código alterado não quebrou nada. Uma vez que o código é analisado e os testes unitários passam com 100% de sucesso, fazemos o merge do PR na branch de desenvolvimento.

Caso o PR não seja aprovado, ele será barrado, e a pessoa desenvolvedora verá que houve falhas, precisando fazer ajustes e executar novamente. Essa é a primeira etapa de verificação. Após a aprovação, fazemos o merge do PR na branch de desenvolvimento. É necessário a aprovação de outras pessoas desenvolvedoras? Com certeza. Além da etapa de CI, possivelmente haverá pessoas designadas para revisar o PR. Quando aprovado por elas e passando pela verificação de CI, o merge do PR na branch de desenvolvimento é realizado.

Gerando versões de teste e produção

Após o merge, sempre que alguém realiza o merge do PR na branch de desenvolvimento, automaticamente geramos uma versão de teste, uma versão de homologação, para que o time possa testar. Essa versão é enviada para o Firebase App Distribution para que o time verifique se a entrega foi feita conforme o esperado, seja uma correção simples ou uma nova funcionalidade. No nosso fluxo, é obrigatório gerar essa versão após o merge do PR, mas em outros fluxos pode ser diferente. Por exemplo, só gerar a versão de teste quando abrir o PR para a main.

No nosso caso, após o merge do PR na branch de desenvolvimento, geramos a versão de teste. Depois que a versão é aprovada, passamos para o próximo fluxo, que é gerar uma versão de produção. Pegamos a branch de desenvolvimento e abrimos um PR para a main. Ao abrir esse PR, executamos os testes automatizados. Não executamos os testes automatizados ao abrir o PR para a branch de desenvolvimento porque eles são mais caros e demorados. Executamos apenas quando abrimos o PR para a main, pois os testes automatizados são fluxos pré-definidos que devem funcionar de ponta a ponta.

Concluindo o processo de entrega contínua

Quando os testes automatizados passam e estão corretos, fazemos o merge do PR na main. Após o merge na main, os aplicativos são publicados automaticamente nas lojas para Android e iOS. Esse é um fluxo que criamos para trabalhar com nosso time, mas pode ser adaptado conforme necessário. O pipeline ideal é aquele que melhor se adequa ao trabalho com o time. Em alguns casos, a versão não é gerada ao fazer o merge na branch de desenvolvimento, mas sim ao abrir o PR para a main, executando tanto os testes automatizados quanto gerando a versão para testes.

O importante é ter um processo automatizado, onde a pessoa desenvolvedora só precisa abrir o PR. Assim, tudo o que precisa ser feito para garantir a qualidade será realizado automaticamente, sem a necessidade de rodar nada manualmente, evitando esquecimentos. Esses processos garantem a qualidade e é exatamente isso que vamos implementar ao longo deste curso.

CI/CD, Testes e Análise de Código - Testes e analise de código

Iniciando a configuração do pipeline

Vamos iniciar a configuração do nosso pipeline, definindo o processo a ser seguido. Precisamos passar por cada etapa, como já mencionado, para configurar e implementar CI e CD no contexto adequado. Vamos seguir um workflow, uma sequência de etapas para realizar essa configuração, começando pela primeira etapa: analisar o código e executar os testes unitários ao abrir um pull request (PR) para a branch de desenvolvimento.

Teremos um aplicativo, um código, e ao abrir um PR para a branch de desenvolvimento, analisaremos o código e executaremos os testes unitários. Vamos abrir o projeto, que é o sorteador de Amigo Secreto. O link para cloná-lo e utilizá-lo estará disponível, mas recomendamos que, caso vá utilizar ou outro projeto, tenha o projeto publicado na Play Store ou na Apple Store para seguir bem os passos. Nesta primeira aula, não é necessário ter o aplicativo publicado ainda, mas nas aulas futuras, será importante publicá-lo na Apple Store e na Play Store para seguir o passo a passo que estamos realizando.

Configurando o ambiente no GitHub

Dentro do nosso projeto, vamos iniciar a configuração. A ideia é, ao abrir o PR para a branch de desenvolvimento, analisarmos o código e executarmos os testes unitários. Para isso, criaremos uma pasta chamada .github. Utilizamos o GitHub para versionamento de código, então, caso utilize outro tipo de versionamento, será necessário avaliar, mas a ideia do curso é utilizar as actions do GitHub para executar esses processos. Com a pasta .github criada, criaremos uma nova pasta chamada "Workflows", onde estarão os contextos que serão disparados. Dentro de "Workflows", criaremos o primeiro contexto, que será o cidev.yml. Não é exatamente igual ao pubspec.yaml, mas a estrutura do arquivo é semelhante.

Com o arquivo criado, iniciaremos a configuração. Primeiro, daremos um nome para esse workflow, por exemplo, cidev. Em seguida, definiremos quando esse workflow será disparado, utilizando o método on. Neste caso, será on: pull_request nas branches de desenvolvimento, disparando o trigger desse workflow.

name: "CI Dev"
on:
  pull_request:
    branches: dev

Definindo os jobs e steps

Com isso, iniciaremos as configurações das atividades que serão executadas, chamadas de jobs. Esses são os pontos que serão aplicados ao executar o workflow.

O primeiro job que executaremos será o job de Analyzer. Daremos o nome Analyzer e definiremos onde esse job será executado, utilizando runs-on. Por exemplo, será em um dispositivo Ubuntu, MacOS ou Windows. Existem diferenças de preços entre esses modelos, e é importante avaliar qual utilizar. Por exemplo, em novembro de 2025, o custo de utilizar o MacOS é quatro vezes maior que o do Ubuntu no GitHub. Portanto, se for possível executar no Ubuntu, é mais viável financeiramente. Neste caso, executaremos o Flutter de forma anônima, então podemos utilizar o Ubuntu. Definiremos runs-on: ubuntu-latest.

jobs:
  analyze:
    name: Analyze
    runs-on: ubuntu-latest

Com isso, já definimos o nome e onde ele rodará. Agora, definiremos os steps, ou seja, o passo a passo de execução. O primeiro step será entrar no código e fazer um checkout. Definiremos o primeiro step como name: checkout no código. Utilizaremos a action actions/checkout@v4, que é a versão mais estável para essa execução.

    steps:
      - name: Checkout no código
        uses: actions/checkout@v4

Configurando o Flutter e executando testes

Com o código em mãos, configuraremos o Flutter. Utilizaremos a action subosito/flutter-action@v2 para configurar o Flutter. Precisamos definir a versão do Flutter, que será 3.5.6, o canal, que será o stable, e se desejamos cachear para não precisar baixar toda vez que executarmos a action. Isso otimiza o tempo de execução.

      - name: Configurar Flutter
        uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.35.6'
          channel: 'stable'
          cache: true

Com o Flutter configurado, a próxima etapa é executar o Flutter PubGet para baixar todas as dependências e, em seguida, realizar o analyze.

      - name: Flutter pub get
        run: flutter pub get
      - name: Analyse
        run: flutter analyze

Com isso, temos uma estrutura mínima que baixa o código, o Flutter, faz o PubGet e o analyze.

Separando jobs para análise e testes

Podemos agora executar os testes. Podemos separar em dois jobs: um para analisar e outro para executar os testes, ou incorporar todos em um único job. A sugestão é separar, pois facilita a identificação de onde ocorreu o erro. Assim, teremos o analyze em um job e os testes em outro. Os testes são similares ao analyze. Podemos copiar o job de analyze e colar, alterando o nome para testes. Rodaremos no Ubuntu, faremos o checkout do código, configuraremos o Flutter, e se já estiver em cache, ele buscará do cache. Executaremos o Flutter PubGet e, no lugar do analyze, executaremos os testes com run: flutter test.

  tests:
    name: Tests
    runs-on: ubuntu-latest
    steps:
      - name: Checkout no código
        uses: actions/checkout@v4
      - name: Configurar Flutter
        uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.35.6'
          channel: 'stable'
          cache: true
      - name: Flutter pub get
        run: flutter pub get
      - name: Executar testes
        run: flutter test

Ajustando a estrutura dos jobs

Com isso, temos os dois jobs configurados: analyze e testes. Um ponto que passou despercebido é que dentro dos jobs, precisamos instanciar qual é o job, pois não ficou 100% definido onde inicia e termina cada job.

Dentro dos jobs, vamos criar um job chamado analyze. Em seguida, vamos ajustar a indentação de todos os elementos dentro do analyze para garantir que esteja correta. Dentro do analyze, teremos um nameAnalyze, que será executado no Ubuntu, com seus respectivos steps. Antes de executar os testes, também teremos um job de testes que seguirá o mesmo padrão. Precisamos garantir que a estrutura esteja correta, ajustando a indentação de todas as actions e steps do teste.

Agora, temos o job analyze, com seus steps a serem executados, e o outro job, testes, também com seus steps. Estamos na branch de desenvolvimento e vamos fazer o commit dessa correção no GitHub. Vamos realizar um commit com a mensagem "fix implements ci/ci dev". Após adicionar as alterações, sincronizaremos pelo cursor.

Testando o workflow com pull request

Com o workflow configurado, sempre que abrirmos um pull request (PR) para a branch de desenvolvimento, os jobs serão executados. Para testar isso, vamos abrir um PR para a branch de desenvolvimento. Na home page, faremos uma pequena alteração na cor de background, mudando de primary para secondary.

backgroundColor: AppColors.secondary,

Criamos uma nova branch chamada fix/change/home/background/color, adicionamos o arquivo e fazemos o commit com a mensagem "change home background color". Publicamos essa branch e, em seguida, vamos ao GitHub para abrir o PR e verificar a execução.

No GitHub, a branch que subimos é change/home/background/color. Clicamos em "compare and pull request". Em vez de abrir para a main, abrimos para a branch de desenvolvimento. Podemos adicionar uma descrição, mas o objetivo é criar o PR e observar as actions sendo executadas. Após a criação, verificamos que a action de desenvolvimento está em execução. Ao abrir a aba, vemos que temos dois jobs: analyze e test, que serão executados. Aguardamos a execução e, ao finalizar, voltamos para apresentar o resultado.

Corrigindo erros e finalizando o merge

Após a execução, verificamos que o analyze falhou, mas os tests passaram. O analyze falhou devido a pendências no projeto. Ao abrir o job, podemos ver que ele rodou o setup, fez o checkout do código, configurou o Flutter, mas ao executar flutter analyze, ocorreu um erro devido a problemas no projeto. No Flutter, identificamos problemas no drop down input widget, que utiliza uma opção depreciada, e no input widget, que está faltando a variável key como super.

const DropdownInputWidget({
  required this.selectedOption,
  required this.onChanged,
  required this.options,
  super.key,
});

Dentro do PR, podemos ver as execuções cidev-analyze e cidev-tests, mostrando o que ocorreu de certo e errado. Para melhorar a implementação, sugerimos abrir a aba de settings do projeto no GitHub, acessar "Code Automation" e "Branches", e adicionar uma proteção de branch. Definimos o nome da branch a proteger, neste caso, dev, e os requisitos para merge. Exigimos que todos os status checks passem antes do merge.

Dentro das actions, temos home background color com os jobs analyze e tests. Os tests passaram, mas dividimos em dois jobs para ter duas visualizações: uma que deu certo e outra que deu errado. Podemos optar por centralizar todos os jobs em um único job para um processo mais estruturado.

Agora, vamos corrigir os problemas e atualizar o PR. Adicionamos o super e utilizamos initial value no drop down. Corrigimos o input widget para usar super params.

initialValue: selectedOption, // Valor selecionado

const InputWidget({
  required this.controller,
  this.prefix,
  this.hintText,
  this.labelText,
  this.focusNode,
  super.key,
});

Com as correções feitas, fazemos o commit com a mensagem "fix code problems in input widget and drop down input widget". O PR será atualizado automaticamente e as actions serão executadas novamente.

Após a execução, que foi rápida devido ao cache, tudo passou com sucesso. O botão de Merge está habilitado, e vamos realizar o Merge do PR. Com isso, concluímos a primeira etapa do nosso processo de CI/CD, que é a etapa de desenvolvimento. Ao abrir um PR para desenvolvimento, executamos a análise do código e os testes unitários.

Sobre o curso CI/CD Mobile: automação para aplicativos Android e iOS

O curso CI/CD Mobile: automação para aplicativos Android e iOS possui 155 minutos de vídeos, em um total de 28 atividades. Gostou? Conheça nossos outros cursos de Flutter em Mobile, ou leia nossos artigos de Mobile.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda Flutter acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas