okuzawatsの日記

Android / Kotlin / GitHub Actions Enthusiast 🤖

GitHub Actionsで始めるFlutter CI入門

この記事は何か

Flutter でのアプリ開発を始めてみたものの CI をどうしようかと迷っている方、Flutter の CI をやってみたいけどどこから始めていいか分からない方、GitHub Actions を使ってみたいと思っている方を対象として、GitHub Actions を用いて Flutter の CI を構築するひとつの方法を説明します。

なぜ GitHub Actions なのか

GitHub Actions は GitHub のファーストパーティ製サービスであり、GitHub との相性がとても良いです。例えば、GitHub にプルリクエストが作成された時や、GitHub にプッシュされた時など、GitHub 上の様々なイベントを CI/CD のトリガーとすることが可能です。また、GitHub Marketplace 上にたくさんのアクションと呼ばれる定型処理が公開されており、これらを用いることで CI/CD の整備を容易に行うことができます。GitHub のリポジトリ内で CI/CD の管理を完結させることができるため、GitHub 上で日々の開発を完結させることができる点も魅力です。

GitHub ActionsによるFlutterテスト

Widget テスト

Flutter で新規プロジェクトを作成した時、testディレクトリの下にwidget_test.dartというファイルが作られていると思います。Flutter のバージョン 1.22.6 で新規プロジェクトを作成した場合、widget_test.dartの内容は以下のようになっています。このコードは Widget に対するテストコードで、Widget テストと呼ばれるものです。

void main() {
  testWidgets('Counter increments smoke test', (WidgetTester tester) async {
    // Build our app and trigger a frame.
    await tester.pumpWidget(MyApp());

    // Verify that our counter starts at 0.
    expect(find.text('0'), findsOneWidget);
    expect(find.text('1'), findsNothing);

    // Tap the '+' icon and trigger a frame.
    await tester.tap(find.byIcon(Icons.add));
    await tester.pump();

    // Verify that our counter has incremented.
    expect(find.text('0'), findsNothing);
    expect(find.text('1'), findsOneWidget);
  });
}

まずはこの Widget テストを GitHub Actions 上で動かしてみたいと思います。Widget テストを動かすための GitHub Actions のワークフローを作りましょう。プロジェクト直下に.github/というディレクトリを作成し、さらにその下にworkflows/というディレクトリを作成します。この中に yaml ファイルを作成し、yaml ファイルを編集することで GitHub Actions のワークフローを定義します。この yaml ファイルの名前は任意の名前で良いので、今回はwidget_test.yamlとします。プロジェクトの直下から見て、.github/workflows/widget_test.yamlという位置に yaml ファイルができていれば良いです。

yaml ファイルの中身を書いていきます。まずはワークフローの名前を定義します。Widget テストのワークフローなので、「widget test」としておきます。

name: widget test

次に、トリガーを設定します。GitHub Actions のトリガーについては、公式のリファレンスを参照してください1。今回は、mainブランチに向けてプルリクエストが作成された時、及びソースブランチが更新された時にワークフローがトリガーされるように設定します。

on:
  pull_request:
    types: [opened, synchronize]
  push:
    branches:
      - main

最後に、ワークフローの中で実行するアクションを定義していきます。今回はいくつかのアクションを使って、Flutter の Widget テストを動かします。リポジトリのチェックアウトと環境構築を行うアクションとflutter pub getを行ったのち、GitHub 上で Flutter の Widget テストをflutter test test/widget_test.dartとして実行しています。

jobs:
  widget_test:
    name: flutter widget test
    runs-on: ubuntu-latest
    steps:
      - name: set up repository
        uses: actions/checkout@v2
      - name: set up java
        uses: actions/setup-java@v1
        with:
          java-version: '12.x'
      - uses: subosito/flutter-action@v1
        with:
          flutter-version: '1.22.6'
      - name: flutter pub get
        run: flutter pub get
      - name: flutter widget test
        run: flutter test test/widget_test.dart

最終的に、widget_test.yamlはこうなりました。

name: widget test

on:
  pull_request:
    types: [opened, synchronize]
  push:
    branches:
      - main

jobs:
  widget_test:
    name: flutter widget test
    runs-on: ubuntu-latest
    steps:
      - name: set up repository
        uses: actions/checkout@v2
      - name: set up java
        uses: actions/setup-java@v1
        with:
          java-version: '12.x'
      - uses: subosito/flutter-action@v1
        with:
          flutter-version: '1.22.6'
      - name: flutter pub get
        run: flutter pub get
      - name: flutter widget test
        run: flutter test test/widget_test.dart

このワークフローを動かしていきたいですが、まずは Widget テストを失敗させてみましょう。widget_test.dartの 27 行目に以下のコードがあると思います。

expect(find.text('1'), findsOneWidget);

