0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Tosca Commander から Tosca Cloud への移行を効率化する流れを検討してみた

0
Last updated at Posted at 2026-06-12

はじめに

Tosca Commander で作成・運用してきたテスト資産を Tosca Cloud に移行するにあたってはTosca Commanderに付随する機能を使ってアップロードが可能です。

このプロセスについて、移行の棚卸と検証を含めて、どう効率化するかを考えてみました。

この記事では、Tosca Commander 側で TestCase 一覧を取得し、Tosca Cloud へ接続したうえで、TQL を使って対象を絞り込みながら一括アップロードする流れを整理します。最後に、Tosca Cloud 側の結果確認を MCP 経由で行うところまでを想定します。

想定読者

  • Tosca Commander から Tosca Cloud への移行を検討している方
  • 既存 TestCase の棚卸しを効率化したい方
  • 移行後の確認作業を API や MCP で自動化したい方
  • Tosca Cloud への段階移行を計画している QA / 自動化担当者

全体方針

今回のアプローチは、移行対象を先に可視化してからアップロードする流れです。

  1. Tosca Commander で CustomReport を作成し、TestCase 一覧を取得する
  2. Tosca Commander を Tosca Cloud に接続する
  3. TQL を利用して対象 TestCase を一括アップロードする
  4. Tosca Cloud の結果を MCP で確認する

ポイントは、いきなり移行作業を始めるのではなく、まず Commander 側で「何を移すのか」を一覧化することです。TestCase 数、フォルダ構成、利用している Module、ExecutionList との関係を把握しておくと、移行後の差分確認がしやすくなります。

移行前に確認しておきたいこと

作業前に、少なくとも以下は確認しておくと安全です。

観点 確認内容
対象範囲 どの Project / ComponentFolder / TestCaseFolder を移行対象にするか
除外条件 古い TestCase、メンテナンス対象外、PoC 用資産を除外するか
依存関係 Module、Reusable TestStepBlock、TestCaseDesign、TestData などの関連資産
命名規則 Cloud 側で識別しやすいフォルダ名・タグ・属性を付けるか
実行確認 移行後にどの Playlist / ExecutionList 相当で確認するか

特に、移行対象を TQL で抽出する場合は、対象フォルダや属性に一定のルールがあると楽になります。移行前に一時的な属性を付ける、対象フォルダを整理する、といった準備も有効です。

Step 1: CustomReport で TestCase 一覧を取得する

まず Tosca Commander 側で CustomReport を作成し、既存 TestCase の一覧を出力します。

ここで取得する情報の例は以下です。

項目 用途
TestCase 名 移行前後の突合
フォルダパス 移行対象範囲の確認

以下が作成したTest Case一覧を取得するレポートです。

手順概要

STEP 1.1: Report Definition の新規作成

Tosca Commander 左ペインで Reporting フォルダを右クリック
Create Report Definition を選択
生成された Report Definition の名前を変更(例:TC_Folder_List)

STEP 1.22: TestCases フォルダをリンクする

ワークスペースツリーの TestCases フォルダを見つける
TestCases フォルダをドラッグ → 作成した Report Definition の行にドロップ
Details ビューの Object type 列に TCFolder が表示されることを確認

⚠️ ここが空白のままだとデータが出力されない。必ず確認すること。

STEP 1.3: Dataset Definition の設定(フォルダ用)

Report Definition 配下に自動生成された Dataset Definition を選択し、以下を設定する。

列設定値Link ->SELF Object type TCFolder Columns Name

STEP 1.4: Dataset Definition の追加(TestCase 用)

STEP 3 で設定した Dataset Definition を右クリック
Create Dataset Definition を選択(子 Dataset Definition が追加される)
新しい子 Dataset Definition に以下を設定:

列設定値Link ->SUBPARTS Object type TestCase Columns Name

完成後の構造:
image.png

image.png

STEP 1.5: Designer Definition を Excel Export に設定

