単体テストのテストメソッドにわかりやすい名前をつけて品質向上させよう

はじめに
こんにちは。KINTO FACTORYでバックエンドエンジニアをしている上原(@penpen_77777)です。
普段はGoやRustを使って開発をしており、エディタはVim/NeoVimを愛用しています。
今回は、テストコードの品質向上について、特にテストメソッド名の付け方にフォーカスしてお話しします。
今回の内容について以下の書籍を参考にさせていただきました。
テストコードに関わる「辛さ」
皆さんは日々テストコードを書いていると思いますが、こんな経験はありませんか?
- テストコードはあるが中身が理解できていないため、テストの結果が信用できない、十分にテストできている自信がない
- 他の人が書いたテストコードをレビューしていて、内容がよくわからないけれどなんとなくApproveしてしまった
- どういうテストをしているかよくわからないけどCIが落ちてしまうとPRを出せないので、テストがPASSするように雰囲気でテストコードを直した
このような課題を解決するために、まずは テストメソッド名を改善してみませんか? ということを提案したいと考えています。
なぜテストメソッド名を改善する必要があるのか?
テストコードの品質についてなぜテストメソッド名を改善する必要があるのかについて、具体例を見ながら考えていきましょう。
コード例: ECサイトシステム
Go言語で記述されたECサイトシステムで、以下のような商品を購入するメソッドを実装しました。
商品の在庫があれば注文することができ、なければエラーを返して注文できません。
// ECサイトシステム構造体
type ECSystem struct {
// 商品ごとの在庫数
stock map[string]int
}
// 商品を購入するメソッド
func (c *ECSystem) Order(itemID string) error {
// 商品がない場合はエラーを返す
if c.stock[itemID] <= 0 {
return ErrOutOfStock
}
// 商品があれば在庫数を減らす
c.stock[itemID]--
// 正常終了
return nil
}
改善前のテストメソッド名
このプロダクションコードに対して単体テストを行いたいため、 以下のようなテストコードを作成したとします。
func Test_正常系_在庫数が0より大きい場合(t *testing.T) { }
func Test_異常系_在庫切れエラー(t *testing.T) { }
これらの名前を見ると、以下のような疑問が浮かびます:
🤔 「在庫数が0より大きい場合、どういう結果が出てくるのが正しいのだろう?」
🤔 「在庫切れエラーとは、どういう条件で発生するのだろう?」
このパターンのテストメソッド名は意外と見かけることが多いと筆者は感じています。
どうテストしていくか、テストメソッド内の中身をどう書いていくか考えるのに気が取られて、テストメソッド名の検討に時間が取れなかった結果が現れていると感じています。
今回はサンプルコードでメソッド内の実装も薄いのでテストメソッド名からどういうテストか容易に推測できますが、
実際のプロダクトコードだとテストメソッドの実装量も数も多くなるため、どういうテストか理解できなくなってしまいます。
第一段階の改善
改善前のテストメソッド名がわかりづらいと感じる1つの要因は、名前から「事前条件」もしくは「想定結果」のどちらか一方が欠落しているためです。
func Test_正常系_在庫数が0より大きい場合(t *testing.T) { } // 「想定結果」の欠落
func Test_異常系_在庫切れエラー(t *testing.T) { } // 「事前条件」の欠落
逆に言えば、テストメソッド名に「事前条件」と「想定結果」を両方書けばわかりやすくなるということなので、
{ テスト対象メソッド }_{ 事前条件 }_{ 想定する結果 }
の形式に統一してみましょう。
func Test_Order_在庫数が0より大きい場合_正常終了(t *testing.T) { }
func Test_Order_在庫数が0の場合_在庫切れエラー(t *testing.T) { }
少し良くなりましたが、まだ改善の余地があります。
問題点:How(どうテストするか)が含まれている
現在のテストメソッド名にはHow(どうテストするか)が書かれているため、読みにくく感じます。
🤔 「Orderが正常終了ってことは...ユーザが商品を購入できるってことか...」
🤔 「Orderが在庫切れエラーを返すってことは...ユーザが商品を購入できないってことか...」
つまり、読んだときに脳内でWhat(どういう振る舞いをテストしているか)へ言い換えることで、脳に負荷がかかり、読みづらく感じます。
この命名規則について「単体テストの考え方/使い方」でも以下のように述べられています。
私はこの十年近く様々な命名規則を試してきたのですが、その中でも、非常に有名で、おそら
く、もっとも役に立たないのが次のような命名規則です :
{テスト対象メソッド }{事前条件 }{想定する結果 }
厳密な命名規則に従うのではなく、それぞれのテストに応じて適切な名前を考えるべきだという主張なのですが、 個人的には「もっとも役立たない」というのは言い過ぎかなと感じています。
命名に迷ったら規則に従うくらいの温度感で運用することで、チームメンバー間の命名のレベルを揃えられるメリットを考慮すれば非常に有用だと考えています。
最終的な改善案:Whatを書く
テストメソッド名には「How(どうテストするか)」ではなく「What(振る舞い)」を書きましょう。
「How」の部分(正常終了の確認、エラー発生の確認)はテストメソッド内に実装します。
func Test_商品の在庫がある場合_ユーザは商品を購入できる(t *testing.T) {
// ここで正常終了しているかをみる
}
func Test_商品の在庫がない場合_ユーザは商品を購入できない(t *testing.T) {
// ここで在庫エラーが発生しているかをみる
}
さらには、非エンジニア(ドメイン・エキスパート)にも伝わるように端的に書くことが重要だと「単体テストの考え方/使い方」では述べられています。
非エンジニアにも分かる名前にする理由
「テストコードは開発者だけが見るものなので、非エンジニアに分かる名前にしなくても良いのでは?」という意見もあるでしょう。
単体テストの考え方/使い方では、この意見について以下のように述べられています。
なぜなら、暗号めいた名前は開発者なのかどうかにかかわらず誰にでも認知的負荷をかけるもの
だからです。このような名前はテスト・ケースが実際に何を検証するのか、そして、このテ
スト・ケースの内容がどのようなビジネス要求と関係しているのかを把握するのに開発者に
対して余計な認知的負荷をかけることになります。
開発者だけがわかるコードを書いても結局は
- テストで何を検証し、どのような要件と対応しているか把握しにくくなる
- コードのメンテナンスが困難になる
- 書いた本人でも数ヶ月後には読めなくなる
- 他のエンジニアがコードレビューする際に理解しづらい
となってしまい、最初に記述したテストコードに関わる「辛さ」
- テストコードはあるが中身が理解できていないため、テストの結果が信用できない、十分にテストできている自信がない
- 他の人が書いたテストコードをレビューしていて、内容がよくわからないけれどなんとなくApproveしてしまった
- どういうテストをしているかよくわからないけどCIが落ちてしまうとPRを出せないので、テストがPASSするように雰囲気でテストコードを直した
につながるわけです。
わかりやすいテストメソッド名をつけるのは難しい
正直に言うと、わかりやすいテストメソッド名をつけるのって、めちゃくちゃ難しいです。
なぜでしょうか?それは、一度キーボードから手を離して、モニターから目を上げて、こう自分に問いかける必要があるからです。
「我々は一体、何を作ろうとしているんだ?」
コードの海に溺れそうになりながら、ふと立ち止まって考えてみてください。目の前のメソッドは、誰のどんな悩みを解決しようとしているのでしょうか?
- そのオブジェクトは、システムの中でどんな役割を担っているのか?
- 今やっているタスクは、本当にユーザーを幸せにするのか?
- この業務領域で使われているドメイン知識を、我々ははちゃんと知っているのか?
テストメソッド名を真剣に考えるって、実はこういうことなんです。
そして気づくんです。わかりやすいテストメソッド名を追求していくと、不思議なことが起こることに。
テストコードが読みやすくなる。プロダクションコードも自然と整理される。チームでの会話も、なんだかスムーズになっていく。
たかがテストメソッド名、されどテストメソッド名。
小さな改善が、開発チーム全体の幸せにつながっていく。そんな体験を、ぜひあなたにも味わってもらいたいのです。
まとめ
今回はテストメソッド名の話を書かせていただきました。
- どのようなテストをしているか把握しやすくするためにテストメソッド名をわかりやすくしよう
- 非エンジニアでも理解できるくらいまで書いてみよう
- テストメソッドの名前を改善するだけでテストコードの品質が向上できるのであれば、コストパフォーマンスが良い改善方法だと思いませんか?
この記事でテストメソッド名の重要性を感じ取っていただければ幸いです。
今回参考にさせていただいた書籍「単体テストの考え方/使い方」は全400ページくらいありますが、テストメソッド名に割かれている説明は6ページくらいで決して長くはありません。
ですが、個人的にはこの書籍で一番重要で学びになる箇所ではと感じています。
単体テストで悩みがあれば、ぜひ「単体テストの考え方/使い方」を読んでみてください。
きっとあなたの悩みに対する答えを与えてくれるはずです!
参考文献
関連記事 | Related Posts
We are hiring!
【QAエンジニア】QAG/東京・大阪・福岡
QAグループについて QAグループでは、自社サービスである『KINTO』サービスサイトをはじめ、提供する各種サービスにおいて、リリース前の品質保証、およびサービス品質の向上に向けたQA業務を行なっております。QAグループはまだ成⾧途中の組織ですが、テスト管理ツールの導入や自動化の一部導入など、QAプロセスの最適化に向けて、積極的な取り組みを行っています。
【フロントエンドエンジニア(リードクラス)】プロジェクト推進G/東京
配属グループについて▶新サービス開発部 プロジェクト推進グループ 中古車サブスク開発チームTOYOTAのクルマのサブスクリプションサービスである『 KINTO ONE 中古車 』のWebサイトの開発、運用を中心に、その他サービスの開発、運用も行っています。“とりあえずやってみる”から始まる開発文化。