この箇所を以下のように修正します。これで Widget テストが失敗するはずです。

expect(find.text('0'), findsOneWidget);

今回の変更をfeature/create-widget-test-workflowというブランチを作成し、コミットします。コミット後、GitHub にブランチをプッシュし、mainブランチに向けてプルリクエストを作成しましょう。プルリクエスト作成後、GitHub Actions のワークフローが開始されたことを、GitHub 上のプルリクエスト内から確認できます。

Fig.1-1-1 処理中

しばらくすると、以下のように表示が変わり、想定どおりに Widget テストが失敗したことがわかります。

Fig.1-1-2 失敗

失敗の内容は 「Details」→「flutter widget test」と辿ると確認することができます。今回は、以下のようにエラー内容が表示されていました。

This was caught by the test expectation on the following line:
  file:///home/runner/work/flutter-ci-sample-app/flutter-ci-sample-app/test/widget_test.dart line 27
The test description was:
  Counter increments smoke test
════════════════════════════════════════════════════════════════════════════════════════════════════

00:12 +0 -1: Counter increments smoke test [E]
  Test failed. See exception logs above.
  The test description was: Counter increments smoke test

Widget テストに失敗する箇所を修正して再度プッシュすると、再び GitHub Actions のワークフローが開始され、しばらく待つと以下の表示に変わります。緑色のチェックマークが表示され、すべてのチェックが成功したことがわかります。

Fig.1-1-3 成功

ユニットテスト

次に、Flutter のユニットテストを GitHub Actions 上で動かしていきます。まずはpubspec.yamltestを追加します5

dev_dependencies:
  test:

次に、testディレクトリの下にunit_test.dartを作成し、簡単なユニットテストを書いておきましょう。

import 'package:test/test.dart';

void main() {
  test('1 + 1 = 2', () {
    expect(1 + 1, equals(2));
  });
}

ユニットテスト用のワークフローを.github/workflows/unit_test.yamlとして作成します。中身はflutter unit testの箇所を除いて Widget テストのワークフローと同じです(ワークフローを分ける必要はないかもしれませんが、便宜上分けています)。

name: unit test

on:
  pull_request:
    types: [opened, synchronize]
  push:
    branches:
      - main

jobs:
  unit_test:
    name: flutter unit test
    runs-on: ubuntu-latest
    steps:
      - name: set up repository
        uses: actions/checkout@v2
      - name: set up java
        uses: actions/setup-java@v1
        with:
          java-version: '12.x'
      - uses: subosito/flutter-action@v1
        with:
          flutter-version: '1.22.6'
      - name: flutter pub get
        run: flutter pub get
      - name: flutter unit test
        run: flutter test test/unit_test.dart

Widget テストの時と同様に、今回の変更をfeature/create-unit-test-workflowというブランチにコミットし、mainブランチに向けてプルリクエストを作成します。プルリクエスト作成後、GitHub Actions のワークフローが開始し、しばらく待つと Widget テストとユニットテストの両方が成功するかと思います。

Fig.1-2-1 成功

GitHub ActionsによるFlutter lint

lint

Flutter の lint ルールとして、pedantic6を導入します。pedanticは、Google 内部で使われている Dart の静的解析のルールを提供してくれる Dart のパッケージです。

It documents how Dart static analysis is used internally at Google, including best practices for the code we write. https://pub.dev/packages/pedantic

pubspec.yamlpedanticを追加します。

dev_dependencies:
  # 省略
  pedantic: ^1.9.0

pedanticの lint ルールを適用するために、プロジェクトの直下にanalysis_options.yamlを作成し、以下の記述を追加します。この記述を追加することで、Flutter のプロジェクトにpedanticの lint ルールが適用されます。

include: package:pedantic/analysis_options.yaml

lint ルールに違反しているコードがあれば、この時点で IDE 上に警告が表示されます。例えば、main.dartの import 部分を以下のように変更すると、「Duplicate import」という警告が表示されます。lint のワークフローを設定した後、このコードをコミットしてプッシュすると GitHub Actions の lint のワークフローが失敗し、lint チェックが働いていることがわかるかと思います。

import 'package:flutter/material.dart';
import 'package:flutter/material.dart';

lint のワークフローを作成します。基本的には、第 1 章で定義した Widget テストやユニットテストのワークフローと変わるところはありません。最後に実行するコマンドがflutter analyzeとなっています。

name: lint

on:
  pull_request:
    types: [opened, synchronize]
  push:
    branches:
      - main

jobs:
  lint:
    name: flutter analyze
    runs-on: ubuntu-latest
    steps:
      - name: set up repository
        uses: actions/checkout@v2
      - name: set up java
        uses: actions/setup-java@v1
        with:
          java-version: "12.x"
      - uses: subosito/flutter-action@v1
        with:
          flutter-version: "1.22.6"
      - name: flutter pub get
        run: flutter pub get
      - name: flutter analyze
        run: flutter analyze