Report Definition 配下の を右クリック
Create Designer Definition → Excel Export を選択

image.png

STEP 1.6: レポートの出力

Tosca Commander で TestCases フォルダを右クリック
Print Report... を選択
対象の Report Definition(TC_Folder_List)を選択
出力先を指定して保存(例:tc_export.xml)

レポートはXMLで出力されます。
本記事の最下部のスクリプトなどで適宜フォーマット変換するとより管理しやすくなります。

Step 2: Tosca Commander を Tosca Cloud に接続する

次に Tosca Commander から Tosca Cloud へ接続します。

ここでは、Cloud 側のワークスペース、認証情報、接続権限を確認します。接続後にアップロード操作を行うため、作業ユーザーには対象資産を読み取り、Cloud 側へ登録できる権限が必要です。

ポイントは以下です。

  1. Tosca Cloud のテナント URL を確認する
  2. 対象 Workspace にアクセスできることを確認する
  3. Tosca Commander 側から Cloud 接続を設定する
  4. 接続テストを行い、認証エラーがないことを確認する
  5. 小さなサンプル TestCase でアップロード可否を確認する

最初から大量の TestCase を対象にするのではなく、代表的なパターンを 1 つか 2 つ選んで接続とアップロードの流れを確認するのがおすすめです。

Commanderの右下部よりConnect to Tosca Cloudを行い、画面に従い認証する
image.png

テストケースを選択し、右クリックにて1件アップロードの動作確認を行う
image.png

Step 3: TQL を利用して一括アップロードする

接続が確認できたら、TQL を使って移行対象の TestCase を抽出し、一括アップロードします。

TQL を使うメリットは、対象を条件で絞り込めることです。たとえば、特定フォルダ配下、特定の属性を持つ TestCase、最終更新日が一定期間内の TestCase などを対象にできます。

TQL の考え方

実際の TQL は環境やオブジェクト構成に合わせて調整が必要ですが、考え方としては以下のように整理します。

移行対象 = TestCase
  かつ 対象フォルダ配下
  かつ アーカイブ配下ではない
  かつ MigrationTarget = true

TQL 例

=>SUBPARTS:TestCase[(IsTemplate=?"False")]

特定のフォルダパス配下の一覧を検索する(あいまい検索)

=>SUBPARTS:TestCase[NodePath=?"/TestCases/Vechicle Insurance - Automobile/Upload/"]

特定のフォルダパス配下の一覧を検索する(パス指定)

->SUBPARTS:TCFolder[Name=="Standard module examples"]
  ->SUBPARTS:TCFolder[Name=="TBox XEngines"]
  ->SUBPARTS:TCFolder[Name=="JSON"]
  ->SUBPARTS:TCFolder[Name=="Array"]
  ->SUBPARTS:TestCase

image.png

Test Casesフォルダを選択した状態でTestCase全件を取得する
image.png

一括アップロード時の注意点

一括アップロードでは、以下のような点でつまずきやすいです。

  • 対象が多すぎて失敗する
  • Test Case Templateはアップロードできない(除外する)
  • Test Case Designで作成したTest Caseはアップロードできない。一度コピー&ペーストで別フォルダに作成する事でアップロードが可能

以下のように、一覧から右クリックで一括選択する事でアップロード範囲を指定する手間を効率化できます。
image.png

アップロード後は元のフォルダ構造を保持している
image.png

一括アップロードでTestCaseが作成された事もDashboardで確認可能
image.png

Step 4: Tosca Cloud の結果を MCP で確認する

移行後は、Tosca Cloud 側で TestCase が期待通りに登録されているかを確認します。ここでは MCP を使って、Cloud 側の状態確認を効率化する想定です。

確認したい観点は以下です。

確認観点 内容
件数 Commander 側の移行対象件数と Cloud 側の登録件数が一致するか
名前 TestCase 名が想定通りか
フォルダ 移行先の構成が意図通りか

|

