6-2. Assert.ThrowsException
例外が出ることを確かめるテストは、従来のデスクトップアプリ用のMSTestではExpectedException属性を使って次のように書いていました。
[TestMethod]
[ExpectedException(typeof(ArgumentOutOfRangeException))]
public void TestSay0Error() {
string result = this._testTarget.Say(0);
Assert.Fail("例外が発生せず、'{0}'が返りました。", result);
}
Windowsストアアプリでは、Microsoft.VisualStudio.TestPlatform.UnitTestFramework.AppContainer名前空間のAssertクラスにThrowsExceptionメソッドがあり、それを使います。AreEqualメソッドなどを持っているAssertクラスは、別にMicrosoft.VisualStudio.TestPlatform.UnitTestFramework名前空間にあるので、クラス名の衝突を避けるために1手間余計に掛かります。
ThrowsExceptionメソッドは、型引数に期待される例外の型を与え、第1引数には実行するテストのラムダ式(ただし、Task型を返さねばならない)、第2引数には失敗時のメッセージ文字列を与えます。また、ThrowsExceptionメソッドは、成功した時には発生した例外オブジェクトを返してきますので、その後で例外の内容を検査することもできます(次のコード)。
Windows Phoneでは、Assert.ThrowsExceptionはサポートされておらず、また、従来のExpectedException属性も使えないため、try~catchするしかありません。WindowsとPhoneで共有したいテストでは、この書き方をすることになります(次のコード)。
n = 0のときは例外を出す
using System;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
#if WINDOWS_APP
// Assertクラスの名前衝突を避けるため、クラス名のエイリアスを切っておく
using AppContainerAssert
= Microsoft.VisualStudio.TestPlatform.UnitTestFramework
.AppContainer.Assert;
#endif
namespace CsTdd08Tests.Shared
{
[TestClass]
public class FizzBuzzPlayerTest
{
// n=0のときは、例外が出る
#if WINDOWS_APP
// WindowsではThrowsExceptionが使えるので、簡潔に書ける
[TestMethod]
public async Task TestSay0Error()
{
CsTdd08App.FizzBuzzPlayer p = new CsTdd08App.FizzBuzzPlayer();
// ThrowsException で、期待する型の例外が出ることをチェック
var resultEx
= await AppContainerAssert
.ThrowsException<ArgumentOutOfRangeException>(
() => Task.Run(() => p.Say(0)),
"想定した例外が出ませんでした。"
);
// 例外のメッセージをチェック
StringAssert.StartsWith(
resultEx.Message,
"FizzBuzz ゲームは 1 から始まります。"
);
}
#endif
#if WINDOWS_PHONE_APP
// PhoneではThrowsExceptionが使えないので、try~catchするしかない
[TestMethod]
public void TestSay0Error()
{
CsTdd08App.FizzBuzzPlayer p = new CsTdd08App.FizzBuzzPlayer();
string result = null;
try
{
result = p.Say(0);
}
catch (ArgumentOutOfRangeException ex)
{
// 想定した例外が出た。
// 例外のメッセージをチェックする。
StringAssert.StartsWith(
ex.Message,
"FizzBuzz ゲームは 1 から始まります。"
);
return;
}
catch (Exception ex)
{
Assert.Fail("想定外の例外'{0}'が返されました。", ex.GetType().Name);
}
Assert.Fail("例外が発生せず、'{0}'が返されました。", result);
}
#endif
}
}
6-3. UITestMethod
UI部品をテストするときは、UIスレッドで実行する必要があります。そのためには、Dispatcherを使って次のようなテストコードを書いていました。
Dispatcherを使ってUIスレッドでテストを実行する(これはWindows/Phoneともに動作する)
using System;
using System.Threading.Tasks;
using Windows.ApplicationModel.Core;
using Windows.UI.Core;
using Windows.UI.Xaml.Controls;
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
namespace CsTdd08Tests.Shared
{
[TestClass]
public class MainPageTest
{
[TestMethod]
public async Task SetTitleTextTest()
{
var dispatcher = CoreApplication.MainView.CoreWindow.Dispatcher;
await dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
// UIスレッドでテストを実行する
var frame = new Frame();
frame.Navigate(typeof(CsTdd08App.MainPage));
// フレームのコンテンツはMainPageのはず
var mainPage = frame.Content as CsTdd08App.MainPage;
Assert.IsNotNull(mainPage);
// MainPageにSetTitleTextメソッドとTitleTextプロパティを実装する
mainPage.SetTitleText("新しいタイトル");
Assert.AreEqual<string>("新しいタイトル", mainPage.TitleText);
});
}
}
}
Windowsストアアプリでは、UITestMethod属性を使って簡潔に書けるようになりました(次のコード)。Dispatcherを取得する方法で悩まなくて済むのもありがたいです。
UITestMethod属性を使ってUIスレッドでテストを実行する(Phoneでは動作しない)
using System;
using System.Threading.Tasks;
using Windows.ApplicationModel.Core;
using Windows.UI.Core;
using Windows.UI.Xaml.Controls;
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
#if WINDOWS_APP
// 名前空間のエイリアスを切っておく。(単にusingするとAssertクラスが衝突する)
using AC = Microsoft.VisualStudio.TestPlatform.UnitTestFramework.AppContainer;
#endif
namespace CsTdd08Tests.Shared
{
[TestClass]
public class MainPageTest
{
#if WINDOWS_APP
// WindowsではUITestMethod属性が使える
[AC.UITestMethod]
public void SetTitleTextTest_Windows()
{
var frame = new Frame();
frame.Navigate(typeof(CsTdd08App.MainPage));
// フレームのコンテンツはMainPageのはず
var mainPage = frame.Content as CsTdd08App.MainPage;
Assert.IsNotNull(mainPage);
// MainPageにSetTitleTextメソッドとTitleTextプロパティを実装する
mainPage.SetTitleText("新しいタイトル");
Assert.AreEqual<string>("新しいタイトル", mainPage.TitleText);
}
#endif
}
}
まとめ
- Shared Project Reference Managerを使って共有プロジェクトを作り、そこにユニットテストを置くこともできる(現在は有償版のVisual Studioのみ)
-
非同期メソッドをテストするには、テストメソッドのシグネチャに「
async Task」を付ける -
パラメタライズドテストが可能になった(
DataTestMethod属性/DataRow属性) -
例外が出ることを確かめるテストは
Assert.ThrowsExceptionで(Windowsストアアプリのみ) -
テストをUIスレッドで実行するには
UITestMethod属性(Windowsストアアプリのみ)
今回は前後編に渡って、ユニバーサルプロジェクトでユニットテストを作る方法と、MSTestの新機能について解説してきました。いかがだったでしょうか、ユニットテストも「ユニバーサル」に書ける自信を持ってもらえたと思います。
パラメタライズドテストのところでFizzBuzzPlayerクラスを作りました。記事で解説する余裕はなかったのですが、サンプルソースにはそのUIも実装してあります。
- CsTdd08App.Shared/FizzBuzzPlayer.cs:FizzBuzzのロジック(記事中で作成)
- CsTdd08App.Shared/UI/FizzBuzzViewModel.cs:UIにバインドするクラス(TDDで実装)
- CsTdd08App.Shared/UI/FizzBuzzControl.xaml:FizzBuzzのUI(ユーザーコントロール)
- CsTdd08Tests.Shared/UI/FizzBuzzViewModelTest.cs:FizzBuzzViewModelのユニットテスト
FizzBuzzViewModelはごく簡単なものですが、それでもFizzBuzzViewModelTestには26個のテストメソッドがあります(実行される回数はその2倍の52回)。UIにバインドするためのプロパティやコマンドをTDDで実装するときの参考にしてください。


