はてな今週のお題「テスト」らしいです。

第1回 TDD勉強会in仙台 - connpass に行ってきました。

TDD でひさしぶりにVisual Studio立ち上げたー。 MVP ネタにされすぎw— YAMAMOTO Masaki (@nnasaki) 2014, 7月 3

今日のMVP様ネタ。混んでいる仙台駅前でイラついてるMVP様を見て。 @nnasaki pic.twitter.com/mIsPuWK7Gx— ネモトノリユキ (@nemorine) 2014, 7月 4

MVP受賞直後ということで、皆さんにネタにされた祝っていただきました。本当に皆さんのおかげでここまでやってこれたなーと感じました。

これを貰うためだけに来ました。(`・ω・´)ゞ (@ ソシラボ SOCiAL Laboratory) https://t.co/vBB0Vqs33F pic.twitter.com/JXAHuYRI64— いまいまさのぶ (@masanobuimai) 2014, 7月 3

あとはいまいさんにキーボードを譲って終了、、、ではなくて、本題へ。

お題は「会議室の予約」

シンプルながら状態持ったり日付使ったりとFizz Buzzよりずっと難しいと感じました。

Macbook の電源アダプターを持ってきていなかったので、VMWindows を立ち上げるのはバッテリーが持つかちょっと不安でした。なので、IntelliJ を起動して Java でやる気満々だったところ、 @bonprosoft さんが電源アダプターを何故か持っているとのこと。(Mac持ってきてないのに!)

ありがたくお借りして Visual Studio を立ち上げて、Chaining Assertion と MSTest を使用した。

最初のテストはこれだけです。

        [TestMethod]
        public void 会議室を予約できること()
        {
            var meetingRoom = new MeetingRoom();
            meetingRoom.Reserve().Is(true);
        }

        [TestMethod]
        public void 会議室が予約可能と確認できること()
        {
            var meetingRoom = new MeetingRoom();
            meetingRoom.CanReserve.Is(true);
        }

ここは bool だけで済みます。この後日付指定が入ってきます。

        [TestMethod]
        public void 日付を一日指定して予約できること()
        {
            var meetingRoom = new MeetingRoom();
            var reserveDate = DateTime.Parse("2014/7/1");
            meetingRoom.Reserve(reserveDate).Is(true);
        }

まぁ、ここまではまだ良い。でも、別な日に予約したいとなるとテストは Red になります。

        [TestMethod]
        public void 日付を一日指定後に次の日付で予約できること()
        {
            var meetingRoom = new MeetingRoom();
            var reserveDate = DateTime.Parse("2014/7/1");
            meetingRoom.Reserve(reserveDate).Is(true);

            var nextDate = DateTime.Parse("2014/7/2");
            meetingRoom.Reserve(nextDate).Is(true);
        }

理由は当初の bool を引きずって、日付を一つしか入れてないから。このあとも、bool を引きずってしまい、Class Reserved { public bool reserved; public DateTime reservedDate; } のようなクラスをつくって、Listとかごちゃごちゃやり出したら、最初のテストが通らなくなり実装している間に終わってしまった。

となりの @bonprosoft さんは私がまごまごしている間に実装終わっていたらしく、パラメタライズテストをしていたけど、Listのテストが期待通りにならなくてForEachでやってましたとのこと。そこで、Linq ですよとやろうとしたら、List.Where が通らなくて時間切れ。

後で見返したら、using System.Linq; じゃなくて using System.Data.Linq; になってるからだった。35歳定年が近づいているのを感じる。生きるのが辛い。

.NET Framework における List の Assert の仕方

さて、宿題にしてちょっと考えてみました。

まずは、1〜5のRangeをListにして考えてみます。先にも言ったとおり、Chaining Assertion を使用します。

        [TestMethod]
        public void Listの並び順を気にしてテストする()
        {
            var list = new List<int> { 1, 2, 3, 4, 5 };
            list.Is(1, 2, 3, 4, 5);
            list.IsNot(5, 4, 3, 2, 1);
        }

順番通りなら true で、逆順は false ここまでは良いですね。次にパラメタライズテストをしてみます。

        [TestMethod]
        [TestCase(1)]
        [TestCase(2)]
        [TestCase(3)]
        [TestCase(4)]
        [TestCase(5)]
        public void パラメタライズテスト()
        {
            var list = new List<int>();
            TestContext.Run((int x) =>
            {
                Trace.Write(x);
                list.Add(x);
            });

            list.Is(1, 2, 3, 4, 5); // Failed
        }

これはトレースすると(5,4,3,2,1)となっているからでした。(まぁ、普段こんな書き方しないだろというのは置いておいて)

並び順を気にせずに比較する

会議室の予約日付なので、別に並び順が異なっててもデータ的には問題ありません。(人の目で見るときは見にくいかもしれないけど) なので、並び順を気にしないで比較したいです。そんなとき Java で Hamcrest Matchers を使用すると、[containsInAnyOrder](http://hamcrest.org/JavaHamcrest/javadoc/1.3/org/hamcrest/Matchers.html#containsInAnyOrder(java.util.Collection%29) といったよさげなものがあります。

MSTest は CollectionAssert.AreEquivalent メソッド を使うと良さそうです。

            var list = new List<int> { 5, 4, 3, 2, 1 };
            var expected = new List<int> { 1, 2, 3, 4, 5};
            CollectionAssert.AreEquivalent(expected, list);

            var list2 = new List<int> { 5, 4, 3, 2, 1, 6 };
            CollectionAssert.AreNotEquivalent(expected, list2);

素数が違う場合もエラーとなってくれます。

そもそも List を使わない

@i_takehiro さんいわく、会議室の日付予約の順番は必要無く、単純な集合を使ったほうが良いとのことでした。Java でいう Set は .NET だと HashSet っぽかったのでそれで書いてみます。

            var set = new HashSet<int> { 5, 4, 3, 2, 1 };
            var expected = new HashSet<int> { 1, 2, 3, 4, 5 };
            // ビルドエラーになってしまう
            // CollectionAssert.AreEquivalent(expected, set);
            set.SetEquals(expected).IsTrue();

            var set2 = new HashSet<int> { 5, 4, 3, 2, 1, 6 };
            set2.SetEquals(expected).IsFalse();

残念ながら MSTest の CollectionAssert.AreEquivalent は HashSet を取り扱えないらしくてビルドエラーとなってしまいます。NUnit のならいけるっぽい。( via System.Collections.Generic.HashSetクラスを使ってみた - プログラマーな日々)

皆様の感想とか

TDD 勉強会終了。 ハンズオンの勉強会は久しぶりに出たけど、すごくためになった。 やはり、こういうのはやりこんでいる人のやり方を見せてもらうのがイチバン。— yscode (@yscode) 2014, 7月 3

本日の第1回 TDD勉強会in仙台は、最終的に10名の参加がありました。大分ゆるふわな感じの勉強会でしたが、とても面白かったです。参加者の皆さん、ありがとうございました。また近々開催すると思いますのでよろしくお願いします。 http://t.co/71m1LoyWrc— Takehiro Inoue (@i_takehiro) 2014, 7月 3

しばらく Visual Studio 使ってなかったから色々辛い。。。

こんな感じでリハビリしていければなーと思います。