Flutterの自動テストをGithub Actionsで実行する

Flutterのユニットテストウィジェットテスト、インテグレーションテスト(エミュレータを使ったテスト)をGithub Actionsで実行する方法についてです。

対象のディレクトリ構成

ユニットテストウィジェットテストのワークフロー

上記ディレクトリファイルにて、testディレクトリのユニットテストウィジェットテストをGithub Actionsで実行する場合、次のようなワークフロー定義ファイルflutter_test.yamlを作成します。
push、プルリクをトリガに、単にflutter実行用のアクション上で、テスト実行コマンドを実行しています。

name: flutter component test

on: [push, pull_request]

jobs:
  component_test:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v2
      - uses: subosito/flutter-action@v1
        with:
          channel: 'stable'
      - run: flutter pub get
      - run: flutter test

インテグレーションテストのワークフロー

次にtest_driverディレクトリのインテグレーションテストをGithub Actionsで実行する場合、次のようなワークフロー定義ファイルflutter_integration_test.yamlを作成します。
この例では「iPhone 12 Pro Max (14.2)」「iPhone 12 (14.2)」のシミュレータでテストを実行します。処理としては、xcrunで利用可能なシミュレータ一覧を取得し、awkでそこから対象のUDIDを取得してエミュレータを起動します。そしてflutter driveコマンドでテスト対象・テストコードを実行します。

name: flutter integration test

on: [push, pull_request]

jobs:
  integration_test:
    strategy:
      matrix:
        device:
          - "iPhone 12 Pro Max (14.2)"
          - "iPhone 12 (14.2)"
      fail-fast: false
    runs-on: macos-latest
    steps:
      - name: "start simulator"
        timeout-minutes: 10
        run: |
          UDID=$(
            xcrun instruments -s |
            awk \
              -F ' *[][]' \
              -v 'device=${{ matrix.device }}' \
              '$1 == device { print $2 }'
          )
          xcrun simctl boot "${UDID:?No Simulator with this name found}"
      - uses: actions/checkout@v2
      - uses: subosito/flutter-action@v1
        with:
          channel: 'stable'
      - run: "flutter drive --target=test_driver/app.dart"

アジャイルテストでの計画・管理のツール:Test MatrixとTest Mindmap

ソフトウェアテストの小ネタ Advent Calendar 2020」の記事です。

Agile Testing CondensedMore Agile Testingでは、リリーステスト(システム全体を対象とするテストレベル)の計画のやり方として、「リリース全体を俯瞰する視点で作成する。計画の作成・運用ではテストマインドマップ(Test Mindmap)やテストマトリクス(Test Matrix)を活用できる」のような解説がされています。そして下図でその流れを紹介しています。

f:id:goyoki:20201216031526p:plain

執筆時点で日本語の解説がなく気になったので、今回はここで言及されている、計画と管理のツールであるテストマインドマップとテストマトリクスの内容についてメモしたいと思います。

共通する特徴

テストマインドマップ、テストマトリクスいずれも、マネジメントとチームのコラボレーションを支えるためのモデルとして活用します。具体的には、話し合いながら、チームでアイデアを出し、チームでアクティビティを共有し、チームで進行状態を共有するための手段として、テストマインドマップ、テストマトリクスを活用しています。

テストマインドマップ

モデルの形式

テストマインドマップは、テスト実行のアクティビティをマインドマップのノードで表現したモデルです。

次のようなモデルになります。

f:id:goyoki:20201216031541p:plain

モデルの目的

次の目的で作成します。

  • チーム全体のテストのアクティビティ抽出の助けにする。例えばマインドマップでアイデアを発散させながら、チームメンバーでアクティビティを抽出する作業に用いる。
  • チームのテストのアクティビティの全体像を高位レベルで可視化する。
  • チームのテストのアクティビティの進捗情報を高位レベルで可視化する。具体的にはマインドマップのノードにアクティビティ完了のマーカーを付与して、完了したアクティビティを可視化する。

テストマトリクス

モデルの形式

