🔎

テスト管理システム Kiwi TCMSの調査

に公開

はじめに

このドキュメントではKiwi TCMSというテスト管理ツールについての調査結果をまとめます。

このドキュメントの対象読者はテスト計画、テストケースなどのドキュメントについて知見があり、テスト実行の管理ツールを探している人物を対象とします。

このドキュメント中で実験した環境は以下で再現できます。

https://github.com/mima3/research_tms/tree/main/kiwi

Kiwi TCMSとは

Kiwi TCMSはオープンソースのテスト管理システムです。
以下のような機能を有しています。

  • テスト計画の作成
  • テストケースの作成
  • テスト実行の割り当てと実施記録
  • 簡易なバグ管理システム/Jiraなどの外部のバグ管理システムとの連携
  • XML-RPC/JSON-RPCによる外部からの操作
  • メールによる通知機能

Enterprise 版もあり、いくつかの機能がさらに拡張されます。
詳細は機能一覧を参照してください:
https://kiwitcms.org/features/

機能については詳しくは以下を参照してください。
https://kiwitcms.org/features/

また、Kiwi TCMS は比較的軽量で、検証用途なら小さめのインスタンスでも動作します(例:t2.small 程度)。ハードウェア要件の目安は以下を参照してください:
https://kiwitcms.readthedocs.io/en/latest/hardware_performance.html#hardware-requirements

データベースは SQLite でも動作するため、お試し(検証)には敷居が低い構成です。
一方で 本番運用では SQLite は非推奨のため、MariaDB / PostgreSQL を利用してください。クラウド運用の例としては、Aurora Serverless v2(最小キャパシティ 0 ACU 運用)を選べばコスト最適化もしやすいです:
https://aws.amazon.com/jp/blogs/database/introducing-scaling-to-0-capacity-with-amazon-aurora-serverless-v2

参考資料

Dockerコンテナでの起動

Kiwi TCMS は 使用DBをSQLiteにすることで、Webコンテナを起動するだけで試用可能です。
ここでは Docker 上で動かして実験を行います。

  1. docker-compose.ymlを用意してdockerコンテナを起動する

docker-compose.ymlをまず用意します。

services:
  kiwi_web_dev:
    container_name: kiwi_web_dev
    image: pub.kiwitcms.eu/kiwitcms/kiwi:latest
    restart: always
    ports:
      - "80:8080"
      - "443:8443"
    environment:
      # ← SQLite を使う
      KIWI_DB_ENGINE: django.db.backends.sqlite3
      KIWI_DB_NAME: sqlite/kiwi.sqlite3
      # メールを送らないような設定
      EMAIL_BACKEND: "django.core.mail.backends.console.EmailBackend"
      # 以前はHTTPS強制を無効化できましたが、現在はできません。
      # KIWI_DONT_ENFORCE_HTTPS: "true"
    volumes:
      - ./uploads:/Kiwi/uploads
      - ./sqlite:/Kiwi/sqlite
      # 独自証明書を使うなら /Kiwi/ssl をマウント
      # - ./ssl:/Kiwi/ssl

参考

その後、docker composeを起動します。

cd kiwi/docker
docker compose up -d

注意1:今回はテスト用にDBをsqliteにしています。本番運用する場合はMariaDBやPostgreSQLを設定してください。
SQLite で動作はしますが、本番運用は非推奨になっています。特にスキーマ変更を含むアップグレードで壊れやすいです。

注意2:今回はメールの送信を停止しています。コンテナ上のEMAIL_BACKEND環境変数でメール送信方法を制御可能です。

  1. 初期化スクリプトの実行

初期化スクリプトを実行してDBの初期化と管理ユーザーを追加します。
途中でユーザー名などを入力する必要があります。

