Magic Podのテスト結果をQualityForwardに投入する

# Magic Podのテスト結果をQualityForwardに投入する

## はじめに

自動テストツールのテスト結果をテスト管理ツールに自動投入したい場合、それぞれのAPIを利用して連携する方法があります。今回は、**Magic Pod**の自動テスト結果を**QualityForward（以降QF）**に投入する方法について、サンプルコードを交えて説明します。

APIを使った投入が主題なので、Magic PodとQFの使い方に関する詳細な説明や、投入を自動で行うためのCIツールの利用は割愛しています。

なお今回、Magic PodのAPIを利用する都合と説明簡略化のため、下記の前提を設けています。

- Magic Pod Web APIで取得できるのは1回の実行に対する各テスト結果（succeeded/failedなど）の**件数のみ**で、テストケースごとの結果を取得できない。そのため、次のルールで一括して結果を投入する。
  - 全て`succeed`の場合は対応するテストケース全てに`PASS`を投入
  - 1つでも`succeed以外`があれば対応するテストケース全てに`保留(SKIPPED)`を投入。自動テスト結果を分析後、手動で更新を行うこととする。
- Magic Pod上の1テストケースは、QF上の1テストケースに対応させ、IDを一致させる。IDが一致していない場合は、ID対照表などを用意し補完すること。