テストマトリクスは、縦軸にテスト対象項目(アジャイル開発ならフィーチャ等。フィーチャを縦軸にしたものはフィーチャテストマトリクスと呼称)、横軸に抽象的なテスト条件あるいはテストケイパビリティ(テストチームが実施可能なテストタイプ)を配置したマトリクスを用います。
マトリクス縦軸・横軸の形式は、ゆもつよメソッドのテスト分析マトリクスや、QUINTEEのテストマップと類似しています。ただテストマトリクスでは、マトリクス中の項目に、さらに以下の情報を表現します。

  • テスト実行の対象か、対象外かの情報
  • テスト実行の対象ならば、その優先度と進行状態の情報

テストマトリクスは例えば次のようなモデルとなります。今回はセルの背景色で上記の情報を表現しています(More Agile Testingでは背景色と背景パターンの2つを使って表現を使い分けています)。

f:id:goyoki:20201216031531p:plain

モデルの目的

マトリクスの横軸・縦軸はテスト分析マトリクスやテストマップと類似していますが、目的・用途は異なります。
このテストマトリクスは次の目的で作成します。

  • チームのテストのアクティビティの全体像を高位レベルで可視化する。
  • チームのテストのアクティビティの優先度と進行状態を可視化する。具体的には、次に優先すべきテストの明示化、未着手or残作業ありor完了の進行状態の明示化を行う。

機械学習による決定木分析でクラシフィケーションツリーを洗練させる

機械学習の手法の一つである決定木分析を使うと、入出力データから、対象の内部ロジックをある程度推測できるようになります。これはデバッグやテストの洗練に活用できる余地があります。

今回はその一例として、決定木分析の主要なアルゴリズムであるCART法を使って、クラシフィケーションツリー法でのクラシフィケーションツリーを洗練させるアプローチを説明します。

題材

イメージとしては、特定条件でログにワーニングやエラーが記録されるかの確認を行うような、ログ機能のテストを想定します。
テストはクラシフィケーションツリー法を使って作成します。作成したクラシフィケーションツリーは以下の通りです(処理をシンプルにするため簡略化した例を用います)。

f:id:goyoki:20200705230642p:plain

今回は、この作成したクラシフィケーションツリーが妥当かチェックするため、テスト実行時に決定木分析を使う場面を扱います。目的としては、例えばテスト設計の妥当性の確認や、リグレッションテストの洗練などを想定します。

データの取得

まず実際にテスト対象を動かして、なるべく多くの入出力データを取得します。

取得対象ですが、クラシフィケーションを取得対象データとします。また取得データはCART法を適用できるように、クラスを参考にして数値化します(例えばクラスが真偽なら1、0で記録します)。

データ取得範囲については、実行空間を全網羅する入力の全組み合わせを実現するのが理想です。ただ一般的に実現不可能なので、QuickCheckのように全体を大まかに網羅するランダムデータを生成してデータ取得します。

例えば以下のようなデータを取得します。

# インプットデータ:
input_name = ["input1", "input2", "input3", "input4"]
inputs = [
    [0, 0, 0, 88],
    [1, 1, 0, 2],
    [0, 1, 1, 26],
    [0, 0, 1, 55],
    [1, 0, 0, 29],
    [1, 1, 0, 12],
    ・・・
]
# 計測したアウトプットデータ:
# (0:"non-error", 1:"error1", 2:"error2", 3:"error3", 4:"warning1"):
output_name = ["non-error", "error1", "error2", "error3", "warning1"]
outputs = [
    0,
    4,
    0,
    4,
    4,
    3,
    ・・・
]

決定木の生成

次に取得したデータからCART法で決定木を生成します。そしてクラシフィケーションツリーの評価のため、決定木のイメージと正解率を求めます。
scikit-learnでの実装は次のようになります。

from io import StringIO
from sklearn import tree
import pydotplus
from sklearn.metrics import accuracy_score
 
def create_tree(inputs, input_name, outputs, output_name):
    clf = tree.DecisionTreeClassifier(max_depth=5) #仕様規模に応じてmax_depthを制限する。
    clf = clf.fit(inputs, outputs)

    dot_data = StringIO()
    tree.export_graphviz(clf, out_file=dot_data,
        feature_names=input_name, class_names=output_name,
        filled=True, rounded=True)
    graph = pydotplus.graph_from_dot_data(dot_data.getvalue())
    graph.write_png('./tree.png')

    predicted_outputs = clf.predict(inputs)
    accuracy = accuracy_score(outputs, predicted_outputs)
    print(f'accuracy:{accuracy}')

前述したデータで決定木を生成すると次が得られます。

f:id:goyoki:20200705230808p:plain