# 内部のバグ管理システムを有効化して初期化
docker compose exec kiwi_web_dev sh -lc 'cd /Kiwi && ./manage.py initial_setup'
# 実行中に以下を聞かれる
Username (leave blank to use '1001'): admin # ユーザ名
Email address: admin@test.co.jp # メールアドレス
Password:  # パスワードを入力
Password (again): 
Bypass password validation and create user anyway? [y/N]: y # 簡単なパスワードなどだときかれる
Superuser created successfully.
3. Setting the domain name:
Enter Kiwi TCMS domain: localhost # メールなどの通知につかうドメイン名
  1. https://localhost/にアクセスする。警告がでるが検証目的のためエラーを無視する。

詳細設定ボタンを押下する
alt text

「localhost にアクセスする(安全ではありません)」リンクを押下する
alt text

  1. ログイン画面が表示されるのでユーザー名とパスワードを指定してログインする

alt text

  1. トップ画面が表示される

alt text

テスト実行までの操作

実際にテストを実行するまでの流れをおこないます。
Kiwiでの基本的なテスト実行までの流れは以下のとおりになります。

  • ユーザーなどの必要な情報を登録する
  • テスト計画を作成する
  • テスト計画に紐づけるテストケースを作成する
  • テスト計画中で紐づけられたテストケースを選択してテスト実行を作成する
  • テスト実行上で各テストケースの実行結果を登録する

公式のチュートリアルは以下にあります。
https://kiwitcms.readthedocs.io/en/stable/tutorial.html

ユーザー登録

まずは管理権限を持った管理者ユーザーでテストを実施するユーザーの登録を行う必要があります。
ここで登録したスタッフ権限のユーザーがテスターとしてテストケースを書いたり、テストを実行します。

  1. 画面上部のメニューの「管理者」から「ユーザー」を選択します。

alt text

  1. 「ユーザ」画面に遷移したら「ユーザーを追加」ボタンを押下します。

alt text

  1. 「ユーザーを追加」画面でユーザーでユーザー名とパスワードを入力して「保存して編集を続ける」を実行します。

alt text

  1. ユーザの追加情報を入力できるので必要に応じて入力します。権限だけは必須になります。

alt text

スタッフ権限をあたえてTesterグループを選択

alt text

管理情報の追加

テスト計画などを書く前に、テスト対象となる製品の情報を記述する必要があります。
この操作には管理権限が必要になります。

まず画面上部のメニューの「管理者」から「他のすべて」を選択します。

alt text

Classificationsの追加

Productsの分類を追加します。
類似した性質を持つ製品をグループ化するために使用されるタイトルです。
例:社内向け/A社向け/A事業用 etc。

  1. 「Classifications」リンクを押下します。

alt text

  1. 「classification を追加」ボタンを押下します。

alt text

  1. Nameを入力後、「保存」ボタンを押下します。

alt text

Productsの追加

Productsはテスト対象となる製品情報です。
すべてのテスト計画、テストケースは特定のProductsに対して記述されます。

  1. 「Products」リンクを押下します。

alt text

  1. 「Products」の編集画面の「productを追加」を押下します。

alt text

  1. Name, Classifications, Descriptionを入力して保存します。

alt text

なお、ID列の番号をクリックすることで、Productsの編集や削除が可能です

alt text

alt text

