クラウド Mac で iOS App Intents の引数とショートカットを検証する

AI 自動化 ·約 10 分

クラウド Mac で iOS App Intents の引数とショートカットを検証する

「読書を記録する」ショートカットが開発機では見つかるのに、リモートビルドで使うシミュレータには表示されない。そんなとき、すぐにアプリの処理を変更する必要はありません。引数が有効か、Intent を実行できるか、システムがアクションを検出できるかは、それぞれ別の確認事項です。クラウド Mac は最初の二つのテスト環境を固定するのに適しています。最後の確認は、アプリをインストールしたシミュレータで行います。

自動テストの対象を明確にする

この記事では、ディスクに書き込まないサンプルアクションで手順を確認します。書籍 ID を受け取り、前後の空白を取り除いて、正規化した ID を返すものです。これなら、まずストレージの権限やネットワークの変動を切り離せます。後からデータベースにつなぐ場合は、書き込みと再実行時の扱いを別途テストしてください。「成功として返った」ことを「データが保存された」ことと混同しないようにします。

受け入れ確認は三層に分けます。Swift の純粋なロジックで空値と不正な文字を検査し、XCTest から perform() を呼んで返り値とエラーを確認し、最後にシミュレータ上でアクションの表示、引数の入力、結果の読み取りを手動で確かめます。最初の二層が失敗したらマージを止めるべきです。三層目はシステムのインデックス作成に左右されるため、固定秒数の待機を入れたテストではなく、リリース前の確認項目に適しています。

ショートカットの表示を待つためだけに、自動テストでアプリのアンインストールと再インストールを繰り返さないでください。まずコードの契約が正しいことを証明し、その後でアプリのインストール、起動、システムによる検出を調べます。

入力ルールを直接テストできる型に切り出す

次のルールは、1–32 文字の ASCII 英字、数字、アンダースコア、ハイフンを受け付けます。空白が許されるのは入力の前後だけで、それ以外の文字には明確なエラーを返します。この例の perform() は ID を返すだけで、永続化の副作用はありません。

import AppIntents
import Foundation

enum ReadingIDError: LocalizedError {
    case invalid

    var errorDescription: String? {
        "编号须为 1–32 位字母、数字、下划线或连字符"
    }
}

enum ReadingID {
    static func normalize(_ raw: String) throws -> String {
        let value = raw.trimmingCharacters(in: .whitespacesAndNewlines)
        let pattern = #"^[A-Za-z0-9_-]{1,32}$"#
        guard value.range(of: pattern, options: .regularExpression) != nil else {
            throw ReadingIDError.invalid
        }
        return value
    }
}

struct CheckReadingIDIntent: AppIntent {
    static var title: LocalizedStringResource = "检查书籍编号"
    static var openAppWhenRun = false

    @Parameter(title: "书籍编号")
    var bookID: String

    func perform() async throws -> some IntentResult & ReturnsValue<String> {
        let normalized = try ReadingID.normalize(bookID)
        return .result(value: normalized)
    }
}

引数名、アクション名、エラーメッセージはプロジェクトのローカライズ用リソースに含めてください。ここにある中国語はサンプルの文言であり、多言語アプリですべての言語向けにそのまま出荷するテキストではありません。ルール自体が受け付けるのは ASCII の ID だけです。漢字など、ほかの文字を業務上認めるなら、先に許可する文字集合を定義し、正規表現とテストケースを変更します。

正常系だけでなく境界条件もテストする

少なくとも、" book_7 " が "book_7" になり、空文字列、途中に空白を含む ID、32 文字を超える ID が拒否されることを確認します。エラー時は「何らかのエラーが投げられた」だけでなく、エラーの型を検証してください。そうしないと、下位の依存処理が予期しないエラーを投げてもテストが通ってしまいます。perform() のテストでは、返り値が正規化ルールと一致することも確認します。純粋なルールのテストと Intent の呼び出しテストを分けておけば、失敗時に入力ルールとシステム API を包む部分のどちらが変わったかを判断できます。

登録箇所とシミュレータでの表示を確認する

システムに候補アクションを表示させたい場合は、アプリのターゲットに AppShortcutsProvider を追加できます。フレーズにはアプリ名のプレースホルダを残し、システムがアクションを現在のアプリに関連付けられるようにします。これはシェル文字列ではありません。