また得られた正解率は今回は0.73となりました。

クラシフィケーションツリーの評価と改善

そして次に、得られた決定木と正解率から、クラシフィケーションツリーを評価します。

以下に該当する場合、不具合の可能性(あくまでこの決定木は既存実装から機械学習で生成したものであり、正しい仕様に基づいたものではない点には注意が必要です)があるか、クラシフィケーション・クラスの抽出漏れの可能性があります。

  • 仕様と比べて決定木が複雑
  • 正解率が低い

 
また、以下に該当する場合、不具合あるいはデータ不足の可能性があるか、クラスの分け方に問題がある可能性があります。

  • 決定木の条件と、クラスの境界が異なる

 
また、以下に該当する場合、不具合あるいはデータ不足の可能性があるか、冗長なクラシフィケーションがある可能性があります(ただ不具合・データ不足の可能性の存在から、冗長であるとしてクラシフィケーションを削除するのは安易に行なえません)。

 

今回は決定木が複雑すぎるのと、正解率が低すぎる点が見受けられます。すなわちクラシフィケーションの抽出漏れの可能性があります。そこで、クラシフィケーションツリーを見直し、次のようにクラシフィケーションを追加します。

f:id:goyoki:20200705230933p:plain

再評価

このクラシフィケーションツリーに基づいて入出力データを再取得し、前述のコードで決定木を生成した結果が以下になります。

f:id:goyoki:20200705230953p:plain

また正解率は1.00となりました。
決定木がシンプルになったのと、正解率が高まった点から、クラシフィケーションの抽出漏れがあり、改善によりそれが是正されたと推測できます。

設計向けとテスト設計向けの適切な仕様表現の差異(デシジョンテーブルを例として)

設計向けとテスト設計向けでは、適切な仕様表現について認識に差異が出ることがあります。

題材として、以下の仕様をデシジョンテーブルで表現する場合を考えます:

題材とする仕様

  • 組み込み機器の単軸制御モード機能が対象

  • この機能は次の3つの入力を持つ
    • 省電力化モード(とり得る値:ON、OFF)
    • 制御方針(とり得る値:精密重視、速度重視)
    • 目標との距離(とり得る値:整数値[cm])

  • この機能は次の出力を持つ
    • 速度モード(とり得る値:高速モード、中速モード、低速モード)

  • この機能は出力の速度モードを次のように決定する
    • 省電力化モードがONの場合、低速モードで固定する
    • 省電力化モードがOFFの場合、以下に従う
      • 目標との距離が10cm未満の場合、低速モードを選択する
      • 目標との距離が10cm以上かつ、制御方針が精密重視なら、中速モードを選択する
      • 目標との距離が10cm以上かつ、制御方針が速度重視なら、高速モードを選択する

 

設計向けのデシジョンテーブルの例

設計では、実装の方針がわかる前提下ならば、デシジョンテーブルを簡単化しても支障はありません。むしろ必要十分な範囲内で情報を簡単化した方が、都合が良い場合が多いです。

今回の仕様では、以下のようにデシジョンテーブルを表記できます。

f:id:goyoki:20200628225659p:plain

 

テスト設計向けのデシジョンテーブルの例

一方、テスト設計向けに、デシジョンテーブル法に基づいてテーブルを作る場合、上記のようにデシジョンテーブルを簡単化できるとは限りません。どこまで簡単化できるかが、処理順序や品質リスクに依存するためです。

まず以下のような実装が行われている場合について考えます。

# 速度モード設定処理()にはモードに応じた障害のリスクがあるとする

def 単軸制御モード機能(省電力化, 制御方針, 目標との距離):
    if 省電力化 == ON:
        速度モード設定処理(低速モード) #1
        return

    if 目標との距離 < 10:
        速度モード設定処理(低速モード) #2
        return
    
    if 制御方針 == 精密重視:
        速度モード設定処理(中速モード) #3
        return
    else:
        速度モード設定処理(高速モード) #4
        return

この場合、シンプルな分岐構造で仕様が実装されているため、前述の簡単化デシジョンテーブルを使っても問題ありません。テスト技法に則り、デシジョンテーブルの各列をテストケースに展開すれば、すべての速度モード設定処理()を網羅するテストケースを用意できます。

一方で、次のような実装が行われている場合について考えます。

# 速度モード設定処理()にはモードに応じた障害のリスクがあるとする

