Rails: テスティングアンチパターン –前編(翻訳)

次記事: Rails: テスティングアンチパターン –後編(翻訳) 概要 原著者の許諾を得て翻訳・公開いたします。 英語記事: RubyOnRails testing antipatterns — part 1/2 原文公開日: 2018/01/03 著者: Błażej Kosmowski 訳文見出しには番号とアンカーを追加しました。 Rails: テスティングアンチパターン –前編(翻訳) テストスイートを準備していると、つい誘惑に負けて近道を選んでしまい、テストの可読性や理解しやすさはもちろん、実装を先に進めるための柔軟性までがっくり下がってしまうことがあったりします。本記事では、よくある手法の中から、テストスイートを健全に保つうえで避けるべきものをリストアップしてみたいと思います。本文中の例の多くは、問題に焦点を当てるためにかなり簡素化してありますのでご了承ください。 アンチパターン01: privateメソッドをテストする privateメソッドのテストがよくないのは、privateメソッドを自然に使う状況は実際には起きないはずだからです。代わりにpublicインターフェイスをテストすべきです。privateメソッドのテストが書かれているということは、実装の後でテストを書いた(これ自体アンチパターンです)か、publicインターフェイスがどこかの時点でprivateインターフェイスに変わってしまった可能性があります。privateメソッドをテストしてしまうと、実装とテストの結合が密になってしまい、コードがリファクタリングを嫌がるようになってしまいます。 privateメソッドのテストに直接対抗するには、それらを実際に使うpublicインターフェイスの方を常にテストすべきです(つまりprivateメソッドのテストは間接的に行われます)。simplecovなどのテストカバレッジツールを使うと、テストされていない(つまり今後使われない可能性のある)privateメソッドを手軽に検出できます。TDD(テスト駆動開発)も、publicインターフェイスのみテストを無理なく推進する手法のひとつです。 (Gist) # 残念なspec # ========== describe CloseOrder do # テスト対象オブジェクトのdouble(身代わり)を半端に使って # #callをテストすると不自然になり、コードが重複している感じがする describe ‘#call’ do it do order = create(:order) service = CloseOrder.new(order) allow(service).to receive(:send_notification) service.call expect(service).to have_received(:send_notification) end end describe ‘#send_notification’ do it ‘注文のクローズを顧客に通知する’ do order = create(:order, customer_email: ‘tony@stark.com’) service = CloseOrder.new(order) expect { service.send(:send_notification) # We are forced to use #send to test private method }.to change { ActionMailer::Base.deliveries.count }.by(1) notification = ActionMailer::Base.deliveries.last expect(notification).to have_attributes(subject: ‘Order closed!’, recipients: [‘tony@stark.com’]) end end end # よいspec # ========== describe CloseOrder do describe ‘#call’ do it ‘注文のクローズを顧客に通知する’ do order = create(:order, customer_email: ‘tony@stark.com’) service = CloseOrder.new(order) expect { service.call }.to change { ActionMailer::Base.deliveries.count }.by(1) notification = ActionMailer::Base.deliveries.last expect(notification).to have_attributes(subject: ‘Order closed!’, recipients: [‘tony@stark.com’]) end end end # 実装側 # ============== class CloseOrder def initialize(order) @order = order end def call send_notification end private def send_notification OrderMailer.order_closed_notification(@order).deliver_now end end アンチパターン02: フレームワークのメソッドをスタブ化する フレームワークのメソッドをスタブ化すると、多くの場合実装の自由がほとんど失われてしまうので、よくありません。簡単のため、以下のスニペットで考えてみましょう。 allow(User).to receive(:where).with(name: “Tony”) { [instance_double(User, name: “Tony”)] } フレームワーク(ここではActiveRecordのクエリインターフェイス)をスタブ化すると、要件を実装する方法が1種類に縛られてしまい、リファクタリングの余地がほぼなくなってしまいます。 フレームワークの機能をスタブ化するのではなく、ファクトリーなどの手法を用いて必要なデータをフレームワークに与えるだけにとどめましょう。以下の例では、シンプルなcreate(:user, name: “Tony”)で事足ります(Gist)。 … Continue reading Rails: テスティングアンチパターン –前編(翻訳)