Building Windows 8 ブログ
Windows Store 開発者向けブログ
IEBlog 日本語
Windows チームの各ブログ (英語)
Inside Windows Live ブログ (英語)
Visual Studio ブログ (英語)>
Windows 8 Release Preview をダウンロード (英語)
Dev Center - Metro スタイル アプリ (英語)
@windevs をフォロー (英語)
Build Windows カンファレンス (英語)
Windows Metro スタイル アプリ
Windows ストア アプリのテストに関するブログ記事では、主にアプリのテスト項目について説明しました。前回の記事で説明した検証分野は手動でテストすることも可能ですが、検証分野の一部を自動化すればさらに効率的です。アプリ検証の自動化には多くのメリットがあります。自動テストによって、手動テストの実施で必要になる時間とコストを節約できます。自動テストを 1 回作成すれば最小限のコストで繰り返し実行でき、かかる時間も手動テストに比べてかなり短くなります。これによって、アプリの毎回の新規リリースで、品質を維持しながらコストを抑えることができます。面倒な手動テストでは最も注意深いテスト担当者にさえミスが発生することを考えると、自動テストによって検証の精度が高まるとも言えます。
この記事では、Windows 8 アプリのテストを自動化する際のヒントとテクニックをいくつか紹介します。覚えておいていただきたいのは、自動テストが強力なテクニックであるということと、そのメリットをフル活用するにはある程度の初期投資が必要ということです。この記事の説明とサンプルは自動化の基本的な部分を紹介するものであり、皆さんはこれらを踏まえた上で独自の基盤を構築、維持する必要があります。より軽量なテスト テクニックをお探しの場合は、Visual Studio を使ったアプリのテストに関する最近のブログ記事をご覧ください。
アプリの自動テストの一般的なワークフローを構成する手順を以下に示します。
それでは、これらの各手順の詳しい説明と、各手順で使用できるツールとテクノロジを見ていきましょう。
自動化の具体的なトピックに進む前に、Windows RT 上でのテストに関する注意点を簡単に説明します。x86/64 プロセスを構築または移植して Windows RT 上で実行させることはできません。このため、今回の記事のこれ以降で説明するツールやテクノロジを Windows RT 上のテストに適用することはできません。代わりに、Windows RT 上のテストには Visual Studio を使う (英語) ことをお勧めします。
アプリをテストするには、最初にテスト マシンにアプリをインストールする必要があります。アプリ パッケージを作成してテスト マシンにインストールする方法としてお勧めなのは、Visual Studio を使ってアプリ パッケージをローカルに共有する方法です。この方法では、Visual Studio によって作成されるフォルダーに、すべての関連ファイルと PowerShell スクリプトが格納されます。このスクリプトによって適切な証明書とライセンス、依存パッケージ、アプリ パッケージ自身がインストールされます。アプリのパッケージ化は手動で行う必要がありますが、インストールは PowerShell ベースで自動化できます。インストール方法を以下に示します。
PowerShell スクリプトの実行を有効にします。セキュリティ上の理由から既定の PowerShell 実行ポリシーでは PowerShell スクリプトの実行が制限されているため、このポリシーを上書きする必要があります。この手順はユーザーの操作が必要なため手作業となります。さいわい、この作業はマシンごとに 1 回実行すれば済みます。PowerShell スクリプトの実行を有効にするには、管理者特権の PowerShell ウィンドウで以下のコマンドを実行します。
PS C:\> Set-ExecutionPolicy AllSigned
Visual Studio によって作成されたアプリ パッケージ フォルダーをテスト マシンにコピーして、PowerShell ウィンドウで Add-AppDevPackage PowerShell スクリプトを実行します。以下のコマンドを使用します。
PS C:\JSGrid1_1.0.0.0_AnyCPU_Debug_Test> .\Add-AppDevPackage.ps1
開発者用ライセンスを取得します。この手順はユーザーの操作が必要なため手作業となります。ただしライセンスの有効期間中にマシンごとに 1 回しか実行する必要がありません。テスト マシンで既に開発者用ライセンスを取得済みの場合、この手順はスキップできます。
ユーザー アカウント制御のメッセージに同意して、指示に従って開発者用ライセンスを取得します。以下のスクリーンショットでは、表示される画面を紹介していきます。
最初のスクリーンショットは、開発者用ライセンスのインストールにあたってライセンス条項への同意が求められる画面を示しています。先に進むには [I Agree] (同意します) をクリックします。
図 3: 開発者用ライセンス取得のためのメッセージに同意する
[Microsoft account] (Microsoft アカウント) ダイアログ ボックスで Microsoft アカウントを入力します。Microsoft アカウントをお持ちでない場合は [Sign up] (新規登録) をクリックしてアカウントを作成します。
図 4: Microsoft アカウント資格情報でサインインする
開発者用ライセンスが作成されたことと、その有効期限を伝える以下の確認メッセージが表示されます。
図 5: 開発者用ライセンスの取得に成功
以下のスクリーンショットは、開発者用ライセンスが取得でき、パッケージのインストールに成功したことを示しています。
図 6: インストール完了
アプリ パッケージがテスト マシンにインストールされ、アプリを起動できる状態になりました。アプリのアクティベーションは、IApplicationActivationManager インターフェイスを使うことで自動化できます。この API は、Visual Studio 2012 と同時に既定でインストールされる Windows SDK に含まれています。アプリの起動には IApplicationActivationManager::ActivateApplication (英語) メソッドを使います。このメソッドの使用方法を以下のコード スニペットで示します。
#include "stdafx.h"#include <shlobj.h>#include <stdio.h>#include <shobjidl.h>#include <objbase.h>#include <atlbase.h>#include <string>/*++ Routine Description: This routine launches your app using IApplicationActivationManager. Arguments: strAppUserModelID - AppUserModelID of the app to launch. pdwProcessId - Output argument that receives the process id of the launched app. Return value: HRESULT indicating success/failure--*/HRESULT LaunchApp(const std::wstring& strAppUserModelId, PDWORD pdwProcessId){ CComPtr<IApplicationActivationManager> spAppActivationManager; HRESULT hrResult = E_INVALIDARG; if (!strAppUserModelId.empty()) { // Instantiate IApplicationActivationManager hrResult = CoCreateInstance(CLSID_ApplicationActivationManager, NULL, CLSCTX_LOCAL_SERVER, IID_IApplicationActivationManager, (LPVOID*)&spAppActivationManager); if (SUCCEEDED(hrResult)) { // This call ensures that the app is launched as the foreground window hrResult = CoAllowSetForegroundWindow(spAppActivationManager, NULL); // Launch the app if (SUCCEEDED(hrResult)) { hrResult = spAppActivationManager->ActivateApplication(strAppUserModelId.c_str(), NULL, AO_NONE, pdwProcessId); } } } return hrResult;}int _tmain(int argc, _TCHAR* argv[]){ HRESULT hrResult = S_OK; if (SUCCEEDED(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED))) { if (argc == 2) { DWORD dwProcessId = 0; ++argv; hrResult = LaunchApp(*argv, &dwProcessId); } else { hrResult = E_INVALIDARG; } CoUninitialize(); } return hrResult;}
このスニペットをコンパイルして、以下のように使うことができます。
C:\>Win8AppLaunch.exe Microsoft.BingNews_8wekyb3d8bbwe!AppexNews
このコード スニペットで欠かせないのが CoAllowSetForegroundWindow (英語) への呼び出しです。この呼び出しが設定されていない場合、アプリは起動しますがフォアグラウンドに移動しません。経験上、起動ツールを記述しようとしている方の多くが、この部分でつまづいているようです。
最後に AppUserModelId の注意点を簡単に説明します。この方法の場合にアプリの起動に必要な入力は、アプリの AppUserModelId です。AppUserModelId はアプリの一意の識別子として機能します。AppUserModelId の取得には PowerShell の使用をお勧めします。マシンにインストールされているすべてのアプリの AppUserModelId を取得する方法を、以下の PowerShell に示します。
$installedapps = get-AppxPackageforeach ($app in $installedapps){ foreach ($id in (Get-AppxPackageManifest $app).package.applications.application.id) { $app.packagefamilyname + "!" + $id }}
また、IAppxManifestReader (英語) を使ってパッケージ内のアプリを列挙し、IAppxManifestApplication::GetAppUserModelId (英語) メソッドを使って AppUserModelId を取得することも可能です。ただし、主な目的がテスト対象のアプリ 1 つだけの場合は、アプリのマニフェストを読み取るツールを作成するよりも、レジストリを使った方がはるかに簡単です。
アプリがテスト マシンにインストールされ、自動化によってアプリを起動できる状態になりました。次の手順では、アプリのコア機能に対するテストを自動化します。これは、単体テストとアプリ自動化を組み合わせることで実現できます。アプリの自動化は、UI オートメーションを使ってユーザー インターフェイス経由で実行します。
単体テストと UI オートメーションを組み合わせてそれぞれの技術を相互補完的に活用することで、アプリを隅々までテストし、品質を高めることができます。単体テストではアプリのコア ビジネス ロジックを自動化できます。UI オートメーションによるテストでは、アプリのユーザー インターフェイスの使用をシミュレーションすることで、アプリの機能を検証できます。これらを組み合わせることでアプリのテスト カバレッジを広げることができます。
では、それぞれの方法で使用できるツールとテクニックを見ていきましょう。
単体テストは、アプリのコア機能を検証するための強力なテクニックです。Visual Studio 2012 では、C# または C++ で記述されたアプリの単体テスト作成がサポートされています。Visual Studio 2012 の単体テスト作成と実行の詳細については、「Windows Metro スタイル アプリでの単体テストの作成と実行」をご覧ください。その他のフレームワークを使った単体テストに既に慣れている場合は、Windows ストア アプリでもそれらを引き続きお使いいただけます。
単体テストはアプリの内部的な動作をテストするには便利ですが、アプリのユーザー インターフェイスを試すことはできません。ユーザー インターフェイスを使ったアプリの機能の検証には UI オートメーション (UIA) をお勧めします。
Windows 8 のセキュリティ モデルでは、UI オートメーション クライアントになるために必要な特権がアプリに与えられません。ただし、デスクトップ アプリを作成して、現在のアプリを対象とするオートメーション クライアントとして動作させることができます。これを行うには「UI オートメーションのセキュリティの概要」に記載のアクセス許可 UIAccess を使って、デスクトップ オートメーション クライアント アプリを作成します。
UI オートメーション クライアントの良い例が、Windows SDK に含まれている Inspect.exe と AccEvent.exe というツールです。Inspect では、両方の種類のアプリの UI オートメーション ツリーを検査できます。AccEvent は UIA イベントをリッスンします。Windows SDK がインストールされているマシンの場合、これらのツールは通常 %ProgramFiles(x86)%\Windows Kits\8.0\bin\<architecture> にあります。2 つのツールが動作するようすを以下に示します。
図 7: Bing ニュース アプリに対して実行中の Inspect ツール
図 8: Bing スポーツ アプリに対して実行中の AccEvent ツール
これらのツールが使用しているアプローチと同様の方法で、アプリを自動化するデスクトップ クライアント アプリを UIA を使って作成できます。UIA クライアントの作成方法を初めて学ぶ場合は、ブログ記事「UI オートメーション クライアント アプリケーションを C++ および C# で作成する」(英語) が非常にお勧めです。この記事では従来型の UI オートメーションについて説明していますが、同じテクニックはあらゆるアプリに当てはまります。
「ドキュメント コンテンツ用 UI オートメーション クライアントのサンプル」(英語) では、UIA コントロール パターンを使ってターゲット アプリのウィンドウの各種コンテンツ (ヘッダー、コメント、現在の選択項目など) を取得する方法がわかります。また、Touch Injection のサンプル (英語) では、Touch Injection API (英語) を使ってアプリ検証のためのタッチをシミュレーションする方法がわかります。
前述のとおり、単体テストと UI オートメーションを相互補完的なテクニックとして使うことで、アプリ内のコア ビジネス ロジックと、ユーザー インターフェイスから実行されたアプリの機能の両方を自動化することができます。これら両方のテクニックを使ってアプリをデバッグしてください。
アプリ ライフサイクルの管理に関するブログ記事で説明されているように、アプリはさまざまな実行時状態を遷移することができます。テストという観点では、これらのすべての状態でアプリを検証することが重要です。これらの状態の遷移を自動化するには Windows 用デバッグ ツールに同梱のツール (PLMDebug) を使います。PLMDebug は設定不要で使えるコマンドライン ツールで、アプリによるライフサイクルの状態間の遷移を自動化できます。
PLMDebug ではニーズを満たせない場合は、IPackageDebugSettings (英語) インターフェイスを使って独自のツールを実装することで、アプリのライフサイクルの状態を変更できます。この API は、Visual Studio と同時に既定でインストールされる Windows SDK に含まれています。IPackageDebugSettings API を使ってアプリのライフサイクルの状態を変更し、状態遷移中にアプリが期待どおりに動作しているかどうかを検証するためのコードを以下に示します。
#include "stdafx.h"#include <stdio.h>#include <shobjidl.h>#include <objbase.h>#include <atlbase.h>#include <string>/*++ Routine Description: This routine changes the lifecycle state of a Windows Store app depending on the input argument. Arguments: strPackageFullName - Package Full Name of the Windows Store app strOperation - Operation to take (/enabledebug, /suspend, /resume, /terminate, /cleanTerminate, /disabledebug) Return Value: HRESULT indicating success/failure--*/HRESULT ChangeLifecycleState(const std::wstring& strPackageFullName, const std::wstring& strOperation){ CComPtr<IPackageDebugSettings> spPackageDebugSettings; HRESULT hrResult = E_INVALIDARG; if (!strPackageFullName.empty() && !strOperation.empty()) { // Instantiate IPackageDebugSettings hrResult = CoCreateInstance(CLSID_PackageDebugSettings, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&spPackageDebugSettings)); // Depending on the operation specified as the command line arg, change the lifecycle state of the app if (SUCCEEDED(hrResult)) { if (_wcsicmp(strOperation.c_str(), L"/enableDebug") == 0) { // Increment debug ref count on the app package - you must do this before you can suspend/resume/terminate hrResult = spPackageDebugSettings->EnableDebugging(strPackageFullName.c_str(), NULL, NULL); } else if (_wcsicmp(strOperation.c_str(), L"/suspend") == 0) { // Asynchronously suspend the app hrResult = spPackageDebugSettings->Suspend(strPackageFullName.c_str()); } else if (_wcsicmp(strOperation.c_str(), L"/resume") == 0) { // Resume the app hrResult = spPackageDebugSettings->Resume(strPackageFullName.c_str()); } else if (_wcsicmp(strOperation.c_str(), L"/terminate") == 0) { // Terminate the app hrResult = spPackageDebugSettings->TerminateAllProcesses(strPackageFullName.c_str()); } else if (_wcsicmp(strOperation.c_str(), L"/cleanTerminate") == 0) { // Clean terminate the app - suspend, then terminate hrResult = spPackageDebugSettings->StartServicing(strPackageFullName.c_str()); if (SUCCEEDED(hrResult)) { hrResult = spPackageDebugSettings->StopServicing(strPackageFullName.c_str()); } } else if (_wcsicmp(strOperation.c_str(), L"/disableDebug") == 0) { // Decrement debug ref count on the app package hrResult = spPackageDebugSettings->DisableDebugging(strPackageFullName.c_str()); } else { hrResult = E_INVALIDARG; } } } return hrResult;}int _tmain(int argc, _TCHAR* argv[]){ HRESULT hrResult = S_OK; if (SUCCEEDED(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED))) { if (argc == 3) { std::wstring strOperation(argv[1]); std::wstring strPackageFullName(argv[2]); hrResult = ChangeLifecycleState(strPackageFullName, strOperation); } else { hrResult = E_INVALIDARG; } CoUninitialize(); } return hrResult;}
C:\>Win8AppLifecycleManager.exe /enableDebug Microsoft.BingNews_1.2.0.98_x64__8wekyb3d8bbweC:\> Win8AppLifecycleManager.exe /suspend Microsoft.BingNews_1.2.0.98_x64__8wekyb3d8bbweC:\> Win8AppLifecycleManager.exe /resume Microsoft.BingNews_1.2.0.98_x64__8wekyb3d8bbweC:\> Win8AppLifecycleManager.exe /terminate Microsoft.BingNews_1.2.0.98_x64__8wekyb3d8bbweC:\> Win8AppLifecycleManager.exe /cleanTerminate Microsoft.BingNews_1.2.0.98_x64__8wekyb3d8bbweC:\> Win8AppLifecycleManager.exe /disableDebug Microsoft.BingNews_1.2.0.98_x64__8wekyb3d8bbwe
最後に PackageFullName の注意点を簡単に説明します。これまでに説明してきたライフサイクルの状態を管理する手法のすべてで、アプリを識別するための入力引数として PackageFullName が必要になります。PackageFullName の取得には、以下の例に示す Get-AppxPackage PowerShell コマンドレット (英語) の使用をお勧めします (読みやすくするため出力は一部省略しています)。
PS C:\> Get-AppxPackageName : Microsoft.BingNewsPublisher : CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=USArchitecture : X64ResourceId :Version : 1.2.0.98PackageFullName : Microsoft.BingNews_1.2.0.98_x64__8wekyb3d8bbwe ←InstallLocation : C:\Program Files\WindowsApps\Microsoft.BingNews_1.2.0.98_x64__8wekyb3d8bbweIsFramework : FalsePackageFamilyName : Microsoft.BingNews_8wekyb3d8bbwePublisherId : 8wekyb3d8bbwe
アプリのアンインストールには PowerShell コマンドレット (英語)、具体的には Remove-AppxPackage (英語) の使用をお勧めします。以下に例を示します (読みやすくするため出力は一部省略しています)。
PS C:\> Get-AppxPackageName : Microsoft.SDKSamples.ListViewEssentials.JSPublisher : CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=USArchitecture : NeutralResourceId :Version : 1.0.0.0PackageFullName : Microsoft.SDKSamples.ListViewEssentials.JS_1.0.0.0_neutral__8wekyb3d8bbwe ←InstallLocation : C:\Users\user1\Downloads\Samples\Controls_ListViewBasic\JS\bin\Debug\AppXIsFramework : FalsePackageFamilyName : Microsoft.SDKSamples.ListViewEssentials.JS_8wekyb3d8bbwePublisherId : 8wekyb3d8bbwePS C:\> Remove-AppxPackage Microsoft.SDKSamples.ListViewEssentials.JS_1.0.0.0_neutral__8wekyb3d8bbwe
このブログ記事では、アプリのテストを自動化するためのさまざまなヒント、ツール、テクニックを紹介しました。テストを自動化することで、アプリ検証のレベル向上と品質の維持を効率的に実現できます。ただし一方で、アプリの検証に人間的な視点を取り入れることができる手動テストの役割も、検証では重要だということを覚えておいてください。ですから、自動テストと手動テストを組み合わせることで、バランスの良いアプローチを使ってアプリをテストできるようになります。
-- Windows 主任テスト リード、Ashwin Needamangala
今回の記事作成にあたっては、Mete Goktepe 氏、J. Kalyana Sundaram 氏、Ben Betz 氏、Will Wei 氏、Chris Edmonds 氏、Craig Campbell 氏、Jake Sabulsky 氏にご協力いただきました。ありがとうございました。