AngularJSのテスト(Unit Test/end-to-end test)って何が良いの?

  • 121
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

AngularJSを利用したのなら、絶対にKarmaとか Protractorを活用すべきです。ちゅうか、活用しなさい。と言いたいです。システムを保守フェーズに安定的にメンテナンスするならそう考えるのがふつうになるべき。
(※Protractorについては、こちらの記事がとても参考になります)

例によって「Mastering web application development with AngularJS」を読んで書かれていたポイントを読んだ感想をこちらにまとめてますが、
2章のテスト解説部分に下記の文章があります。

There is a quote saying that writing code without a Version Control system (VCS) is like skydiving without a parachute. Today one would hardly consider running a project without using a VCS. Same applies to the automated testing and we Could say: "Writing software Without an automated test suite is like climbing Without a rope or skydiving without a parachute". You can try but the results will be almost for sure disastrous. 

経験者には、引用された内容が身に染みてわかります。

ただし、ここで記述した内容は、一般的なテストコードを作成することにつながる内容がほとんどで、AngularJSではない開発を行っている方々にもぜひ導入してもらいたい考え方になります。

別にKarumaであればそんなJavaScriptコードもテスト実行できますし、「何チャラUnit」とかJasmineそのものなどさまざまなJSのテストツールやテストランナーはあるのでしょうからそれらを使えば、フレームワークをAngularJSにしなくてもどうにかなるのでしょう。
なので、ところどころ、AngularJSを利用した場合の利点も踏まえて、良いことがあるの理由も含めて文章立てできればと考えています。

(1)CI(JenkinsやtravisCIそしてWerkerなど)との親和性が高い。

TDDやBDDの導入で一番重要なことは、チーム内で如何に問題点の吊るし上げを効率的に行えるか。というところにあると最近思います。

運用ルールで「コミット前にテストコードを実行して確認してください!」という風にしたとしても、結局最後は納期に押されて今回はテストコードはメンテしないことにしましょう。。。という悲しい結末になることが多いのではないでしょうか(もしくは、誰かが専門担当になってしまうような形になるかもしれません。テストが作成されるだけましかもしれませんが、この場合、属人的になり継続が難しくなるので避けるべきです)。

そこで、案件の開始当初から徹底して、作業がひと段落してcommit後しても、CIでオールグリーンにならないと作業完了とはみなしません。という絶対的なルールを作ってしまえば、誰も逃げれません。これを案件当初からやっておけば、文化となります(ランナーズハイを得られます)。

Karmaは、各種CIと親和性が高いです。実際、私の方でもJenkinsで試すにあたって、一夜漬けで環境を構築することができました(こちらを参照)。

(2)CI導入の結果、開発に関わるデバッグプロセスが短かくなる。

上記の「(1)」に関わりますが、自動テストを導入することで、どのコミットから問題になったのか?ということが明示的にわかります。
良いチームの場合、CIでレッド状態が発生したその場で、状況を共有して問題点を解消するでしょう。
もしくは、「だれやー」という声とともに何らかの解消行動が発生するでしょう。

システムの動作確認を後回しにすると、製造した本人も含めて記憶があいまいになり、何が起因なのかやバグの場所を特定する際の確認範囲が広くなります。
第三者の確認者的な存在としてCIが君臨してくれることで、問題を早期解決させる流れを作ります。

(3)リファクタリングの実施決断の判断基準が大幅に軽くなる

テストが自動的に実施されてかつオールグリーン状態の状況が続けられてくると、製造担当者のほとんどはコードの修正に対して恐怖やお億劫さを感じる度合いが、テスト自動化する以前にくらべると、減っているはずです(経験的にそうなることがなんとなくわかっています)。
つまり、リファクタリングへの決断に対する勇気を奮う量が減ることになって、日々より良いコードに改善する行動が生まれるきっかけになります。

(4)真のTDDで内部設計の精度が上がる