MCP で確認できるようにしておくと、移行後のチェックを人手だけに頼らず、差分確認やサマリー作成を自動化できます。

MCPを介してSTEP1で取得した一覧との差異チェックを依頼する
image.png

再チェックの結果の差分を出力してくれる(ここではTest Case Templateが差異として表示されている)
image.png

段階移行のすすめ

Tosca Commander から Tosca Cloud への移行は、一度にすべて移すよりも、段階的に進めた方がリスクを抑えやすいです。

おすすめの進め方は以下です。

  1. 小さなフォルダ単位で試験移行する
  2. CustomReport と Cloud 側結果を比較する
  3. 依存 Module や実行設定の不足を洗い出す
  4. TQL 条件を調整する
  5. 対象範囲を広げて再実行する

特に初回は、代表的なパターンを含む小さな単位を選ぶのが良さそうです。単純な Web TestCase、Reusable TestStepBlock を含む TestCase、TestData を使う TestCase などを混ぜておくと、移行時の問題を早く見つけられます。

まとめ

Tosca Commander から Tosca Cloud へ移行する際は、いきなりアップロードするのではなく、以下の流れで進めると管理しやすくなります。

  1. CustomReport で既存 TestCase を棚卸しする
  2. Commander から Tosca Cloud への接続を確認する
  3. TQL で移行対象を絞り込み、一括アップロードする
  4. MCP で Cloud 側の登録結果・差分・実行結果を確認する

移行作業の肝は、アップロードそのものよりも、移行前後の差分を説明できる状態にしておくことです。CustomReport、TQL、MCP を組み合わせることで、対象選定から移行後確認までを一連の流れとして整理できます。

参考リンク

参考 XML -> JSON 変換サンプル

"""
tosca_to_json.py
Tosca Excel Export (XML) → ネスト JSON 変換スクリプト
用途: AI Agent に Tosca TestCase 階層構造を読み込ませる

使い方:
    python tosca_to_json.py <input.xml> [output.json]

例:
    python tosca_to_json.py tc6.xml tosca_structure.json
"""

import sys
import json
import xml.etree.ElementTree as ET
from pathlib import Path
from collections import Counter


NS = {"ss": "urn:schemas-microsoft-com:office:spreadsheet"}

INCLUDE_TYPES = {"TCFolder", "TestCase"}


# ---------------------------------------------------------------------------
# 1. XML パース
# ---------------------------------------------------------------------------
def parse_sheet(ws) -> list[dict]:
    table = ws.find("ss:Table", NS)
    if table is None:
        return []
    rows = table.findall("ss:Row", NS)
    if not rows:
        return []
    headers = []
    for cell in rows[0].findall("ss:Cell", NS):
        data = cell.find("ss:Data", NS)
        headers.append(data.text if data is not None else "")
    records = []
    for row in rows[1:]:
        cells = row.findall("ss:Cell", NS)
        record = {}
        for i, cell in enumerate(cells):
            if i < len(headers):
                data = cell.find("ss:Data", NS)
                record[headers[i]] = data.text if data is not None else ""
        if any(record.values()):
            records.append(record)
    return records


def parse_tosca_xml(xml_path: str) -> tuple[list[dict], list[dict]]:
    """
    (folder_records, testcase_records) を返す。
    シート1枚目 = フォルダ、2枚目以降 = TestCase として扱う。
    """
    tree = ET.parse(xml_path)
    root = tree.getroot()
    worksheets = root.findall(".//ss:Worksheet", NS)

    folder_records = []
    testcase_records = []

    for i, ws in enumerate(worksheets):
        name = ws.get("{urn:schemas-microsoft-com:office:spreadsheet}Name", f"Sheet{i+1}")
        records = parse_sheet(ws)
        print(f"      シート '{name}': {len(records)} レコード")
        if i == 0:
            folder_records = records
        else:
            testcase_records.extend(records)

    return folder_records, testcase_records


