◆ MSTest

VisualStudio のメニューバーに 「テスト」 ってありますよね
メニューにあれこれありすぎて使っていないのが多いですけど今回はこれを使ってみます

テストって

テストというと書いたコードがちゃんと動いてるかなーって確認するものです

例えばこういうコード書いたときに
private bool isOdd(int val)
{
    return val % 2 == 1;
}
isOdd(2) // false
isOdd(9) // true
isOdd(0) // false
という感じで適当に数値入れて思い通りの結果になってるか確認しますよね

こういうのを自動でするための機能です


これくらいのシンプルな関数だと中身を変更しない限り結果変わらないですし変更したらその場で確認をしてしまいますし自動でやる必要もないかもです

ですが 複雑な関数で どこかひとつ変えたらあっちこっちが影響するってときに全部を手動で確認してまわるのは大変ですよね
確認するのにデータを準備しないといけないときもあったりしますし

まぁ大丈夫でしょ と確認しなかったときほどバグがあったりするものです


というわけで ある程度大きなものでバグがないことをちゃんと確認しないといけないものを作るときにはテストツールは役立ちます

テストツール

VisualStudio についてるテストツールは MS Test というもののようです
このツールでのテストは VisualStudio にもともとついてる機能なのでとっても簡単にできます

ほかにも .NET 用の NUnit というのもあるようですが こっちは外部ツールなので VisualStudio の拡張機能が必要そうです
VisualStudio 自体を拡張する機能は Express では使えなくて Community 以上でないといけないです
そういうこともあって こだわらないなら何もしないで使える MSTest でいいと思います

テストというと
  • ブラックボックステスト
  • ホワイトボックステスト
  • 回帰テスト
  • 単体テスト
  • 結合テスト
  • 負荷テスト
  • E2Eテスト

などなど色々種類が出てきますが MSTest は単体テスト (Unit test) ツールに当たるようです
単体テストというと最小の単位でひとつひとつの機能がちゃんと動いてるか確認するものです

単体テストツールというと基本は Assert があるだけです
Assert の引数に true になるべき値を渡します
もし false なら例外が起きます
例外が起きたかどうかをまとめてレポートしてくれるのがツールの機能です

Assert は引数が true になるかだけのシンプルなものあれば 引数が 2 つで同じ値になっているか や コレクションと長さを渡してコレクションの長さが指定のものになっているかなど楽で見やすくなるようメソッドの種類がたくさんあるものもあります

もしなくても
Assert(x1 == x2);
Assert(items.Count == count);

のように true かどうかの判定だけでもできなくないですが やっぱり
AssertEqual(x1, x2);
AssertCountEqual(items, count);
こう書けたほうがいいですからね

注意ですが↑のコードはあくまで例なので MSTest の書き方ではないです

MSTest

VisualStudio でテストを作るのは簡単
エディタ部分を適当に右クリックすれば 「単体テストの作成」 というのがあります
これを選ぶと自動でテストが作られます

右クリックしたのがメソッドの中だとそのメソッドだけが クラスの中のメソッド一覧のテストケースが作られます

こんなウィンドウが出てきます
vstest1


プロジェクトの名前やテストのメソッド名や名前空間などの設定ができます
MSTest 以外のテストツールを拡張機能で入れているとそれに変更もできます

こだわりがないならデフォルトで おっけいです
ただ インナークラスのテストを自動生成などで名前空間が競合するような構造になることもあるのでそういうときはエラーにならないように修正します


自動で作られたテストファイルには
using Microsoft.VisualStudio.TestTools.UnitTesting;

が書かれていて ここに Assert 系関数があります


自動で作られるのは public なメソッドだけです
private はリフレクション使えばテスト可能ですが 自動では作ってくれません

また class も public でないといけないです
デフォルトは internal なのでリフレクションしないとテスト用別アセンブリから参照できないからです
クラスは自動で作ることが多くて public 書いてないのが多いのでテスト作るときにまとめて public することになることが多そうです

サンプル

こんなクラスを作ります
namespace wpf_sample_proj
{
    public class Class1
    {
        public int num { get; set; } = 0;

        public Class1()
        {
        }

        public Class1(int n)
        {
            this.num = n;
        }

        public void plus(int n)
        {
            this.num += n;
        }

        public void minus(int n)
        {
            this.num -= n;
        }

        public int get()
        {
            return this.num;
        }
    }
}

Class1 を右クリックして単体テストの作成を選んで自動で作られたファイルの各メソッドの中をうめます
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace wpf_sample_proj.Tests
{
    [TestClass()]
    public class Class1Tests
    {
        [TestMethod()]
        public void Class1Test()
        {
            var inst = new Class1();
            Assert.AreEqual(0, inst.num);
        }

        [TestMethod()]
        public void Class1Test1()
        {
            var inst = new Class1(3);
            Assert.AreEqual(3, inst.num);
        }

        [TestMethod()]
        public void plusTest()
        {
            var inst = new Class1();
            inst.plus(19);
            Assert.AreEqual(19, inst.num);
        }

        [TestMethod()]
        public void minusTest()
        {
            var inst = new Class1();
            inst.minus(4);
            Assert.AreEqual(-4, inst.num);
        }

        [TestMethod()]
        public void getTest()
        {
            var inst = new Class1(55);
            Assert.AreEqual(inst.num, inst.get());
        }
    }
}

