こんにちは!R&D事業部(以下R&D)の西久保です。
前回、日本語研修合宿レポートを書いてから久々の執筆となります。皆さん、よろしくお願いいたします。

今回のテーマは、R&Dの開発における「テスト」についてです。

チーム開発におけるテスト自動化とその課題について_メイン画像

以前、本ブログでテストについて紹介させていただいたことがありますが、こちらの記事はもうご覧になりましたか?
新米エンジニアに聞いちゃいます! テスト編

この記事は2015年のものになりますが、R&Dにおけるテストの考え方や開発者の持つべき意識が細かく書かれております。
今回はこの記事から少し進んで、R&Dが実際に行っているテスト手法と、最適化する中で出てきた課題と解決策について話したいと思います。

ここからの内容はプログラミング経験者向けの話です。プログラミングを学んでいる方、さらに技術を極めたいとお考えの方は、一緒にテストの役割やR&Dの取り組みを見ていきましょう。

テストのフェーズについて

システム開発におけるテストには、大きく分けて以下の4つのフェーズがあります。

1. 単体テスト
2. 結合テスト
3. シナリオテスト
4. 受入テスト

単体テストとは、プログラムを構成する小さな単位に対するテストであり、通常1クラスに存在する1メソッドに対して行うテストです。
結合テストはモデル、コントローラ、ビューといった、それぞれのモジュールを合わせた1つの機能に対するテストを指し、シナリオテストは、業務フローを考慮した機能間テストになります。
受入テストは納品後に発注者が行うテストです。
このように、様々なフェーズでテストを行います。

これまでのテストの問題点と解決策

R&Dでは、単体テスト、結合テスト、シナリオテストについては各々のローカルでブラウザ等を用いながら行い、また誰がどのようにテストを行ったか記録を残すようにしてきました。
しかしながら、そのような目視によるテストと記録を行っていると、以下のような問題が生じてしまいます。

1. リファクタリングの際の回帰テストのコストが大きい
2. 機能改修の際、テスト記録のメンテナンスが困難
3. 記録されたテスト結果の信頼性が、開発者によって異なる

これらの問題についてR&Dでは、テストをコード化・自動化することによって解決されるはずだと考えました。
R&Dでは主にPHPで開発を行っていますので、以前より関心を持っていたテスト自動化ツール「PHPUnit」を導入し、まずは単体テストから自動化を進めることにしました。

また、開発の現場ではPHPフレームワーク「CakePHP」を使用することが多いのですが、CakePHPはPHPUnitを公式にサポートしているため、敷居が低かったことも自動化に踏み込んだ理由の一つです。

下記の画像は単体テストの自動化の例になります。

単体テスト実装例

この単体テストの自動化を行ったことにより、回帰テストがコマンド一つで終わるのでリファクタリング時のコストが下がりました。また、テスト内容はコードとして確認できるので、テストの質も保証され、機能修正が入った際のテスト記録のメンテナンスもしやすくなりました。

しかしながら、テスト自動化を進める中で様々な課題が見つかり、導入にはかなりのコストがかかっています。以下にそれらの課題と、実践した解決策を紹介します。

テスト自動化にあたり直面した課題と解決策

チーム開発におけるテスト自動化とその課題について_その2

課題1. 手動テストよりも融通がきかなくなる

PHPUnit導入前は、「これまで手動で行っていたテストをコードにするだけだ」と軽く考えていたところがありましたが、実際にテストコードをかくと、手動テストを行っていたときよりもテストの仕方を明確にしなければ、開発者がどうコーディングすべきか迷ったり、品質がバラついたりしてしまうことがわかりました。
そこで、R&Dでは大きく分けて以下の3つのルールを設けて、テストの安定化をはかることに。

1. 条件分岐が存在する場合、全ての分岐を網羅するようにテストケースを書く
2. テスト対象の単体が、他クラスのメソッドを使用している場合、必ずモック(※1)で置き換える
(補足: フレームワークが用意しているクラスのメソッドについては、モックで置き換える必要はない)
3. データベースを介するテストを行う場合は、外部キー制約を無視してよい