import AppIntents

struct ReadingShortcuts: AppShortcutsProvider {
    static var appShortcuts: [AppShortcut] {
        AppShortcut(
            intent: CheckReadingIDIntent(),
            phrases: ["在 \(.applicationName) 检查书籍编号"],
            shortTitle: "检查书籍编号",
            systemImageName: "book"
        )
    }
}

まず、このファイルがテストターゲットだけでなくアプリのターゲットにも含まれていることを確認します。ビルドしたアプリをインストールして一度起動し、シミュレータの「ショートカット」を開いてアクション名を検索します。有効な ID を入力して実行した後、不正な ID も送信し、エラーが読める形で表示されるか確認してください。アクションが表示されない場合は、システムのバージョン、アプリのビルドバージョン、インストール後に行った操作の順序を記録します。「画面を開き直したらたまたま表示された」ことを確実な修正方法として書かないでください。

テスト端末と結果の保存先を固定する

ZSHMac のクラウド Mac では、まず xcrun simctl list devices available でインストール済みかつ使用可能なシミュレータを確認します。端末の UDID を選び、SIM_UDID を設定してください。以下のコマンドは、プロジェクトに Reader という共有 scheme があることを前提としています。実行時には自分のプロジェクトの scheme を使います。既存ファイルが原因でテストコマンドが即座に失敗しないよう、実行ごとに結果バンドルのパスを新しくします。

mkdir -p artifacts
test -n "${SIM_UDID:-}" || { printf '%s\n' 'SIM_UDID is required' >&2; exit 1; }
result="$PWD/artifacts/app-intents-$(date +%Y%m%d-%H%M%S)-$$.xcresult"
xcodebuild test \
  -scheme Reader \
  -destination "platform=iOS Simulator,id=$SIM_UDID" \
  -resultBundlePath "$result"

CI では、コマンドの終了コードと結果バンドルを両方保存します。終了コードでジョブの成否を判定し、失敗後は結果バンドルから該当するテストケースを調べます。scheme が共有されていない、または対象のシミュレータが使えない場合は、Intent のロジックの失敗と分類せず、まず実行設定を直してください。手動の受け入れ確認では、アクションの表示、入力案内の正しさ、有効・無効な引数それぞれの実際の動作を記録します。

失敗した層から原因を絞る

現象 最初に確認すること 最初にしないこと
純粋なルールのテストが失敗する 文字の範囲、長さ、前後の空白の処理 シミュレータを再インストールする
perform() の結果が一致しない 引数の受け渡しと返り値の型 システムのインデックス作成を待つ
テストコマンドが端末を見つけられない 使用可能な端末の一覧と UDID Swift のルールを変更する
ショートカットにアクションがない アプリターゲットへの所属、インストール、初回起動 テストに固定の遅延を加える
アクションはあるが実行時にエラーになる 引数の案内、エラーの型、対象システムのバージョン すべてのエラーを握りつぶす

層を分けて確認する目的は、障害の範囲を絞ることです。インストール直後にシステム上の入口が必ず見えると保証するものではありません。コードのテストが通り、シミュレータのアクションでも有効・無効な入力を確認できて初めて、今回の受け入れ確認は完了です。実際の書き込みを追加したら、再実行とデータの永続化についてもテストを追加してください。この例の「ID を返すだけ」という合格基準をそのまま使うことはできません。

よくある質問

XCTest が成功してもショートカットに表示されないのはなぜですか?

AppShortcutsProvider を含むアプリのインストールと起動、対象 OS とターゲット設定を確認してください。システムの索引には時間差が生じることがあります。

引数の検証はどこに置くべきですか?

再利用できる Swift の型に規則を置き、perform() から呼び出します。これならシステムの索引状態に依存せず単体テストできます。

画面を使わない CI だけで検証を完了できますか?

できません。CI は入力と返り値を確認し、アクションの表示と入力画面は対象 OS のシミュレータまたは実機で確認します。

専用のクラウド Mac をお探しですか?

機種、ロケーション、利用期間を選ぶだけ。専有の物理マシンを提供します。実際の利用可否は、コントロールパネルに表示される最新情報をご確認ください。

構成を確認して注文