zephiransasのチラシの裏

とあるJava/Rubyプログラマのメモ代わりブログ

Lambda-behaveでテストを書こう

これはJavaアドベントカレンダー2014の12/16分の記事です。

昨日はgrimroseさんの、[書評] Gradle徹底入門 でした。

明日は@com4dcさんの、はじめて触るStreamの世界 です。

自分はJavaのテストフレームワークである、lambda-behaveについて紹介します。

自分は普段はRailsでの開発を行っているのですが、現場では主にRSpecを使ってテストを記述しています。RSpecでのテストは以下のような感じです。

1
2
3
4
5
6
7
describe 'Sample' do
  context 'hogeメソッドについて' do
    it 'fugaを返すこと' do
      Sample.hoge.should == "fuga"
    end
  end
end

RSpecでは上記のようにDSLを使って、なにをテストしているかを構造的に記述することができます。 lambda-behaveを使うと、このようなDSLっぽい記述のテストを、Java8のLambdaを使って書くことができるようになります。

最初のテスト

まずはテスト対象となるメソッドを準備します。

1
2
3
4
5
public class Sample {
    public static int includeTax(Integer price) {
        return 0;
    }
}

上記のようなstaticなメソッドを準備します。includeTaxメソッドは引数を一つ取り、その税込み金額を返すメソッドとします。実にギョーミーですね!

今回はTDD的なノリで実装していきますので、ここでは中身の実装はおこないません。

それでは実際のテストを書いて行きましょう。ここでのテストシナリオは

  • includeTaxメソッドに100を渡した場合に、108が返ってくること

をテストするとします。これをlambda-behaveで書くと、以下のようになります。

1
2
3
4
5
6
7
8
9
10
import static com.insightfullogic.lambdabehave.Suite.*;

@RunWith(JunitSuiteRunner.class)
public class SampleSpec {{
    describe("includeTax", it -> {
        it.should("税込み価格が取得できること", expect ->
            expect.that(Sample.includeTax(100)).is(108)
        );
    });
}}

static importを使ってlambda-behaveのメソッドを使えるようにし、こられを使って記述していきます。

JUnitのランナーも用意されていますので、これを@RunWithで指定すれば、IDEからも簡単にテストを実行可能です。早速、テストを実行してみましょう。

screen1

includeTaxは、まだ実装をおこなっていませんので、当然このとおりテストが失敗します。

次に、includeTaxを実装してみます。

1
2
3
4
5
6
public class Sample {
    public static int includeTax(Integer price) {
        final float TAX_RATE = 0.08;
        return (int)Math.floor(price * (1 + TAX_RATE));
    }
}

再度、テストを実行すれば、テストが成功しています。

screen2

複数のテストデータでチェック

先の例では1つの値でしかテストしませんでしたが、lambda-behaveでは同時に複数の値でテストすることもできます。以下のようになります。

1
2
3
4
5
it.uses(100, 108)
  .and(200, 216)
  .toShow("税込み価格が取得できること", (expect, price, includeTax) -> {
     expect.that(Sample.includeTax(price)).is(includeTax);
  });

itのあとにuseとandをチェインして値を準備し、これを使ってtoShowメソッド内でテストを行います。toShowメソッド内ではlambdaの引数で準備した値を利用できますので、lambda内でその値をexpectするようにしています。

生成した値でチェック

lambda-behaveではランダムな値を生成する機能も準備されています。適当な数値を5つほど生成して、そのテストを行うコードは以下のようになります。

(あまり例がよくないですが・・・)

1
2
3
4
5
it.requires(5)
  .example(Generator.integersUpTo(1000))
  .toShow("税込み価格が取得できること", (expect, price) -> {
    expect.that(Sample.includeTax(price)).is((int)Math.floor(price * 1.08));
  });

requiresでランダムに準備する値の数を指定します。

exampleではどのようなテストデータを生成するかを指定します。ここではlambda-behaveで準備されたGenerator.integersUpToを使用しています。この他にも適当な文字列を生成するasciiStringsメソッドなどもあります

例外が発生することをチェック

引数にnullが渡された場合にNullPointerExceptionの発生をチェックすることも、JUnitと同様に可能です。

まず、includeTaxにnullチェックを記述します。

1
2
3
4
5
6
7
8
9
public class Sample {
    public static int includeTax(Integer price) {
        if(price == null) {
            throw new NullPointerException();
        }
        final float TAX_RATE = 0.08f;
        return (int)Math.floor(price * (1 + TAX_RATE));
    }
}

これをテストするlambda-behaveのコードは以下のようになります。

1
2
3
4
5
it.should("nullの場合ぬるぽ", expect -> {
    expect.exception(NullPointerException.class, () -> {
        Sample.includeTax(null);
    });
});

exceptionメソッドの第1引数に、発生する予定の例外を指定し、第2引数には例外が発生する処理をlambdaで記述します。

lambdaに慣れていないと少々書きづらいかもしれないですが、DSLちっくに書けるのはJavaっぽくなくてステキですよね!

こちらからは以上です。