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

:::note info
この記事は[Ruby Advent Calendar 2023](https://qiita.com/advent-calendar/2023/ruby) 18日目の記事です
:::

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

ついこの前、[power_assert](https://github.com/ruby/power_assert)を使っている箇所で下記のようなコードを見かけました。
```ruby
assert { my_func?(hoge) == true }
```
自分はこのコードに対して
> == trueがなかったとして、my_func?(hoge)がtrueを返すならこのテストは通る。== trueは冗長だ

と指摘しました。
しかし、その直後
> == trueという書き方にも意味はあるんだよ

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

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

## 前提

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

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

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

:::note warn

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

:::



## Javaの場合

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

```java:1
boolean isThree(int num){
    return num == 3;
}
```

```java:2
String retThree(int num){
    if(num == 3) return "さん";
    return "さんじゃない"
}
```

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

```java:1
// 3の時trueが返される
assertTrue(isThree(3));
// 3以外の時falseが返される
assertTrue(!isThree(0));
```

```java:2
// 3の時"さん"が返される
assertTrue(retThree(3).equals("さん"));
// 3以外の時"さんじゃない"が返される
assertTrue(retThree(0).equals("さんじゃない"));
// あっ間違えて書いちゃった
assertTrue(retThree(3));
```
実行してみたら2の3つ目のコードが失敗します！`boolean`が求められているところに`String`を返す関数を入れてしまったのだから当たり前です。

## Rubyの場合

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

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


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

この関数をテストしてみましょう！assertメソッドでテストを書いてみます([power_assert](https://github.com/ruby/power_assert)の想定)。
```ruby:1
# 3の時trueが返される
assert { is_three?(3) }
# 3以外の時falseが返される
assert { !is_three?(0) }
```
```ruby: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
irb(main):011> print("3でした") if(ret_three(3))
3でした=> nil
```
また、先ほどのテストコードも通ってしまいます。。
```ruby:2:テストは通るがNG
# あっ間違えて書いちゃった
assert { ret_three(3) } # 実際には"さん"という文字列が返されている
```

逆にJavaのようなコードにはTruthyやFalsyの考え方はありません。よって次のようにif文で分岐した先の`3でした`は出力されません。
```shell
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でもありません**。そのため、次のテストはどちらも失敗します。
```ruby:2:テスト落ちる
assert { re_three(3) == true } # 戻り値である"さん"はtrueではない
assert { nil == false } # nilはfalseではない
```
**つまり、1つ目の関数のテストは実際には何が返されているのかわからず、何も検証できていません**。

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

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

### 自作関数の場合

次のように、明示的に`== true | false`を書きます。
```ruby:1:テスト通る
# 3の時trueが返される
assert { is_three?(3) == true }
# 3以外の時falseが返される
assert { is_three?(0) == false }
```

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

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

自身で検証する必要はないため、`== true | false`は省略してOKです。
```ruby:テスト通る
assert { SomeLibrary.three?(calc_plus(1, 2)) } # 真偽値が返ってくることがわかりきっている
```

## 参考文献

https://zenn.dev/bugbearr/articles/69e1101ccc676a4b28b6