Visual Studio 2008単体テスト機能のすべて特集:Visual Studio 2008単体テスト機能徹底活用(前編)(2/4 ページ)

» 2008年11月14日 00時00分 公開
[尾崎義尚,]

さまざまな種類のテスト

 ここまでで基本的な単体テストの作り方と実行方法を解説してきた。ここではもう一歩進めて、さまざまなパターンのテストについて解説していく。

■Privateメソッドをテストするには

 ご覧いただいたように、単体テストは基本的に外部からクラスをインスタンス化し、そのメソッドを呼び出して行うが、それではPrivateメソッド(=privateのアクセシビリティを持つメソッド)のテストはどうなるのだろうか。

 実は、Privateメソッドも同じようにテストを実施することができる。同じようにメソッドを右クリックして、単体テストを作成してみると、以下のようなテスト・コードが生成される。

01: /// <summary>
02: ///引き算 のテスト
03: ///</summary>
04: [TestMethod()]
05: [DeploymentItem("計算.dll")]
06: public void 引き算Test()
07: {
08:   四則演算_Accessor target = new四則演算_Accessor(); // TODO: 適切な値に初期化してください
09:   int x = 0; // TODO: 適切な値に初期化してください
10:   int y = 0; // TODO: 適切な値に初期化してください
11:   int expected = 0; // TODO: 適切な値に初期化してください
12:   int actual;
13:   actual = target.引き算(x, y);
14:   Assert.AreEqual(expected, actual);
15:   Assert.Inconclusive("このテスト・メソッドの正確性を確認します。");
16: }

