インテグレーションテストを書き始めた話

Nightmare

こんにちは。Tokyo Otaku Mode (以下TOM) エンジニアのシンバです。今期のアニメは「甘々と稲妻」が一番の楽しみになっています。特に飯田小鳥を演じる早見沙織さんの声を聴くのが楽しみです。早見さんは9月に公開される映画「聲の形」のヒロイン役も担当されるそうで、そちらも非常に楽しみにしています。ヒロインのビジュアルがすごくかわいい。

さて、TOMは現在、ECサイトやニュースサイトなどを http://otakumode.com/ で展開しています。このシステムの開発にはNode.jsが使用されており、Mochaを使用してモデル層などのテストを書いています。ただ、このテストは関数一つ一つの入出力をチェックするために書かれているので、例えば複数の関数を組み合わせた際の結果や、色々処理した結果、ページ上に何が表示されるのかといったテストは書いていませんでした。
そこでインテグレーションテストも書いて、コードによる動作保証をより網羅的にしようと取り組み始めました。

インテグレーションテストのメリット

インテグレーションテストには以下のようなメリットがあると考えています。

サービスの利用者が実際に見ることになる情報をチェックできる

ヘッドレスブラウザを利用してJavaScriptやCSSを解釈しつつテストを実行することで、正しい文言がページ上に表示されるか、などといったことを確認することができます。

手作業による動作確認が軽減できる

TOMでは、新しい機能の追加やバグ修正などを行ったあと、GitHub上でPull requestを作り他の開発者に変更内容をレビューしてもらう文化が根付いています。レビューを行う人はソースコードの確認だけでなく、期待通りに動作しているかを手作業で確認しています。修正箇所によってはこの動作確認に結構な時間を使ってしまっているため、機械的に確認できるところはテストコードでカバーすべきです。

デグレが起こりにくくなる

インテグレーションテストに限らない話ですが、十分にテストを書くことで、思わぬ箇所が動かなくなるという状況を減らすことができます。「思わぬ箇所」は動作確認漏れに繋がりやすいため、テストコードを書いて常にチェックされるようにすべきです。

システムの仕様が理解しやすくなる

テストコードを読むことで、その機能を実行するために必要なデータや、何を操作するとどうなるのかといった処理の流れを把握することができます。
TOMでは「いつの間にか新しい機能が追加されていた」といった怖い状況を無くすため、「仕様共有会」というミーティングを週一で行い、最近追加された機能や古の機能の全容を開発者全員に周知させるように努めています。インテグレーションテストのコードは、この会を補助する役割もあると考えています。

「Nightmare」の活用

TOMでは、NightmareとMochaを組み合わせてインテグレーションテストを書いています。NightmareはWebブラウザ上での操作を自動化するためのライブラリで、npmのパッケージになっているため、テストを実行する環境は npm install を実行するだけで整えることができます。
Mochaと組み合わせたテストコードは以下のようになります。これはサインアップ機能のテストから一部抜粋したものになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
describe "Signupページ", ->
context "正しいSignup情報を入力してSignupボタンを押したとき", ->
it "Signupできる", (done)->
Nightmare()
.goto("http://localhost/signup")
.type("input[name='email']", "new@otakumode.com")
.type("input[name='password']", "newCustomer")
.click(".c-btn--login")
.wait(".p-shop-account-menu")
.evaluate ->
document.querySelector(".p-shop-account-menu").innerText
.run (err, text)->
should.not.exists err
text.should.eql "Hello, new!\nAccount Menu\n"
done()
  • サインアップページにアクセスして
  • メールアドレスを入力して
  • パスワードを入力して
  • ログインをボタンを押して
  • 次のページでアカウントメニューが表示されるまで待って
  • 表示されたらメニューのテキストを取得して
  • 取得した文言が正しいかどうかをチェックする

