自動テスト ruby

AAA を意識して単体テストを書く

自動テスト ruby

 こんにちは。EC事業部CREチームの rotelstift といいます。

 私は主にカラーミーショップへのお客様からの技術的なお問い合わせに答え、希望された機能追加や発覚したバグの修正などを担当しています。

 プログラマという職業に就いたのはペパボが1社目で35歳からというスロースターターですが、ペパボという会社はいるだけで成長できる会社だということを実感しています。

 今回は、職業プログラマになる前はほとんど書いたことの無かったテストコードについて学んだこととして、読みやすさを劇的に改善する AAA (arrange, act, assert) と呼ばれる指針の解説をしたいと思います。

 なお、この記事では RSpec を題材にした単体テストを扱います。

読みにくいテストコードの例

 まず最初に、以下のテストコードを見てみましょう。なんか読みにくいと感じます。これが読みにくいのは AAA の各要素がひとまとめになっていないテストコードだからだと思います。

describe "ものを売ること" do
  context "正常に注文が計算される" do
    let!(:item) { create(:item, id: 1, price: 100, stock: 10) }
    let!(:order_detail) { create(:order_detail, item: item, num: 3, sub_total: 0) }
    let!(:order) { create(:order, details: [order_detail], total_amount: 0) }

    it "order_detail と item が正しく更新される" do
      pre_stock = item.stock
      order.sold() # sold() は order_detail.sub_total と item.stock と 自身の total_amount を更新します
      expect(order_detail.sub_total).to eq (item.price * order_detail.num)
      expect(item.stock + order_detail.num).to eq pre_stock
    end

    it "合計金額が正しい" do
      order.sold()
      expect(order.total_amount).to eq (order.details.sum(&:sub_total))
    end
  end
end

AAA (arrange, act, assert) という指針

 では、AAA とは何なのでしょう?

 それは、テストコードを読みやすくするための指針です。AAA は arrange, act, assert の略語で、arrange は『準備』、act は『実行』、assertは『検証』を表します。

 準備はテスト用のデータの作成、実行はテストしたい関数やメソッドを実行すること、検証は実行の結果得られた値や変化が期待したものであるかどうかを確認することになります。

 この3つができるだけそれぞれひとまとまりになって書かれていると、テストコードが読みやすくなると思うのです。

 このことを意識しながら、上のテストコードを読んでみましょう。arrange であるはずの let や pre_stock の定義、 act であるはずの order.sold()、assert であるはずの expect があちこちに散らばって書かれていることがわかります。

 そして AAA の各要素をできるだけひとまとめにすることを意識して書いたテストコードが以下になります。上記のものと比べて、だいぶ分かりやすくなったと感じます。

describe "ものを売ること" do
  context "小計、在庫数、合計金額が正しい" do
    # arrange
    let!(:item) { create(:item, id: 1, price: 100, stock: 10) }
    let!(:order_details) { create(:order_detail, item: item, num: 3, sub_total: 0) }
    let!(:order) { create(:sales, details: [order_details], total_amount: 0) }
    let!(:pre_stock) { item.stock }

    before do
      # act
      order.sold() # sold() は order_detail.sub_total と item.stock と 自身の total_amount を更新します
    end

    it "小計が正しく更新される" do
      # assert
      expect(order_detail.sub_total).to eq (item.price * order_detail.num)
    end

    it "在庫数が正しく更新される" do
      # assert
      expect(item.stock + order_detail.num).to eq pre_stock
    end

    it "合計金額が正しい" do
      # assert
      expect(order.total_amount).to eq order.details.sum(&:sub_total)
    end
  end
end

 まず、arrange としてテストに必要なデータを作る箇所をひとまとめにします。また、assert に必要な pre_stock もここで定義します。この pre_stock を用意する理由は、act の前後で item.stock の値が変わってしまうからです。

 そして、act である order.sold() は before に入れます。こうすることで各 it の度に order.sold() が実行されることが1回の記述で済むようになります。

 it ブロックの中は assert のみします。また、一つの it には一つの assert を書くといいと思います。一つの it の中に複数の assert を書いてしまうと、もしテストが通らなかったときの原因を探すのが難しくなるからです。(ただし、実行速度などを考慮してまとめる場合もあります)

まとめ

 テストは arrange, act, assert の順番に実行することがとても大切です。AAA の各要素をなるべくひとまとめにしたテストコードを書くのは、この実行順を守りやすくするためです。

 AAA を意識しないで漫然とテストコードを書いていると arrange, act, assert をどこで行っているのか分かりづらくなってしまいます。そしてそれをそのまま放置してしまうと、arrange, act, assert の順番が守られていないテストコードになってしまう可能性があります。例えば、arrange によるデータ作成が終わっていないのに act を行ってしまうことなどです。

 AAA を意識し読みやすいテストコードを書くことは、可読性を高めるだけではなく、こうした危険性を減らす効果もあるのです。

 テストコードは大規模プロダクトになるほどその効力を発揮すると思います。私は職業プログラマになる前は趣味で小規模なプログラムを書いていましたが、テストコードの意義も書き方もわからないまま過ごしていました。

 今、カラーミーショップという大規模プロダクトでテストコードの効力を実感することができたのは、職業プログラマになれて良かったと思うことの大きなひとつです。

 この記事がどこか少しでも読者の方の参考になりましたら、これに勝る幸いはありません。

 ご拝読、ありがとうございました。