はじめに
今回はNUnitのAttributeで最も重要な
テストメソッド、テストクラスに付けるAttributeを調べてみた。
▼NUnit関連記事
[.NET][C#]NUnitを使用して単体テスト自動化 基本編
[.NET][C#]NUnitを使用した単体テストのカバレッジレポートを自動生成する
[.NET][C#]NUnitのClassic Modelアサーションメソッド一覧 ※Assertクラス
[.NET][C#]NUnitのStringAssertアサーションメソッド一覧
[.NET][C#]NUnitのCollectionAssertアサーションメソッド一覧
[.NET][C#]NUnitのFileAssert・DirectoryAssertアサーションメソッド一覧
[.NET][C#]NUnitのAttribute_テストメソッド、テストクラス系を試す
実施環境
.NET:3.1.401
C#:8.0
NUnit:3.12.0
NUnit Attribute
ソースコードがテスト用のものであることを実行環境に伝えるマーキングで
テスト用のクラスやメソッドに付ける。
また、テスト用のパラメータの指定や事前・事後処理などの実行順序の定義するものなど、様々なAttributeがある。
今回はテストの実施前後に処理を行うための
Setup,TearDown系Attributeを試す。
1. Setup、TearDown
テストメソッドの前後の共通処理を定義したい場合に使用するAttribute。
[Setup]
を付けたメソッドは各テストメソッド実行前に、
[TearDown]
を付けたメソッドは各テストメソッド実行後に
それぞれ実行される。
Setup、TearDownを付けたメソッドはテストメソッドの実行の度に前後で実行される。
そのため、各テストの共通する処理等を実装するのがいいだろう。
public class SetupTearDownAttributesTest
{
[SetUp]
public void Init() { TestContext.WriteLine("SetUp処理");}
[TearDown]
public void CleanUp() { TestContext.WriteLine("TearDown処理"); }
[TestCase]
public void TestA()
{
TestContext.WriteLine("A method.");
Assert.Pass();
}
[TestCase]
public void TestB()
{
TestContext.WriteLine("B method.");
Assert.Pass();
}
}
public class NonSetupTearDownAttributesTest
{
[TestCase]
public void TestC()
{
TestContext.WriteLine("C method.");
Assert.Pass();
}
}
√ TestA() [1ms]
標準出力メッセージ:
SetUp処理
A method.
TearDown処理
√ TestB() [< 1ms]
標準出力メッセージ:
SetUp処理
B method.
TearDown処理
√ TestC() [16ms]
標準出力メッセージ:
C method.
テストメソッドの単位でSetup、TearDownの処理が実行されているのが分かる。
当然だが、Setup、TearDownのスコープは、同一クラス内のテストメソッドとなる。
上記例の場合、TestCメソッドは別クラスのテストメソッドのため、Setup、TearDown処理は実行されない。
Setup、TearDownメソッドがあるクラスを継承した場合、
サブクラスのテストメソッドにもSetup、TearDownの実行が適用される。
[Category("SetupTearDownAttributesTest")]
public class SetupTearDownAttributesSubTest : SetupTearDownAttributesTest
{
[TestCase]
public void TestD()
{
Console.WriteLine("SubClass D method.");
Assert.Pass();
}
}
√ TestD() [< 1ms]
標準出力メッセージ:
SetUp処理
SubClass D method.
TearDown処理
なお、 SetUpメソッドで例外がスローされた場合、テストメソッド、およびTearDownメソッドは実行されない。
2. OneTimeSetUp、OneTimeTearDown
テストクラスの前後の共通処理を定義したい場合に使用するAttribute。
[OneTimeSetUp]
を付けたメソッドはクラスのテストメソッド実行前に、
[OneTimeTearDown]
を付けたメソッドはクラスのテストメソッド実行後に
それぞれ1回だけ実行される。
public class OneTimeSetupTearDownAttributesTest
{
private string setupStr;
private string teardownStr;
[OneTimeSetUp]
public void Init() { setupStr = "OneTimeSetUp処理";}
[OneTimeTearDown]
public void CleanUp() { teardownStr = "OneTimeTearDown処理"; }
[TestCase]
public void TestA()
{
TestContext.WriteLine($"A method. setupStr={setupStr}, teardownStr={teardownStr}");
Assert.Pass();
}
[TestCase]
public void TestB()
{
TestContext.WriteLine($"B method. setupStr={setupStr}, teardownStr={teardownStr}");
Assert.Pass();
}
}
√ TestA() [17ms]
標準出力メッセージ:
A method. setupStr=OneTimeSetUp処理, teardownStr=
√ TestB() [< 1ms]
標準出力メッセージ:
B method. setupStr=OneTimeSetUp処理, teardownStr=
少し例が分かり辛いが、TestAメソッド
、TestBメソッド
実行時にメンバ変数setupStr
に値が代入されていて、メンバ変数teardownStr
には値が入っていないことが分かる。
これは、テストメソッド実行時のタイミングでは、OneTimeSetUpメソッドが一度だけ実行されており、OneTimeTearDownメソッドはまだ実行されていないためだ。
(クラスが持つ全てのテストメソッド実行後にOneTimeTearDownメソッドが呼び出される。)
※OneTimeSetUpメソッド、OneTimeTearDownメソッド内で標準出力してもdotnet test
コマンド実行時に表示されないため。上手いやり方がイマイチ思いつかなかったため、このようなやり方とした。
このようにクラス単位のテスト実施前後で一度だけ実行されることから、
テストに使用する外部ファイルの読み込むやDB接続が必要なときのコネクション生成など
各テストメソッドで利用するリソースの準備、および開放などは
このAttributeで実施した方がテスト実行効率が良くなるだろう。
3. SetUpFixture
namespace単位で前後の共通処理を定義したい場合に使用するAttribute。
[SetUpFixture]
を付けたクラスが存在する場合、このクラスのnamespace配下のテストが実行される前後で
このクラスが持つOneTimeSetUpメソッド、OneTimeTearDownメソッドが一度だけ実行される。
namespace SetUpFixturePractice
{
[SetUpFixture]
public class MySetUpClass
{
public static Dictionary<string, object> Config = new Dictionary<string, object>();
public static string SettingKeyA = "setting-A";
public static string SettingKeyB = "setting-B";
public static string SettingKeyC = "setting-C";
[OneTimeSetUp]
public void RunBeforeAnyTests()
{
Config.Add(SettingKeyA,"aaa");
Config.Add(SettingKeyB,"bbb");
Config.Add(SettingKeyC,"ccc");
}
[OneTimeTearDown]
public void RunAfterAnyTests()
{
Config.Clear();
}
}
public class SetUpFixtureAttributesTestA
{
[TestCase]
public void TestA()
{
TestContext.WriteLine($"A method. settings={MySetUpClass.Config[MySetUpClass.SettingKeyA]}");
Assert.Pass();
}
}
public class SetUpFixtureAttributesTestB
{
[TestCase]
public void TestB()
{
TestContext.WriteLine($"B method. settings={MySetUpClass.Config[MySetUpClass.SettingKeyB]}");
Assert.Pass();
}
}
}
√ TestA() [20ms]
標準出力メッセージ:
A method. settings=aaa
√ TestB() [< 1ms]
標準出力メッセージ:
B method. settings=bbb
上記結果を見てわかるとおり、
SetUpFixtureAttributesTestAクラス
およびSetUpFixtureAttributesTestBクラス
のテストメソッド実行時にMySetUpClass.RunBeforeAnyTestsメソッド
でセットされた値が取り出せていることが分かる。
これは、namespace:SetUpFixturePractice
の配下のテストが実行される前にMySetUpClassクラス
のOneTimeSetUpメソッドが呼び出されているためだ。
また、テスト実行後、MySetUpClass.RunAfterAnyTestsメソッド
が呼ばれている。(上記例では分からないが)
このようにnamespace単位で事前・事後の処理を定義できる。
テスト全体の共通処理はここに書くと良いだろう。
まとめ
事前・事後処理メソッドはテスト実施時のパフォーマンスに影響するので
上手いこと活用したいところ。
メソッド単位、クラス単位、namespace単位で何を事前・事後処理させるかの設計手腕が問われそう。