試すにあたり、QualityForward Blogの[QualityForwardで手動テストと自動テストを一元管理しよう](https://qf-support.hatenablog.com/entry/2021/05/20/095905)（前編/中編/後編があります）を参考にさせていただきました。手動テストと自動テストを１つのテスト管理ツールで管理する工夫について詳しく書かれています。

## 使用するツール

メインで使う２つのツールについて簡単にご紹介します。

### [Magic Pod](https://magic-pod.com/)

モバイルアプリおよびウェブアプリに対応した**AI自動テストツール**です。

ノーコードでテスト実装が可能なことや、AIを活用したメンテナンス性の高さが特長です。

### [QualityForward](https://www.veriserve.co.jp/qualityforward/)

テストの資産管理、実行管理、結果管理のための**テスト管理ツール**です。

テスト実行の進捗状況や、計画に対する予実、検出されたバグなどを自動的に集計し可視化します。これにより、テスト実行者とテスト管理者のやり取りや集計を効率化することができます。

## 環境

- Windows10 Pro
- Python 3.7.4
  - requests 2.23.0
- Magic Pod 0.83.0
  - [Magic Pod Web API](https://magic-pod.com/api/v1.0/doc/)
- QualityForward

## 大まかな流れ

1. QF上でテストスイート・テストフェーズを作成する
2. Magic Podの自動テストを実行する
3. Magic Podのテスト結果をQFに投入する

## 1. QF上でテストスイート・テストフェーズを作成する

自動投入を行うための準備として、QF上でテストスイートとテストフェーズを作成します。

### 1-1. テストスイートの作成

QF上の識別IDとMagic Pod上のテストケースIDが等しくなるようにテストケースを定義します。必要に応じて、検証ポイントや機能区分といった項目を自由に設定してください。

**使用するテストスイートの項目（全てQFデフォルト項目を使用）**

| 項目名       | 内容                                                 |
| ------------ | ---------------------------------------------------- |
| 識別ID       | Magic Podと対応するテストケースIDとして扱う          |
| 優先度       | 集計単位のひとつ。手動/自動の区分に使うとよい        |
| テスト結果   | PASS, FAIL, 保留, Delete, E, N/A, Q                  |
| テスト実施者 | 自動テスト作成者のQFユーザID                         |
| テスト実施日 | 自動テスト実行開始日                               |
| 環境         | Magic Podのテスト結果URLを入力する。項目名は変更可能 |

### 1-2. テストフェーズを作成する

作成したテストケースを紐づけて、テストフェーズを作成します。


## 2. Magic Podの自動テストを実行する

Magic Pod Web APIを用いて自動テストを実行します。APIリクエストの発行には、`requests`ライブラリを使用します。

```sh
$ pip install requests
```

### 2-1. リクエストに必要なパラメータを指定

**必要なパラメータ**

以下の項目を指定し、`mp_api_url`としてURLを設定します。プロジェクト画面のURLを参照すると分かりやすいです。

例: `https://magic-pod.com/HogeHoge/SampleProject/`

| 項目名         | 変数名            | 備考                                       | 例              |
| -------------- | ----------------- | ------------------------------------------ | --------------- |
| 組織名         | organization_name | -                          | HogeHoge        |
| プロジェクト名 | project_name      | -                  | SampleProject   |
| テストケースID | test_cases_num    | 実行するテストケースのID。リスト形式で指定 | ["1", "2", "5"] |
| 一括実行設定   | post_data         | Request bodyに指定する一括実行設定         | -               |
| APIトークン    | mp_api_key        | ユーザメニューの「APIトークン」から取得    | -               |

```python
import requests
import json

base_url = "https://magic-pod.com/api/v1.0/"
organization_name = "HogeHoge"
project_name = "SampleProject"
test_cases_num = ["1", "2", "5"]
mp_api_key = "xxxxx"
```

### 2-2. 自動テストを実行

`requests.post()`メソッドでPOSTリクエストを発行し、自動テストを実行します。

```python
# URL
mp_api_url = (
    base_url
    + organization_name + "/"
    + project_name + "/"
    + "batch-run/"
    )

# 実行設定: 必要に応じて項目を追加
post_data = {
  "environment": "magic_pod",
  "os": "ios",
  "device_type": "simulator",
  "app_type": "installed",
  "bundle_id": "com.apple.MobileAddressBook",
  "test_case_numbers": ",".join(test_cases_num)
}

# 自動テストを実行
res = requests.post(
    mp_api_url,
    headers={
        "accept": "application/json",
        "Authorization": "Token " + mp_api_key,
        "Content-Type": "application/json"
    },
    data=json.dumps(post_data)
    )
```

### 2-3. GETリクエストを発行し、テスト結果を取得

`requests.get()`メソッドでGETリクエストを発行し、Magic Podで実行された**最新のテスト結果**をjson形式で取得します。取得したテスト結果はQFへの投入に使用するため、`変数 mp_results`に格納します。

```python
# URL
mp_api_url = (
    base_url
    + organization_name + "/"
    + project_name + "/"
    + "batch-runs/?count=1"
    )

# テスト結果を取得
res = requests.get(
    mp_api_url,
    headers={
        "accept": "application/json",
        "Authorization": "Token " + mp_api_key
        }
    )

mp_results = res.json()["batch_runs"][0]
```

※自動テスト実行後、最新のテスト結果を取得するまでに待機したい場合は、`time`モジュールなどを使ってもよいでしょう。`mp_results["status"]`で自動テストの実行状況（running, succeeded, failed, aborted, unresolved）を取得できるため、「`running`の間待機させる」といった方法があります。

## 3. Magic Podのテスト結果をQFに投入する

QF Web APIを用いて、QFのテストサイクルに対してMagic Podのテスト結果を投入します。

今回は簡略化のため既存のテストサイクルを使用しますが、Magic Podのテスト実行のタイミングでテストサイクルを自動生成したい場合もあると思います。その場合は、テストサイクルを作成するAPIリクエストを利用するとよいでしょう。

```POST /api/v2/test_phases/:test_phase_id/test_suite_assignments/:test_suite_assignment_id/test_cycles.json```

※テストフェーズの設定で「テストサイクルの自動生成」をONにし、フェーズ作成日を開始日として2週間で自動生成することもできます。

### 3-1. リクエストに必要なパラメータを指定

**URLに指定するパラメータ**

以下の値を指定し、`qf_api_url`としてURLを設定します。テストサイクル画面のURLを参照すると分かりやすいです。

例: `https://cloud.veriserve.co.jp/projects/0000/test_phases/1111/test_suite_assignments/2222/test_cycles/3333`

| 項目名           | 変数名                    | 備考                                         | 例    |
| ---------------- | ------------------------- | -------------------------------------------- | ----- |
| テストフェーズID | test_phases_id            | -                                            | 1111  |
| テストスイートID | test_suite_assignments_id | -                                            | 2222  |
| テストサイクルID | test_cycles_id            | -                                            | 3333  |
| APIトークン      | qf_api_key                | プロジェクトダッシュボードの「設定」から発行 | xxxxx |

```python
# パラメータ例
base_url = "https://cloud.veriserve.co.jp/api/v2/"
test_phases_id = "1111"
test_suite_assignments_id = "2222"
test_cycles_id = "3333"
qf_api_key = "xxxxx"

# URL
qf_api_url = (
    base_url
    + "test_phases/"
    + test_phases_id
    + "/test_suite_assignments/"
    + test_suite_assignments_id
    + "/test_cycles/"
    + test_cycles_id
    + "/test_results?api_key="
    + qf_api_key
    )
```

**Request bodyに指定するパラメータ**

Magic Podのテスト結果を加工し、QFに投入するテスト結果として以下の値を指定します。

- `"test_result[result]"`
  - テスト結果（PASS, FAIL, Skipped, Delete, E, N/A, Q）
  - `mp_results["status"]`の値を、ルールに基づいて`qf_results`に変換し使用
- `"test_result[test_case_no]"`
  - QFのテストケース識別ID。Magic PodのテストケースIDと対応する
  - テスト実行時に指定した`test_cases_num`の要素を使用
- `"test_result[user_id]"`
  - テスト実施者。テスト結果を投入するユーザのID
  - `https://cloud.veriserve.co.jp/api/v2/users?api_key=qf_api_key`を使ってあらかじめ取得しておく
- `"test_result[executed_at]"`
  - テスト実施日。Magic Podの実行開始日を指定
  - `mp_results["started_at"]`の値を使用
- `"test_result[content1]"`
  - 環境。Magic Podのテスト結果URLを入力する。項目名は変更可能
  - `mp_results["url"]`の値を使用


```python
# パラメータ例
user_id = "999"

# 全て`succeed`の場合は対応するテストケース全てに`PASS`を投入
# 1つでも`succeed以外`があれば対応するテストケース全てに`保留(SKIPPED)`を投入
qf_results = 1 if mp_results["status"] == "succeeded" else 3
```

### 3-2. POSTリクエストを発行し、テスト結果を投入

`requests.post()`メソッドでPOSTリクエストを発行し、Magic Podのテスト結果（`mp_results`）をQFに投入します。


```python
for i, case in enumerate(test_cases_num):
    # テスト結果
    post_data = {
        "test_result[test_case_no]":case,
        "test_result[result]":qf_results,
        "test_result[user_id]":user_id,
        "test_result[executed_at]":mp_results["started_at"],
        "test_result[content1]":mp_results["url"]
    }

    # テスト結果を投入
    res = requests.post(
        qf_api_url,
        headers={"Content-Type": "application/x-www-form-urlencoded"},
        data=post_data
    )

    time.sleep(1)  # 1秒間に1リクエストを超えないように設計
```

## おわりに

今回は少し不完全燃焼で、前提条件ありの連携になりました。が、将来的にMagic Pod Web APIでテストケースごとの結果が取得できるようになれば、ほとんど同じ方法でMagic PodとQFのテスト結果を同一に投入できると思うのでここに残しておきます。

また、API以外のMagic Pod Desktop等を使った手段は試せていません。機会があれば試してみようと思います。

## 参考
- [【導入事例の紹介】 QualityForwardで手動テストと自動テストを一元管理しよう <前編> - QualityForward Blog](https://qf-support.hatenablog.com/entry/2021/05/20/095905)
- [API経由でテスト結果を投入する - QualityForward Blog](https://qf-support.hatenablog.com/entry/2020/03/19/120857)
- [Magic Pod Web API](https://magic-pod.com/api/v1.0/doc/)