業務を通して学んだ自動テストとの向き合い方

5 min read読了の目安(約4600字

この記事は ソフトウェアテストアドベントカレンダー 11日目の記事です。

はじめに

良い機会だと思ったので、業務を通して学んだ自動テストとの向き合い方についてまとめます。

すでに経験がある人にとってはなんてことない内容ですが、これからソフトウェアエンジニアを志している、または業務経験が1,2年目の方に読んでいただけたら幸いです。

なぜテストコードを書くのか

さて、あなたはテストコード (以下テスト) を書いていますか?
テストというのは書いたコードが正しく動くのか検証するためのコードです。

初めてテストを書くことを知った時は、コードをコードで検証するなんて変な話だなと思いましたね。では、なぜテストを書くのでしょうか。

スクリーンショット 2020-11-09 13.54.15.png

紹介されている本によると、自動化されたテストのコストは手動テスト4回で損益分岐点を上回るというデータがあります。時間が経つほど手動テストとかかる時間に差が開いていくことが分かります。

このデータを見ると「テストコードなんか書かずに、毎回手動でテストをしろ!」とは言えませんよね。今ではこの価値を理解し、多くの企業が自動テストを導入しています。逆にテストが書かれていないプロジェクトは直感では「やばい」(効果的な品質保証の手法が実証されているにも限らず、何かしらの理由で導入できていない)という見方ができてしまいます。

数年の経験を得ているエンジニアであれば、自動テストの価値を良く理解し、適切に書けることが求められてきます。エンジニアの面接時にも「自動テストはどの程度書かれていますか?」と言った質問は良く出てきます。仮に自分が仕様について詳しくなく、間違った修正をした場合でもテストが落ちてくれるので、安心して変更できる状態にあるからです。

いつテストを書くか

なぜテストを書くのかについて説明しました。
では、どのようなタイミングでテストを書けば良いのでしょうか。

頻度が高いもの

  • 新機能の追加時
  • エラーを修正する時
  • 機能を改善・修正する時
    • 処理内容に不安を覚える時
    • 他の人が間違えそうな時

新機能を追加するときはテストもセットで書きます。このタイミングで仕様について一番詳しいのは自分ですが、チームメンバーにとってはそうではありません。メンバーが間違った修正をしないためにも、テストを書いて動作を担保しましょう。また、驚くことに機能実装後にブラウザで動作確認できたとしても、テストを書いてみると考慮漏れがあったことに気づくことが往々にしてあります。その度にテスト書いて良かった〜と思い直しています。

エラーを修正する時にもテストとセットで入れるとベターです。エラーを修正する際は慌てず、まずはエラーが再現するテストを書きます。その後に、そのテストが通るように修正を入れます。このようにテストを積んでいくと、同じエラーが発生し辛くなり、より堅牢なシステムになっていきます。

また、実装しているコードに既にテストがあったとしても、修正を加える際に不安を覚えたらテストコードを追加するのが好ましいです。よほどでない限り追加したテストの実行時間はコンマ数秒で済むことが大半です。不安な気持ちを抱えるよりはテストケースを追加して、問題がないことを確認しましょう。他の人がエラーを出さないためのガードレールにもなります。

頻度が低いもの

  • 大きなリファクタリングをする前
  • フレームワークのバージョンアップデート前

一定規模のリファクタリング前やフレームワークのバージョンアップデート前にも、修正後に機能が壊れていないか不安だなと思う箇所にはテストを追加します。思ってもいない箇所でエラーを発生させないように、事前にテストを充実させてスムーズにアップデートを遂行します。

Rails アップグレードガイドという資料では、第一にテストのカバレッジをあげることを書かれています。他フレームワークでも参考になるので一読しておくと良いでしょう。

何をテストするのか

では何をテストすれば良いのでしょうか。
ここでは多くのフレームワークで採用されている MVC + ビジネスロジック層を例に挙げます。

補足ですが、テストにおいて入力の数は山ほどあるので、全てのケースを網羅しようとすると途方に暮れてしまいます。今回紹介するのは一例なので、どの層に対してどの程度テストを書くのかはプロジェクト毎に共通認識を持つのが好ましいです。

Model

モデルに書くロジックは、コントローラーやビジネスロジックなど、いくつかの場所で再利用されます。モデルの処理を修正すると広範囲に影響が及ぶ可能性が高いため、テストを書く際の優先度として高いです。モデルにロジックを追加する場合は、テストも一緒に追加します。

View

View はユーザーへの表示部分であり、デザインや文言の変更頻度から Model や Controller に比べて変更される頻度が高いです。テストを書くに越したことはないのですが、テストの変更頻度も高くなるので優先度は低いです。しかし、サーバーサイドとフロントエンドで分業されている場合、フロントエンドにも重要なロジックが書かれることが多いため、積極的にテストを追加していきます。

Controller

コントローラーはリクエストを受け取って、レスポンスを返す層です。テストケースでは考えられるリクエストと想定するレスポンスが帰ることを担保するように書きます。重要なロジック部分は別のテストケースで担保されているとしても、例えばリクエストに対して想定したステータスコードが返るのかぐらいはテストするのが好ましいです。

ビジネスロジック層

ビジネス上重要なロジックが書かれた層なので、テストの優先度は高いです。
ここもモデルと同様に処理を追加したらテストを一緒に追加します。

Ex.) E2E