※1 モックとは単体テストツールにおいて、テスト対象の単体で使用されているオブジェクトを置き換えるものであり、CakePHPのUnitテストを行う上ではモデルメソッドの返り値を操作することなどに使われます。
参考リンク:https://phpunit.readthedocs.io/ja/latest/test-doubles.html#test-doubles-mock-objects

プロジェクトによっては、上記のルールに加え、「システム権限のみでテストを行う」などのコスト削減を目的としたルールを追加することもあります。
他にも細かいルールはありますが、完璧なテストを追及しすぎるとコストばかりが膨らむので、プロジェクトリーダーが開発者メンバーのレベル等を考慮しつつ、効率・品質のバランスをとるルール作りが必要です。

課題2. テストデータの共有方法
単体テストを手動で行っていたときは、自分のローカルのデータベースを操作してテストしていたため、データがどのように挿入・更新されても特に問題はありませんでした。しかし、自動化ツールを導入したことによりテストデータがフィクスチャファイル(※2)というコードで管理されることになり、開発者間で衝突が起きないように共有方法を考える必要が出てきました。

R&Dでは、単体ごとに使用するテストデータのID範囲を機能実装前に確定することにし、さらにそのIDの範囲が他の開発者と被らないように、メンバー間で共有するようにしています。現状ではこの様に対策していますが、CakePHPではフィクスチャデータを分割することもできるので、改善の余地がありそうです。

※2 フィクスチャとは単体テスト実行時に、データベースのクリーンアップを行うための設定のことであり、CakePHPではこの設定に対してサポートをしています。
参考リンク:https://book.cakephp.org/3.0/ja/development/testing.html#test-fixtures

課題3. テストの実行が面倒
※2の注意書きで説明したフィクスチャという機能によって、テストケース実行毎にデータベースのクリーンアップが行われますが、テーブル削除・生成とデータ挿入が行われるため、処理コストが大きく、時間がかかります。
PCの性能とテスト内容にもよりますが、私が使用しているPCのスペック(CPU: Intel Core i7, メモリ: 16GB)だと、5~6ケースのテストを実行するだけでかかる時間は10~20秒ほど。

また、テストをまわすには、以下のようなコードを実行する必要がありますが、コードを打つのが面倒なのと、たまに書き方を忘れます。

▼テスト実行コマンド
vendor/bin/PHPUnit tests/TestCase/Controller/HogehogeControllerTest –group=”fuga”

そこで、PHPUnitがサポートしている@groupアノテーションを使ってテスト対象の単体を1つのグループとし、さらにテストケースごとにもIDのようなものを付与。単体ごと、テストケースごとのテストが実行できるようにしました。

また、テストコードのコメントに、実行コードを書くことにいたしました。些細なことではありますが、この取り組みによってテスト効率が劇的に向上いたしました。

テストコードコメント例

まとめ

以上、テストの自動化に伴い直面した3つの課題とその解決策を紹介させていただきました。
上記の課題は数あるうちの一部ではありますが、開発効率を下げないため、またテスト方式の変更による開発メンバーのストレスを軽減するために優先的に取り組んだ事項になります。

この取り組みによって、テスト自動化を取り入れた開発も軌道に乗せることができました。最初はやり辛さを感じていた開発メンバーも、安全性を実感しながら開発を進められています。テストフローやテストケース洗い出しに関する改善案を考えたメンバーも現れるなど、ポジティブな反応もみられました。

既に述べました通り、テスト自動化の導入にはコストもかかりますので、ただやればいいというものではありません。
大事なことは、自動化をする目的をはっきりさせることと、チームが自動化を実現できるレベルにあるかを見極めることだと思います。

今後の発展について

いかがでしたでしょうか。
現在はCakePHPを使ったプロジェクトでのみ、テスト自動化を導入しておりますが、今後は他のフレームワーク、他の言語を用いたプロジェクトでも導入していきたいと思います。そのための準備として、CakePHPに限定されないルール作りが必要です。

また、さらなる品質向上・効率化を求め、シナリオテストに対応するSeleniumやChromelessなどのテストツールもトライしていきたいです。
以上、R&Dの西久保でした。