対象とするクラスに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();