01: ''' <summary>
02: '''引き算 のテスト
03: '''</summary>
04: <TestMethod(), _
05: DeploymentItem("計算.dll")> _
06: Public Sub 引き算Test()
07:
08:   Dim target As 四則演算_Accessor = New四則演算_Accessor()' TODO: 適切な値に初期化してください
09:   Dim x As Integer = 0 ' TODO: 適切な値に初期化してください
10:   Dim y As Integer = 0 ' TODO: 適切な値に初期化してください
11:   Dim expected As Integer = 0 ' TODO: 適切な値に初期化してください
12:   Dim actual As Integer
13:   actual = target.引き算(x, y)
14:   Assert.AreEqual(expected, actual)
15:   Assert.Inconclusive("このテスト・メソッドの正確性を確認します。")
16: End Sub

Privateメソッドをテストするためのテスト・コード(上:C#、下:VB)

 コードをよく見てみると、8行目でインスタンス化しているクラスがこれまでの「四則演算」ではなく、「四則演算_Accessor」に変わっている。

 コード上の「四則演算_Accessor」という文字列を右クリックして、[定義へ移動]を選択すると、C#の場合、[メタデータから]と表示された次のようなコードが表示される(VBの場合は、オブジェクト・ブラウザが表示される)。

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;

namespace 計算
{
  [Shadowing("計算.四則演算")]
  public class四則演算_Accessor : BaseShadow
  {
    protected static PrivateType m_privateType;

    [Shadowing(".ctor@0")]
    public 四則演算_Accessor();
    public 四則演算_Accessor(PrivateObject __p1);

    public static PrivateType ShadowedType { get; }

    public static四則演算_Accessor AttachShadow(object __p1);
    [Shadowing("引き算@2")]
    public int 引き算(int x, int y);
    [Shadowing("足し算@2")]
    public int 足し算(int x, int y);
  }
}

テスト対象クラスへのアクセサ・クラス(C#のみ)
テスト対象クラスのメソッドが自動的に生成されるが、アクセスするためのソース・コードは存在していない。VBでは、コードではなくオブジェクト・ブラウザが表示されるのみ。

 以前(VS 2005)では、単体テスト作成時にリフレクションと呼ばれる機能を使用してPrivateメソッドにアクセスするアクセサ・コードが自動生成されていたが、メソッドのパラメータが変更されたときに、それを再度作成し直さなくてはならないという問題があった。VS 2008では、テスト・プロジェクトのビルド時にアクセサが自動生成されるようになったため、ソース・コードが変更されてもアクセサを作り直す必要がなくなっている。

 このようにPrivateメソッドであっても、メソッドにアクセスするためのアクセサ・クラスが自動的に生成されるため、単体テストを実施することができるようになっている。

■Internal(Friend)メソッドをテストするには

 Internal(VB ではFriend)メソッドとは、同じアセンブリ内部からのみアクセスできるメソッドである。単体テストは、プロジェクトの外部からアクセスする形になるため、通常であればInternal(Friend)メソッドも単体テストを実施することができない。そのため、Internalメソッドの単体テストを作成しようとすると、以下のメッセージが表示される。

Internalメソッドの単体テストを作成する際に表示される確認ダイアログ

 ここで[はい]をクリックすると、テスト対象のプロジェクトに含まれるAssemblyInfo.cs(TestFriend.vb)に以下のコードが追加される。

[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("計算Test")]

<assembly: System.Runtime.CompilerServices.InternalsVisibleTo("計算Test")>

Internal(Friend)メソッドの単体テストのために追加されるコード(上:C#、下:VB)

 プロジェクトにこの記述が追加されることによって、計算Testというテスト・プロジェクトから、Internal(Friend)メソッドを参照できるようになる。

■境界値チェックをするには

 境界値チェックとは、メソッドが処理可能な上限値/下限値や、基準値とその前後の値などを入力値とし、判定ロジックが正しく記述されていることを、想定している結果が得られることでチェックするテストである。Visual Studioの単体テスト機能には境界値チェックをする方法がいくつかあるので、それぞれ紹介していこう。

 最もシンプルなのが、以下のテスト・コードのように、値ごとにテストを作成する方法だ。ここでは、年齢を計算するGetAgeというメソッドの処理が正しいことを判定するテスト・コードを使用して解説する。GetAgeメソッドは、生年月日と年齢を計算する基準日を引数として受け、年齢を数値で返すメソッドである(以下、VB版は省略)。

/// <summary>
///GetAge のテスト(誕生日前日)
///</summary>
[TestMethod()]
public void GetAgeTest1()
{
  ClassLibrary2.Class1 target = new ClassLibrary1.Class1();

  System.DateTime birthday = new System.DateTime(1976,12,14);
  System.DateTime targetDate = new System.DateTime(2008,12,13);
  int expected = 31;
  int actual;

  actual = target.GetAge(birthday, targetDate);
  Assert.AreEqual(expected, actual);
}

/// <summary>
///GetAge のテスト(誕生日当日)
///</summary>
[TestMethod()]
public void GetAgeTest2()
{
  ClassLibrary2.Class1 target = new ClassLibrary1.Class1();

  System.DateTime birthday = new System.DateTime(1976, 12, 14);
  System.DateTime targetDate = new System.DateTime(2008, 12, 14);
  int expected = 32;
  int actual;

  actual = target.GetAge(birthday, targetDate);
  Assert.AreEqual(expected, actual);
}

/// <summary>
///GetAge のテスト(誕生日翌日)
///</summary>
[TestMethod()]
public void GetAgeTest3()
{
  ClassLibrary2.Class1 target = new ClassLibrary1.Class1();

  System.DateTime birthday = new System.DateTime(1976, 12, 14);
  System.DateTime targetDate = new System.DateTime(2008, 12, 15);
  int expected = 32;
  int actual;

  actual = target.GetAge(birthday, targetDate);
  Assert.AreEqual(expected, actual);
}

境界値チェックの例1
テスト・パターンごとにテスト・メソッドを作成している。

 この方法の利点は、何といっても結果が分かりやすいことだ。テスト対象のコードは、意図的に誕生日当日の年齢を誤って返すようにしてある。

 このためテスト結果を見ると、誕生日当日の日付を渡したGetAgeTest2メソッドのテストが失敗していることが分かる。ここでは行っていないが、メソッド名を渡している値が分かるような名前や「年齢計算に失敗するべき」のように想定している結果を示す名前にしておくと、何のテストで失敗しているのかがより明確になる。

入力値ごとにテスト・メソッドを作成したテスト結果
テストの実施結果を見ることで、どのテストで失敗したか分かりやすい。

 失敗したテストをダブルクリックすると、以下のようなテスト結果の詳細が表示される。

 以下の画面では、テストの結果を評価しているAssert.AreEqualメソッドの第3パラメータにメッセージを指定することで、どこでエラーが発生したかが分かるようにしている。「エラーのスタック トレース」に表示されている情報を見ると、エラーが発生している行番号が表示されている。また、ソース・ファイルがリンクになっており、これをクリックすると、実際にエラーが発生した行が選択された状態でソース・コードが表示される。

テスト失敗の詳細情報
テストが失敗した詳細な情報とエラーの行数を確認することができる。また、リンクをクリックすることで、実際にエラーが発生したソースを確認できる。

 このように1つのメソッドに対して複数のテストを実施する場合には、次に解説する、CSVファイルやデータベースから値を読み込むテストをお勧めする。

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。