def 単軸制御モード機能2(省電力化, 制御方針, 目標との距離):
    if 制御方針 == 精密重視:
        if 省電力化 == ON:
            速度モード設定処理(低速モード) #1
        elif 目標との距離 < 10:
            速度モード設定処理(低速モード) #2
        else:
            速度モード設定処理(中速モード) #3
        return
    else:
        if 省電力化 == ON:
            速度モード設定処理(低速モード) #4
        elif 目標との距離 < 10:
            速度モード設定処理(低速モード) #5
        else:
            速度モード設定処理(高速モード) #6
        return

この実装の場合、前述の簡単化したデシジョンテーブルに従ってテストケースを導出すると、一部の速度モード設定処理を網羅できず品質リスクが残ります。

この実装に基づいて適切にデシジョンテーブルを簡単化するならば、一例として以下のようなものになるでしょう。

f:id:goyoki:20200628225724p:plain

また機能の処理が複雑なコードで分散実装されていたり、見えにくい品質リスクが散在していたりする場合だと、実装から順序性や品質リスクが読み取れなくなる場合があります。そうなると、デシジョンテーブルは全く簡単化できない場面もあり得ます。

すなわち、設計と同じように簡単化できない場合があるということです。

なおこの話の参考文献として、 ソフトウェアテスト技法練習帳 に解説があり、お勧めできます。

 

設計向けとテスト設計向けの適切な仕様表現の差異

テストの導出元にするデシジョンテーブルをどこまで簡単化できるかは、順序性、広く言えば品質リスクに依存します。そしてテスト設計ではより包括的に品質リスクに対応する必要があることから、設計のための仕様より、簡単化できない場面が多いです。言い換えると、テスト設計では、設計と比べ、より広い視点で、情報が補強された仕様表現が求められる場面が多いです。

なお「より広い視点で、情報が補強された仕様表現が求められる」といっても、現実でそれを十分に実現できる場面は多くありません。テスト用に仕様を作り直すコストはないのが普通ですし、そもそも品質リスクは広く・見えにくく潜在していて、仕様化ですべて対応できないためです。その現実に立ち向かうため、以下のような活動が重要になってくると感じます。

  • 設計や実装でのリスクコントロールの工夫。少ないテストでも大丈夫なように作る。
  • 探索的テストによる補完。仕様の情報不足を、テストエンジニアの能力や学習結果で補完する。
  • グレーボックステストの拡充。設計やコードの構造に基づいてテスト設計の穴を補強する。
  • 無則のテストの導入。ペアワイズ法などで仕様化されていない条件もテストする。
  • 品質保証の重ね合わせ。コードレビュー、ユニットテストシステムテスト、専任チームによる品質保証などを組み合わせて、品質リスクの見落としを減らす。

UTP2(UML Testing Profile 2)でテスト設計の成果物を一通り表現する

 UMLのプロファイル(特定用途向けに、ステレオタイプ、タグ付き値、モデル要素の制約・関係性の拡張をくわえたもの)の中に、ソフトウェアテストのためのプロファイルUTP2(UML Testing Profile 2)があります。

 UTP2は、テストでモデルベースドアプローチを実現して、ソフトウェアテストの各種活動でモデリングの恩恵を受けることを目的としています。恩恵とは、例えばテスト分析・設計での思考をモデルで補助する、トレーサビリティを明示化する、テスト成果物のレビューなどの協業を支える、モデル駆動などのツールの恩恵を得る、といったものです。
 UTP2の内容は、UMLによる開発のモデリングのこれまでの蓄積を活かして、テスト環境とテスト実行の仕様定義が多めとなっています。
 現状は知名度・普及度は低い状態です。今後普及するかは、使い勝手の良いモデリングツールが出てくるかに依存していると思います。

 今回はUTP2の紹介として、テスト設計の一通りの活動の成果物を、UTP2で表現していきたいと思います。

前置き

 今回は以下の2.1betaに基づいてテスト設計を行います。

UML Testing Profile 2.1beta
https://www.omg.org/spec/UTP2/About-UTP2/

 なおUTP2は仕様が大きく、使用する際はテーラリングした上での部分採用の形をとることになります(実際、UTP2仕様書中のExampleは、それぞれでかなり仕様を絞り込んでいて表現がばらばらになっています)。今回の例示もUTP2の一部の仕様のみを使用しています。

