この記事はRuby Advent Calendar 2023 18日目の記事です
こんにちは!春までJavaを書いてた新人Rubyist @Yu_yukk_Y です!
ついこの前、power_assertを使っている箇所で下記のようなコードを見かけました。
assert { my_func?(hoge) == true }
自分はこのコードに対して
== trueがなかったとして、my_func?(hoge)がtrueを返すならこのテストは通る。== trueは冗長だ
と指摘しました。
しかし、その直後
== trueという書き方にも意味はあるんだよ
と指摘への指摘をいただきました、、
今日はその内容について共有します!
前提
突然ですが、次のような関数があるとします。
- 数字を受け取って、その数字が3ならtrueを、それ以外ならfalseを返す関数
- 数字を受け取って、その数字が3なら
さん
という文字列を返し、それ以外ならさんじゃない
という文字列を返す関数
この関数をテストすることを考えます!
最小限の内容で趣旨を伝えるため、この記事ではコードの全体像については取り扱っていません。Javaのコードにクラスがなかったり所々違和感を覚えるかもしれませんが、ご了承ください。
Javaの場合
それぞれ次のような関数として実装してみます。
boolean isThree(int num){
return num == 3;
}
String retThree(int num){
if(num == 3) return "さん";
return "さんじゃない"
}
この関数をテストしてみましょう!assertTrueメソッドでテストを書いてみます。
// 3の時trueが返される
assertTrue(isThree(3));
// 3以外の時falseが返される
assertTrue(!isThree(0));
// 3の時"さん"が返される
assertTrue(retThree(3).equals("さん"));
// 3以外の時"さんじゃない"が返される
assertTrue(retThree(0).equals("さんじゃない"));
// あっ間違えて書いちゃった
assertTrue(retThree(3));
実行してみたら2の3つ目のコードが失敗します!boolean
が求められているところにString
を返す関数を入れてしまったのだから当たり前です。
Rubyの場合
それぞれ次のような関数として実装してみます。
一つ目のメソッドは本来three?
という名前で実装すべきですが、記事を記事を分かりやすくするためにJavaと関数名を揃えて、is_three?
という名前で実装させていただきます。
def is_three?(num)
return num == 3
end
def ret_three(num)
return "さん" if(num == 3)
"さんじゃない"
end
この関数をテストしてみましょう!assertメソッドでテストを書いてみます(power_assertの想定)。
# 3の時trueが返される
assert { is_three?(3) }
# 3以外の時falseが返される
assert { !is_three?(0) }
# 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
また、先ほどのテストコードも通ってしまいます。。
# あっ間違えて書いちゃった
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でもありません。そのため、次のテストはどちらも失敗します。
assert { re_three(3) == true } # 戻り値である"さん"はtrueではない
assert { nil == false } # nilはfalseではない
つまり、1つ目の関数のテストは実際には何が返されているのかわからず、何も検証できていません。
# 3の時trueが返される
assert { is_three?(3) } # 文字列が返されてもこのテストは通る
# 3以外の時falseが返される
assert { !is_three?(0) } # nilが返されてもこのテストは通る
では真偽値を返す関数はどのようにテストすれば良いのか
自作関数の場合
次のように、明示的に== true | false
を書きます。
# 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)) } # 真偽値が返ってくることがわかりきっている
参考文献