easybのDSLを用いて仕様を記述する

このクラスの仕様を、easybのDSLを使って書き表してみよう。easybのDSLには、以下に示すような2種類が存在する。

  1. Story DSL…「こういう状況で(given)、こういう条件を満たしたとき(when)、このように振る舞う(then)」という流れのストーリー形式で仕様を書き表す
  2. Behavior DSL…RSpec(Ruby用のDSLフレームワーク)由来のDSLで、「このクラス(it)はこう振る舞うべき」という形で仕様を書き表す。

ここでは、Story DSLを使って仕様を書き表すことにする。Story DSLは拡張子「.story」というファイルに保存し、その中に複数の「シナリオ(scenario)」を記述するという形式をとる。シナリオは、「given(前提条件として与えられるもの)→ when(何かが発生したとき)→ then(引き起こされる結果)」という流れで記述することになる。given、when、thenは複数記述することも可能で、その場合はandという接続詞を使うことになる。これを大まかに表したのが、以下のコードだ。

scenario "シナリオの説明", {
  given "シナリオの前提条件を表す説明", {
    ...
  },
  when "発生する条件の説明", {
    ...
  },
  then "結果の説明", {
    ...
  }
}

この文法に従って、先ほどのFilePathクラスの仕様を記述してみたのがリスト1のコードだ(サンプルコードの全文は、記事の最後に掲載する)。「/a/b/c.txt」といった完全なファイルパスを与えられたとき、getDirPath()やgetFileName()がどのように振る舞うかを規定している。

リスト1 FilePathBehavior.storyからシナリオを一部抜粋

scenario "「/」から始まる完全なファイルパスを解析する", {
    given "完全なファイルパスを表す文字列(「/a/b/c.txt」)", {
        path = "/a/b/c.txt"
    }
    when "完全なファイルパスを渡し、FilePathのインスタンスを生成する", {
        filePath = new FilePath(path)
    }
    then "ディレクトリのパスは「/a/b/」になるはず", {
        // ※「should」を用いて検証を行っている
        filePath.dirPath.shouldBe "/a/b/"
    }
    and "ファイル名は「c.txt」になるはず", {
        // ※「should」を用いて検証を行っている
        filePath.fileName.shouldBe "c.txt"
    }
}
...

シナリオの構造は先ほど説明した通りなので、特に説明は不要だろう。注目すべきは、※が振られた部分のコードだ。

// ※「should」を用いて検証を行っている
filePath.fileName.shouldBe "c.txt"

FilePathクラスのgetFileName()を呼び出した結果が、"c.txt"と一致する必要がある、ということを表している。

このように、検査対象のオブジェクトに対して「shouldXXX」というメソッド呼び出しを行って動作の検証を行うことができる。「shouldXXX」には、以下のような種類のメソッドが用意されている。

  • 同値検査用(例: value.shouldBe 1)
    • shouldBe
    • shouldEqual
    • shouldBeEqual
    • shouldBeEqualTo
    • shouldNotBe
    • shouldNotEqual
    • shouldntBe
    • shouldntEqual
  • 型を検査するメソッド(例: value.shouldBeAn Integer)
    • shouldBeA
    • shouldBeAn
    • shouldNotBeA
    • shouldNotBeAn
  • 値の比較(例: value.shouldBeGreaterThan 5)
    • shouldBeGreaterThan
    • shouldBeLessThan
  • コレクション内に存在すべき値の検査(例: values.shouldHave [1, 2])
    • shouldHave
    • shouldNotHave

Groovyの動的な性質のおかげで、数値や文字列などの組み込みオブジェクトや、自作したクラスのインスタンスなど、すべてのオブジェクトに対してこれらのメソッドを呼び出すことができる(ただし、それができるのはthenメソッドのクロージャ内に限られる)。英文法と似た並びで検証コードを記述できるので、非常に読みやすい。