その他の管理用設定項目の概要

  • Management
    • 「Components」の追加・変更・削除
      • Productsを構成する要素。(ex 認証機能、SSH連携機能 etc
      • テストケースに複数設定できる
    • 「タグ」の追加・変更・削除
      • テスト計画、テストケース, テスト実行に複数設定できるメタ情報
    • 「Versions」の追加・変更・削除
      • Productsのリリース単位情報
      • テスト計画に紐づける
    • 「ビルド」の追加・変更・削除
      • Versionのさらに細かい単位。
      • テスト計画、テスト実行に紐づける
  • Testcases
    • 「Bug trackers」
      • 外部のバグトラッキングシステムと連携する場合に設定する
    • 「カテゴリ」の追加・変更・削除
      • テストケースの分類情報
      • 1つのテストケースに1つだけわりあてる

詳しくは以下を参照してください。
https://kiwitcms.readthedocs.io/en/latest/admin.html

テスト計画の作成

テスト計画はテストの目標、およびそれらを達成するための手段とスケジュールの詳細な記述をするドキュメントです。
また、テスト計画は階層構造を作成することができ、別のテスト計画を親としたテスト計画を作成することができます。

  1. 「テスト中」メニューから新しいテスト計画を選択する

alt text

  1. 必要な項目を入力後、画面下部の「保存」ボタンを押す

alt text

  1. テスト計画が作成される。必要に応じて「添付ファイル」やタグを追加する

alt text

画面上部の歯車ボタンより現在表示中のテスト計画の「編集」と「削除」が可能

alt text

テストケースの追加

テストケースを追加するテスト計画を表示します。
もし、テスト計画登録後の画面を閉じてしまっているのであれば、画面上部の「検索」メニューから「テスト計画の検索」でテスト計画を表示することが可能です。

  1. テスト計画中のテストケースのメニューから「新しいテストケース」を選択する

alt text

  1. 概要やテスト方法を入力して画面下部の保存ボタンを押す

alt text

補足:テンプレートを選択するとテストケースのテンプレートが表示されます。

  1. テストケースが追加されるのを確認する

alt text

必要に応じて、テストケースに対して「タグ」や「コンポーネント」を付与することができます。

テストケースを編集、削除したい場合は画面上部の歯車ボタンのメニューから選択可能です。

alt text

テスト実行

テストケースを作成したのちテストを実際に実行させるために「テスト実行」を作成する必要があります。
このためには、テスト計画を表示します。
テストケースの「テスト計画」のブロックのリンクを使用するか、画面上部の「検索」メニューから「テスト計画の検索」で遷移します。

  1. テスト計画から作成したテストケースが、「テストケース」ブロックに表示されています。

alt text

テストケースの状態が「プロポーズ(PROPOSED)」の場合、テスト実行に紐づけてテストを行うことはできません。まず、テストケースの内容をレビューして「確認済み」にする必要があります。

  1. テストケースの右側のメニューから「状態」を「確認済み」に変更します。
    alt text
    alt text

テストケースが赤い背景色から白い背景色に変わっていることが確認できます。この状態のテストケースはテスト実行が可能になります。

alt text

  1. テスト実行を行いたいテストケースにチェックをつけて、「新しいテスト実行」を選択します。

alt text

alt text

  1. 「ビルド」や「デフォルトのテスター」などを入力して保存をします。

alt text

  1. テスト実行が作成されたことを確認します

alt text

画面上部の歯車ボタンより、編集と削除が行えます。

alt text

tester1でのテスト

デフォルトテスターで割り当てられたtestuser1のアカウントでkiwiにログインをすると、割り当てられたテストが確認できます。

alt text

テスト実行中のリンクを辿ると割り当てられた「テスト実行」画面が表示されます。

alt text

各テストケースの詳細を開いてテストの結果を記載します。

alt text

バグなどが発生した場合は、テストケース右上のメニューから「ハイパーリンクを追加」をしてバグ管理システム上のバグのURLを追加します。

alt text

alt text

バグとハイパーリンクに項目が追加されていることが確認できます。

alt text

alt textを押下するとテストを失敗として記録できます。

alt text

Test Plan / Case / Run を中心にしたデータモデル

ここまでで説明したKiwiにおける、Test Plan(テスト計画)/ Test Case(テストケース)/ Test Run(テスト実行)についての必要最小限のデータモデルを説明します。

エンティティ名 説明
Classifications Productsの分類。類似した性質を持つ製品をグループ化するために使用されるタイトルです。例:社内向け/A社向け/A事業用など。
Products 製品。全ての製品はProductsに基づく
Version Productのリリースに紐づくバージョン。
Build Version中のBuild単位。TestRunやTestExecutionに紐づく
PlanType そのテスト計画が表すテストの性格(Acceptance / Smoke / Regression / System など)。TestPlan に1つだけ紐づく
Category Productsに紐づくカテゴリ。テストケースにわりあてる。
タグなどと違って1種類しか付与できない
TestPlan(テスト計画) テストの目標、およびそれらを達成するための手段とスケジュールの詳細な記述をするドキュメント。
テストケースはテスト計画に基づいて実行される。
テスト計画は別のテスト計画を親とする階層構造にもできる
TestCase(テストケース) テスト前提条件、入力やステップ、期待される結果などを記述したドキュメント
作成したテストケースはテスト計画とテスト実行に紐づける必要がある。
Kiwiではテストケースの状態を使用してレビューで「確認済み」のテストケースのみテスト実行に紐づけることができる。
テストケースを作成するにはProductとCategoryが必要になる
TestCasePlan テスト計画と確認済みのテストケースを紐づける。
Kiwiでは検索→テスト計画の検索→テスト計画を選択したのち、テスト計画とテストケースを紐づける
TestRun(テスト実行) 特定のBuildに関連する特定のテスト計画に関係するテストケースを実際に実行した結果を記録する集合
TestExecution テスト実行中のテストケースのテスト結果を記録する

関連図

正確なDBのスキーマは以下を参照

https://kiwitcms.readthedocs.io/en/stable/db.html

XML-RPC/JSON-RPCによる操作

KiwiはXML-RPC/JSON-RPCにより外部から操作がおこなえます。

Pythonから操作する場合、tcms-apiライブラリを使用して操作することになります。

実際の使用例については以下を参考にしてください。

https://github.com/mima3/research_tms/tree/main/kiwi/automate

junit.xmlのkiwiに登録する例

junitが出力したレポートファイルをXML-RPCを使用してkiwiに登録する処理です。内部でtcms-apiを使用しています。

まずjunit.xml-pluginをインストールします。

pip install kiwitcms-junit.xml-plugin

次に~/.tcms.confファイルを作成します。
Windowsの場合はC:\Users\ユーザー名\.tcms.confとなります。

.tcms.conf

[tcms]
url = https://localhost/xml-rpc/ 
username = tester1
password = tester1tester1 

次に必要な環境変数を指定してスクリプトを実行します。

export TCMS_PLAN_ID=1
export TCMS_PRODUCT="製品A"
export TCMS_PRODUCT_VERSION="ver1.0"
export TCMS_BUILD="unspecified"
tcms-junit.xml-plugin /path/to/junit.xml

もし、SSL関連のエラーが発生した場合は以下のようなラッパースクリプトを作成して、無理やりエラーを無視することができます。

src/tcms_junit_plugin.py

from tcms_junit_plugin import main
import ssl
import sys

# 検証目的のためだけに使用
ssl._create_default_https_context = ssl._create_unverified_context

main(sys.argv)  # type: ignore
python -m src.tcms_junit_plugin ./demo_report.xml

以下が実行結果になります。完了したテスト実行を作成して、そこにテスト結果が記録されます。

pytestの結果をkiwiに送信する方法

kiwitcms-pytest-pluginを使用することでpytestの結果をkiwiに登録できます。

まず、kiwitcms-pytest-pluginをインストールします。

pip install kiwitcms-pytest-plugin

次に~/.tcms.confファイルを作成します。

テストコードを記載します。

import ssl
import os
import pytest

# 自己認証対策 検証目的のためだけに使用
ssl._create_default_https_context = ssl._create_unverified_context

os.environ["TCMS_PRODUCT"] = "製品A"
os.environ["TCMS_PRODUCT_VERSION"] = "unspecified"
os.environ["TCMS_BUILD"] = "unspecified"


def test_should_pass_when_assertion_passes():
    assert True


def test_should_fail_when_assertion_fails():
    assert 1 == 2


@pytest.mark.skip("We've decided not to test this")
def test_should_skip_if_marked_as_such():
    assert True

このテストコードを「--kiwitcms」オプション付きで実行すると結果をkiwiに送信します。

pipenv run pytest -p tcms_pytest_plugin --kiwitcms src/test_plugin_kiwi.py

以下が実際に作成されたテスト結果になります。


関数名でテストケースの名前がきまり、テスト結果として、そのケース名とassertionの結果しか表示されません。
つまり、kiwiだけをみて、どんなテストをしたかを理解するのは困難になります。

tcms-api を使って自前実装する例

tcms-apiを自分で使用して類似のことも可能です。
以下はkiwitcms-pytest-pluginと同じようにテスト実行の登録、テストケースの追加、テスト実行の結果を自力で登録する例です。

自力実装の例

kiwi_service.py

import ssl
from datetime import datetime
from tcms_api import TCMS
from typing import Iterable, Optional


# 自己認証のSSL対応 検証目的のためだけに使用
ssl._create_default_https_context = ssl._create_unverified_context


class KiwiService:
    def __init__(self, endpoint, user, password):
        self.tcms = TCMS(endpoint, user, password)
        self.rpc = self.tcms.exec


class KiwiProductService(KiwiService):
    def __init__(self, endpoint, user, password, product_id: int):
        self.product_id = product_id
        super().__init__(endpoint, user, password)

    def create_task(
        self,
        summary: str,
        text: str,
        *,
        category_id: int = 1,
        priority_id: int = 1,
        is_automated: bool = True,
        notes: str = "",
        tags: Optional[Iterable[str]] = None,
        case_status: int = 1
    ) -> dict:
        # ref.
        # https://kiwitcms.readthedocs.io/en/latest/modules/tcms.testcases.models.html#tcms.testcases.models.TestCase
        case_data = {
            "summary": summary,
            "case_status": case_status,  # 1: PROPOSED, 2: CONFIRMED, 3: IN_PROGRESS ... (KIWI上のステータスID)
            "priority": priority_id,  # 優先度ID
            "is_automated": is_automated,  # 自動テストかどうか (True/False)
            "script": "",
            "text": text,
            "category": category_id,
            "product_id": self.product_id,
            "notes": notes,
            # 必要に応じて他のフィールドを追加
            # "note": "note",
            # "category": 1,         # カテゴリID(製品カテゴリなど)
            # 'plan': テストプランのID,
            # 'component': コンポーネントのID,
            # 'tag': タグ など
        }
        # https://kiwitcms.readthedocs.io/en/latest/modules/tcms.rpc.api.testcase.html#tcms.rpc.api.testcase.create
        new_case = self.rpc.TestCase.create(case_data)
        print(new_case)
        for tag in tags:
            # https://kiwitcms.readthedocs.io/en/latest/modules/tcms.rpc.api.testcase.html#tcms.rpc.api.testcase.add_tag
            self.rpc.TestCase.add_tag(new_case.get("id"), tag)
        return new_case

    def filter_by_summary(self, summary: str) -> dict:
        # https://kiwitcms.readthedocs.io/en/latest/modules/tcms.rpc.api.testcase.html#tcms.rpc.api.testcase.filter
        test_case = self.rpc.TestCase.filter(
            {
                "summary": summary,
                "category__product": self.product_id,
            }
        )
        return test_case

    def add_test_case_to_plan(self, case_id, plan_id):
        if not self.rpc.TestCase.filter({"pk": case_id, "plan": plan_id}):
            self.rpc.TestPlan.add_case(plan_id, case_id)

    def create_test_run(self, plan_id: int, build_id: int, summary: str) -> dict:
        # テスト計画を作った人間をマネージャーとみなす
        manager_id = self.rpc.TestPlan.filter({"pk": plan_id})[0]["author"]
        data = {
            "summary": summary,
            "manager": manager_id,
            "plan": plan_id,
            "build": build_id,
            "start_date": self._current_date(),
        }
        # https://kiwitcms.readthedocs.io/en/latest/modules/tcms.rpc.api.testrun.html#tcms.rpc.api.testrun.create
        new_run = self.rpc.TestRun.create(data)
        return new_run

    def add_test_case_to_run(self, case_id: int, run_id: int) -> dict:
        result = self.rpc.TestRun.add_case(run_id, case_id)
        if isinstance(result, list):
            return result[0]
        else:
            return result

    def _current_date(self) -> str:
        return datetime.now().isoformat().replace("T", " ")[:19]

    def update_test_execution(self, test_execution_id: int, status_id: int, comment: str):
        args = {
            "status": status_id,
            "start_date": self._current_date(),
            "stop_date": self._current_date(),
        }
        self.rpc.TestExecution.update(test_execution_id, args)
        if comment:
            self.rpc.TestExecution.add_comment(test_execution_id, comment)

    def finish_test_run(self, run_id: int):
        self.rpc.TestRun.update(
            run_id,
            {
                "stop_date": self._current_date(),
            },
        )

sample.py

from .kiwi_service import KiwiProductService


product_id = 1
plan_id = 1
build_id = 1

kiwi = KiwiProductService("https://localhost/xml-rpc/", "tester1", "tester1tester1", product_id)
# テスト実行を作成
run = kiwi.create_test_run(plan_id, build_id, "テスト実行 (自動)")

# テストケースを作成
contents = """**Steps to reproduce**:
1.
2.
3.

**Expected results**:
1.
2.
3.
"""
case_list = kiwi.filter_by_summary("テストケース1_自動追加")
if not case_list:
    case = kiwi.create_task(
        "テストケース1_自動追加", contents, category_id=1, priority_id=1, case_status=2, notes="noteeee", tags=["abc", "efg"]
    )
else:
    case = case_list[0]
print(case)
kiwi.add_test_case_to_plan(case.get("id", 0), plan_id)
execution = kiwi.add_test_case_to_run(case.get("id", 0), run.get("id", 0))
# status_id:4:成功 8:却下 7:Error 5:失敗
kiwi.update_test_execution(execution.get("id", 0), 4, "コメント")

# テストケースを作成
contents = """**Steps to reproduce**:
1.
2.
3.

**Expected results**:
1.
2.
3.
"""
case_list = kiwi.filter_by_summary("テストケース2_自動追加")
if not case_list:
    case = kiwi.create_task(
        "テストケース2_自動追加", contents, category_id=1, priority_id=1, case_status=2, notes="noteeee", tags=["abc", "efg"]
    )
else:
    case = case_list[0]
print(case)
kiwi.add_test_case_to_plan(case.get("id", 0), plan_id)
execution = kiwi.add_test_case_to_run(case.get("id", 0), run.get("id", 0))
# status_id:4:成功 8:却下 7:Error 5:失敗
kiwi.update_test_execution(execution.get("id", 0), 5, "コメント")

# テスト実行終了
kiwi.finish_test_run(run.get("id", 0))

その他機能の簡単な説明

テレメトリー

テレメトリーにより現在のテスト実行の状況が確認できます。

ブレイクダウン

トレンド

テストケースの状況

バグトラッキングシステムとの連携

Kiwi には簡易的なバグ管理モジュールがあります。

登録したバグはテスト結果にハイパーリンクとして関連づける必要があります。

また、外部のバグトラッキングシステム(Jira / Azure Boards / GitHub Issues / Bugzilla / Redmine など)との連携も可能です。
詳しくはバグトラッキングシステムの構成を参照してください。

テストケース中のパラメータ

テストケースの文章中に${パラメータ名}を入れることで、パラメーターで置き換えたテスト実行を作成できます。

alt text

テスト実行まで作成すると、パラメータの組み合わせ分のTestExecutionが作成されるます。

alt text

この機能は結果を変えることはできないので結果が同じになるパターンのテストだけでしか使えません。
また、技術プレビューなので注意が必要です。

詳細は下記を参照してください。

https://kiwitcms.org/blog/atodorov/2022/01/24/feature-showcase-test-matrix-generation/

メール設定

SMTPの設定をおこなうことでメールによる通知が可能になります。
また、Kiwi TCMS Enterprise版の場合はAmazon SESが使用できます。

詳細は下記を参照
https://kiwitcms.readthedocs.io/en/latest/configuration.html#e-mail-settings

昨今のメール送信の厳しさからメール以外の通知を検討する場合は、XML-RPCで自作する必要があります。

インポート・エクスポート機能

インポート・エクスポート機能はないので、XML-RPCを叩いて自作する必要があります。

まとめ

今回はKiwi TCMSについて調査しました。
TestLinkでサポートされているテストスイートや要求仕様の機能は存在しませんが、テスト管理ツールとして十分に検討する価値のあるシステムだと思います。

Discussion