という流れをとてもわかりやすく書くことができます。
もしテストがうまく実行できなかったときは screenshot() というメソッドを呼び出してテスト実行中のスクリーンショットを撮ったり、Nightmare() コンストラクタに show オプションを渡してWebブラウザを起動し、実際の挙動を目視で確認したりしています。

`show` オプションを渡して目視確認

いくつかテストを書いていると同じ処理を何度も実行したいときがあります。そういったときはそのアクションを1つの関数にまとめ、再利用するようにしています。例えばサインアップのアクションは以下のように定義しています。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module.exports = (nightmare, options, callback)->
if typeof options is "function"
callback = options
options = {}
nightmare = nightmare or Nightmare()
email = options.email or "hogehoge@example.com"
password = options.password or "hellohello"
callback null,
nightmare
.goto("http://localhost/signup")
.type("input[name='email']", email)
.type("input[name='password']", password)
.click(".c-btn--login")
.wait(".p-shop-account-menu")

アクション関数では、第一引数にNightmareのインスタンスを渡すようにしています。これはアクション関数が呼ばれる前にNightmareインスタンスに登録された操作 (goto(), click() など) を保持して、アクション関数を呼び出したあとそれらをまとめて実行するためになります。Nightmareの内部では、goto()click() などの操作は _queue という配列に格納され、run() メソッドなどが実行されたとき、この配列の中の操作を全て実行する仕組みになっています。

今抱えている問題

上記のような環境でテストを書いてきましたが、テストを書くにあたっていくつか問題が浮上しているので、そちらもご紹介します。。

テストの実行が遅い問題

比較的複雑な機能をテストすると、長いときは1つのテストに30秒以上かかることがあります。まだシステムの一部分のテストしか書けていないので全てのテストを実行し終えるまで何とか待てますが、今後テストの数が増えていくと…というのが現状になります。

Nightmareに限らず、ヘッドレスブラウザを操作するライブラリの処理は比較的重めなので、極力使わないようにする、といったことから対応を始めています。例えば上のほうでご紹介したサインアップのアクション関数はなるべく使わず、登録ユーザが必要な場合は予めユーザデータを生成して対応する、といった修正を行っていきます。

いつ書くか問題

インテグレーションテストによって機能の安定性が担保されるのは非常にありがたいですが、テストを書くのにもある程度時間がかかるため、機能追加のスピードが落ちる…という問題です。

この問題の一つの解決策として、レビューを行う人がテストを書くという案がチーム内で出ています。テストを書きつつ手作業で動作確認をするフローを続けることで、最終的に手作業による動作確認の時間をテストを書く作業に割り当てられるのでは、という考えです。また、今までの手作業による動作確認では、人によって確認する箇所が異なる問題もあったため、レビューを行う人がテストを書くことでどの辺りを確認したのかが明確になるといったメリットも考えられます。

他の解決策として、「20%プロジェクト」で一気にテストを書くという手も考えられます。「20%プロジェクト」は毎週金曜日に実施している活動で、開発チームのメンバーが各々感じている様々な問題点を共有し、改善していく活動になります。金曜日は月曜日から木曜日までの通常業務とは(ほぼ)切り離されているため、システムや会社の改善に繋がるものであれば何でも行っても良い日になっています。この日にまとめてテストを書くという手もアリかなと思います。実際に、テストの修正などはこの日に行うことが多いです。

たまにテストがコケる問題

これが個人的に一番心をエグッてくる問題になります…。実行のタイミングによってたまにテストが失敗したり、ひどいときはテストの実行が終わらない(反応が無くなる)ことがあったりします。これは遭遇したとき適宜修正するようにしています (が、しばらくするとまたコケることも…)。安定を求めて模索しています。

まとめ

TOMではこのようにインテグレーションテストを書き始めました。まだ道半ばですが、継続的に取り組みたいと思います。

Tokyo Otaku Modeでは一緒にプロダクトを開発するメンバーを募集しています。現在はフロントエンドエンジニアとサーバサイドエンジニアを募集しています。ご興味ありましたら、ぜひ遊びに来てください。