TDDではない作り方だと、漠然とした仕様のイメージを基にメソッドや関数の内部コードを作る事から始まり、製造完了後のコードレビューや結合で第三者から使い勝手を指摘されるまでは自分の設計の価値がわかりません(経験度が高くてもなぜかプログラマは狭い目的にとらわることが多いです)。

それが、TDDを取り入れて開発し、日々テストコードから作るようなちょっと手間のかかる手順に挑戦し続けると、そのうちに、

  1. これから作るメソッド(もしくは関数)が具体的に利用されるときのコードを書いて
  2. その結果をイメージしてアサーションを記述する
  3. あれ?この内部設計使いづらくないかい?となり、ちょっと利用されるときのコードを直す
  4. もう一度アサーションを記述しなおす。
  5. これならいいかも?となり、実際のコーディングを開始する

というような流れができ、後から大幅な内部設計し直しや、やりたくても直せないもどかしさを心の片隅にためながら納期と戦うような状況を可能な限り低減できるはずです。

(5)別担当者にあっさり作業をお願いできるようになる。

ぜひ、
「このテストコードが仕様です。」
と言えるようになることの素晴らしさをぜひ感じてほしいです。
また、BDDの場合は、
「この手順を参考に触って問題点を洗い出してみてください」
とテスターに言えたりします。

この優れている点は、従来のExcelとかで記述されたテスト仕様書の場合に起こるような、後から、「ここは古い部分なので気にしないでください(ないし、無視してください)」とか言わなければならないもどかしさがなくなることです。テストコードの形でメンテナンスしていてオールグリーンになっているテストにおいて、実際に動いているプロダクトと食い違った仕様が記述されているのはことが発生することはまずありません。もしくは、保留中のテストはあるかもしれませんが、メンテナンスされていれば問題ありません。

(6)Jasmine(巷でSpecなどとも呼ばれる)が標準のテストツールであり、テストを書きやすい

やっとAngularJSにちなんだ内容になりますが、
UnitTestはJasmineが標準になっており、テストが書きやすいです。

describe('sample tests:', function ()  {
    describe('hello World test', function ()  {
        var greeter; 
        beforeEach(function ()  {
            greeter = new Greeter(); 
        }

        it('should say hello to the World', function() {
            expect(greeter.say('World')).toEqual('Hello, World!');
        });
    });

    describe('Hoge World test', function ()  {
        var greeter; 
        beforeEach(function ()  {
            greeter = new Greeter(); 
        }

        it('should say hoge to the World', function() {
            expect(greeter.say('World')).toEqual('Hoge, World!');
        });
    });
});

上記は説明するためのJasmineのテストコードを記載しました(書籍中にあるサンプルを変えています)。
Greeterは、sayメソッドが実装されていて、引数に渡された文字列を組み込まれた文字列に連結した文字列を返します(何を返すのかはテストコードでわかるかと思います)。

テストが節立てで記述されていて、変数の取り扱うスコープも分割されているはずです。
(サンプルでは、beforeEachでGreeterをnewしています。)

(7)AngularJSのDIの仕組みはテストでも同様に便利に利用できる

慣れてしまえばものすごく便利になる仕組みとして、AngularJSのDIの仕組みをテストに利用する方法があります。

できることとしては、

  • AngularJSアーティファクト(Service, Factory, Controllerなど)を単体テストできる
  • AngularJSのサービス(「$」で始まる仕組み)のモックを利用したテストを便利に使える
  • XHRの記述をモックを利用して単体テストできる

以上になります。

私はまだほかのクライアントMVCを利用したことがほとんどないので強く言えませんが、組み込みサービスに対してテストしやすいフレームワークはたぶんAngularJSくらいじゃないでしょうか。
そんな気がします。

(8)テスト時に目的のテストだけを選択して実行できる

Jasmineの記述を拡張させて下記のテスト選別書式を利用できます。

  • 「x」文字列をつける
    • この文字列を付けたテストかテストスイートは実行対象から外れます
  • 「d」文字列をdescribeに付ける
    • このテストスイートだけが実行されて、それ以外のテストスイートは無視されます。
  • 「i」文字列をつける
    • テストかテストスイートだけを指定して実行されて、指定された対象以外はすべて無視されます。

テストは案件の規模によって、様々に大量に記述することになります。
その際に面倒になるのは、目的のテストだけを実行させる方法です。
上記の方法を利用することで、テスト作業に無駄なコストをかけずに済みます。

書籍中にあったサンプルをそのまま記載します。

describe('tips & tricks', function ()  {
    xdescribe('none of the tests here will execute‘, function ()  {
        it('wont execute - spec level', function ()  {
        }); 

        xit('won't execute  test level', function ()  {
        }); 
    });

    describe('suite with one test selected', function ()  {
        iit('will execute only this test', function ()  {
        });
    });
});

