2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Ruby】my_func? == true が冗長だと思っていた時期が僕にもありました

Last updated at Posted at 2023-12-17

この記事はRuby Advent Calendar 2023 18日目の記事です

こんにちは!春までJavaを書いてた新人Rubyist @Yu_yukk_Y です!

ついこの前、power_assertを使っている箇所で下記のようなコードを見かけました。

assert { my_func?(hoge) == true }

自分はこのコードに対して

== trueがなかったとして、my_func?(hoge)がtrueを返すならこのテストは通る。== trueは冗長だ

と指摘しました。
しかし、その直後

== trueという書き方にも意味はあるんだよ

と指摘への指摘をいただきました、、:innocent:

今日はその内容について共有します!

前提

突然ですが、次のような関数があるとします。

  1. 数字を受け取って、その数字が3ならtrueを、それ以外ならfalseを返す関数
  2. 数字を受け取って、その数字が3ならさんという文字列を返し、それ以外ならさんじゃないという文字列を返す関数

この関数をテストすることを考えます!

最小限の内容で趣旨を伝えるため、この記事ではコードの全体像については取り扱っていません。Javaのコードにクラスがなかったり所々違和感を覚えるかもしれませんが、ご了承ください。

Javaの場合

それぞれ次のような関数として実装してみます。

1
boolean isThree(int num){
    return num == 3;
}
2
String retThree(int num){
    if(num == 3) return "さん";
    return "さんじゃない"
}

この関数をテストしてみましょう!assertTrueメソッドでテストを書いてみます。

1
// 3の時trueが返される
assertTrue(isThree(3));
// 3以外の時falseが返される
assertTrue(!isThree(0));
2
// 3の時"さん"が返される
assertTrue(retThree(3).equals("さん"));
// 3以外の時"さんじゃない"が返される
assertTrue(retThree(0).equals("さんじゃない"));
// あっ間違えて書いちゃった
assertTrue(retThree(3));

実行してみたら2の3つ目のコードが失敗します!booleanが求められているところにStringを返す関数を入れてしまったのだから当たり前です。

Rubyの場合

それぞれ次のような関数として実装してみます。

一つ目のメソッドは本来three?という名前で実装すべきですが、記事を記事を分かりやすくするためにJavaと関数名を揃えて、is_three?という名前で実装させていただきます。

1
def is_three?(num)
    return num == 3
end
2
def ret_three(num)
    return "さん" if(num == 3)
    "さんじゃない"
end

この関数をテストしてみましょう!assertメソッドでテストを書いてみます(power_assertの想定)。

1
# 3の時trueが返される
assert { is_three?(3) }
# 3以外の時falseが返される
assert { !is_three?(0) }
2
# 3の時"さん"が返される
assert { ret_three(3) == "さん" }
# 3以外の時"さんじゃない"が返される
assert { ret_three(0) == "さんじゃない" }
# あっ間違えて書いちゃった
assert { ret_three(3) }

お、全てのテストが通りました!よかった!!!
、、、本当に??

TruthyとFalsy

Javaには存在せずRubyに存在する考えとしてTruthyとFalsyというものがあります。
RubyにおけるFalsyとはfalse以外の値だとしてもfalseとみなされる値のことで、以下の値のことを指します。

nil, false

TruthyとはFalsy以外の全てです。Falsyな値ではない値は全てtrueとみなされます。
そのため、たとえば次のコードではret_three(3)の返り値の“さん“がtrueとみなされ、if文で分岐した先の3でしたは標準出力に出力されます。

irb(main):011> print("3でした") if(ret_three(3))
3でした=> nil

また、先ほどのテストコードも通ってしまいます。。

2:テストは通るがNG
# あっ間違えて書いちゃった
assert { ret_three(3) } # 実際には"さん"という文字列が返されている

逆にJavaのようなコードにはTruthyやFalsyの考え方はありません。よって次のようにif文で分岐した先の3でしたは出力されません。

jshell> if(retThree(3)) System.out.println("3でした");
|  エラー:
|  不適合な型: java.lang.Stringをbooleanに変換できません:
|  if(retThree(3)) System.out.println("3でした");
|     ^---------^

じゃあTruhyはtrue、Falsyはfalseなんだね!!!!←いいえ違います

タイトルの通りですが、TruthyとFalsyは条件分岐やテストにおいてはそれぞれtrue、falseとみなされますが、実際はtrueでもfalseでもありません。そのため、次のテストはどちらも失敗します。

2:テスト落ちる
assert { re_three(3) == true } # 戻り値である"さん"はtrueではない
assert { nil == false } # nilはfalseではない

つまり、1つ目の関数のテストは実際には何が返されているのかわからず、何も検証できていません

1:テストは通るがNG
# 3の時trueが返される
assert { is_three?(3) } # 文字列が返されてもこのテストは通る
# 3以外の時falseが返される
assert { !is_three?(0) } # nilが返されてもこのテストは通る

では真偽値を返す関数はどのようにテストすれば良いのか

自作関数の場合

次のように、明示的に== true | falseを書きます。

1:テスト通る
# 3の時trueが返される
assert { is_three?(3) == true }
# 3以外の時falseが返される
assert { is_three?(0) == false }

この記事のタイトルにもあるassert { myFunc? == true }は無駄に冗長な書き方だったわけではなく、Truthyではなくtrueが返ってくることをちゃんと検証していたというわけです。

OSSなどの自作ではない関数の場合

自身で検証する必要はないため、== true | falseは省略してOKです。

テスト通る
assert { SomeLibrary.three?(calc_plus(1, 2)) } # 真偽値が返ってくることがわかりきっている

参考文献

2
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?