HOME会社概況業務内容開発分野開発事例CANモジュールソフテックだよりお問い合わせ
HOME > ソフテックだより > 第277号(2017年3月1日発行) 技術レポート「WindowsアプリケーションにおけるMSTestの活用」

「ソフテックだより」では、ソフトウェア開発に関する情報や開発現場における社員の取り組みなどを定期的にお知らせしています。
さまざまなテーマを取り上げていますので、他のソフテックだよりも、ぜひご覧下さい。

ソフテックだより(発行日順)のページへ
ソフテックだより(技術分野別)のページへ
ソフテックだより(シーン別)のページへ


ソフテックだより 第277号(2017年3月1日発行)

技術レポート

「WindowsアプリケーションにおけるMSTestの活用」

1. はじめに

私はWindowsアプリケーション開発を担当する機会が多い入社15年超の社員です。今回のソフテックだよりでは、Microsoft Visual Studioに標準で搭載されているユニットテストのフレームワーク MSTestを使った単体試験についてご紹介させていただきます。

2. 単体試験について

単体試験(内部試験やユニットテストと呼称されることもあります)は、一般的にプログラムを構成するモジュールが設計どおり正しく動作することを検証する対応になり、モジュール製作者が対応することが多いのが特徴です。試験対象としては、プログラム構成単位となるクラスなどのインターフェース(関数やメソッド)が対象となる場合が多いと思います。
以前『ソフテックだより第121号:品質向上への取り組み〜単体試験編〜』でも取り上げさせていただきましたように、個々のモジュールの品質が低いと結合試験(組合試験やシステムテストと呼称されることもあります)の時にシステム品質が安定せずに大きな問題となります。
ユニットテストのフレームワーク(ツール)を活用することで、単体試験の確認を自動化させるアプローチが可能になりますが、対応イメージや特徴などを記載していきます。

3. MSTestによるユニットテスト実行方法について

MSTestは、Microsoft Visual Studioに標準搭載されているユニットテスト用のフレームワークです。
C++、C#、または Visual Basic で作成されたマネージアセンブリ(*.dll、*.exe)の参照から、クラスのメソッド呼び出し、適切なパラメータを渡して、戻り値が期待された値かを確認します。

MsTestにおける、単体試験の対応方法をTargetApp(ターゲットアプリケーション)のテストプロジェクト作成→Calculateクラスモジュールのテスト実行を例にして、以下に簡単に示します。

MSTest:対応イメージ例
図1. MSTest:対応イメージ例

(1) ユニットテスト用のプロジェクトを作成

[新規作成]→[プロジェクト]から、テストプロジェクト/プロジェクト名を指定します。

MSTest:テストプロジェクトの作成
図2. MSTest:テストプロジェクトの作成

(2) テスト対象 の参照を追加

ユニットテストプロジェクトに、テスト対象(TargetApp)の参照を追加します。

MSTest:テストプロジェクトへのテスト対象 参照追加
↓↓↓
MSTest:テストプロジェクトへのテスト対象 参照追加
図3. MSTest:テストプロジェクトへのテスト対象 参照追加

(3) テストコードを記述

[追加]→[新しいテスト]でユニットテスト用クラスを新規作成して、テストコードを記述します。
テストコードは、Windowsアプリケーション開発経験のある方であれば、実際のソフトウェアをコーディングするのと同じ感覚で、ユニットテストのテストパターン作成が行えると思います。

下記の例では、TargetApp プロジェクトに定義されたCalculateクラスのテストコード記述例を示します。
※ Calculate クラスの下記3つのメソッドの単体試験を行うテストコードになります。

  • Sigmaメソッド … シグマ計算
  • Averageメソッド … 平均計算
  • StandardDevメソッド … 標準偏差計算

/// <summary>
/// ユニットテスト1 クラス
/// </summary>
[TestClass]
public class UnitTest1
{
  /// <summary>テスト対象クラス</summary>
  static Caluculate _Target = null;
  /// <summary>テストデータ</summary>
  static double[] _TestData = new double[] { 5.67, 1.23, 4.56, 3.45, 2.34 };

  /// <summary>シグマ計算 期待値</summary>
  static double _TestExpectedSigma = 17.25;
  /// <summary>平均計算 期待値</summary>
  static double _TestExpectedAverage = 3.45;
  /// <summary>標準偏差計算 期待値</summary>
  static double _TestExpectedStandardDev = 1.56977705423413;

