対象とするクラスにTestClass属性を、メソッドにTestMethod属性を指定します。
public class MyClass
{
static public int Method()
{
return 0;
}
}
[TestClass]
public class UnitTest
{
[TestMethod]
public void TestMethod1
{
int a = MyClass.Method();
Assert.AreEqual(1, a);
// Assert.AreEqual に失敗しました。<1> が必要ですが、<0> が指定されました。
}
}
クラス | テスト対象 |
---|---|
Assert | 一般 |
CollectionAssert | コレクション |
StringAssert | 文字列 |
AssertFailedException | 例外 |
AssertInconclusiveException | |
UnitTestAssertException |
属性 | 意味 |
---|---|
TestClassAttribute | このクラスは、テスト対象である |
TestMethodAttribute | このメソッドは、テスト対象である |
ExpectedExceptionAttribute | このテストから例外が投げられることが期待される |
IgnoreAttribute | このテストは一時的に無視される |
TimeoutAttribute | タイムアウト期間がある |
︙ |
テスト対象とするメソッドは、以下の条件を満たす必要があります。
この条件を満たすメソッドは、次のような形式となります。
[TestMethod] public void TestMethod() { }
このときメソッドがasyncでマークされていると、.NETのバージョンによってはテスト対象と認識されないことがあります。Asynchronous unit tests with .NET 4.0 - Stack Overflow
この場合はテストメソッドからTaskを返すようにするか、asyncを用いず同期で処理します。
[TestMethod] public void TestMethod() { Task.Delay(1).Wait(); }
ただしテストのスレッドで待機させると問題がある場合には、この方法は使えません。
たとえば1000ミリ秒以内に完了しないときは失敗とするならば、次のように指定します。
[TestMethod, Timeout(1000)] public void TestMethod() { }
一般的なオブジェクトをテストできます。
検証内容 | メソッド | |
---|---|---|
肯定 | 否定 | |
オブジェクトが等しいか | AreEqual() | AreNotEqual() |
オブジェクトが同一か | AreSame() | AreNotSame() |
指定の型であるか | IsInstanceOfType() | IsNotInstanceOfType() |
trueであるか | IsTrue() | IsFlase() |
nullであるか | IsNull() | IsNotNull() |
このAssertクラスは、DebugクラスのAssert()メソッドとは異なります。
コレクションの検証にはCollectionAssert、文字列の特殊な検証にはStringAssertを用います。
メソッド | 機能 |
---|---|
Fail() | AssertFailedExceptionを投げる |
Inconclusive() | AssertInconclusiveExceptionを投げる |
ReplaceNullChars() | '\0'を"\0"に置き換えられる (V1 frameworkとの互換性を維持するためにある) |
Assert.AreEqualにあるように、多数のオーバーロードがあります。
public static void AreEqual( Object expected, // テストで予期するオブジェクト Object actual // テストで生成したオブジェクト )Assert.AreEqual メソッド (Object, Object) (Microsoft.VisualStudio.TestTools.UnitTesting) | MSDN
浮動小数点数は演算時に丸めによって誤差が生じるため、精度を指定できるAreEqual(Double, Double, Double)の形式で検証します。
public static void AreEqual<T>( T expected, // テストで予期するジェネリック型データ T actual // テストで生成したジェネリック型データ )Assert.AreEqual(T) メソッド (T, T) (Microsoft.VisualStudio.TestTools.UnitTesting) | MSDN
public static void AreEqual( double expected, // テストで予期する値 double actual, // テストで生成した値 double delta // 精度 )Assert.AreEqual メソッド (Double, Double, Double) (Microsoft.VisualStudio.TestTools.UnitTesting) | MSDN
public static void AreEqual( string expected, // テストで予期する値 string actual, // テストで生成した値 bool ignoreCase // trueならば、大文字/小文字を区別しない )Assert.AreEqual メソッド (String, String, Boolean) (Microsoft.VisualStudio.TestTools.UnitTesting) | MSDN
第3引数を指定しないとobjectとして比較されるため、ZERO WIDTH NO-BREAK SPACEのような特殊文字を含むときに異なる結果となります。
このオーバーロードではカルチャに依存した結果となるため、非依存としたければ第3引数を指定せずobjectとして比較します。
string str1 = new String(new char[] { 'A' }); string str2 = new String(new char[] { 'A', '\uFEFF' }); // U+FEFF (ZERO WIDTH NO-BREAK SPACE) string expected = "A"; Assert.AreEqual(expected, str1, false); // 成功 Assert.AreEqual(expected, str2, false); // 成功 (特殊文字が無視されている) Assert.AreEqual(expected, str1); // 成功 Assert.AreEqual(expected, str2); // Assert.AreEqual に失敗しました。<A> が必要ですが、<A> が指定されました。
これはStringの既定の比較方法である、StringComparison.Ordinalと同じ結果です。
bool r1 = string.Equals(expected, str2, StringComparison.InvariantCulture); // true bool r2 = string.Equals(expected, str2, StringComparison.Ordinal); // false
カルチャを指定しないオーバーロードではインバリアント カルチャが使用され、次のように指定するのと同じです。
Assert.AreEqual(expected, str2, false, CultureInfo.InvariantCulture);
コレクションをテストできます。
検証内容 | メソッド | |
---|---|---|
肯定 | 否定 | |
同一であるか (同じ値の要素が同じ順番で同じ数ある) | AreEqual() | AreNotEqual() |
等価であるか (同じ値の要素が同じ数ある) | AreEquivalent() | AreNotEquivalent() |
指定の要素を含むか | Contains() | DoesNotContain() |
別のコレクションのサブセット (部分集合) であるか | IsSubsetOf() | IsNotSubsetOf() |
検証内容 | メソッド |
---|---|
すべての要素が指定の型であるか | AllItemsAreInstancesOfType() |
すべての要素がnullでないか | AllItemsAreNotNull() |
すべての要素が一意であるか (同一の要素がない) | AllItemsAreUnique() |
public static void AreEqual ( System.Collections.ICollection expected, System.Collections.ICollection actual );AreEqual(ICollection, ICollection) - CollectionAssert.AreEqual Method (Microsoft.VisualStudio.TestTools.UnitTesting) | Microsoft Learn
int[] actual = Method(); int[] expected = new int[]{ 1, 2, 3 }; CollectionAssert.AreEqual(expected, actual);
ICollectionを実装していれば良いため、Dictionary<TKey,TValue>なども対象とできます。
Dictionary<int, string> expected = new Dictionary<int, string> { [0] = "a", [3] = "b", [5] = "c", }; CollectionAssert.AreEqual(expected, actual);
複雑なコレクションは、IComparerを引数に取るメソッドで検証します。
public static void AreEqual ( System.Collections.ICollection expected, System.Collections.ICollection actual, System.Collections.IComparer comparer );
この場合には比較用に、IComparerを実装したクラスを用意します。
public class MyComparer : IComparer { int IComparer.Compare(Object x, Object y) { return (x == y) ? 0 : 1; } }
コレクションにnullの要素があるか、コレクションがnullのときに失敗します。
object[] c1 = new[] { new object(), new object() }; CollectionAssert.AllItemsAreNotNull(c1); // ok object[] c2 = new[] { new object(), null }; CollectionAssert.AllItemsAreNotNull(c2); // Collectionassert.AllItemsAreNotNull に失敗しました。 object[] c3 = null; CollectionAssert.AllItemsAreNotNull(c3); // CollectionAssert.AllItemsAreNotNull に失敗しました。パラメーター 'collection' は無効です。値を null にすることはできません。。
文字列をテストできます。
このクラスは多様な条件で文字列を評価するためのものであり、単純に等しいかどうかを検証するだけならば、AssertクラスのAreEqual()を用います。
検証方法 | メソッド | |
---|---|---|
肯定 | 否定 | |
指定の正規表現に一致するか | Matches() | DoesNotMatch() |
検証方法 | メソッド |
---|---|
指定の文字列を含むか | Contains() |
指定の文字列で始まるか | StartsWith() |
指定の文字列で終わるか | EndsWith() |
public static void Contains ( string value, // substringを含むことが予期される文字列 string substring // value内に現れることが予期される文字列 );Contains(String, String) - StringAssert.Contains Method (Microsoft.VisualStudio.TestTools.UnitTesting) | Microsoft Learn
valueにはテストで生成した値を、substringにはテストの結果として予期する値を指定します。これはAreEqual()などの引数とは順が異なるため注意が必要です。
public static void StartsWith ( string value, // substringで始まることが予期される文字列 string substring // valueの接頭辞として予期される文字列 );StartsWith(String, String) - StringAssert.StartsWith Method (Microsoft.VisualStudio.TestTools.UnitTesting) | Microsoft Learn
例外の検証には、ExpectedExceptionAttributeを属性に指定します。ExpectedExceptionAttribute クラス (Microsoft.VisualStudio.TestTools.UnitTesting) | MSDN
この属性が付けられたメソッドでは、指定の例外が投げられないとテストに失敗します。
[TestMethod] [ExpectedException(typeof(System.Exception))] public void TestMethod1() { }
たとえばこの場合は、「テスト メソッド TestMethod1 は例外をスローしませんでした。」「テスト メソッドで定義されている属性 ExpectedExceptionAttribute で例外が予期されていました。」として失敗します。
[TestMethod] [ExpectedException(typeof(System.Exception))] public void TestMethod2() { throw new System.Exception(); }
一方で例外が投げられれば、テストに成功します。このとき例外はメソッドの外に投げられる必要があるため、メソッド内で捕捉しては失敗となります。
[TestMethod] [ExpectedException(typeof(System.Exception))] public void TestMethod3() { try { throw new System.Exception(); } catch (Exception) { } }
また例外が投げられることを期待する処理は、テストメソッドの最後にします。さもなくばそれ以降の処理が評価されないまま、テストに成功してしまうことになります。
[TestMethod]
[ExpectedException(typeof(System.Exception))]
public void TestMethod2()
{
throw new System.Exception();
Assert.Fail(); // ここが評価されないまま、テストに成功する
}
指定の例外から派生している型も含めるならば、AllowDerivedTypesプロパティをtrueとします。ExpectedExceptionAttribute.AllowDerivedTypes Property (Microsoft.VisualStudio.TestTools.UnitTesting) | Microsoft Learn
[ExpectedException(typeof(System.Exception), AllowDerivedTypes = true)]
例外が投げられなかったことでテストが失敗したときに、テスト結果に含めるメッセージを指定できるオーバーロード、
public ExpectedExceptionAttribute (Type exceptionType, string noExceptionMessage);ExpectedExceptionAttribute(Type, String) - ExpectedExceptionAttribute Constructor (Microsoft.VisualStudio.TestTools.UnitTesting) | Microsoft Learn
がありますが、そのメッセージの確認方法は不明です。c# - Where does the string in ExpectedException.NoExceptionMessage appear? - Stack Overflow
Ignore属性をテストメソッドに追加することで、そのテストを一時的に無効にできます。
[TestMethod] [Ignore] public void TestMethod1() { }IgnoreAttribute クラス (Microsoft.VisualStudio.TestTools.UnitTesting) | MSDN
無効化したテストを再び有効にするには、Ignore属性を削除するか、コメントアウトします。方法 : テストを無効または有効にする | MSDN
TestMethod属性を削除することでもテストを無効にできますが、それではテストの存在そのものが隠されてしまうため、Ignore属性を用いるようにします。
複数テストの有効・無効を一括して切り替えるには、#ifディレクティブでIgnore属性の指定を制御するか、TestCategory属性でテストをまとめます。c# - Ignore IgnoreAttribute - Stack Overflow
テスト対象に共通の名前を付加することで、それらをグループ化して一括して選択できます。TestCategoryAttribute Class (Microsoft.VisualStudio.TestTools.UnitTesting) | Microsoft Learn
この属性はAttributeUsageでAttributeTargets.Methodが指定されており、メソッドだけに指定できます。
[TestMethod] [TestCategory("CategoryName")] public void TestMethod1() { }
この指定に対して、Visual StudioではTrait:"CategoryName"
のようにフィルタを指定することで、CategoryNameだけのテストを抽出できます。
テストに優先順位を付けることができます。PriorityAttribute Class (Microsoft.VisualStudio.TestTools.UnitTesting) | Microsoft Learn
これは順序指定テスト (ordered test) や、順位によるフィルタとして利用できます。What is PriorityAttribute Used for in MSTEST / Visual Studio - Stack Overflow
下表の属性を付加することで、テストに共通して必要な処理を行うメソッドを定義できます。
属性 | 用途 | 適用回数 | 適用条件 | 実行時機 |
---|---|---|---|---|
AssemblyInitializeAttribute | アセンブリの初期化 | アセンブリ内で1つだけ | staticメソッドで、TestContextを引数にとる | アセンブリのすべてのクラスの読み込み前 |
ClassInitializeAttribute | クラスの初期化 | クラス内で1つだけ | クラスのすべてのテストメソッドの読み込み前 ※1 | |
TestInitializeAttribute | テスト対象の初期化 | 制限なし | なし | クラスのテストメソッドの読み込み前 |
TestCleanupAttribute | テスト対象の終了処理 | クラスのテストメソッドの実行後 | ||
ClassCleanupAttribute | クラスの終了処理 | クラス内で1つだけ | staticメソッド | クラスのすべてのテストメソッドの実行後 ※1 |
AssemblyCleanupAttribute | アセンブリの終了処理 | アセンブリ内で1つだけ | アセンブリのすべてのクラスの実行後 |
※1 そのクラスのTestMethodが付加されているメソッドの実行前後にのみ呼ばれる。つまりTestMethodが付加されていないpublic staticなメソッドが他のクラスから実行されても、呼ばれない。
たとえば次のようにテスト対象のクラスが定義されているとき、
[TestClass] public class UnitTest { // 初期化 [AssemblyInitialize] public static void AssemblyInit(TestContext context) { Debug.WriteLine("AssemblyInitialize"); } [ClassInitialize] public static void ClassInit(TestContext context) { Debug.WriteLine("ClassInitialize"); } [TestInitialize] public void TestInit() { Debug.WriteLine("TestInitialize"); } // 終了処理 [TestCleanup] public void TestCleanup() { Debug.WriteLine("TestCleanup"); } [ClassCleanup] public static void ClassCleanup() { Debug.WriteLine("ClassCleanup"); } [AssemblyCleanup] public static void AssemblyCleanup() { Debug.WriteLine("AssemblyCleanup"); } // テスト [TestMethod] public void Test1() { Debug.WriteLine("TestMethod1"); } [TestMethod] public void Test2() { Debug.WriteLine("TestMethod2"); } }
テスト対象のメソッドTest1()を実行すると、
の順で呼ばれます。またTest1()とTest2()を同時に実行したときには、
のように、アセンブリとクラスの処理はいずれかで1度だけ呼ばれます。
[AssemblyInitialize] public static void AssemblyInit(TestContext context) { }AssemblyInitializeAttribute Class (Microsoft.VisualStudio.TestTools.UnitTesting) | Microsoft Learn
この属性を付けたメソッドは、アセンブリ内で1つしか認められません。複数あると「UTA013: ***: 1 つのアセンブリ内で、AssemblyInitialize 属性を伴う 2 つ以上のメソッドを定義することはできません。」として実行時にエラーとなります。
アプリケーションを開始した実行可能ファイルのパスから、テストから実行されたことを検出できます。
if (Application.StartupPath.EndsWith("TESTWINDOW")) { // テストから実行された }
バージョン | パス |
---|---|
Visual Studio 2015 | C:\PROGRAM FILES (X86)\MICROSOFT VISUAL STUDIO 14.0\COMMON7\IDE\COMMONEXTENSIONS\MICROSOFT\TESTWINDOW |
Visual Studio 2022 | C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\Extensions\TestPlatform |
Debugger.IsAttachedで、[テストのデバッグ]で実行されていることを検出できます。
テストの終了後にSystem.InvalidOperationExceptionが投げられ、「進行中のテストの実行がないので、テストの実行を取り消すことができません。(The cancelation of the test run is not possible as there is no test run which is in progress.)」と報告されることがあります。これはデバッグの設定で[マイ コードのみを有効にする]を有効にすることで解決できます。visual studio 2012 - Unit Test: The cancelation of the test run is not possible as there is no test run which is in progress - Stack Overflow
Visual Studioでテストをデバッグ実行したときにFormが表示されないときは、FormのShowInTaskbarをfalseに設定します。winforms - Displaying Windows Forms inside unit test methods - Stack Overflow
Form form = new Form(); if (System.Diagnostics.Debugger.IsAttached) { form.ShowInTaskbar = false; } form.ShowDialog();