ワークフローを作成後、プルリクエストの作成時に lint チェックが働くようになり、もし lint エラーがあれば GitHub Actions の CI が失敗するようになります。

danger を用いたプルリクエストへのコメント

次に、danger7danger-flutter_lint8を用いて、lint ルールに違反している箇所を GitHub のプルリクエスト上に自動でコメントするようにします。プロジェクト直下にGemfileを作成し、danger とdanger-flutter_lintの gem を追加します。

source 'https://rubygems.org'

gem 'danger'
gem 'danger-flutter_lint'

次にDangerfileを作成し、danger-flutter_lintに対する danger のルールを設定します。flutter_lint.only_modified_files = trueの部分は、プルリクエストでの差分についてのみコメントするルールです。flutter_lint.report_path = "flutter_analyze_report.txt"の部分は、fluter analyzeの結果を出力するファイルのパスを設定しています。flutter_lint.lint(inline_mode: true)の部分は、プルリクエストの lint ルール違反の箇所に直接コメントするルールです。

flutter_lint.only_modified_files = true
flutter_lint.report_path = "flutter_analyze_report.txt"
flutter_lint.lint(inline_mode: true)

lint のワークフローを修正し、lint ルールの違反があった場合に danger を用いてプルリクエストに対するコメントを行うようにします。

name: lint

on:
  pull_request:
    types: [opened, synchronize]
  push:
    branches:
      - main

jobs:
  lint:
    name: flutter analyze
    runs-on: ubuntu-latest
    steps:
      - name: set up repository
        uses: actions/checkout@v2
      - name: set up java
        uses: actions/setup-java@v1
        with:
          java-version: "12.x"
      - uses: subosito/flutter-action@v1
        with:
          flutter-version: "1.22.6"
      - name: flutter pub get
        run: flutter pub get
      - name: flutter analyze
        run: flutter analyze > flutter_analyze_report.txt
      - name: setup ruby
        if: ${{ failure() }}
        uses: actions/setup-ruby@v1
        with:
          ruby-version: "2.6"
      - name: setup danger
        if: ${{ failure() }}
        run: |
          gem install bundler
          bundle install          
      - name: execute danger
        if: ${{ failure() }}
        uses: MeilCli/danger-action@v5
        with:
          plugins_file: "Gemfile"
          install_path: "vendor/bundle"
          danger_file: "Dangerfile"
          danger_id: "danger-pr"
        env:
          DANGER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}

flutter analyzeの部分をflutter analyze > flutter_analyze_report.txtと修正し、上記で設定したファイルにflutter analyzeの結果を出力するようにします。その後、MeilCli/danger-action9を用いて、lint ルールの違反があった場合にプルリクエストにコメントをするようにしています。

lint ルールの違反があった場合、以下の画像のようにプルリクエストの該当部分にコメントされるようになります。

Fig.2-2-1 lint ルール違反のコメント


  1. https://docs.github.com/ja/actions/reference/events-that-trigger-workflows ↩︎

  2. https://github.com/actions/checkout ↩︎

  3. https://github.com/actions/setup-java ↩︎

  4. https://github.com/subosito/flutter-action ↩︎

  5. https://flutter.dev/docs/cookbook/testing/unit/introduction ↩︎

  6. https://pub.dev/packages/pedantic ↩︎

  7. https://github.com/danger/danger ↩︎

  8. https://github.com/mateuszszklarek/danger-flutter_lint ↩︎

  9. https://github.com/MeilCli/danger-action ↩︎

#GitHub Actions #Flutter

書いている人 😎

profile

茨城県つくば市在住のモバイルアプリケーションアーキテクト(Androidが得意です)。モバイルアプリのアーキテクチャ、自動テスト、CI/CDに興味があります。いわゆる「レガシーコード」のリファクタリング・リアーキテクチャが好きです。

Androidプロジェクトの開発速度低下にお悩みで、お手伝いが必要でしたら、メールフォームよりお気軽にお問い合わせください。

👉 もっと詳しく

著書 ✍

Android MVVMアーキテクチャ入門 🛠

Androidアプリ開発の初学者に向けた、MVVM(Model-View-ViewModel)アーキテクチャの入門書を書きました。初学者の方を確実にネクストレベルに引き上げる技術書です。NextPublishingより出版されています。

販売サイトへ 🏃

Android ユニットテスト ヒッチハイク・ガイド 🧳

Androidアプリのユニットテストに入門するためのガイダンスです。初学者が混乱せずにAndroidアプリのユニットテストを書き始めることができる、ということを目的としています。

販売サイトへ 🏃

関連記事 👀

お問い合わせ 📨

お名前

メールアドレス

お問い合わせ内容