実行はテストケースファイルのエディタ上で右クリックして 「テストの実行」 をクリックします
breakpoint つけて途中の値をみたりデバッグしたいときは 「テストのデバッグ」 です

ここも クラスをクリックするかメソッドの中をクリックするかでクラス全体のテストを行うかメソッド単体のテストを行うかが変わります

実行するとデフォルトだと左側にテストエクスプローラがでてきます
vstest2

成功したら緑色 失敗したら赤色になります

テストエクスプローラを手動で表示するときはメニューバーの 「テスト>ウィンドウ>テストエクスプローラ」 です
他のソリューションエクスプローラやエラー一覧などと同じようなサブウィンドウですがメニューの「表示」じゃなくて「テスト」にいるのでちょっと注意です

テスト前や後になにかしたい

それぞれのテスト前に初期化したり テスト後に終了処理したり ということもできます
テストツールによっては setup/teardown だったりしますが MSTest では initialize/cleanup です

こんな感じです
namespace Tests
{
    [TestClass()]
    public class SampleClass
    {
        public SampleClass()
        {
            Debug.WriteLine("test-constructor");
            new FileWriter().write("test-constructor");
        }

        ~SampleClass()
        {
            Debug.WriteLine("test-destructor");
            new FileWriter().write("test-destructor");
        }

        [ClassInitialize()]
        public static void classInitialize(TestContext test_context)
        {
            Debug.WriteLine("class-initialize");
            new FileWriter().write("class-initialize");
        }

        [ClassCleanup()]
        public static void classCleanup()
        {
            Debug.WriteLine("class-cleanup");
            new FileWriter().write("class-cleanup");
        }

        [TestInitialize()]
        public void testInitialize()
        {
            Debug.WriteLine("test-initialize");
            new FileWriter().write("test-initialize");
        }

        [TestCleanup()]
        public void testCleanup()
        {
            Debug.WriteLine("test-cleanup");
            new FileWriter().write("test-cleanup");
        }

        [TestMethod()]
        public void testMethod()
        {
            Debug.WriteLine("test");
            new FileWriter().write("test");
            Assert.IsTrue(true);
        }
    }
}
12:51:08.537: test-constructor
12:51:08.541: class-initialize
12:51:08.545: test-initialize
12:51:08.549: test
12:51:08.554: test-cleanup
12:51:08.563: class-cleanup
12:51:08.568: test-destructor

ClassInitialize/ClassCleanup はクラス単位の最初と最後にする処理です
TestInitialize/TestCleanup はテスト(メソッド)単位の最初と最後にする処理です

どちらもメソッド名ではなく属性(メソッド定義の上の [] )で指定します

メソッドの中身ですが System.Diagonostics.Debug.WriteLine で出力できます
System.Console.WriteLine ではコンソールに表示されません

また デバッグモードで実行しないと Debug.WriteLine でもコンソールに出ないです

通常実行した場合で出力を見たいときは テストエクスプローラの各項目の「出力」から見れます
ただし 複数テストメソッドがあった場合に ClassInitialize/ClassCleanup が別れてしまうのとテストメソッドの実行順序がわからなくなるのとデストラクタは表示されないなどの問題があります

なので 例ではファイル出力にしてます
デバッグモードで実行すればいいのですが あいだあいだに余計なのがいっぱい入ってきて邪魔なんですよねー


ところで ClassInitialize/ClassCleanup は static メソッドです
static にしないとエラーになります

static だとインスタンスメソッドが呼べないとかプロパティを設定できないとか不便なところがあるのでそういうことはコンストラクタでやってしまうとよさそうです

private のテスト

上の方にも書きましたが自動生成では public なものしか作れません
private なメソッドは public から呼び出されて実行されるのが普通ですし public メソッドの動作が完璧なら中で使われてる private メソッドにも間違いはないはずです

なので基本的には public だけで十分だと思います
public から呼び出されてない あるのに実際は使われていない private メソッドがあってもそれをムダにチェックしなくて済みますしね

でも クラスに public メソッドがひとつだけで中に private メソッドがいっぱいある作りでメソッドごとにチェックした方がわかりやすいってときもあります

private メソッドは自動で生成されないだけで リフレクションを使えば外からも実行できます
なのでテストすることは可能です

こんな感じです
[TestMethod()]
public void privateTest()
{
    var type = Type.GetType("project1.Class1, project1");
    var instance = Activator.CreateInstance(type);
    var method = type.GetMethod("privateMethod", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
    var result = method.Invoke(instance, new object[] { 1 });
    Assert.AreEqual(1, result);
}

リフレクションなので長めですが 共通部分をまとめてしまえばもうちょっと短くできます

続く

Assert の種類や UIA も書きたかったのですが長くなってきたので別記事にします
↓書いたらリンクが付きます
 
Assert
UIA