🚀

Launchable Advent Calendar 23日目 - custom plugin

2022/12/25に公開

Launchableの各機能の利用方法を紹介する Launchable Advent Calendar 23日目です。

はじめに

launchableinc/cliは現在(12/23時点)、約20個のテストランナーをサポートしています。
しかし、使用しているテストランナーがサポートされてないなかったり、独自のテストランナーを使っている場合、出力するレポートフォーマットが独自な場合もあるかもしれません。

そのためlaunchableinc/cliは独自プラグインの利用もサポートしています。
今日は独自プラグイン(custom plugin)の作成方法と使い方を紹介します。

custom plugin

カスタムプラグインが置いてあるディレクトリを launchable コマンドの --plugins オプションで指定します。
指定したディレクトリの下の *.py ファイルを launchbleinc/cliがcustom profileとして読み込み、利用可能になります。

今日はサンプルとして架空のテストランナー/テストフォーマットに対応するプラグインを作成してみます。

対応するテストランナーはE2Eテストを実行する e2e という架空のコマンドです。
UseCase > Scenario > TestCase という階層でテストを記述でき、UseCase名.Scenario名 のフォーマットでテストランナーに渡すことで個別にテストが実施できます。
また e2e --list でテストの一覧が UseCase名.Scenario名 のフォーマットでテストする一覧が出力できます。

Predictive Test Selection(PTS)を行う場合はこのようなコマンドになります。

launchable --plugins custom-plugin/e2e subset e2e > launchable-subset.txt

テストレポートはXML形式で以下のフォーマットのレポートを出力します。テスト結果のレポート時はこのようなコマンドになります。

launchable --plugins custom-plugin/e2e record tests e2e ./report.xml
report.xml
<result>
  <UseCase name="Buy New Item">
    <Scenario name="Purchase">
      <TestCase name="success" time="3.6" result="success" />
      <TestCase name="soldout" time="1.6" result="error" />
    </Scenario>
  </UseCase>
</result>

カスタムプラグイン

実際に作成したcustom pluginのファイルはこのようになります。
コードはKonboi/launchable-e2e-pluginにもアップしてあります。

このように実行します。

$ cat Konboi/launchable-e2e-plugin/test-list.txt | launchable --plugins Konboi/launchable-e2e-plugin subset --target 100%  e2e
'Buy New Item'.'Purchase','Buy New Item'.'Order'
Launchable created subset 17 for build 1671608132 (test session 23) in workspace Konboi/advent-calendar-2022

|           |   Candidates |   Estimated duration (%) |   Estimated duration (min) |
|-----------|--------------|--------------------------|----------------------------|
| Subset    |            2 |                      100 |                3.33333e-05 |
| Remainder |            0 |                        0 |                0           |
|           |              |                          |                            |
| Total     |            2 |                      100 |                3.33333e-05 |

Run `launchable inspect subset --subset-id 17` to view full subset details

$ launchable --plugins Konboi/launchable-e2e-plugin record tests e2e Konboi/launchable-e2e-plugin/example-report.xml
Launchable recorded tests for build launchable-e2e-test (test session 23) to workspace Konboi/advent-calendar-2022 from 1 files:

|   Files found |   Tests found |   Tests passed |   Tests failed |   Total duration (min) |
|---------------|---------------|----------------|----------------|------------------------|
|             1 |             2 |              1 |              1 |                 0.0867 |
e2e.py
import click
from xml.etree import ElementTree as ET
from typing import Dict, Generator, List, Optional
from ..commands.record.case_event import CaseEvent, CaseEventType

from launchable.test_runners import launchable

@launchable.subset
def subset(client):
    # read lines as test file names
    for t in client.stdin():
        client.test_path(t.rstrip("\n"))

    client.separator = ","
    client.run()


@click.argument('reports', required=True, nargs=-1)
@launchable.record.tests
def record_tests(client, reports):
    for r in reports:
        client.report(r)

    def parse_func(p: str) -> Generator[CaseEventType, None, None]:
        tree = ET.parse(p)


        for use_case in tree.iter("UseCase"):

            if len(use_case) == 0:
                continue

            use_case_name = use_case.attrib.get('name')
            if use_case_name is None:
                continue

            for scenario in use_case.iter("Scenario"):
                if len(scenario) == 0:
                    continue

                scenario_name = scenario.attrib.get('name')
                for testcase in scenario.iter('TestCase'):
                    testcase_name = testcase.attrib.get('name')
                    duration = testcase.attrib.get('time')
                    result = testcase.attrib.get('result')

                    status = 0
                    if result == 'error':
                        status = 1

                    test_path = [
                        {"type": "UseCase", "name": use_case_name},
                        {"type": "Scenario", "name": scenario_name},
                        {"type": "TestCase", "name": testcase_name},
                    ]

                    yield CaseEvent.create(
                        test_path, duration, status, None, None, None, None)

    client.parse_func = parse_func
    client.run()

split_subset = launchable.CommonSplitSubsetImpls(__name__).split_subset()

解説

メソッドの意味や、渡ってくる引数などの細かい説明はここではしません。

基本的には @launchable.subset, @launchable.record.tests デコレータを使ってcustom pluginの機能を追加していきます。
launchableinc/cliclickを利用してコマンドラインのオプションのパースをしています。
そのためclickを利用してcustom pluginにオプションを追加できます。

e2e.py
@launchable.subset
def subset(client):

...

@click.argument('reports', required=True, nargs=-1)
@launchable.record.tests

Subset

subsetでは e2e --list で実行するテスト一覧が取できるので、それをlaunchableinc/cliコマンドに改行を除いて渡しています。
これがLaunchableに渡されPTSが実行されます。

e2e は個別テストをカンマ区切りで渡す必要があるのでデフォルト改行(\n)の区切りを , に変更しています。

Record tests

Launchableはテスト情報を type, name で表現し、階層情報を配列で表現します。
e2eでは Usecase > Scenario > TestCase の階層なので出力されたXMLから階層情報を test_path のフォーマットに直してLaunchableに渡しています。

test_path = [
  {"type": "UseCase", "name": use_case_name},
  {"type": "Scenario", "name": scenario_name},
  {"type": "TestCase", "name": testcase_name},
]

SplitSubsetも launchable subset と同じようにコマンドを定義する必要がありますがここでは省略しています。

さいごに

今日はcustom pluginの作り方を紹介しました。自分の使っているtest runnerがサポートされていない場合は作ることも検討してみてください。
明日はraw profileについて紹介します。

Discussion