# ---------------------------------------------------------------------------
# 2. ルート親 ID を検出
# フォルダシートの ParentSurrogateNum で最頻値 = TestCases フォルダの ID
# ---------------------------------------------------------------------------
def detect_root_parent(folder_records: list[dict]) -> str:
    counter = Counter(
        r.get("ParentSurrogateNum", "")
        for r in folder_records
    )
    return counter.most_common(1)[0][0]


# ---------------------------------------------------------------------------
# 3. ツリー構築
# ---------------------------------------------------------------------------
def build_tree(folder_records: list[dict], testcase_records: list[dict], root_parent_id: str) -> list[dict]:
    # 全レコードをまとめる(重複排除)
    all_records = folder_records + testcase_records
    seen = set()
    filtered = []
    for r in all_records:
        if r.get("ObjectType") in INCLUDE_TYPES:
            sid = r.get("SurrogateNum", "")
            if sid and sid not in seen:
                seen.add(sid)
                filtered.append(r)

    # ノードマップ
    node_map: dict[str, dict] = {}
    for r in filtered:
        node_map[r["SurrogateNum"]] = {
            "name":         r.get("Name", ""),
            "type":         r.get("ObjectType", ""),
            "surrogate_id": r.get("SurrogateNum", ""),
            "children":     [],
        }

    # 親子を組み立てる
    roots = []
    for r in filtered:
        sid  = r["SurrogateNum"]
        pid  = r.get("ParentSurrogateNum", "")
        node = node_map[sid]

        if pid == root_parent_id or pid not in node_map:
            roots.append(node)
        else:
            node_map[pid]["children"].append(node)

    return roots


# ---------------------------------------------------------------------------
# 4. ユーティリティ
# ---------------------------------------------------------------------------
def count_nodes(nodes: list[dict]) -> tuple[int, int]:
    folders, testcases = 0, 0
    for n in nodes:
        if n["type"] == "TCFolder":
            folders += 1
        elif n["type"] == "TestCase":
            testcases += 1
        f, t = count_nodes(n["children"])
        folders += f
        testcases += t
    return folders, testcases


def print_tree_preview(nodes, indent=0, limit=40, counter=None):
    if counter is None:
        counter = [0]
    for n in nodes:
        if counter[0] >= limit:
            return
        icon = "📁" if n["type"] == "TCFolder" else "🧪"
        print("  " * indent + f"{icon} {n['name']}")
        counter[0] += 1
        print_tree_preview(n["children"], indent + 1, limit, counter)


# ---------------------------------------------------------------------------
# 5. メイン
# ---------------------------------------------------------------------------
def main():
    if len(sys.argv) < 2:
        print("使い方: python tosca_to_json.py <input.xml> [output.json]")
        sys.exit(1)

    xml_path    = sys.argv[1]
    output_path = sys.argv[2] if len(sys.argv) >= 3 else "tosca_structure.json"

    print(f"[1/3] XML 読み込み中: {xml_path}")
    folder_records, testcase_records = parse_tosca_xml(xml_path)
    print(f"      フォルダ系: {len(folder_records)}件 / TestCase系: {len(testcase_records)}件")

    root_parent_id = detect_root_parent(folder_records)
    print(f"[2/3] TestCases フォルダ ID: {root_parent_id}")

    print(f"[3/3] ツリー構築 → {output_path}")
    tree = build_tree(folder_records, testcase_records, root_parent_id)

    result = {
        "source":            Path(xml_path).name,
        "testcases_root_id": root_parent_id,
        "children":          tree,
    }

    with open(output_path, "w", encoding="utf-8") as f:
        json.dump(result, f, ensure_ascii=False, indent=2)

    folders, testcases = count_nodes(tree)
    print(f"\n✅ 完了: TCFolder={folders}件 / TestCase={testcases}件")
    print(f"   出力: {output_path}")
    print(f"\n--- ツリープレビュー(先頭40件)---")
    print_tree_preview(tree)


if __name__ == "__main__":
    main()

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?