質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.52%

  • Java

    15527questions

    Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

  • JUnit

    195questions

    JUnitは、Javaで開発されたプログラムのユニットテストを行うためのアプリケーションフレームワークです。簡単にプログラムのユニットテストを自動化することができ、結果もわかりやすく表示されるため効率的に開発時間を短縮できます。

関数型IFを返却するメソッドのjUnitでの検証

解決済

回答 4

投稿

  • 評価
  • クリップ 0
  • VIEW 964

7tsuno

score 300

前提・実現したいこと

関数型IFを返却するメソッドの戻り値を検証したい場合、どうしたらいいでしょうか。

発生している問題・エラーメッセージ

以下のようなメソッドがあった場合

    public Function<String, String> sampleMethod(boolean flg) {

        if (flg) {
            return str -> str + "hoge";
        } else {
            return str -> str + "fuga";
        }

    }

引数がtrueの場合に戻り値が[ str -> str + "hoge" ]であることを検証したい場合
jUnitではどう検証を行えばいいでしょうか?
また、関数型IFに適したMathcerのライブラリなどをご存知の方がいればご教示いただきたいです。

試したこと

    Main target = new Main();

    @Test
    public void test_sampleMethod() {

        // Exercise
        Function<String, String> actual = target.sampleMethod(true);

        // Verify
        Function<String, String> expect = str -> str + "hoge";
        assertThat(actual.apply("hoge"), is(expect.apply("hoge")));
    }

という検証をつくるとテスト自体はOKにはなるのですが、

    Main target = new Main();

    @Test
    public void test_sampleMethod() {

        // Exercise
        Function<String, String> actual = target.sampleMethod(true);

        // Verify
        Function<String, String> expect = str -> "hogehoge";
        assertThat(actual.apply("hoge"), is(expect.apply("hoge")));
    }

でもOKになってしまうため、正しい検証にはなっていません。

補足情報(言語/FW/ツール等のバージョンなど)

java : 1.8.0_111
jUnit : 4.12

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 4

checkベストアンサー

+4

少々乱暴な方法ですが、
関数と関数が同じかを確かめる方法の一つに、
十分多い数、引数をランダム生成して、
全部通るか検証する方法があります。

"悪意を持った"実装者がよほど運が良くなければテスト通りません。


いや、特定の引数に対して変な値を返すように"悪意をもって"実装すれば高確率でテスト通りますが、
要するにテストの文面でランダム値を使うことで、
実装者に「入力としてあり得るすべての値でテスト通るようにしろ」
という意図を伝える事ができるってことです。

ただ、この程度の関数だと、質問文のコードのままで意図が十分伝わるはずので
全然問題ないことになっちゃいますがね。


※回答じゃないです

質問文だとexpectの方を書き換えちゃってますが、

public Function<String, String> sampleMethod(boolean flg) {

        if (flg) {
            return str -> str + "hoge";
        } else {
            return str -> str + "fuga";
        }

    }

という実装がされることを期待して

    Main target = new Main();

    @Test
    public void test_sampleMethod() {

        // Exercise
        Function<String, String> actual = target.sampleMethod(true);

        // Verify
        Function<String, String> expect = str -> str + "hoge";
        assertThat(actual.apply("hoge"), is(expect.apply("hoge")));
    }

というテストを書いたが、
これだと

public Function<String, String> sampleMethod(boolean flg) {

        if (flg) {
            return str -> "hogehoge";
        } else {
            return str -> str + "fuga";
        }

    }


みたいにいくらでも抜け道ができて困る
ということですよね?


余談

str -> str + "hoge"自体のテストを抜け道潰して行おうとすると
exceptで同じ関数を実装することになって意味がなくなります。
こういう場合はProperty based testというものを行います。

f(x).substring(0,x.length()).equals(x);

みたいな関数それ自体の性質を何個か作り、それをテストします。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/02/07 13:38

    そういうことです。もともと返却値が関数のようなものなので試験なんて出来ないんでしょうかね。

    キャンセル

  • 2017/02/07 15:02

    やはり返却された関数も同様に試験的なことをしないと無理ですよね。
    この場合は簡潔ですが、返却された関数が複雑なメソッドの場合二重に試験を行わなければいけなくなりそうですね。

    キャンセル

  • 2017/02/08 09:15

    やることは同じでも、「二重に試験を行う」という発想をしなくてもsampleMethodは、実のところ、flag, strを取る二引数関数をカリー化したものだといえます。そうしたらsampleMethodとその戻り値をあわせて二引数関数と捉え、単に二引数関数をテストするのだと考えればそれほどおかしいこと、二重に試験をしているというわけではないと言えます。

    キャンセル

  • 2017/02/08 09:51

    一つの関数として考えればっていうことですね。
    その考え方もありですね。勉強になります。

    キャンセル

+2

私ならこう書くかなぁ。
実際のテストでは特にこっちの方がわかりやすいとおもう。

    Main target = new Main();

    @Test
    public void test_sampleMethod() {

        // Exercise
        Function<String, String> actual = target.sampleMethod(true);

        // Verify
        assertThat(actual.apply("hoge"), is("hogehoge"));
        assertThat(actual.apply("fuga"), is("fugahoge"));
    }

追記

実際に近い命名をしてみました。

戻り値のFunctionはHogeを連結する関数型なので、
テストする内容はHogeが連結されていることになります。

