伊藤氏の自己紹介
伊藤淳一氏:リーダブルコードという発表です。いきなり余談から入りますが、今日仕事をしていたらテストコードに助けられました。
仕様変更がいつ入ったのかを調べなきゃいけなくなってコミットを追いかけていったら、過去の僕がすごくわかりやすいテストコードを書いていて、仕様Aを仕様Bに変えることがdiffを見れば一目瞭然というようなものを作っていました。リーダブルなテストコードを書いてて良かったと思った日がこの勉強会の開催日で、ナイスタイミングだと思いました。
自己紹介をします。伊藤 淳一といいます。ソニックガーデンという会社でRailsのプログラマーをやっています。プログラミングスクールのフィヨルドブートキャンプでメンターもやっています。住んでいるのは兵庫県西脇市です。よく西宮と間違えられますが、西脇市です。10年くらい自宅からリモートワークをやっていて、今日も自宅からお送りしています。
僕はどれくらいテストコードを書いているのかと思って振り返ってみたら、17~18年書いていました。最初はJUnit、NUnit、最近はRubyでRSpecという感じです。それなりにベテランじゃないかなと思っています。Twitterやブログもやっているので、今日の発表が良かったら購読をお願いします。
あとQiitaでもよく記事を書いています。今はユーザーランキング1位だそうです。「DIAMOND賞」も受賞して、家にスライドのようなトロフィーがあります。
『プロを目指す人のためのRuby入門』というRubyの本も書いています。表紙がさくらんぼなので「チェリー本」と呼ばれています。2021年12月に改訂2版が出て、Ruby3.0に対応しました。Rubyが学べるのはもちろんですが、サブタイトルに「テスト駆動開発」とあるので、テストコードの書き方も学べます。
実はニュースがあって、ちょうど今日重版でき、増刷が決まりました。イェイ、ワーイということで。たくさんの人に読んでいただいて増刷が決まったので、読んでない方がいたらぜひ手に取ってください。
あと、翻訳ですが『Everyday Rails - RSpecによるRailsテスト入門』というRSpecの本も出しています。これは電子書籍で売っています。2014年にリリースして、アップデートを重ねて、2022年1月にRails7.0に対応しました。RSpecを学びたい方はぜひこちらもどうぞ。
本セッションの概要
(スライドを指して)自己紹介を終えて、今日はだいたいこんな内容で話そうと思っています。「テストコードにおいて、過度なDRYは読みやすさの敵」「賢くてロジカルなテストコードより、誰でも読める愚直なテストコードを!」「脳内メモリを使わないテストコードほどリーダブル」「実行可能なAPIドキュメントだと思ってコードを書こう」です。
コンテキストとしては「プログラマーが自分で書くユニットテスト」を想定しています。なので、QAエンジニアの方は自分の仕事には適用しづらいことがあるかもしれませんが、そのあたりはご了承ください。
事前知識として必要なもの、サンプルコードが出てきますが、RSpecはRubyで書いています。でもRubyを知らないとか、RSpecを書いたことがない人でも大丈夫です。テストコードの経験があればだいたい理解できるんじゃないかと思います。なぜなら、この発表はリーダブルテストコード、読みやすいテストコードという発表になっているからです。
ただ、もしかしたら字が小さいかもしれません。(スライドを示して)字のサイズはこれくらいです。もし、読めなかったらツイートのスライドを開いてほしいです。
テストコードにおいて過度なDRYは読みやすさの敵
そろそろ本編に入りましょう。リーダブルテストコードです。最初にみなさんに質問です。こんな経験ありませんか? GitHubなどでコードレビューしているところを想像してください。コードをダーッと眺めていって、次にテストコードのところが出てきて、テストコードをレビューするわけですけれど、わかるようなわからないようなものがある。
でも、アプリケーション側のコードを書いてくれた人が書いたテストコードだし、CIのテストも全部パスしているし、たぶんいいんじゃないかなみたいな感じで、「approve」ボタンを押したくなったことがないですか? でも、それはNGです。ようわからんのにapproveしちゃダメじゃないですかという話です。
先ほどのツイートには続きがあって、良いテストコードというのは、プログラムがどんな仕様でどんな動きをしているのかが読み取りやすいコードです。次に大事なことですが、DRYを目指せば目指すほど、テストコードのわかりやすさは失われていく傾向にあります。
大事なポイントですね。「テストコードにおいて、過度なDRYは読みやすさの敵」ということです。DRY、重複をなくすことはプログラムを書く上で非常に大事な考えかたなので、優秀なプログラマーのみなさんはいつもコードをDRYにしようと思っているとは思います。
しかし、テストコードの時はちょっと話が違うんです。重複が見つかったとか、もっとDRYにしたいとか、いろいろなテクニックを使ってDRYにする、「もっともっと」みたいなことをやっていると、わかりにくいテストコードが生まれてしまう。昔の僕もこんな感じで重複をなくそうとしていましたが、数年後に読み返すとメッチャ読みにくい感じになっていました。
読みやすいテストコードの3つのポイント
どういうテストコードなら読みやすいのかというと、ポイントは3つくらいあると思っています。まずは「ドキュメントのように上から下に素直に読み下せること」。それから「変数を使わずに文字列や数字がベタ書きしてあること」「凝ったテクニックを乱用しない」ということだと思っています。
この条件を反対にすると「テストコードの中にループ処理や条件分岐が頻発している」という感じです。そうなると、コードを読む時にもループ処理を直す視線がぐるぐる上下に来たり、if〜elseで視線がジャンプしたりします。あとは、数値や文字列のような単純なデータまで全部変数に入っているとか。
shared examplesやsubjectはRSpecの機能ですが、こういったテスティングフレームワークや機能を乱用していると、読みにくいテストコードができ上がってしまいます。
というわけで優秀なプログラマーのみなさん、短いコードがいい、重複のないコードがいい、かっこいいコードを書きたいという気持ちはわかりますが、賢くてロジカルなテストコードよりも、誰でも読める愚直なテストコードを書きましょうということを伝えたいです。この“誰でも”というのは、非エンジニアの人が読んでもわかるようなテストコードが理想です。
脳内メモリを消費するテストコード実例
(スライドを示して)ここまで全部言葉で説明してきましたが、実例があったほうがわかりやすいだろうということで、実際に僕がコードレビューしたものを持ってきました。
Userクラスにageメソッドというものが追加されました。ageメソッドはRubyで書かれています。Rubyがわからない方も大丈夫です。今回ageメソッドは主役ではないので、読めなくても大丈夫。あえて仕様やロジックは説明しません。
こういう年齢計算のメソッドがあるということだけ踏まえて、テストコードを見てほしいんです。
こんな感じのテストコードを僕がレビューしていました。ここでみなさんに問題を出したいと思います。先ほど見たUserクラスのageメソッドがどんな仕様なのかを、スライドのテストコードを見て予想してほしい。10秒時間を与えますのでちょっと見てください。考えてみてください。用意スタート。
時間がないので駆け足になりましたが、どうですか。テストコードだけを見てメソッドの仕様がわかるかと言われたら、「うーん、わかるようなわからんような」という先ほどのツイートみたいな気持ちになりませんか。
もしみなさんがレビュアーだったら、先ほどのテストコードをapproveしますか、しませんか。僕はapproveしません。さすがにあれはリーダブルとは思えないと考えます。
(スライドを示して)先ほどのテストコードがいったいどんなふうになっていたのか、カラクリを説明しましょう。まず、RSpecに詳しくない方にピンク色で補足説明を書いています。
letというものがありますが、これは変数宣言みたいなものです。あとtravel_toというメソッドがあって、このメソッドを使うとシステムの日付を一時的に変更できます。user.ageはテストしたいメソッドのことです。
解説しますが、これはライブラリを使ってユーザーの誕生日をランダムに決めています。そして、システム日付を取ってきます。今年(2022年)に実行すれば2022が返ってきます。そして、動的に今年の誕生日を決めています。
2022年3月10日とか2022年6月15日みたいなものを動的に決めて、そこから引き算をして、2022引く1970で何歳とか。50歳とか32歳とか(結果を)出して、システム日付を今年の誕生日に変えて、上で算出した年齢とメソッドの戻り値が一致するかを検証したりして、誕生日の前日に変更して1歳若くなるかを検証しているのがこのテストコードです。
ここまで解説してようやくわかるテストコードって、いかがなものかと思うわけです。先ほどのテストコードを見てわかることは「脳内メモリを消費するテストコードはリーダブルではない」。つまり、先ほどのテストコードを見る時は、頭の中で変数の中身などをどんどん展開していかないとコードが理解できないんです。
こういうことをやっているとリーダブルではないということです。ちなみにこの“脳内メモリ”という考え方は、「Clean Test Code Revised」というスライドから表現を拝借させてもらっています。
脳内メモリを消費するテストコードを読みやすくする方法
読みやすくするためにはどうしたらいいのか。変数をなくしてみましょう。(スライドを示して)僕ならこんなふうに書きます。
ユーザーの誕生日。いつでもいいですが、例えば1977年7月17日生まれだったとしましょう。今日の日付が2022年7月17日なら、そのユーザーは45歳だし、前日の7月16日だったら44歳です。こういうふうに書けばシンプルで非常に読みやすくないでしょうか。
さらにこのテストは上から下に読めるので、1977年7月17日生まれの人がいて、2022年7月17日になれば45歳だし、7月16日なら44歳という読みかたもできますよね。こちらのほうがリーダブルじゃないかと僕は思いますが、みなさんいかがでしょうか。
ということで、これからわかるのは「脳内メモリの消費を抑えれば抑えるほど、リーダブルなテストコードになる」ということです。
テストコードは仕様書
次はちょっと観点を変えます。テストコードって、仕様書みたいなものなんです。テストコードを見ればメソッドの振る舞いがすぐわかるのが理想で、先ほどこのageメソッドを見せて、別にRubyを知らなくてもいいという話をしました。でも、テストコードのほうはRubyを知らなくてもなんとなくわかる。読めるという状態になっているのが理想的です。
この考え方は、APIドキュメントのサンプルコードと同じだと思っています。(スライドを示して)これはRubyの「basenameメソッド」というAPIドキュメントを抜粋したものですが、ここに載っているサンプルコードはベタ書きですよね。引数ベタ書き、戻り値ベタ書きだからこういう書き方になっていると、Rubyを知らない人でもだいたい予想がつくと思うんです。
これがもし、こんなAPIドキュメントだったらどうしましょう。変数があって、それの戻り値が式で表現されていると、さすがに辛いと思いませんか。でもプログラマーがテストコードを書くとこれに近いことをよくやるので、プログラマーは注意しましょう。
みなさん、テストコードを書く時はプログラムを書いているのではなく、ドキュメントを書いている、動かせるAPIドキュメントみたいなものを書いている。そういうふうに考えてほしいです。
E2Eテストの考え方
残りの時間でその他のトピックをお話しします。E2Eテストです。ブラウザを自動実行するようなテストでも考え方は同じです。「user.nameのリンクをクリックすると、user.nameの日記というページが表示されます」みたいなテストコードを書く人、プログラマーに多いんですよね。
これはベタ書きでいいじゃないですか。「ありす」というリンクをクリックしたら「ありすの日記」というページが表示されるという、ベタ書きでいいと思います。
この2行くらいなら読みやすさは変わりませんが、E2Eテストはけっこう長くなりがちです。こういうコードを書いていると、チリツモでどんどん脳内メモリを消費するので注意しましょう。
他にも話したい内容がいっぱいありましたが、時間がないですね。テストデータの話とかdescribeは丁寧に書きましょうとか、1画面に収めましょうとか。
あと、今日はDRY禁止みたいな話をしましたが、これはあくまで原則であって、明らかにメリットが大きい場合やいい感じの抽象化とかは否定しません。そういう話もいっぱいしたいんですが時間がないので、僕のQiitaやブログの記事を読んでください。
レビュアーから自信を持って「ヨシ!」と言われるテストコードを
今日のまとめです。今日はこんなお話をしました。「subjectにおいて、過度なDRYは読みやすさの敵」「賢くてロジカルなsubjectより、誰でも読める愚直なsubject」「脳内メモリを使わないsubjectほどリーダブル」。
あれ? なんかこのスライドは読みづらいですね。
すみません。ついDRYに書きたくなる癖が出ました。というわけで、ちゃんとベタ書きしないとダメですよね。
(スライドを示して)書き直しました。こっちのほうが読みやすいですね。
テストコードにおいて過度なDRYは読みやすさの敵とか、ロジカルなテストコードより愚直なテストコードを書きましょうとか、脳内メモリを使わないテストコードほどリーダブルとか、ドキュメントだと思ってテストコードを書きましょうというお話をしました。
何度も見せている、こちらのスライドです。
「なんかようわからんけど、まあいいかな」「ええんちゃう」みたいな話ではなくて、リーダブルなテストコードを書いて、レビュアーから自信を持って「ヨシ!」してもらえるような、そういうテストコードを書いてほしいと思います。
今日の発表がみなさんの参考になれば幸いです。ご清聴ありがとうございました。よかったらTwitterとかフォローしてください。よろしくお願いします。