  /// <summary>
  /// 最初のテスト実行前の処理
  /// </summary>
  /// <param name="testContext">[in] テストコンテキスト</param>
  [ClassInitialize()]
  public static void ClassInitialize(TestContext testContext)
  {
    //テスト対象インスタンス生成
    _Target = new Caluculate();
  }

  /// <summary>
  /// 全てのテスト実行後の処理
  /// </summary>
  [ClassCleanup()]
  public static void ClassCleanup()
  {
    _Target = null;
  }

  /// <summary>
  /// 各テストを実行する前の処理
  /// </summary>
  [TestInitialize()]
  public void TestInitialize()
  {
  }

  /// <summary>
  /// 各テストを実行した後の処理
  /// </summary>
  [TestCleanup()]
  public void TestCleanup()
  {
  }

  /// <summary>
  ///Sigma のテスト
  ///</summary>
  [TestMethod()]
  public void Calculate_Sigma_Test()
  {
    double dActual = _Target.Sigma(_TestData);
    Assert.AreEqual(_TestExpectedSigma, dActual);
  }

  /// <summary>
  ///Average のテスト
  ///</summary>
  [TestMethod()]
  public void Calculate_Average_Test()
  {
    double dActual = _Target.Average(_TestData);
    Assert.AreEqual(_TestExpectedAverage, dActual);
  }

  /// <summary>
  ///StandardDev のテスト
  ///</summary>
  [TestMethod()]
  public void Calculate_StandardDev_Test()
  {
    double dActual = _Target.StandardDev(_TestData);
    Assert.AreEqual(_TestExpectedStandardDev.ToString("F10"), dActual.ToString("F10"));
  }
}

図4. MSTest:テストコードの記述例

(4) テストコードの実行

[テスト(T)]→[実行(R)]→[ソリューションのすべてのテスト(A)]の実行で、ユニットテストを実行します。
実行結果は、テスト結果ウィンドウに表示されます。

MSTest:テストプロジェクト実行結果
図5. MSTest:テストプロジェクト実行結果

上記のような対応方法の流れで、「テストコード」と呼ばれる単体試験用のプログラムコードを書くことにより単体試験を自動実行できます。
ユニットテスト用のフレームワークは、MSTest以外にもサードパーティでいくつか提供されているものがあります。統合開発環境のVisual StudioでWindowsアプリケーションを開発する際には、ほとんどのフレームワークがユニットテスト用のプロジェクトを作成することで簡単に単体試験を行えるようになるので重宝します。

4. テストフレームワーク活用による利点

MSTestに限りませんが、ユニットテスト用のフレームワークを活用する上での利点として、下記のような点が挙げられます。

(1) 単体試験用のプログラムコードを書くことで、テスト対象の問題に気づく機会になる。

テストパターン(テスト条件:境界値、エラー)は、実際のプロジェクトに組み込んで使うシチュエーションを想定しながら「テストコード」の対応を行います。この対応時に、テスト対象の「テストコードの記述しにくさ(I/Fの問題)」や「設計時の考慮不足による異常系の処理実装漏れ」などに気づくことができます。
早い段階(結合テスト前)で内在した問題箇所に気づける機会になる点だけでもテストフレームワークを活用する意味があると思います。

(2) 入力と期待値が明確で、様々なテストデータパターンが想定される試験を的確に試験できる。

単調で、同じようなテストの繰り返しの場合、テストフレームワークを利用したユニットテスト環境を構築すれば、テストコードに沿って確実に繰り返し試験を実行することが可能です。
単体試験項目書を作成する感覚で「テストコード」を作成すれば良いので、実行するテストパターンが作成できれば、テストは自動で行えることがメリットに感じます。

(3)モジュールに変更を加えずに試験できる。

例えば、外部非公開のPrivateメソッド/プロパティについても、「PrivateObject」クラスを利用することで、試験対象とすることが可能です。このような機能により、テストのためにテスト対象のモジュールを変更するといったリスクを負わずにすみます。
クラス内でのみ共通利用するモジュール系は、外部非公開のメソッドである場合がほとんどだと思いますので、試験対象の内部処理が複雑なケースでは、外部非公開I/Fを試験対象とすることで品質向上が見込めるような場合は積極的に対応していきたいところです。

(4) ユニットテスト環境の再利用性。

単体試験を自動実行できるので、繰り返し(回帰)テストで大きなメリットが得られると思います。
共通モジュール部に変更を加える場合などに、該当モジュールをコールしている箇所への影響がないことの確認が容易に行えます。
長期にわたってメンテナンスが行われ、定期的な改修が見込まれる場合などにも有用です。