題材

 今回は医療用デジタル体温計のプロダクトシステムを対象とします。

UTP2に基づいたテストの活動

テストコンテキストの定義

 UTP2ではまずテストの活動をテストコンテキストの集まりで表現します(注:ここでいうテストコンテキストは、UTP2独特の定義づけがされた言葉です)。

 テストコンテキストは、以降のテストの活動が行いやすいように、テストレベルやテストタイプなどを基準に、テストの活動を分けたものです。テストコンテキストは、テストの成果物を含む、テストの活動にかかわる情報の集合として表現されます。

 比喩を使うと、例えばシステムを開発する際は、システムをサブシステムに分割し、サブシステムごとに分かれて開発を進めます。そこでいうシステムとサブシステムの関係が、テスト活動全体とテストコンテキストの関係に当てはまります。

 テストコンテキストはTestContextをステレオタイプにもつパッケージと定義されており、パッケージ図でモデリングします。

 図中のとおり、テストコンテキストはテストタイプとテストレベルをパラメータとして持っています。
 今回はこのうち、システムテストの機能正確性テストに属する、温度計測精度テストに絞ってテスト設計を進めます。

 なおテストコンテキストを定義するためには、その前にテストレベルやテストタイプの分析・計画が必要です。UTP2ではテストタイプとテストレベルをValueSpecificationと定義しており、分析はクラス図あるいはオブジェクト図で行うことになります。ただそれらテストコンテキスト定義以前の活動については、UTP2でほとんど扱われていないため今回は省きます。

テスト目的とテスト要求の分析

 次にテストコンテキストごとにテスト分析・設計を進めていきます。

 まず、テストコンテキストのテスト目的を、以降のテスト分析ができるようになるまで具体化します。テスト目的はTestObjectiveのステレオタイプを付与したクラスと定義されており、クラス図でモデリングしていきます。

 テスト目的を具体化したら、その目的に基づいてテスト要求を定義します。テスト要求は、テストしたいと思えるテスト対象の特質です。テスト目的を満たすように、テスト対象の要求を参照しながら定義します。テスト要求はTestReqirementのステレオタイプを付与したクラスと定義されており、クラス図でモデリングしていきます。

テスト設計

 次に、テスト要求とテスト目的からテスト設計方針を立て、その方針に基づいてテストケースを作成します。モデルにおいては、テスト設計方針にはTestDesignDirective、テストケースにはTestCaseのステレオタイプを付与して表現します。
 テスト要求・テスト設計方針・テストケースの関係性については、2.1beta時点のUTP2仕様書では仕様定義と文章による解説が中心で、一貫性を持った例示を行っていません。ただ以下のようなモデルで表現できると思います。

 なお上記ではテスト要求との関係を表現するためクラス図を用いていますが、TestDesignDirectiveはインスタンスと定義されており、それ単体でモデリングする際はオブジェクト図を用いることになります。

 別の活用例として、SysMLのようにテスト要求モデルにテストケースを関連付けて、両者のトレーサビリティをモデルで表現する方法も提示されています。またテスト設計方針や、テストケースの集まりであるテストセットは、オブジェクト図やパッケージ図で構造化しながら設計を進めることもできます。

テスト環境の設計

 一方、テストを実行するためのテスト環境をモデリングします。テスト環境はTest Configurationとして表現されます。テスト対象にTestItem、テスト環境のコンポーネントにTestComponentのステレオタイプを付与して、クラス図やコンポジット構造図でモデリングします。


テスト実装

 そしてテストケースごとにテストケースの手順を作成します。
 手順はTestCaseのステレオタイプを付与したふるまいモデルで表現します。今回は仕様書の例示に従ってシーケンス図でモデリングしています。

Prometheusでファイルサービスディスカバリを使って監視対象を動的に変える

監視対象が動的に増減するような状況においてPrometheusで監視を行う場合、サービスティスカバリ機能が便利です。今回はその例として、Prometheusのファイルサービスディスカバリで監視対象を動的に変更する方法についてまとめます。

動作環境の構築

まずdocker-composeで最低限の環境を構築します。
次のディレクトリ構成を用意します。

docker-compose.yaml 
prometheus/
  prometheus.yaml
  targets.json

docker-compose.yamlは次の通りです。prometheusとnode-exporterのdockerコンテナを用意します。

