Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

[.NET][C#]NUnitのAttribute_Setup,TearDown系を試す

はじめに

今回は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を付けたメソッドはテストメソッドの実行の度に前後で実行される。
そのため、各テストの共通する処理等を実装するのがいいだろう。

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の実行が適用される。

サブクラスで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回だけ実行される。

OneTimeSetUp、OneTimeTearDown実行例
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メソッドが一度だけ実行される。

SetUpFixture実行例
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単位で事前・事後の処理を定義できる。
テスト全体の共通処理はここに書くと良いだろう。

まとめ

NUnit_ExecuteFlow.png

事前・事後処理メソッドはテスト実施時のパフォーマンスに影響するので
上手いこと活用したいところ。
メソッド単位、クラス単位、namespace単位で何を事前・事後処理させるかの設計手腕が問われそう。

suganury
レガシー脱却目指すおじさん
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away