型チェックの弱い言語でのWebサービス開発において、なぜ自動テストが重要なのか?

Webサービスのリリース(ソフトウェア更新)の周期は短い

現在のソフトウェア・Webサービスでは、ユーザーのフィードバックを受けて機能追加・修正を行い、どれだけはやく次のリリースを行うのかが重要視されています。


パッケージアプリのリリース周期の短縮に関しては、Google PlayやiTuensに代表されるソフトウェアのアプリストアの登場により、ソフトウェア開発者がアプリの更新・配布を簡単にできるようになったことが要因の一つです。

そして、Saasとして利用されるようなWebサービスは、パッケージアプリのような配布の手間がないことに加えて、全ユーザーが同じ最新版を利用する形になるため複数バージョンのサポートのコストがないこともリリースが短くなる要因の一つです(Webサービスのリリースに関してはサービスの稼働率との関係もありますが・・・)。


このようなリリース周期が短い状況においては、リリース前テストという形で工数のかかる手動テストを行うと、リリース感覚が短いために開発工数の中でテスト工数が非常に大きな割合を占めることになります。
そのため、テスト実施に作業工数のかからない自動テストを充実させる必要があります。

もちろん、リリース前テストをすべて自動化できるかというと難しい点も多く、自動化できる部分とできない部分を見極める必要があります。

インタプリタ方式の型チェックの弱い言語の問題など

C/C++/C#/Java/Haskellでソフトウェアを開発する場合、強い型チェックの存在により、コンパイル時に存在しないメソッドの呼び出しなどはエラーとなるため、プログラムを動作させなくても多くのバグを見つけることができます。
これなら大規模リファクタリングも安心して実施できますね。

例) NekoクラスがmethodAメソッドを持たない場合コンパイルエラー

Neko neko = new Neko();
neko.methodA()


更に、JavaScriptに関しては実行時でさえ、オブジェクトのプロパティのアクセス時にそのプロパティが定義されてない場合も例外は発生せず、単にundefinedが返ります。

例) Nekoクラスがプロパティnameを持たない場合、bはundefinedになる(正確にはJavaScriptに言語上クラスはなくNekoは単なるコンストラクタでありスコープチェーンが・・・という話はここではしない)

var neko = new Neko();
var name = neko.name; // 1. ここでエラーにならない。bはundefined
$('#test').text('height', name); // 1. ここでもエラーにならない。undefinedが'undefined'という文字


このような状況だと、バグはなかなか発見されなくなり、バグ発見時にも原因解明に多くの時間を費やすことになります。
バグの基本原則として、バグの原因となる修正・実装をした時からバグの発見までの時間が、長くなるほど修正コストが上がるので非常に残念な状態といえます。

自動テストを充実させてCIと統合し、個々の機能が正しく動くことを継続的に確認すると、デグレ等を早期発見することでことでバグに対するコストを大幅に削減することができます。


この問題に限定すると、下記のような改善・対策は可能です。
ただし、JavaScriptは言語的に他にも多くの落とし穴があり、jsHintなどを利用して問題の発生頻度を下げたとしても、自動テストでの品質確保はは必須といえるでしょう。

1. C++言語で利用するようなASSERT()関数を定義して、コード上の前提条件などのチェックを増やして、予期せぬundefinedを見つけやすくする

ASSERT()は失敗したら開発時にすぐに分かるようにします。また、自動テスト中にASSERT()が失敗したら、結果は失敗になるようにします。
例) 必須となる引数がない場合、ASSERT()を失敗させる

function Neko(params) {
  ASSERT(params && params.name);
  // params.nameを利用する処理
}
2. プロパティの取得・保存をメソッドでラッピングする

個人的にはMVCフレームワークのModelは基本的にこのくらいのチェックはしてほしい。堅固なModel最高。
例) getメソッドによるアクセサを作る

var neko = new Neko();
var name = neko.get('name'); // getメソッド内でthisにnameプロパティがない場合ASSERT()を失敗させる
3. プロパティの取得をECMAScript6 Proxyを使ってラッピングする

方向性は上記のメソッドのラッピングと同じですが、メソッド呼び出しではなくプロパティアクセスの形でチェックを実行できます。

4. TypeScriptを利用する

TypeScriptはJavaScriptへのコンパイル時に強い型チェックの実行がされるので、存在しないプロパティはコンパイル時にエラーになります。

機能の増加とモジュール化

各サービスのWebAPI、JQueryのPlugin、Node.jsのパッケージ、Rails利用時のGemなど、現在のソフトウェア開発は膨大に存在する個々のモジュールを組み合わせることで、非常に安価に機能の追加ができる時代になっています。
そのため、1つのWebサービスが持つ機能も非常に多くなっています。

また、社内・社外で利用する機能を、パッケージしたライブラリ(以下モジュール)にしたりWebAPIにして提供するということも頻繁に行われるようになりました。


つまり、下記の問題を解決する必要があります。

  • 1. Webサービスが持つ膨大な機能をどうやってテストするのか?
  • 2. 提供したいモジュールの品質をどうやって保つのか?


1に対しては、結合テストの自動テストで対応します。
このとき、Webサービスが持つすべての機能を網羅的に結合テストで行おうとすると、自動テストの非常に時間がかかったり、比較的コーディングコストの高い結合テストコードを大量に作って作業工数に問題が出ます。
そのため、テストの粒度のどこで品質を確保するのかは見定める必要があります。


2に対しては、LightWeight言語のモジュールといえばPerlCPANだった時代から、パッケージ化されたライブラリには単体テストをつける相場が決まっています。
もし単体テストがなければ、そんなモジュールなんて怖くて使ってもらえませんし、問題発生時のサポートが大変です。