version: '3'
services:
  prometheus:
    image: prom/prometheus
    container_name: prometheus
    volumes:
      - ./prometheus:/etc/prometheus
    command: "--config.file=/etc/prometheus/prometheus.yaml"
    ports:
      - 9090:9090
    restart: always
  exporter:
    image: prom/node-exporter:latest
    container_name: node-exporter
    ports:
      - 9100:9100
    restart: always

prometheus.yamlは次の通りです。
ファイルサービスディスカバリを使用する場合、scrape_configsにfile_sd_configsを指定し、その中のfilesにglob形式でターゲットファイルへのパスを記入します(今回は*.json。このyamlファイルからの相対パスで記述)

global:
  scrape_interval: 15s
scrape_configs:
  - job_name: 'node'
    file_sd_configs:
      - files:
        - '*.json'

すると、prometheusはglob形式で指定したファイルを探し出して監視対象に加えてくれるようになります。

targets.jsonは次の通りです。

[
  {
    "labels": {
      "job": "node"
    },
    "targets": [
      "node-exporter:9100"
    ]
  }
]

prometheusで最初の状態を確認する

「docker-compose up -d」でprometheusとnode-exporterを立ち上げます。
「http://【実行環境のアドレス】:9090/graph」にアクセスし、Expressionに「up{job="node"}」を入力してExecuteします。すると以下の結果が出力されます。

Element	Value
up{instance="node-exporter:9100",job="node"}	1

targets.jsonで指定した監視対象がTargetに加えられていることがわかります。

ターゲットファイルを追加して監視対象を変更する

次にprometheusを実行したまま監視対象を変えてみます。
targets.jsonと同じディレクトリに次のtargets2.jsonを追加します。

[
  {
    "labels": {
      "job": "node"
    },
    "targets": [
      "prometheus:9090"
    ]
  }
]

再度prometheusのgraphページにアクセスし「up{job="node"}」をExecuteします。すると以下の結果が出力されます。

Element	Value
up{instance="node-exporter:9100",job="node"}	1
up{instance="prometheus:9090",job="node"}	1

targets2.jsonでの追加分が、監視対象に反映されていることがわかります。


Github Actionsでテストや静的解析に失敗するプルリクエストをマージできないようにする

Github Actionsを活用するとGithub内でCIを完結できます。その活用例として、今回はプリリクエスト作成時にユニットテストと静的解析を実行し、それらが成功しないとプルリクエストのマージを拒絶する仕組みをGithub Actionsで作ります。

対象コード

対象ソースコードは以下です。

https://github.com/hiro-iseri/blog_demo

プロダクトコードをdemoディレクトリに、ユニットテストをtestsディレクトリに格納する構成をとります。

python-package.ymlの作成

/.github/workflows/ディレクトリにpython-package.ymlを作成し、Github Actionsの設定を記述します。
今回の設定内容ですが、Python3.8環境にて、プロダクトコードに対し、flake8で静的解析、標準unittestでtestsディレクトリのユニットテストを実行します。
これをプルリクエストの作成時、更新時に実行するように設定します。
以下記述例です。

name: demo

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  build:

    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: [3.8]
    steps:
    - uses: actions/checkout@v2
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v1
      with:
        python-version: ${{ matrix.python-version }}
    - name: Lint
      run: |
        pip install flake8
        flake8 demo --show-source
    - name: Test
      run:
        python -m unittest discover tests

プルリクエスト画面で静的解析・ユニットテスト結果を表示する

上記yamlをpushすると、プルリクエストの作成・更新時に、Github Actionsが実行されるようになります。
そしてプルリクエストのマージ画面で次のように静的解析・ユニットテスト結果が付記されるようになります。

no_block

静的解析・ユニットテスト失敗時にプルリクエストのマージをブロックする

次に、masterブランチへのプルリクエストを対象に、静的解析・ユニットテスト失敗時のブロックを有効化します。
設定方法ですが、GithubプロジェクトのSettingsタブ→Branchesメニューを選択します。
Branch name patternの対象をmasterとし、「Require status checks to pass before merging」「Require branches to be up to date before merging」を有効化して、前述のyamlで定義したbuildにチェックを入れます。

この設定を有効化すると、masterに対するプルリクエストについて、静的解析・ユニットテストが失敗するものはマージがブロックされるようになります。