end-to-endテスト

残念なことに独自のシナリオランナーは限界が見えていたらしく、先のProtractorを利用した形で、Seleniumベースで記述されることになっているようです。実際のAPI ReferencesのテストコードもすべてProtractorベースになっています。

Protractorについては詳しく調べていないので、こちらでは割愛させていただきます。

簡単にですが、説明をつけておくとするならば、Protractorは、ブラウザを利用した実際の画面制御の結果を確認してくれます。
また、ブラウザ制御の確認ですが、コーディングすることで可能になるのは、面倒なブラウザ操作を回避することができます。

(9)非同期の操作(XHR)に対して制御が可能

Ajaxなどの非同期の操作では、テストを実行することが難しい場合がおおいです。BehatやCucumberではなにも考えずにやると、「XX秒待つ」という何ともアナログなステップを定義することになると思います(もしくは何らかの実行中判断ロジックのステップなど)。テスト負荷が何らかの理由で高いときこの「XX秒待つ」ステップがあるテストは必ず落ちます。また、テスト実行の全体の時間が無駄に増えます。
しかし、AngularJSのend-to-endテストでは、非同期の操作が終わるのを待ってくれるのです。余計な待ちステップは不要になります。

テスト自動化で画面切り替えの手間が減る

エディタの仕組みをちゃんと理解してコーディング作業を行うと、テスト自動化によって、わざわざブラウザを立ち上げたり再読み込みしたりする手間なく、実際の手順に近いテストを行うことができるようになります。
Karmaランナーの実行応答速度が十分に早いので、ファイルを編集後の保存時に自動的にテストを走らせても十分開発作業に集中できます。

つまり、エディタ一つ立ち上げれば、テストコードや製品コードのコーディングに集中しながら、裏でテスト実行させながら、製造作業を行うことができるんです。

実際にその製造環境を構築してみないと、伝わることはないと思いますが、この環境を手に入れてしまうとその後、テストのない開発にストレスを感じることになるでしょう。

テスト自動化のデメリット

下手に独自のCI環境を作るとメンテナンスコストが大きい

Jenkinsは便利です。しかし、それなりのノウハウが必要です。
システムのテストを実行するということは、実際の利用時のイメージを持って環境を構築しなければならないのは、大体わかると思います。
しかし、テスト導入当初はそんな意識は全くないことが多いです。
テスト数が増えてくると様々な負荷やOSの制限に出くわします。
そういったことを経験的に乗り越えていくか、サービスを利用して任せてしまうかは重要な最初の判断になります。

すべてを自動化できないことを理解する

テストをすべて自動化しようとすると、半端なく作業コストが果てしなく高くなります。少なくとも外部サービスとの結合部分やまれな異常系などには目をつむりましょう。これはカバレッジ100%を目指すことをできるだけやめるよう譲歩しましょうということでもあります。

結局、完全なシステムを作るには、現状のところ人間の確認なしにはできないので、ある程度仕様を網羅したテストが組めたら、残りの部分についてテストを作成することでのメリットがあるかどうかを判断するべきであると思います。

ちょっと長文になってしまいまいたが、テスト自動化をやった方がいいということと、少しだけですがAngularJSを利用することでテストがやりやすくなることを取り纏めてみました。

ただ、そもそも上記で提示したことをあらかじめ考えられていることがとても素晴らしいフレームワークだと、書籍を読んで感じました。