
TotT: テストはDRYにするべき?
こんにちは、kubopです。
Googleにはトイレテスト(TotT)という文化があるようで、テストに関するTipsをトイレに貼り出し、テストに関する知識を全社で共有しているらしいです。
昨今はリモート勤務が広まり、TotTの実施は難しく、SlackやBotを用いてもなかなか浸透するかどうか…
そこで、noteに書きつつ自分が勉強するために、少しずつ読んで内容や、所感を書いてみようと思います。
※ 翻訳・解釈の間違いなどあるかもしれません。
その場合はこっそり教えてください。
もっとテストを書いてほしいのです。そう、あなたです。テストは、コードをリファクタリングするときや他の開発者が機能を追加するときに、 あなたを守るセーフティネットであることはもうおわかりでしょう。また、テストがコードの設計に役立つこともご存知でしょう。
…
私たちは、この秘密兵器(this secret weapon)を世界中の人々と共有し、私たちのテストへの情熱を広めるとともに、あなた自身やあなたの会社の他の人たちに、この重要なトリックやテクニックを楽しく簡単に学んでもらうことにしました。
このブログで定期的にエピソードを紹介し、PDFを提供しますので、プリントアウトしてご自分のバスルーム、廊下、キッチン、月面基地、秘密の地下要塞、億万長者の創業者のプリウスなど、どこにでも貼り付けてください。
Testing on the Toilet: Tests Too DRY? Make Them DAMP!
このテストは DRY 原則 ("Don't Repeat Yourself") に従っています。
これは、ヘルパーメソッドを抽出したりループを使ったりして、重複するコードではなく再利用を促すベストプラクティスです。
しかし、これは良いテストなのでしょうか?
def setUp(self):
self.users = [User('alice'), User('bob')] # この部分はテスト間で再利用可能。
self.forum = Forum()
def testCanRegisterMultipleUsers(self):
self._RegisterAllUsers()
for user in self.users: # 全てのユーザーがregisteredになっているか確認。
self.assertTrue(self.forum.HasRegisteredUser(user))
def _RegisterAllUsers(self): # この部分はテスト間で再利用可能。
for user in self.users:
self.forum.Register(user)
テスト本体は簡潔ですが…
このテストを理解するために、例えば self.users の setUp() から _RegisterAllUsers() までの流れを追うなど、読みながら計算しなければいけません…。
テストにはテストがないので、コードの重複が増えることを犠牲にしても、人間が手作業で正しさを簡単に確認出来なければなりません。
つまり、DRY 原則はプロダクションコードではベストプラクティスであっても、 ユニットテストではあまり適さないことが多いということです。
テストでは、一意性よりも可読性を重視する DAMP原則 (Descriptive and Meaningful Phrases) を使用することができます。
この原則を適用すると、コードが冗長になる可能性がありますが、
テストがより明確になります。
def setUp(self):
self.forum = Forum()
def testCanRegisterMultipleUsers(self):
# SetUpではなく、ユーザーを直に作成する。
user1 = User('alice')
user2 = User('bob')
# ループを利用せず、ユーザーを登録する。
self.forum.Register(user1)
self.forum.Register(user2)
# ループを利用せず、ユーザー登録を確認する。
self.assertTrue(self.forum.HasRegisteredUser(user1))
self.assertTrue(self.forum.HasRegisteredUser(user2))
DRY の原則はテストでも有効であることに注意しましょう。
たとえば、値オブジェクトを作成する際にヘルパー関数を使用すれば、 テスト本体から冗長な部分を取り除くことができ、明快さが増します。
理想を言えば、テストコードは読みやすくかつ一意であるべきですが、時にはトレードオフの関係にあることもあります。
ユニットテストを書くときにDRYとDAMPのどちらかを選択しなければならない場合は、DAMPのほうに重きを置いてください。
🤔
コードのDRY原則は、テストには必ずしも有効ではない、という話。
脳に負荷をかけるテストコードではなく、読んで正しいと直感的に理解できるようなコードが必要。
というか、そうじゃないとリファクタするときにプロダクトコードと同じか、それ以上に難解である場合に、新しく入った人や、開発スキルに差があるとバグを出しやすいと思う。
軽くRubyで意訳
def setup
user_names = ['Bob', 'Alice']
@users = user_names.map { |n| User.create(name: n) }
@forum = Forum.new
end
def test_can_register_multiple_users
register_all_users
@users.all? do |user|
expect(@forum.registered?(user)).to eq(true)
end
end
def register_all_users
@users.each do |user|
@forum.register(user)
end
end
def setup
@forum = Forum.new
end
def test_can_register_multiple_users
user_1 = User.create(name: 'Bob')
user_2 = User.create(name: 'Alice')
@forum.register(user_1)
@forum.register(user_2)
expect(@forum.registered?(user_1)).to eq(true)
expect(@forum.registered?(user_2)).to eq(true)
end
Licensed under a Creative Commons
Attribution–ShareAlike 4.0 License