意図が明確になると違和感がないと思います。

    Main target = new Main();

    @Test
    public void test_joinHogeOrFuga() {

        // Exercise
        Function<String, String> joinHoge = target.joinHogeOrFuga(true);

        // Verify
        assertThat(joinHoge.apply("hoge"), is("hogehoge"));
        assertThat(joinHoge.apply("fuga"), is("fugahoge"));
        // 連結をさらに強調したいなら
        assertThat(joinHoge.apply("hoge"), is("hoge" + "hoge"));
        assertThat(joinHoge.apply("fuga"), is("fuga" + "hoge"));
    }

テストは結果や意図が明確であることが大事だと思います。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/02/07 14:59

    やはり返却された関数をさらに試験するようにしないとだめそうですね。こちらで期待した関数とアサーションできないかと考えていたのですがそれは無理そうですよね。

    キャンセル

  • 2017/02/07 15:26 編集

    関数とアサーションする必要があるかどうかというのがポイントになりそうです。

    私が例題の関数は引数によって文字列を装飾してくれるメソッドなので、装飾されていること、それが一目でわかるテストというのが大事だと思います。

    もちろん境界条件などのテストでわかりずらいテストケースも必要ですが、基本のテストは、仕様の説明であるべきで、基本型でチェックを基本とすべきと考えています。

    基本型にすべき理由としては、わかりやすさが一番にありますが、exceptが計算式やメソッド(この場合は式)の場合、中身はチェック対象と全く同じになるため、exceptにバグを作る可能性があります。

    また、例の場合は特に、チェック対象のメソッドと同じものを作成しているので、テストしていないのと同じになってしまいます。

    まあ個人的な見解ですが。。。

    キャンセル

  • 2017/02/07 17:40

    意味のあるメソッドであればそうですね。
    一般的な場合で質問をしたかったので簡単な例を出したのですが、確かにメソッドの意味を考えてテストをするというのもひとつの方法であるかもしれませんね。

    expectをまったく同じ形にしたのはこの条件のときにこの式を返すとしたかったわけです。
    やはりそういう形での検証は出来なそうですね。

    キャンセル

  • 2017/02/07 18:10

    できるできない以前に、メソッドのテストの場合と同じように戻ってきたFunctionのテストをすれば十分だとおもいます。メソッドと同様に複雑なFunctionはありえますから・・・

    とはいえ、必要なパターンも含め、このような純粋な思考も面白く思います。

    キャンセル

  • 2017/02/07 18:19

    もちろん理解はしていますが、
    複雑なFunctionが返ってきた場合は結局そのFunctionの試験もしなければならないのが億劫ですね。
    そのメソッド単体の試験としては「複雑なFunctionが返却された」ということだけ検証できればいいと思っているので、
    assertThat(actual, is(str -> すごく複雑なメソッド(str)));
    のような形でできたらなあという妄想です。

    キャンセル

  • 2017/02/07 21:34

    そうです。ユニットテストにすごく複雑なメソッドを実際に書くことになり、現実的ではないですよ。テストにバグを作ります。

    まともな設計でまともな粒度のテストを書けば、そんな複雑なテストは不要です。逆を言えば、複雑なテストが必要になる時点で設計を間違っていると考えて問題ありません。

    キャンセル

+1

Verifyで書かれている

Function<String, String> expect = str -> "hogehoge";

の期待する動き(何を入れても hogehoge が得られる)と、テスト対象の関数 ( trueのときは、入力した値に hoge を追加するので hogehoge が得られる ) が同じ値となる検証なので、
expect(期待値)の書き方次第で 検証結果がtrueとなってしまうだけです。

検証してる内容が正しくないように思えますが、いかがでしょうか。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/02/07 13:35

    それは分かっています。この検証だとNGなのでどう検証をすればいいのかというのが今回の質問です。

    キャンセル

+1

関数なので単一の値のテストではどのみち不十分ですね。だからといって沢山やる必要もないですが、こんなふうに一々書くことの手間を減らせれば充分という気がしました。

assertThat(actual.apply("foo"), is(expect.apply("foo")));
assertThat(actual.apply("bar"), is(expect.apply("bar")));
...

JUnit初心者なので何が適切かは全然自身ないですが。以下のようなマッチャーを定義してそれを使うといったアイデアが浮かびました。

<T, R> isFor(Function<T, R> function, T... values);
->
assertThat(actual.apply("hoge"), isFor(expect.apply("hoge"), "foo", "bar"));

それとも既に定義されているんでしょうか・・・もしこのようなものがなくても、値のパターンを指定するJUnitの機能が別途あった気がするのでそちらを使う方がよくて、自分でマッチャーを定義する手間をかける必要はないかも知れません・・・

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/02/07 15:01

    マッチャーを定義して値をたくさん入れてっていうのは良さそうですね。
    自動で文字列を生成してランダム文字列を指定回数分チェックとかもできたら整合率もかなり高くできそうですね。

    キャンセル

  • 2017/02/07 19:03

    jPopulatorなんてどうでしょう

    キャンセル

  • 2017/02/07 19:12

    これ面白そうですね!いままで乱数で自分で作ってました...

    キャンセル

同じタグがついた質問を見る

  • Java

    15527questions

    Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

  • JUnit

    195questions

    JUnitは、Javaで開発されたプログラムのユニットテストを行うためのアプリケーションフレームワークです。簡単にプログラムのユニットテストを自動化することができ、結果もわかりやすく表示されるため効率的に開発時間を短縮できます。