読者です 読者をやめる 読者になる 読者になる

世界のやまさ

SEKAI NO YAMASA

第1回 TDD勉強会in仙台 に参加しました。

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

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

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

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

お題は「会議室の予約」

シンプルながら状態持ったり日付使ったりと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 といったよさげなものがあります。

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クラスを使ってみた - プログラマーな日々)

皆様の感想とか

しばらく Visual Studio 使ってなかったから色々辛い。。。
こんな感じでリハビリしていければなーと思います。