業務を通して学んだ自動テストとの向き合い方
この記事は ソフトウェアテストアドベントカレンダー 11日目の記事です。
はじめに
良い機会だと思ったので、業務を通して学んだ自動テストとの向き合い方についてまとめます。
すでに経験がある人にとってはなんてことない内容ですが、これからソフトウェアエンジニアを志している、または業務経験が1,2年目の方に読んでいただけたら幸いです。
なぜテストコードを書くのか
さて、あなたはテストコード (以下テスト) を書いていますか?
テストというのは書いたコードが正しく動くのか検証するためのコードです。
初めてテストを書くことを知った時は、コードをコードで検証するなんて変な話だなと思いましたね。では、なぜテストを書くのでしょうか。
紹介されている本によると、自動化されたテストのコストは手動テスト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
context '存在しているリソースにアクセスした場合' do
context '自分の書いた記事の場合' do
it '200が返ること' do
end
end
context '自分以外が書いた記事の場合' do
it '403が返ること' do
end
end
...
end
context '存在しないリソースにアクセスした場合' do
it '404が返ること' do
end
end
end
もし他にも「特定のパラメータがある時〜」などの仕様があれば、対応するテストを追加していきます。
文章を書くときのコツと一緒で、はじめに全体像(目次)を定義してしまえば、あとは肉付けするだけなのでオススメです。テストしたいケースを先に洗い出すような進め方です。
テストに関する Tips
最後にテストに関する細かい知見をざっと共有していきます。
外部 API とのリクエストはモックを使う
外部と通信するテストを書く場合があります。この際、テストを実行するたびにリクエストを送っていないでしょうか? CI が回るたびに外部へリクエストが飛ぶテストは控えましょう。
テスト時はレスポンスを静的に保存しておき、テストするときは外部と通信をせずにモックをします。
E2E テスト用の data 属性を使う
E2E テストを書く時、デザインが変更されることで判断に使っている id や class 名が変わるのでメンテコストが高いという意見を目にすることがあります。その場合、テスト用の data 属性を作ってテスト時に使うと解決できます。
<div data-test="hoge">表示されて欲しいもの</div>
テスト時には上記の data 属性に対して内容を書いていきます。
E2Eテストの自動化
Autify というAI が自動でE2Eテストを行ってくれるサービスも登場しています。すでに一定規模の QA チームが存在していて、Autify の導入・運用コストが今のコストより下回る場合は検討してみても良いでしょう。
テストを書くのが大変?
テストを書いていると、テストを書くのが大変になる場合があります。
その時は実装が複雑になっているケースが多いです。その場合、複雑な仕様に対してエッジなテストケースを頑張るよりは、シンプルにテストしやすい設計・実装にできないか?と考えてみると良いでしょう。テストが書きやすい設計は良い設計とみなされることが多いです。時には「仕様が複雑なので、機能追加はできない。その前にリファクタリング (とテスト追加) する時間をくれ」と PM と交渉する場合もあります。
最後に
テストに関して業務を通して学んだことを書きました。
これからソフトウェアエンジニアとして活躍する人に参考になれば幸いです。
Discussion