はじめに
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 / 自動化担当者
全体方針
今回のアプローチは、移行対象を先に可視化してからアップロードする流れです。
- Tosca Commander で CustomReport を作成し、TestCase 一覧を取得する
- Tosca Commander を Tosca Cloud に接続する
- TQL を利用して対象 TestCase を一括アップロードする
- 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
STEP 1.5: Designer Definition を Excel Export に設定
Report Definition 配下の を右クリック
Create Designer Definition → Excel Export を選択
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 側へ登録できる権限が必要です。
ポイントは以下です。
- Tosca Cloud のテナント URL を確認する
- 対象 Workspace にアクセスできることを確認する
- Tosca Commander 側から Cloud 接続を設定する
- 接続テストを行い、認証エラーがないことを確認する
- 小さなサンプル TestCase でアップロード可否を確認する
最初から大量の TestCase を対象にするのではなく、代表的なパターンを 1 つか 2 つ選んで接続とアップロードの流れを確認するのがおすすめです。
Commanderの右下部よりConnect to Tosca Cloudを行い、画面に従い認証する

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

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
Test Casesフォルダを選択した状態でTestCase全件を取得する

一括アップロード時の注意点
一括アップロードでは、以下のような点でつまずきやすいです。
- 対象が多すぎて失敗する
- Test Case Templateはアップロードできない(除外する)
- Test Case Designで作成したTest Caseはアップロードできない。一度コピー&ペーストで別フォルダに作成する事でアップロードが可能
以下のように、一覧から右クリックで一括選択する事でアップロード範囲を指定する手間を効率化できます。

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

Step 4: Tosca Cloud の結果を MCP で確認する
移行後は、Tosca Cloud 側で TestCase が期待通りに登録されているかを確認します。ここでは MCP を使って、Cloud 側の状態確認を効率化する想定です。
確認したい観点は以下です。
| 確認観点 | 内容 |
|---|---|
| 件数 | Commander 側の移行対象件数と Cloud 側の登録件数が一致するか |
| 名前 | TestCase 名が想定通りか |
| フォルダ | 移行先の構成が意図通りか |
|
MCP で確認できるようにしておくと、移行後のチェックを人手だけに頼らず、差分確認やサマリー作成を自動化できます。
MCPを介してSTEP1で取得した一覧との差異チェックを依頼する

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

段階移行のすすめ
Tosca Commander から Tosca Cloud への移行は、一度にすべて移すよりも、段階的に進めた方がリスクを抑えやすいです。
おすすめの進め方は以下です。
- 小さなフォルダ単位で試験移行する
- CustomReport と Cloud 側結果を比較する
- 依存 Module や実行設定の不足を洗い出す
- TQL 条件を調整する
- 対象範囲を広げて再実行する
特に初回は、代表的なパターンを含む小さな単位を選ぶのが良さそうです。単純な Web TestCase、Reusable TestStepBlock を含む TestCase、TestData を使う TestCase などを混ぜておくと、移行時の問題を早く見つけられます。
まとめ
Tosca Commander から Tosca Cloud へ移行する際は、いきなりアップロードするのではなく、以下の流れで進めると管理しやすくなります。
- CustomReport で既存 TestCase を棚卸しする
- Commander から Tosca Cloud への接続を確認する
- TQL で移行対象を絞り込み、一括アップロードする
- MCP で Cloud 側の登録結果・差分・実行結果を確認する
移行作業の肝は、アップロードそのものよりも、移行前後の差分を説明できる状態にしておくことです。CustomReport、TQL、MCP を組み合わせることで、対象選定から移行後確認までを一連の流れとして整理できます。
参考リンク
- Tosca Cloud Admin Guide
https://docs.tricentis.com/tosca-cloud/en-us/content/admin_guide/admin_overview.htm - Tosca Cloud Architecture
https://docs.tricentis.com/tosca-cloud/en-us/content/admin_guide/architecture.htm - Tosca Cloud Run tests
https://docs.tricentis.com/tosca-cloud/en-us/content/run_tests/run_tests.htm - Tosca Cloud MCP
https://docs.tricentis.com/tosca-cloud/en-us/content/ai_integration/mcp_server_overview.htm
参考 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()




