Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9707

[.NET][C#]NUnitのAttribute_テストメソッド、テストクラス系

$
0
0

はじめに

今回は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:3.1.401
C#:8.0
NUnit:3.12.0

NUnit Attribute

ソースコードがテスト用のものであることを実行環境に伝えるマーキングで
テスト用のクラスやメソッドに付ける。
また、テスト用のパラメータの指定や事前・事後処理などの実行順序の定義するものなど、様々なAttributeがある。

今回はNUnitを実行するために必須である
テストクラス、テストメソッド系のAttributeを試す。

1. TestFixture

テストクラスであることを示すAttribute。

パラメータ無し
[TestFixture]publicclassTests{[Test]publicvoidTest1(){Assert.Pass();}}

上記のようにクラスに付ける。
クラスに[TestFixture]が付いているだけではテストが実行されるわけではなく、
[Test][TestCase]といったAttributeが付いたテストメソッドを持たないといけない。
逆にテストメソッドがあるのであれば[TestFixture]は無くてもいい。

型を指定
[TestFixture(typeof(string))]publicclassTests<T>{[Test]publicvoidTypeTest(){objectobj=DummyTarget.getAnonymousObject();Assert.IsInstanceOf<T>(obj);}}

上記のようにテストコード中の型を固定したくない場合は、
テストクラスにジェネリクスを付け、[TestFixture]の引数にType型を指定する。

パラメータ指定
[TestFixture("MBr","マリオ","ピーチ","クッパ")][TestFixture("LoZ","リンク","ゼルダ","ガノン")][TestFixture("Pok","サトシ","カスミ","シゲル")]publicclassParamTestFixture{readonlyNintendoGameCharacterNCharacter;readonlystringExpectHero;readonlystringExpectHeroine;readonlystringExpectRival;publicParamTestFixture(stringtitle,stringhero,stringheroine,stringrival){this.NCharacter=DummyTarget.getNintendoGameCharacter(title);this.ExpectHero=hero;this.ExpectHeroine=heroine;this.ExpectRival=rival;}[Test]publicvoidNintendoHeroTest(){Assert.AreEqual(this.ExpectHero,NCharacter.Hero);}[Test]publicvoidNintendoHeroineTest(){Assert.AreEqual(this.ExpectHeroine,NCharacter.Heroine);}[Test]publicvoidNintendoRivalTest(){Assert.AreEqual(this.ExpectRival,NCharacter.Rival);}}

上記のようにテストクラスのコンストラクタの引数を指定することもできる。
引数の型と数は、テストクラスのコンストラクタの引数と一致させる必要がある。
上記の場合、テスト実行時に各パラメータがコンストラクタに指定されたParamTestFixtureオブジェクトが3つ生成され、それぞれのテストメソッドが実行される。

テストクラス内の各メソッドで一貫したテストパラメータを使用したい場合は使うと良さそう。

2. TestFixtureSource

テストクラスに付けるAttribute。
変数等の要素をテストのパラメータとして指定することが可能。

変数をパラメータとして指定
[TestFixtureSource("ParamSource")]publicclassParamTestFixtureSource{staticobject[]ParamSource={newstring[]{"MBr","マリオ","ピーチ","クッパ"},newstring[]{"LoZ","リンク","ゼルダ","ガノン"},newstring[]{"Pok","サトシ","カスミ","シゲル"},};readonlyNintendoGameCharacterNCharacter;readonlystringExpectHero;readonlystringExpectHeroine;readonlystringExpectRival;publicParamTestFixtureSource(stringtitle,stringhero,stringheroine,stringrival){this.NCharacter=DummyTarget.getNintendoGameCharacter(title);this.ExpectHero=hero;this.ExpectHeroine=heroine;this.ExpectRival=rival;}[Test]publicvoidNintendoHeroTest(){Assert.AreEqual(this.ExpectHero,NCharacter.Hero);}[Test]publicvoidNintendoHeroineTest(){Assert.AreEqual(this.ExpectHeroine,NCharacter.Heroine);}[Test]publicvoidNintendoRivalTest(){Assert.AreEqual(this.ExpectRival,NCharacter.Rival);}}

クラス内のフィールド、プロパティ、メソッドをテスト用のパラメータとして指定できる。
フィールドの場合、上記のように変数名を指定する。
指定するパラメータはいずれもstaticである必要がある

上記例ではパラメータに配列を使用しているが、コレクション(IEnumerable実装クラス)でも可。
[TestFixture]でパラメータを指定するのと同様、
コンストラクタのメソッドシグニチャとパラメータの位置や型を一致させる必要がある。

外部クラスの変数をパラメータとして指定
classParamWarehouse{staticobject[]ParamSource={newstring[]{"MBr","マリオ","ピーチ","クッパ"},newstring[]{"LoZ","リンク","ゼルダ","ガノン"},newstring[]{"Pok","サトシ","カスミ","シゲル"},};}[TestFixtureSource(typeof(ParamWarehouse),"ParamSource")]publicclassParamTestFixtureSource2{readonlyNintendoGameCharacterNCharacter;readonlystringExpectHero;readonlystringExpectHeroine;readonlystringExpectRival;publicParamTestFixtureSource2(stringtitle,stringhero,stringheroine,stringrival){this.NCharacter=DummyTarget.getNintendoGameCharacter(title);this.ExpectHero=hero;this.ExpectHeroine=heroine;this.ExpectRival=rival;}[Test]publicvoidNintendoHeroTest(){Assert.AreEqual(this.ExpectHero,NCharacter.Hero);}[Test]publicvoidNintendoHeroineTest(){Assert.AreEqual(this.ExpectHeroine,NCharacter.Heroine);}[Test]publicvoidNintendoRivalTest(){Assert.AreEqual(this.ExpectRival,NCharacter.Rival);}}

上記のように別クラスの変数、プロパティ、メソッドを指定することも可能。
[TestFixtureSource]の第一引数にパラメータを持つクラスのType型、第二引数に変数名などを指定。

使用できるパラメータの条件などは、パラメータのみ指定する場合と同じ。

メソッドでパラメータをロジカルに生成
[TestFixtureSource(nameof(ParamWarehouseMethod))]publicclassParamTestFixtureSource3{readonlyNintendoGameCharacterNCharacter;readonlystringExpectHero;readonlystringExpectHeroine;readonlystringExpectRival;publicParamTestFixtureSource3(stringtitle,stringhero,stringheroine,stringrival){this.NCharacter=DummyTarget.getNintendoGameCharacter(title);this.ExpectHero=hero;this.ExpectHeroine=heroine;this.ExpectRival=rival;}[Test]publicvoidNintendoHeroTest(){Assert.AreEqual(this.ExpectHero,NCharacter.Hero);}staticobject[]ParamGenerateMethod(){returnnewobject[]{newstring[]{"MBr","マリオ","ピーチ","クッパ"},newstring[]{"LoZ","リンク","ゼルダ","ガノン"},newstring[]{"Pok","サトシ","カスミ","シゲル"},};}}

上記のようにパラメータを生成するメソッドを指定することもできる。
上記例では単純にパラメータの配列を返しているだけだが、
ロジカルにパラメータを増幅、生成することも可能だ。

3. Test

テストメソッドであることを示すAttribute。

[Test]
publicclassTestTest{[Test]publicvoidMarioBrosHeroTest(){stringTargetGame="MBr";stringExpectHero="マリオ";Assert.AreEqual(ExpectHero,DummyTarget.getNintendoGameCharacter(TargetGame).Hero);}[Test(Description="ポケモン主人公の妥当性をテストします")]publicvoidPokemonHeroTest(){stringTargetGame="Pok";stringExpectHero="サトシ";Assert.AreEqual(ExpectHero,DummyTarget.getNintendoGameCharacter(TargetGame).Hero);}[Test(ExpectedResult="リンク")]publicstringZeldaHeroTest(){stringTargetGame="LoZ";returnDummyTarget.getNintendoGameCharacter(TargetGame).Hero;}}

このAttributeを付けたメソッドがテストメソッドとして実行される。
下記のAttributeパラメータを指定できる。

Description:テストの説明
ExpectedResult:テスト想定結果

ExpectedResultを指定する場合は、メソッド中Assertクラスによるアサーションはせず、テスト対象の実行結果を戻り値として返すようにする。

簡単なテストなら上記で良いが、
後記の[TestCase]では上記以上のことができるため、
[Test]は使わず[TestCase]で統一していいかもしれない。

4. TestCase

テストメソッドであることを示すAttribute。
[Test]よりも多機能。

[TestCase]
publicclassTestCaseTest{[TestCase]publicvoidMarioBrosHeroTest(){stringTargetGame="MBr";stringExpectHero="マリオ";Assert.AreEqual(ExpectHero,DummyTarget.getNintendoGameCharacter(TargetGame).Hero);}[TestCase("MBr",1983,"マリオ")][TestCase("LoZ",1986,"リンク")][TestCase("Pok",1996,"サトシ")]publicvoidNintendoHeroTest(stringtargetGame,intexpectFirst,stringexpectHero){NintendoGameCharacterNCharacter=DummyTarget.getNintendoGameCharacter(targetGame);Assert.AreEqual(expectFirst,NCharacter.FirstAppearance);Assert.AreEqual(expectHero,NCharacter.Hero);}[TestCase("MBr",ExpectedResult="ピーチ")][TestCase("LoZ",ExpectedResult="ゼルダ")][TestCase("Pok",ExpectedResult="カスミ")]publicstringNintendoHeroineTest(stringtargetGame){returnDummyTarget.getNintendoGameCharacter(targetGame).Heroine;}}

[TestCase]のみの指定であれば[Test]のみ指定するのと同じ。
上から2番目の例のようにテストメソッドに引数を指定することが可能。
また、3番目のように引数と想定結果を指定することも可能。

[TestCase]の様々な名前付きパラメータ
[TestCase(//▼テストケースの作者Author="suganury"//▼カテゴリ. dotnet test --filter TestCategory={CategoryName}のオプションで特定のカテゴリのみテスト実行可能,Category="hogeCategory"//▼テストケースの説明,Description="説明だYO"//▼テストを実行しないプラットフォームをカンマ区切りで指定。OSや.NETバージョンなどを指定可能,ExcludePlatform="Net-1.0,Windows8"//▼想定結果。Assertクラス等によるアサーションではなく、戻り値に実行結果を返すようにする。,ExpectedResult="hoge"//▼テスト無効フラグ。trueにするとテストが実行されなくなる,Explicit=false//▼テスト無効理由。Explicitとセットで使う,Reason="hogeしか返ってこんし"//▼テスト無視フラグと理由。このAttributeが付いているとテストが実行されなくなる,Ignore="hogeしか返ってこんし"//▼Ignoreと全く同じ。どちらか1つでいい,IgnoreReason="hogeしか返ってこんし"//▼テスト名。デフォルトではメソッド名になる,TestName="Hogeテスト"//▼対象のテストクラス,TestOf=typeof(String))]publicstringtest(){return"hoge";}

ExpectedResultの他にも様々なAttributeパラメータが指定できる。
あまり指定しすぎるとゴチャゴチャするので、
[Category]などはそれ用のAttributeを指定するのがいいかもしれない。

5. TestCaseSource

テストメソッドであることを示すAttribute。
変数等の要素をテストのパラメータとして指定することが可能。
[TestFixtureSource]のテストメソッド版。

クラス内の変数をパラメータとして指定
publicclassTestCaseSourceTest{staticobject[]NintendoCharaCases={newobject[]{"MBr",1983,"マリオ"},newobject[]{"LoZ",1986,"リンク"},newobject[]{"Pok",1996,"サトシ"}};staticIList<object>NintendoCharaCaseList=newList<object>{newobject[]{"MBr",1983,"マリオ"},newobject[]{"LoZ",1986,"リンク"},newobject[]{"Pok",1996,"サトシ"}};[TestCaseSource("NintendoCharaCases")][TestCaseSource("NintendoCharaCaseList")]publicvoidNintendoCharaTest(stringtargetGame,intexpectFirst,stringexpectHero){NintendoGameCharacterNCharacter=DummyTarget.getNintendoGameCharacter(targetGame);Assert.AreEqual(expectFirst,NCharacter.FirstAppearance);Assert.AreEqual(expectHero,NCharacter.Hero);}}

上記例のように自クラスの配列もしくはコレクションをテストメソッドのパラメータとして指定できる。
パラメータとして指定するものはstaticである必要がある
使用時の制約としては、[TestFixtureSource]と概ね同じ。

外部のクラスの変数をパラメータとして指定
publicclassTestCaseSourceOtherClass{[TestCaseSource(typeof(TestCaseSourceTest),"NintendoCharaCases")]publicvoidNintendoCharaTestOtherClass(stringtargetGame,intexpectFirst,stringexpectHero){NintendoGameCharacterNCharacter=DummyTarget.getNintendoGameCharacter(targetGame);Assert.AreEqual(expectFirst,NCharacter.FirstAppearance);Assert.AreEqual(expectHero,NCharacter.Hero);}}

上記のように外部のクラスが持つstaticフィールド等をパラメータとして指定することも可能。

メソッドでパラメータをロジカルに生成
[TestCaseSource(nameof(GenerateTestCharacter),newobject[]{"Hero"})]publicvoidNintendoHeroTest(stringexpectHero){List<string>nintendoHeros=DummyTarget.nintendoChara.Values.Select(n=>n.Hero).ToList();CollectionAssert.Contains(nintendoHeros,expectHero);}staticIList<string>GenerateTestCharacter(stringcharaType){IList<string>list=newList<string>();if(charaType.Equals("Hero")){list.Add("マリオ");list.Add("リンク");list.Add("サトシ");list.Add("ロックマン");list.Add("クラウド");}elseif(charaType.Equals("Heroine")){list.Add("ピーチ");list.Add("ゼルダ");list.Add("カスミ");list.Add("ロール");list.Add("ティファ");}elseif(charaType.Equals("Rival")){list.Add("クッパ");list.Add("ガノン");list.Add("シゲル");list.Add("フォルテ");list.Add("セフィロス");}returnlist;}

また、上記のように[TestCaseSource]の第一引数に配列・コレクションを返すメソッド、第二引数にそのメソッドの引数を指定することで、
パラメータをロジカルに生成することが可能。
上記のテスト実行結果は下記となる。

実行結果
NUnit Adapter 3.15.1.0: Test execution complete
  √ NintendoHeroTest("マリオ") [5ms]
  √ NintendoHeroTest("リンク") [1ms]
  √ NintendoHeroTest("サトシ") [< 1ms]
  X NintendoHeroTest("ロックマン") [27ms]
  エラー メッセージ:
     Expected: some item equal to "ロックマン"
  But was:  < "マリオ", "リンク", "サトシ" >

  X NintendoHeroTest("クラウド") [< 1ms]
  エラー メッセージ:
     Expected: some item equal to "クラウド"
  But was:  < "マリオ", "リンク", "サトシ" >

場合によっては様々なパターンのパラメータをテストすることもあるだろうし、それをテストコードにハードコードするのは苦しい場面もあるだろう。
そういったときにこれは便利な機能だと思う。

まとめ

今回記載したAttributeとアサーションメソッドを知っておけば
最低限のテスト実施自体はできると思う。
が、やはりテストのパフォーマンス等を求めるとなると事前・事後処理用のAttributeは欲しいし、
カテゴライズしたり無効にしたりするAttributeも使う機会はあるだろう。

そこらへんはまた今度:tea:

参考

NUnit公式:Attribute一覧ページ


Viewing all articles
Browse latest Browse all 9707

Trending Articles