E2E (エンドツーエンド)と言い、自動でブラウザを立ち上げてユーザーが実際に動作させてるようなテストを書きます。
これはビジネス上、クリティカルな箇所に書きます。例えば以下の処理です。

  • 決済処理
  • 新規登録/ログイン
  • コンテンツの投稿

どうテストを書くか

Rails でよく使われるテストフレームワーク Rspec の例ですが、テストを書くときの流れを紹介します。以下はコントローラーのテスト例です。Qiita の記事編集画面へのリクエストを例に書いてみます。

describe 'GET /drafts/:id/edit' do # テスト対象を describe に書く
end

テストを書く際は、先にアウトラインを書いてしまいます。

describe 'GET /drafts/:id/edit' do
  describe '異常系' do
    context '存在しないリソースにアクセスした場合' do
      it '404が返ること' do
      end
    end
  end

  describe '正常系' do
    context '存在しているリソースにアクセスした場合' do
      context '自分の書いた記事の場合' do
        it '200が返ること' do
        end
      end

      context '自分以外が書いた記事の場合' do
        it '403が返ること' do
        end
      end
      ..
    end
  end
end

もし他にも「特定のパラメータがある時〜」などの仕様があれば、対応するテストを追加していきます。文章を書くときのコツと一緒で、はじめに全体像(目次)を定義してしまえば、あとは肉付けするだけなのでオススメです。テストしたいケースを先に洗い出すような進め方です。

テストに関する Tips

最後にテストに関する細かい知見をざっと共有していきます。

外部 API とのリクエストはモックを使う

外部と通信するテストを書く場合があります。この際、テストを実行するたびにリクエストを送っていないでしょうか? CI が回るたびに外部へリクエストが飛ぶようなテストは迷惑にもなり得るので控えましょう。テスト時はレスポンスを静的に保存しておき、テストするときは外部と通信をせずにモックを使うようにします。

E2E テスト用の data 属性を使う

E2E テストを書く時、デザインが変更されることで判断に使っている id や class 名が変わるのでメンテコストが高いという意見を目にすることがあります。その場合、テスト用の data 属性を作ってテスト時に使うと解決できます。

<div data-test="hoge">表示されて欲しいもの</div>

テスト時には上記の data 属性に対して内容を書いていきます。

テストを書かない選択肢も出てきている

Autify というAI が自動でE2Eテストを行ってくれるサービスも登場しています。すでに一定規模の QA チームが存在していて、Autify の導入・運用コストが今のコストより下回る場合は検討してみても良いでしょう。

テストを書くのが大変?

テストを書いていると、テストを書くのが大変になる場合があります。

その時は実装が複雑になっているケースが多いです。その場合、複雑な仕様に対してエッジなテストケースを頑張るよりは、シンプルにテストしやすい設計・実装にできないか?と考えてみると良いでしょう。テストが書きやすい設計は良い設計とみなされることが多いです。時には「仕様が複雑なので、機能追加はできない。その前にリファクタリング (とテスト追加) する時間をくれ」と PM と対決する場合もあります。

最後に

テストに関して業務を通して学んだことを書きました。
これからソフトウェアエンジニアとして活躍する人に参考になれば幸いです。