5. テストフレーム ワーク利用時の注意点

テストフレームワーク利用時の注意点について下記に示します。

(1) 入力に対する期待値が一意に決まらないものは試験対象にできない。

テストコードは論理的に記述すべき性格のものなので、入力に対する期待値が一意に決まらないものは試験対象にできません。

(2) UI操作/表示周りの確認は試験対象にできない。

テストフレームワークは試験対象のクラスI/Fの単体試験を目的としたものになりますので、UI操作/表示確認周りの試験は行えません。該当試験フェーズは結合試験時になると思いますので、UI操作/表示部の機能実現に使用されるクラスI/Fの品質を、テストフレームワークを使用した単体試験によって担保するのが本来の利用方法になります。

(3)やみくもにテスト対象にすると対応コストが膨大になる。

前述しているように、テストフレームワークを活用することでテスト対象のクラスI/Fの単体試験を自動実行させることが可能ですが、対応できるからといって全てのクラスI/Fをテスト対象とするのはテスト環境(テストパターンの検討&テストコードの作成)の構築コストの観点から現実的ではありません。
また、テストフレームワークで対応するよりも、人手で確認した方が、対応が容易で時間がかからないケースも存在します。

6. テストフレームワークの応用例

MSTestでは、複数のユニット試験を組み合わせて順番に単体試験を行うことも可能です。

(1) 複合的なテストパターンの試験を実行する。

単体試験を組み合わせたテスト条件を構築することで、結合試験前にある程度、複合的なパターンによる試験を実行することも可能です。
ユニットテスト用に作成したテストコードを組み合わせて実行させることで、様々な使用場面を想定した試験を自動化できる点が魅力です。
例えば、テスト対象クラスが管理している内部情報/状態が、複数のI/Fをコールする順番などで挙動が変わるようなケースで利用シーンが見込めます。

MSTest:順序指定テストの追加
図6. MSTest:順序指定テストの追加

(2) テストパターンデータを外部ファイルから読み込む。

様々なテストパターンデータ(入力値)でテストを実現するのに、パターンデータを全てテストコードで記述するのは労力が伴います。
このようなケースでは、テストパターンデータを外部ファイル(エクセルやCSVファイル)や、DB(データベース)で読み込んだ情報を利用することで、効率良く対応できる場合もあります。
注意点としては、テストデータの作成に効率化/自由度を見込める変わりに、試験条件がテストプロジェクトでクローズしなくなるため、試験に使用するテストパターンデータ(外部ファイル or DB)をきちんと管理していく必要性が生じます。

(3) 処理時間に関するテストが可能。

PC OSが管理するシステムタイム精度の制限はありますが、試験対象のモジュール処理時間を計測して、想定時間内に処理が完了するかのテストが可能です。

using System.Diagnostics; //Stopwatchクラス利用

  /// <summary>
  ///StandardDev の処理時間テスト
  ///</summary>
  [TestMethod()]
  public void Calculate_StandardDev_TestTime()
  {
    Stopwatch swTime = new Stopwatch();
    //ターゲットモジュールの処理時間
    swTime.Start(); _Target.StandardDev(_TestData);
    swTime.Stop();

    //処理時間が100msを越えていればエラー
    if (swTime.ElapsedMilliseconds > 100)
    {
      Assert.Fail("Calculate_StandardDev(...) 処理時間100msオーバ");
    }
  }

図7. MSTest:テストコードの記述例 (処理時間テスト)

7. さいごに

WindowsアプリケーションでMSTestを活用する方法についてご紹介させていただきました。
これまで単体試験を人手で行っていたことで、時間の制限からデータパターンの網羅率が低い問題や、単純作業の連続に起因した確認ミスや見逃しの問題などがあり、単体試験以降のフェーズで不具合が発生することもありました。
テストフレームワークを活用することで、この手の問題は減らせる可能性があり、適切な用途に利用することでシステムの品質を向上させることができると思います。

今回のソフテックだよりで取り上げさせていただいた内容が、Windowsアプリケーション開発時の品質向上の手助けになれば幸いです。

(M.S.)

[参考URL]
・単体テストの基本
⇒ https://msdn.microsoft.com/ja-jp/library/dd264975.aspx
・PrivateObjectクラス (プライベートI/Fアクセス)
⇒ https://msdn.microsoft.com/ja-jp/library/ms245564(v=vs.110).aspx

関連ページへのリンク

関連するソフテックだより