ユニットテストを推進して半年経過したまとめ

QUES TECH

こんにちは、ヒューマンクレスト 磯部です。

今回は、私が社内向けに開発・運用している Webアプリの、ユニットテストについて書いてみます。

システム概要

  • 用途
    • アサイン管理
  • ユーザー
    • 社員のみ
    • 月に1回くらいは利用するライトユーザー : 100名程度
    • 毎日利用するヘビーユーザー : 10名程度
  • 開発・運用
    • Webフレームワーク: Ruby on Rails
    • テストフレームワーク: RSpec
    • AWS (EC2, RDS) で運用
    • 開発者 : 1名〜6名 (プロジェクトリーダー含む)

経緯

緩い開発体制

内製アプリで、ユーザーが本社にいる社員のみであるという背景から、開発体制はかなり緩いです。テストコードは思い思いに書いているものの、徹底されていませんでした。当然ですが、リリース後に本番で不具合発生というケースが散見されました。ユーザーに迷惑がかかると同時に、運用担当(=私)が不具合対応に追われるのも、かなり問題でした。

2016年下旬、そろそろヤバイ気配が

さらに、2016年12月から開発者を増やすことにしました。新規参入してもらうのは、Ruby on Railsの開発は未経験、かつ、ベトナム子会社所属で日本語がネイティブではない開発者でした。そこで品質維持・向上の対策の一環として、ユニットテストを徹底する取り組みをしました。

やったこと

ユニットテストの記載方針を明示

それまでは「ここはテストコード書いてください」くらいの個別指示だったのですが、2016年12月にテストコードの指針を明文化しました。基本方針は以下のようなものです。

  • Model
    • 最大限手厚くする。
    • C0カバレッジ 100%は当然。
    • 各メソッドの入力パラメータのパターンを網羅する。
    • 必要ならパラメータ間の組み合わせもテストする。
  • Controller
    • 当初は必要と感じる場合だけとしていたが、方針変更。
    • 各メソッドを最低1回は呼ぶテストを実装するものとした(この経緯は後述)。
  • View
    • Rails 組み込みのヘルパーで実装している部分は不要とする。
    • 条件による色変えや出し分けなどの独自実装はテストする。
    • 部分テンプレートは、テンプレート単位で(必要なものだけ)テストする。

長くなるので詳細や他の要素については割愛しますが、不具合の見逃しや検出の結果を見て方針をカスタマイズしています。ひとまず、新規開発分はテストコードを必須として取り組みを開始しました。

工数

最初は実行コードよりテストコードの方に工数がかかる状態でした。開発者がユニットテストという考え方や、RSpecの文法に慣れていないこともあり、このことは想定内ではありました。即効性のある対策はないのですが、ひたすらコードレビューして少しずつ慣れてもらっています。現状の体感では、テストコード実装の工数は、不具合発生時のコストが無くなった分で十分カバー出来ていると感じています。

Ajax制御 / JavaScriptの不具合が検出できない問題

前述の方針でお気づきの方もいらっしゃると思いますが、この方針では JavaScriptのコードはカバーできません。これに対しては 2つの対策をしています。

まずは Controller のテストを行うことにしました。JavaScriptで呼び出すAPIが InternalServerError となるような不具合が散見されたからです。特にModelの変更を既存機能に適用し忘れた場合など、デグレードのケースでは、軽く手動で確認しただけでは見逃すパターンです。Ajax呼出に限らず、Controller は全メソッドを網羅する方針としました。JavaScriptを直接実行するわけではないのですが、単純なデグレード防止には効果的でした。その後、権限管理について修正を行った際も、Controllerのテストでデグレードが発見するというオマケ効果もありました。

上記で品質の向上は見られたのですが、やはり画面上で複雑な条件や計算などが発生するとJavaScriptを直接実行するテストが必要となります。ごく最近ですが、E2Eテストをユニットテストに組み込みました。PhantomJS + poltergeist で実現しています。とはいえ、E2Eテストは時間がかかるという欠点が大きいので、利用は最低限とする方針です。

手動テストと併用する

品質を全てユニットテストに頼るという段階には至っていません。そこは無理をせずに、手動テストも併用しています。開発者にはテストコードで実現が難しい場合は、自身で手動テストを行うように指示しています。また、影響範囲が大きい開発では、別に手動テストを計画して実施しています。

2017年6月に、権限管理について大きなリリースを行った際は、ユニットテストの他に手動のシナリオテストも行いました。この結果、リリース後のユーザー検出不具合は無しという結果を出すことができました。

カバレッジ

simplecov でカバレッジ(C0)を確認しているので、変化を見てみます。

網羅率は、40.3%から 58.5% に上がり、coverd linesは2倍以上になりました。missed lines の値はほとんど変わらず、これは新規開発部分について徹底するという方針のためです。デグレードバグが発生した際は、関連部分のテストを追加しています。テストもなく、不具合も出ていないという部分については、費用対効果を考えて今後もそのままにするつもりです。

今後の課題

品質の悪いクラス

一部の Model で、リリース後の不具合が多発している部分があります。テストコードも蓄積されているのですが、ユニットテストでNGが出ずに本番で不具合というパターンが散見されます。JavaScriptやControllerにロジックがあったり、Modelのインスタンス生成方法が複数ルートあったりと、バッドパターンに見事にはまってしまっているクラスです。完全に技術的負債となってしまっているので、なんとか設計の見直しをしたいと思っています。リファクタリングの際は、おそらく手動テストも必要となるでしょう。

ユニットテストの肥大化

現状、全テストコードを実行すると 3分くらい時間がかかります。コードをプッシュする前には全テストを実行するように開発者に指示していますが、そろそろこの時間が気になるボリュームになってきました。テストの実行を高速化する施策と、テスト自体を取捨選択していく仕組みも必要と感じています。

感想

実は私自身は、実行コードよりテストコードを書く方が楽しい方です。開発者にとっては私の趣味を押し付けられたようなもので、少し申し訳ない気もしますが。最近はレビューしていて「これは読みやすい!」と唸らせるテストコードも見れるようになり、洗脳成功な感があります。今後も改善を続けていきます!