2013年12月9日月曜日

Javaのアサーション (assert文) の使いどころ

Javaのアサーションについて、少し調べたことをメモしておく。『言語設計者が考えること』の「12章 Java」で、Javaプログラマへの助言として
assert文を至る所に散りばめること
なんて書かれていたけれど、使いどころがよく分らなかったのが、調べたキッカケ。

アサーションを使用したプログラミング (Java言語仕様) によると、使いどころは次の3つ。リンク先には具体例がもあるので、これを見るとおおよその想像がつく。
  • 内部の不変条件
  • 制御フローの不変条件
  • 事前条件、事後条件、およびクラスの不変条件

この内、3つ目の「事前条件、事後条件、およびクラスの不変条件」のさらに「事前条件」には「publicメソッドの引数チェックに使ってはいけない」という但し書きが付く。publicメソッドの引数が満たすべき事前条件はAPI仕様の一部だというのがその理由。『Java セキュアコーディングスタンダード 』の「MET01-J. メソッドの引数の検証にassertを使わない」に詳しい。
アサーションは、public メソッド内の引数チェックに使用しないでください。

引数のチェックは通常、メソッドの仕様 (または規約) の一部になっており、アサーションの有効/無効にかかわらず、この仕様に準拠する必要があります。アサーションを使用して引数をチェックした場合、不正な引数によってランタイム例外 (IllegalArgumentException、IndexOutOfBoundsException、NullPointerException など) が発生する可能性があります。アサーションが失敗しても、適切な例外はスローされません。

「適切な例外をスローする」とあるけれど、例外は使わないで通常の制御フローを前提にアサーションを使うのか、それともアサーション以外の例外を使うのか、一般解は存在しない。例外設計における大罪や『Effective Java 2nd Edition』の「項目57: 例外的状態にだけ 例外を使用する」が参考にはなるけれど、最後は作ろうとしているソフトウェアがどう振る舞うべきか、自分で考えるしかなさそう。

というわけで、指針は掴めたけれど、簡単な話ではない。

References

2013年11月26日火曜日

FindBugsのDetector Pluginチュートリアルをやってみた

FindBugsのDetectorPluginTutorialを試してみたときのメモ。FindBugs Eclipse Pluginへの追加を想定している。使用したバージョンは次の通り。
  • Eclipse 4.2
  • FindBugs 2.0.2
以下、見出しごとに躓いたポイントをメモしておく。



Creating Our Plugin


Setting Up Eclipse

素直にそのままやって問題なかった。

Setting Up the Classpath

findbugs.jar, bcel.jarに加えて、jsr305.jarもClasspathに追加する必要があった。findbugs.jarが依存しているみたい。

Writing the Bug Detector

そのままDetector codeのサンプルをコピーして問題なかった。

Deployment & Testing

Building

以下2つのxmlファイルは、プロジェクトフォルダーのルートに配置する。書かれている通りだけれど、読み落としがち。

findbugs.xml

コメントにあるとおり、公式サイトと要素名が異なるけれど、こちらの記載通りFindBugsPlugin要素で問題なかった。

サンプルには含まれていないけれど、一行目にXML宣言を追加しておく方がベターだと思う。

messages.xml

そのままサンプルをコピーして問題なかった。

Building in Eclipse

そのままやれば問題ないのだけれど、JAR Exportの設定が色々と初期値と異なる点に注意。間違えるとプラグインとして追加しても、Invalidと表示されて機能しない。一度やれば、jardescからその設定で再ビルドできる。

CIするためにAntタスクとして定義したい。

Loading Our Plugin

GUI (Eclipseの設定画面 Java > FindBugs) から追加した。EclipseのpluginsフォルダのFindBugsのpluginフォルダに追加しても、うまくいくかも試したい。


ここまでやって、Quizのコードスニペットを含むクラスでFindBugsを実行してみたら、期待通り動作した。めでたし、めでたし。

以下は、覚え書き。

このチュートリアルの"Debugging"はまだ書かれていない。テストケースの簡単な説明はFindbugsTestCasesにあるけれど、ここを読むだけではちょっと分りそうにない。実際のテストケースを見ながら、試行錯誤が要りそう。

チュートリアルではなぜこうするのか分らないけれど、Plugins4FindBugsで補完できる。Detector Pluginの概要が記載されている。

Eclipse Pluginに特化した内容として、コメントでFindBugs In Eclipse Tutorialが紹介されているのに、後から気がついた。ただ、FindBugs 1.3.9よちょっと情報が古い。

References

2013年8月21日水曜日

Javaのデコンパイラと難読化ツール

Javaのクラスファイルをデコンパイルしたソースコードは読みやすいと聞いて、「どれくらいソースを再現できるのか?」と「難読化したらどれくらいデコンパイル結果が読み辛くなるのか?」について試してみたくなった。というわけで、まずはデコンパイラ (decompiler) と難読化ツール (obfuscator) ついて調べてみた。今回はツールの調査だけで、実行方法についてはおいおい。なお、調査方法は、GoogleとStack Overflow頼み。

デコンパイラはJava Decompilerでいいのかな。デコンパイラとして真っ先に思い浮かんだJADは、更新が止まっていた。Java DecompilerもEclipseプラグイン・JD-Eclipseが最近のEclipseだと動かないようだけれど、少なくとも、Mchr3k - JDEclipse-Realignというフォークがメンテナンスされている。
Java Decompiler (Yet another Fast Java decompiler) has:
  • explicit support for decompiling and analyzing Java 5+ “.class” files.
  • a nice GUI:
decompiler - How do I "decompile" Java class files? - Stack Overflow
ともあれ、こちらはすんなり使えた。クラスファイルからほぼソースそのままにデコンパイルできてビックリ。Live Demoもある。

難読化はProGuardが良さそう。予備知識が無いので手探りだけれど。こちらは現在もアクティブな様子。最近はAndroidアプリの難読化に力を入れているよう。
Well, you can find here a list. ProGuard is pretty good. I've used it myself, but only to "minify" Java code.
obfuscation - Best Java obfuscator? - Stack Overflow
ただ、こちらは簡単には使えなさそう。設定が必要そう。幸い公式サイトのマニュアルが充実しているようなので、それを見ながらもう少し調べよう。

ところで、難読化すると、クラスやメソッドなどの名前が変わるから、リフレクションAPIを使っていると実行時例外が送出される。けれど、ProGuardのIntroduction > Reflectionによると、典型的なリフレクションAPIに対応しているみたい。

References

2013年8月3日土曜日

Out parameter, collecting parameter, visitor pattern

『Effective Java (第二版)』の「項目15 可変性を最小限にする」では不変オブジェクト (Immutable object) を推奨している。これは状態を持たないから、シンプルに扱える。反対に状態を持つ可変オブジェクト (Mutable object) は、扱いに気を遣う。扱おうとしているときにどういう状態なのか意識しないといけない。状態に起因したバグがあったら、最悪、状態が変更されている場所を全て確認しないといけない。

これに真っ向から対立しているのが、Out parameter。状態を変えるために渡す可変オブジェクトのことを指す。Webを検索してみると、アンチパターンとして紹介されている記事が見つかる。

でも、"Collectiong parameter"として『実装パターン』や『パターン指向リファクタリング入門』で紹介されていたりする。また、『パターン指向リファクタリング入門』では、その延長線上に"Visitor Pattern"を適用したパターンも紹介されている。

確かにこちらの方が合理的なケースもありそう。でも、基本的にはあまり使わない方がよさそう。コマンドとクエリは分離するのが原則だ。使うなら、Javadocに明記するなりしないと混乱を呼びそう。C#のoutキーワードに相当するものがJavaにはないから、引数の状態を変えるかどうか宣言だけから読み取れない。

ところで、out (C#)に使用例として「メソッドが複数の値を返すようにする場合に便利」だなんて書かれている。同時に扱いたい複数の値があるなら、クラスとしてまとめた方が読みやすくないだろうか。原則として、入力を与えたら戻り値が返ってくる関数として考えたい。

References

2013年7月6日土曜日

モンスターメソッドを解体する

『レガシーコード改善ガイド』の第22章「モンスターメソッドを変更する必要がありますが、テストを書くことができません」を自分なりに整理して、モンスターメソッドへの対処方法を要約してみる。

何をどこまでテストすれば?

まず、どういう種類のテストを書こうとしているか考える。それには、第13章「変更する必要がありますが、どんなテストを書けばよいのかわかりません」が役に立つ。そこでは、〈仕様化テスト〉と〈狙いを定めたテスト〉が紹介されている。〈狙いを定めたテスト〉を書くのが現実的だろう。モンスターメソッドの振る舞いは多様だから、いきなり全体を対象とした〈仕様化テスト〉は書けないことが多そう。

次に、モンスターメソッドを読み込んで、狙いを定める。どこまで狙いを絞り込めるかは、モンスターメソッドの作りと必要な変更によってケースバイケースになる。少なくとも自分にとっては、技芸の世界で、筋道立った説明をできない。

狙いが定まったら、検証方法を考える。この時、検証対象が一意に識別できなかったり (例: 別のブランチから同じ結果が返ってくる) 、テストから見えなかったりしたら、〈検出用変数〉または〈クエリメソッド〉を導入する。この時点では、これ以上の編集は避けた方が無難だと思う。やり過ぎると〈編集して祈る〉のと変わらない。

テストを書く

ここまでで、何のためにどの範囲でどんな検証をしないといけないか、具体的になってくるはず。ここまで来たら、テストを書き始める。書き始めたはいいが、モンスターメソッドを持っているクラスをインスタンス化できなかったり、モンスターメソッドの引数をインスタンス化できなかったりすることが多いと思う。その場合は、別の章を参照して対処する (ここでは深入りしない)。
  • 第09章 このクラスをテストハーネスに入れることができません
  • 第10章 このメソッドをテストハーネスで動かすことができません

変更とメソッド抽出

無事にテストが書けたら、実行してパスすることを確認する。この後、必要な変更を行うか、〈メソッドの抽出〉を行うかは、悩ましいところ。変更が局所的なら先に変更してもいいと思う。逆に変更がモンスターメソッドのあちこちに散らばるようなら、先にメソッドを抽出しておかないと後が苦しくなる。いずれにせよ、ここでは変更については触れずに、メソッドの抽出についてまとめる。

メソッドを抽出するときには、抽出後に残るメソッドの形を想像しておくと、どういう単位で抽出すればいいか方針を決めやすい。残る形には、〈骨組みメソッド〉と〈処理シーケンス〉の2種類がある。

〈骨組みメソッド〉は制御構造だけを持っていて、条件式や各条件下の処理はメソッドに任せてしまう。『実装パターン』だとConditionalに対応する。『デザインパターン』で例えるとFacadeやMediatorのイメージになる。

もう一つの〈処理シーケンス〉は単に順番にメソッドを呼び出していく。『リーダブル・コード』の無関係の下位問題の抽出を行うイメージ。「いちいちメソッドにするほどの問題ではないんじゃ?」と思っても、メソッドにして意図に即した名前を付けておくとリーダビリティが上がるはず (『実装パターン』のExplaining Message)。

どちらが適しているかは、モンスターメソッドの元々の役割によって変わる。大抵の場合、モンスターメソッドは、実装コストが低いところに追加が集中した結果であって、最初からモンスターメソッドとして生まれたわけではない。処理の振り分けが役割だったなら骨組みメソッド、抽象度の高いAPI提供が目的だったなら処理シーケンスを残す方が自然だと思う。

どういう形を残す決めたら、メソッドを抽出する前に、抽出したメソッドのテストを書く。先にテストを書けばレガシーコードは生まれない。アジャイルじゃなくたって、TDDした方がいいと思う。

さらに分割

ここまで来たら、細かい粒度でテストできるようになっている。でも、モンスターメソッドを持っているオブジェクトは、得てしてGod Objectだったりする。抽出したメソッドを〈メソッドオブジェクト〉として取り出して、委譲するように修正することも考えていく。つまり、『レガシーコード改善ガイド』の〈メソッドオブジェクトの取り出し〉、『リファクタリング』のReplace Method with Method Objectを行う。

References


2013年5月18日土曜日

JavaScriptで依存性を排除するためにデフォルトパラメータを導入する

JavaScriptのクラスHogeをテストしたいのだけれど、次のような状況でそもそもインスタンス化が大変な場合にどうするか? という話。
  • Hogeはコンストラクタ内で別のクラスFugaをインスタンス化してプロパティに設定している。
  • Fugaはインスタンス化の際にサードパーティ・ライブラリのAPIを大量に呼び出している。
  • サードパーティ・ライブラリは、開発環境では使えない(例えば、REST APIを提供するサーバがまだ立っていない)。
この場合をコード例で表してみる。サードパーティ・ライブラリが使えないため、Fugaのコンストラクタ呼び出しでエラーが発生し、Hogeをインスタンス化できないとする。
Hoge = function () {
  this.fuga = new Fuga();
  // ...
}
// ...
Fuga = function () {
  this.thirdPartyLibrary = new ThirdPartyLibrary();
  // ...
}
// ...

FugaがThirdPartyLibraryをラップしているから、テスト時にはテストダブルに入れ替えたい。でも、そもそもHogeもFugaもインスタンス化できない。

こんな状況に対応するためのリファクタリングとして、『レガシーコード改善ガイド』は25.14「コンストラクタのパラメータ化」か、デフォルト引数の追加を紹介している。オーバーロードのないJavaScriptでは、コンストラクタをパラメータ化できないので、デフォルト・パラメータを指定する。

デフォルト・パラメータがtrueと見なせる(false、null、0、""、undefinedのいずれでもない)なら、こんな風に短く書ける。||は真偽値ではなくてfugaを返す性質を利用している。
Hoge = function (fuga) {
  this.fuga = fuga || new Fuga();
  // ...
}
// ...

もっとロバストな書き方は次の通り。こちらは、デフォルト・パラメータがfalseと見なされる場合も使える。実用的なのは、デフォルト値が0やfalseの時。
Hoge = function (fuga) {
  if (typeof fuga === 'undefined') {
    fuga = new Fuga();
  }
  this.fuga = fuga;
  // ...
}
// ...

References



JavaScriptにおける検出用変数

JavaScriptで『レガシーコード改善ガイド』の第22章「モンスターメソッドを変更する必要がありますが、テストを書くことができません」で紹介されている「検出用変数の導入」を行ってみる。

『レガシーコード改善ガイド』のサンプルコードはJavaだからインスタンス変数として導入しているけれど、ここでは関数プロパティとして導入する。JavaScriptでは、関数もオブジェクトだからプロパティを持つことができる。

関数プロパティとして導入した方が、使用する場所に近くなるから読みやすくなるし、何かの拍子に誤ってアクセスする可能性が小さくなる。モンスターメソッドはただでさえ長い上に、そいつを持っているオブジェクトもゴッド・オブジェクトだったりするから、どれだけ慎重になってもなり過ぎるということはないはず。

というわけで、godObject.monsterMethodに検出変数isProcessedを導入してみる。これで複雑な条件をかいくぐって目的のブロックが実行されているかどうかを検出できるようになる(この実行が委譲されているなら、モックを使って検出できるけれど、モンスターメソッドが書かれるような状況では期待薄だと思う)。
var godObject = {
    monsterMethod : function (num) {
        // 検出用変数
        this.monsterMethod.isProcessed = false;
    
        if (true) {
            // ... 
            if (false)  {
                // ...
            } else if (true) {
                if (num === 2) {
                    this.monsterMethod.isProcessed = true;
                    // このブロックが実行されているかどうかを検出したい
                }
            } else {
                // ...
            }
        }
    }
}

検出用変数には、godObject.monsterMethod.isProcessedでアクセスできる。実際には、テストコード中でassertTrue(godObject.monsterMethod.isProcessed)のような形でアクセスすることになるだろうけれど、ここでは簡単に確認するためにconsole.logを使う。
godObject.monsterMethod(1);
console.log(godObject.monsterMethod.isProcessed); // > false
godObject.monsterMethod(2);
console.log(godObject.monsterMethod.isProcessed); // > true
godObject.monsterMethod(3);
console.log(godObject.monsterMethod.isProcessed); // > false

この方法は、『JavaScriptパターン』の4.3「関数プロパティによるメモ化パターン」を参考にしている。関数にその関数のキャッシュ変数を持たせられるなら、検出用変数を持たせられるだろう、と。

References


2013年5月13日月曜日

jgenhtmlでJsTestDriverのCoverageプラグインの出力をHTMLレポートに

JsTestDriverのCoverageプラグインは、LCOVフォーマットのファイルを出力する。「JsTestDriverでUnit Test + Code Coverage」では、Ubuntu上で実行していたからgenhtmlで簡単にHTMLレポートにできたけれど、Windows上ではgenhtmlを使えない。

Windows上でHTMLレポートに変換する方法について調べてみると、javascript - Viewing LCOV file in Windows - Stack Overflowにそのものズバリの質問が。回答を見てみると、Cygwinにgenhtmlをインストールする方法と、Java実装のjgenhtmlを使う方法があるらしい。

というわけで、今度はjgenhtmlを使ってみる。About JGenHtmlに書いてあるとおり、こちらを使うとパスの区切り文字("\"か"/"か) を気にしなくて良いし、JsTestDriverが動く (Javaがインストールされている) なら、jgenhtmlも動かせる。なお、動かしてみたjgenhtmlのバージョンは、1.5.0。

インストールも使い方も簡単。インストールは、jgenhtml-1.5.jar をダウンロードして、任意のフォルダに置けばよい。ここでは、「JsTestDriverでUnit Test + Code Coverage」のディレクトリ構成配下、test-lib/genhtmlに追加配置する。その時の使い方は、コマンドプロンプトで次のコマンドを実行する。
java -Dfile.encoding=UTF-8 -jar test-lib\jgenhtml\jgenhtml-1.5.jar -q -o test-output\coverage test-out\jsTestDriver.conf-coverage.dat
-Dfile.encodingの値は、JavaScriptファイルのエンコーディングに一致させること。そうしないと、HTMLレポートの日本語が文字化けする。jgenhtmlは、JVMのデフォルト・エンコーディングでJavaScriptファイルを読み込み、エンコードに関するオプションを持たないので、JVM側で指定している。エンコードにMS932以外を指定すると、今度はコマンドプロンプト上で文字化けするので、jgenhtmlのオプション-q(uiet)で、表示を止めている。

これで-o(utput)に指定したディレクトリにHTMLレポートが生成されるので、ブラウザで確認すれば良い。ただし、HTML5とCSS3を活用しているので、IEはサポート外とのこと。
The reports produced by jgenhtml use cutting edge HTML5 and CSS3 features.

If you are not using an extremely modern browser stuff probably won't work. If you use Internet Explorer my guess is you have no chance in anything before IE10.
BrowserSupport - jgenhtml - lcov genhtml tool ported to Java - Google Project Hosting
手元のIE9でざっと確認した範囲だと、IE8モードだとソースコードのハイライトを確認できるページがレイアウト崩れしている。

References

WindowsのコマンドプロンプトからJSHint

WindowsのコマンドプロンプトからJShintを実行する方法について書く。いちいちjshint.comのテキストエリアに貼り付けていては、リズムが悪いし、多数のファイルを一度にLintできない。

方法は次の2つが考えられたけれど、『メンテナブルJavaScript』に倣って、後者を選んだ。ざっと検索した感じ、Rhinoをインストールする方が、Nodeとnpmをインストールするより手軽そうだったのも理由の1つ(特にnpmのインストールには、GitとPythonが必要)。

Rhino bundleを使う場合のデメリットは、大きく2つ。前者は規定のオプションを渡すバッチファイルを書けば、代用できると思う。後者は替えが効かないので、将来対応されると嬉しい。
  • 設定ファイルを読み込めない。オプションはコマンドライン引数として渡さないといけない(ソースを直接いじるという荒技もできそう)。
  • コマンドラインフラグを使えない。コマンドラインフラグはNode版しか使えない。

まず、次の環境を構築する。Windows 7へのJava 7のインストールは済んでいるものとする。Rhinoは、Download RhinoからBinariesを、ダウンロードして任意のフォルダに展開すれば良い。JSHintは、Install — JSHintから、Rhino bundleをダウンロードして、任意のフォルダに保存すれば良い。以下、RhinoはRHINO_HOMEに、JSHintはJSHINT_HOMEにインストールしたとする。
  • OS: Windows 7
  • Rhino実行環境: Java 7
  • JSHint実行環境: Rhino 1.7R4
  • JShint (Rhino bundle): jshint-rhino-2.0.1.js

実行するには、次の形式でコマンドを実行すれば良い。-Dfile.encodingの値は、Lint対象のJavaScriptファイルのエンコーディングに一致させること。指定できるオプションについては、JSHint optionsを参照のこと。
java -Dfile.encoding=UTF-8 -jar RHINO_HOME\js.jar JSHINT_HOME\jshint-rhino-2.0.1.js [opt1=val1,opt2=val2,...] [global1=true,global2,global3,...] [list of files]
なお、オプションの書式は、jshint-rhino-2.0.1.jsの11000行目から始まる無名関数のコメントから抜粋。どうやら"="の有無でオプションかどうか判定している。以下はオプションの例。
camelcase=true,curly=true,eqeqeq=true,immed=true,indent=4,latedef=true,newcap=true,nonew=true,quotmark=single,strict=true,unused=true,undef=true,trailing=true,maxlen=80

References

2013年5月6日月曜日

Sinon.JSのテストダブルを使ったユニットテスト

はじめに

JavaScriptでの、テストダブルを使ったユニットテストの書き方について書く。テストランナーにはJsTestDriver, モックライブラリにはSinon.JSを使う。

ベースとなるコードには、Sinon.JS > Getting startedから、Spies, Stubs, Testing Ajax, Fake XMLHttpRequest, Fake Serverの5つを使う。これらのコードはそのままでは実行できない (テストランナーにJasmineやMochaを使う場合のテストメソッドが切り出されている) ので、JsTestDriverで実行できるように書き換えて、サンプルコードとする。

書き換えの際、Sinon.JS > Documentationや『テスト駆動JavaScript』を参考に、次のTIPSを導入する。
  • JsTestDriverとSinon.JSのアサーションの統合
  • サンドボックスの導入
環境は次の通り。「JsTestDriverでUnit Test + Code Coverage」とほぼ同じ。
  • OS: Ubuntu 12.10 (Xubuntu)
  • エディタ: gedit
  • テストランナー: JsTestDriver 1.3.5
  • モックライブラリ: Sinon.JS 1.6.0
  • テストブラウザ: Firefox 20.0

Spies

最初にスパイの書き方。テストコードの書き換えは素直なので、ここでアサーションの統合についても書く。

テスト対象関数は次の通り。この関数は、引数に渡された関数を一度だけ実行し、結果をキャッシュする。
function once(fn) {
  var returnValue, called = false;
  return function () {
      if (!called) {
          called = true;
          returnValue = fn.apply(this, arguments);
      }
      return returnValue;
  };
}
これに対するJsTestDriverのテストコードは次の通り。テストケースの書き換え自体には、特に注意するところはないと思う。

注意すべきは、アサーションの書き方。3つのテストケースで、書き方を変えている。1つ目と2つ目のテストメソッドの書き方は、統合不要。3つ目のテストメソッドの書き方には、統合が必要。それぞれの特徴はコメントを参照。
// JsTestDriverとSinon.JSのアサーションの統合
// 普通は全テストケースで共有するために、グローバルヘルパーで実行する
sinon.assert.expose(this);
 
TestCase('SpyExample', {
  'test calls the original function':function() {
    var callback = sinon.spy();
    var proxy = once(callback);
 
    proxy();

    // JsTestDriverのアサーションを使う場合。メッセージが不親切
    assertTrue(callback.called);
  },
 
  'test calls the original function only once':function() {
    var callback = sinon.spy();
    var proxy = once(callback);
 
    proxy();
    proxy();
 
    // Sinon.JSのアサーションを完全修飾して使う場合。メッセージがフレンドリィ
    sinon.assert.calledOnce(callback);
  },
 
  'test calls original function with right this and args':function() {
    var callback = sinon.spy();
    var proxy = once(callback);
    var obj = {};
 
    proxy.call(obj, 1, 2, 3);
    // Sinon.JSのアサーションを統合して使う場合。
    assertCalledOn(callback, obj);
    assertCalledWith(callback, 1, 2, 3);
  }
});
なお、統合は、sinon.assert.exposeで行っている。メソッド名などオプション引数でカスタマイズできるので、詳しくはリンク先を参照のこと(JsTestDriver以外のテストランナーと統合する場合にも要参照。アサーションを持つオブジェクトによって引数に渡すべき値を変えたり、統合先のテストランナーの失敗の扱い方によってsinon.assert.failのオーバーライドしたりする必要がある)。

Stubs

次に単純なスタブの書き方。テスト対象関数はSpiesと同じ。

テストコードは次の通り。これも特に注意するところはないと思う。
TestCase('StubExample', {
  'test returns the return value from the original function':function() {
    var callback = sinon.stub().returns(42);
    var proxy = once(callback);
 
    assertEquals(42, proxy());
  }
});

Testing Ajax

Ajaxスタブの書き方。ここではサンドボックスを導入する。

テスト対象関数は次の通り。Ajaxを簡単に取り扱うために、jQueryを使っている。
function getTodos(listId, callback) {
  $.ajax({
    url: "/todo/" + listId + "/items",
    success: function (data) {
      // Node-style CPS: callback(err, data)
      callback(null, data);
    }
  });
}
これに対するテストコードは次の通り。ポイントは2行目。スタブ化したグローバルオブジェクト$を復元しないと、他のコードに影響するかもしれない。そこで、sinon.testでテストメソッドをラップしてサンドボックス化している。
TestCase('AjaxExample', {
  'test makes a GET request for todo items':sinon.test(function(stub) {
    this.stub($, 'ajax');
    getTodos(42, sinon.spy());
 
    assertTrue($.ajax.calledWithMatch({url: '/todo/42/items'}));
  })
});
スタブへのアクセスは、this.stubで行う。『テスト駆動JavaScript』ではthisがないけれど、それだと動作しなかった。バージョンアップで変更になったのだと思う。

サンドボックス化すべきテストメソッドが複数ある場合は、代わりにsinon.testCaseでテストクラスをラップできる。次の次のFake Serverで使ってみる。

Fake XMLHttpRequest

続いて、XMLHttpRequestのスタブ。あえてサンドボックスを使わないで書くと、復元が面倒になるという例に使う。テスト対象関数は、Testing Ajaxと同じ。

テストコードは次の通り。setUp, tearDown内でテストダブルを自前で管理しなければならない。
TestCase('FakeXMLHttpRequestExample', {
  setUp: function() {
    this.xhr = sinon.useFakeXMLHttpRequest();
    var requests = this.requests = [];
 
    this.xhr.onCreate = function(req) {
      requests.push(req);
    };
  },
 
  tearDown: function() {
    // Like before we must clean up when tampering with globals
    this.xhr.restore();
  },
 
  'test makes a GET request for todo items': function() {
    getTodos(42, sinon.spy());
 
    assertEquals(1, this.requests.length);
    assertEquals('/todo/42/items', this.requests[0].url);
  }
});

Fake server

最後に、サーバのスタブ。テストケースのサンドボックス化を導入する。なお、これもテスト対象関数は、Testing Ajaxと同じ。

テストコードは次の通り。テストケースをサンドボックス化する場合はこうなるはず(ドキュメントに沿えばこうなると解釈したコードで、テストのパスは確認したがテストダブルの復元までは未確認)。
TestCase('FakeServerExample', sinon.testCase({
  'test calls callback with deserialized data': function(server) {
    this.server = sinon.fakeServer.create();
    var callback = sinon.spy();
    getTodos(42, callback);
 
    // This is part of the FakeXMLHttpRequest API
    this.server.requests[0].respond(
      200,
      {'Content-Type': 'application/json'},
      JSON.stringify([{id: 1, text: 'Provide example', done: true}])
    );
 
    assert(callback.calledOnce);
  }
}));

References

JsTestDriverでテストケースを書く

JsTestDriverで実行するためのテストケースの書き方について記載する。使うバージョンは、1.3.5。テストターゲットが同期処理の場合と非同期処理の場合のそれぞれについて、JsTestDriverのプロジェクトWikiをベースに記載する。なお、テストの実行方法については、「JsTestDriverでUnit Test + Code Coverage」に記載した。

テストケースの書き方には、次の2通りがある。Getting Started with JsTestDriverでは、プロトタイプを使っているが、『JavaScript実践入門』に習って、インライン宣言を使う。インライン宣言だと、テスト名に任意の文字列が使える。また、記述量も少なくなる。
  1. プロトタイプを使う
  2. インライン宣言を使う
Getting Started with JsTestDriverのサンプルコードを元に、インライン宣言を使ってテストケースのスケルトンを書くと、次のようになる。
TestCase('GreeterTest', {
  setUp:function() {
    // 必要に応じてセットアップ処理を実装する。
  },

  'test greet returns Hello World!':function() {
    // Set up
    var sut = new myapp.Greeter();

    // Exercise
    var actual = sut.greet('World');

    // Verify
    assertEquals('Hello World!', actual);
  },

  tearDown:function() {
    // 必要に応じてティアダウン処理を実装する。
  }
});

テストターゲットが非同期の場合は、TestCaseクラスではなくて、AsyncTestCaseクラスを使う。AsyncTestCase - js-test-driverのサンプルコードを元に、インライン宣言を使って書き直すと次のようになるはず(JsHintはおおよそ通したが未実行なので、修正が必要かもしれない)。また、高度な内容(コールバック関数の実行タイミングの制御など)はAsyncTestCase - js-test-driverを参照のこと。
AsyncTestCase('XhrTest', {
  'test XHR using callbacks':function(queue) {
    // Set up
    var xhr = new XMLHttpRequest();
    xhr.open('GET', '/some/path');

    var responseStatus;
    var responseBody;
    
    queue.call('Step 1: send a request to the server and save the response status and body', function(callbacks) {
      var onStatusReceived = callbacks.add(function(status) {
        responseStatus = status;
      });
      
      var onBodyReceived = callbacks.add(function(body) {
        responseBody = body;
      });

      xhr.onreadystatechange = function() {
        if (xhr.readyState === 2) { // headers and status received
          onStatusReceived(xhr.status);
        } else if (xhr.readyState === 4) { // full body received
          onBodyReceived(xhr.responseText);
        }
      };

      // Exercise
      xhr.send(null);
    });

    // Verify
    queue.call('Step 2: assert the response status and body matches what we expect', function() {
      assertEquals(200, responseStatus);
      assertEquals('hello', responseBody);
    });
  }
});

References

2013年5月4日土曜日

JsTestDriverでUnit Test + Code Coverage

はじめに

JsTestDriverで、JavaScriptのユニットテストを実行し、htmlレポートでカバレッジを確認する方法。環境は次の通り。
  • OS: Ubuntu 12.10 (Xubuntu)
  • エディタ: gedit
  • テストランナー: JsTestDriver 1.3.5
  • テストブラウザ: Firefox 20.0
  • カバレッジ・レポーター: LCOV 1.9
アウトラインは次の通り。なお、コードの書き方については深入りしない。JsTestDriverのProject Wikiを参照のこと。
  1. 初期設定
  2. 実行準備
  3. 実行
    • パスの場合
    • 失敗してデバッグの場合
  4. カバレッジ確認

初期設定

ディレクトリ構成は下記の通りとする。jarの配置がJsTestDriverおよびカバレッジ・プラグインのインストールに相当する。プロダクトコードgreeter.jsとテストコードgreetertest.jsの内容については、Getting Started with JsTestDriverを参照のこと。
.
├─ jsTestDriver.conf
├─ src
│   └─ greeter.js
├─ test-lib
│   └─ jstestdriver
│       ├─ JsTestDriver-1.3.5.jar
│       └─ plugins
│           └─ coverage-1.3.5.jar
├─ test-output
│   └─ coverage
└─ test-src
     └─ greetertest.js

設定ファイルjsTestDriver.confの内容は次のようになる。jsの読み込み順序をコントールしたい場合など、詳細については、ConfigurationFileを参照のこと。
server: http://localhost:4224

load:
  - src/*.js

test:
  - test-src/*.js

plugin:
 - name: "coverage"
   jar: "test-lib/jstestdriver/plugins/coverage-1.3.5.jar"
   module: "com.google.jstestdriver.coverage.CoverageModule"

実行準備

ユニットテストを実行する前に、テストサーバを起動しブラウザをキャプチャする必要がある。まずテストサーバを起動するために、シェルで次のコマンド実行する。
# オプション--portをjsTestDriver.confで指定したポート番号に一致させる
$ java -jar test-lib/jstestdriver/JsTestDriver-1.3.5.jar --port 4224
続いて、ブラウザからhttp://localhost:4224/captureにアクセスする。

コマンドライン・フラグで、シェルからテストサーバを起動すると同時に、ブラウザをキャプチャすることもできる。詳細については、CommandLineFlagsを参照のこと。

実行

シェルで次のコマンドを実行する。
$ java -jar test-lib/jstestdriver/JsTestDriver-1.3.5.jar --tests all --testOutput test-output

パスの場合

テストをパスするなら、次のような結果が返ってくる。カバレッジはファイルにしか出力されない。簡易にコンソールで確認したいなら、オプション--testOutputを外しておく。
setting runnermode QUIET
Firefox: Reset
Firefox: Reset
.
Total 1 tests (Passed: 1; Fails: 0; Errors: 0) (0.00 ms)
  Firefox 20.0 Linux: Run 1 tests (Passed: 1; Fails: 0; Errors 0) (0.00 ms)
加えて、test-outputにテスト結果(JUnit XML形式)とカバレッジ(LCOV互換)が出力される。
  • TEST-Firefox_200_Linux.GreeterTest.xml
  • jsTestDriver.conf-coverage.dat

失敗してデバッグの場合

失敗すると、次のような結果が返ってくる。カバレッジ用と思われるLCOV.jsの出力がうるさいけれど、カバレッジ有無を簡単に切り替えられるかどうか未確認。
setting runnermode QUIET
Firefox: Reset
Firefox: Reset
F
Total 1 tests (Passed: 0; Fails: 1; Errors: 0) (1.00 ms)
  Firefox 20.0 Linux: Run 1 tests (Passed: 0; Fails: 1; Errors 0) (1.00 ms)
    GreeterTest.testGreet failed (1.00 ms): AssertError: expected "Hello World!" but was "Hell World!"
      GreeterTest.prototype.testGreet@http://localhost:4224/test/src-test/greeter_test.js:7
      runTest@http://localhost:4224/test/com/google/jstestdriver/coverage/javascript/LCOV.js:203
      TestResultIterator.prototype.runNext@http://localhost:4224/test/com/google/jstestdriver/coverage/javascript/LCOV.js:292
      InstrumentedTestCaseRunner.prototype.run@http://localhost:4224/test/com/google/jstestdriver/coverage/javascript/LCOV.js:250
      InstrumentedTestCaseRunnerPlugin.prototype.runTestConfiguration@http://localhost:4224/test/com/google/jstestdriver/coverage/javascript/LCOV.js:221

失敗したメソッドをデバッグするため、そのメソッドのみを実行するために、シェルで次のコマンドを実行する。
$ java -jar test-lib/jstestdriver/JsTestDriver-1.3.5.jar --tests GreeterTest.testGreet
デバッグするのにconsole.log()で十分なら、コマンドラインフラグに --captureConsoleを付けてテストを実行すればよい。

ブレークポイントが必要なときは、ブラウザのデバッガを利用する。そのためには、キャプチャしたブラウザのデバッガ (Firefoxなら[ツール] > [Web開発] > [デバッガ]、あるいはFireBug) を開き、ブレークポイントを設定すればよい。再度、上記コマンドを再実行すると、ブレークポイントで停止するので、デバッグできる。

console.log()にせよブラウザのデバッガにせよ、ここでもカバレッジ用のコードが挿入されて煩わしいが、カバレッジ有無を簡単に切り替えられるかどうか未確認。

カバレッジ確認

カバレッジデータjsTestDriver.conf-coverage.datの/./を/に置換しておく。本来的には不要な作業だが、置換しておかないとカバレッジのhtmlレポートがリンク切れを起こす。恐らくIssue 367と同じ問題が発生していると思われる。

htmlレポートを生成するには、シェルで下記コマンドを実行する。test-output/coverage以下に多数のファイルが生成されるが、index.htmlがエントリーポイント。genhtmlの詳細については、Linux Test Project - Coverage » lcovを参照のこと。
$ genhtml -o test-output/coverage -f test-output/jsTestDriver.conf-coverage.dat

雑感

カバレッジを測ろうとすると、出力が汚くなる。特にデバッグコードに測定用コードが挿入されてしまうのが煩わしい。簡単に切り替えられると良いのだけれど、カバレッジ有無がコマンドライン・フラグではなくて設定ファイルのようなので面倒。設定ファイルの切り替えにしようとすると、ほぼ複製になってしまう。

Issue 367のgenhtmlで生成するHTMLレポートのリンクが切れる問題は、置換してから渡すようなワンライナーで当座はしのげそう。jsTestDriverというよりLCOVの問題だけれど、変更履歴を見ると1.10では解決されていそう。
- Fixed directory prefix calculation
Linux Test Project - Coverage » lcov

2013年5月2日木曜日

JavaScriptのUnit Test Tool

まとめた先から情報が古くなりそうで躊躇していたけれど、JavaScriptのUnit Testツールについて、最近見かけた情報をまとめてみる。


『JavaScript Unit Test Why? What? How?』はUnit Testツールを次の4レイヤで分離している。特定レイヤのみの機能を提供するツールもあれば、複数レイヤの機能を提供するツールもある。図でテスティングフレームワークがさらに分かれているのは、Mochaが好みのAssertionライブラリ (Chaiなど)を使える設計になっているため。
  • モックライブラリ
  • テスティングフレームワーク
  • リモートテストランナー
  • 実行環境

これらのツールは組み合わせて使うことができる。フレームワークで実践! JavaScriptテスト入門では、次の4パターンを紹介している。また、『JavaScriptの開発効率を高める7つのライブラリ』では、JasmineとSino.JSを組み合わせている。

上記連載の著者のスライド『JavaScriptテストフレームワークを諸々眺めてみる』では、上記の他に次のテストフレームワークを紹介している。

また、モックライブラリSinon.JSについては、Sinon.JSが詳しい。『JavaScriptの開発効率を高める7つのライブラリ』では、モック機能を持つJasmineと組み合わせているし、Sinon.JSが定番という理解でよさそう。

この他のツールとして、上記リンク先の複数がBuster.JSを挙げている。Buster.jsはJsTestDriverと同じレイヤをカバーしていて、Sinon.JSをバンドルしているので、カバー範囲が広い。Vowというものもあるらしい。それから、Yahoo!のYUI TestがIBM developerWorksで紹介されている。

書籍に目をやると、定番とおぼしき2011年発売の『テスト駆動JavaScript』で主に取り扱っているのは、JsTestDriver。それから、最近日本語訳が発売された『メンテナブルJavaScript』では「19章 自動テスト」で次の4つのツールを紹介している。どちらも未読なので、どれくらい詳しく紹介されているかは未確認。
  • YUI.Test.Selenium.Driver
  • Yeti
  • PhantomJS
  • JsTestDriver

上記をざっと見ると、jsTestDriverの支持率が高そう。それから、比較の際に着目されているのは、次の4点。個人的には、加えて出力形式も気になるところ。
  • スタイル: TDD? BDD?
  • 実行環境: 実ブラウザ? ヘッドレスブラウザ?(=Phantomjs) シミュレータ?
  • 非同期対応
  • CI対応

以下、余談。別の観点では、そもそもテスタビリティの低いコードは、悪いコードだというわけで、Lintや静的解析ツールも有用そう。Lintだと、JSLint, JSHint, Closure Linterが有名そう。静的解析ツールはあまり見当たらない。jsmeter, complexityReport.jsあたりか?

2013/05/05追記:
静的解析ツールにplatoというツールも見つかった。

2013/05/09追記:
カバレッジ計測に限れば、JSCoverというJSCoverageの後継のツールもある。

2013年3月1日金曜日

インスタンスを生成するメソッドの名前

インスタンスを生成するメソッドの名前について、よく迷うのでまとめておく。

まずは、スタティックファクトリメソッド。"Effective Java (2nd Edition)"の"Item 2: Consider Static Factory Methods instead of Constructors"に倣って使うようにしているけれど、感覚で名前をつけてしまうことがあるので、気をつけないと。
  • valueOf: パラメータと同じ意味合いの値を持つインスタンスを返す。Integer#valueOf(int)はじめ、型変換メソッドが典型例。
  • of: valueOfと同じ。EnumSetで使われている。
  • getInstance: 指定したパラメータから組み立てたインスタンスを返す。Singletonパターンでよく使われる。
  • newInstance: getInstanceと同様だけれど、必ず新しいインスタンスを返す。だからSingletonパターンでは使えない。
  • getType: getInstanceと同様だけれど、ファクトリメソッドが他のクラスにある場合に使う。Typeは返値の型名で置き換えて使う。
  • newType: newInstanceと同様だけれど、ファクトリメソッドが他のクラスにある場合に使う。Typeは返値の型名で置き換えて使う。

続いて、インスタンスの型変換メソッド。つまりインスタンスを元に別のクラスのインスタンスを作っているメソッドの名前についてもまとめておく。いずれもTypeは変換後の型名で置き換えて使う。こちらは"Item 56: Adhere to Generally Accepted Naming Conventions"の一部。
  • toType: 異なった型の独立したオブジェクトを返す。例えば、Object#toString() 。
  • asType: そのオブジェクトの異なる型での表現を返す。adapter パターン (Effective Java ではView) で使う。例えば、Arrays#asList(T... a)。
  • typeValue: プリミティブ型を返す。例えば、Integer#intValue()。

References

2013年2月6日水曜日

Selenium 2.xで条件が満たされるまで待つ

Selenium 2.X (確認したのは2.28) で何か条件が満たされる(例えば、JavaScriptが実行されてロード時はクリックできない要素がクリックできるようになる)まで待つには、WebDriverWait#until(Predicate)を使う。
WebDriverWait wait = new WebDriverWait(driver, 10);
WebElement element = wait.until(ExpectedConditions.elementToBeClickable(By.id("id")));
引数に渡しているインスタンスが条件を表している。よくある条件はExpectedConditionsクラスに用意されている。not(ExpectedCondition)もあるのでたいていの場合はこれで事足りる。

単に待つだけじゃなく、条件を満たした時のインスタンスを返してくれるのが便利。

独自の条件が必要な場合は、ExpectedCondition<T>インタフェースを実装して、applyメソッドをオーバーライドする。その返値の型は、インタフェースの型パラメータで指定する。nullでもfalseでもない値が返されると、条件を満たしたと判定される。

車輪の再発明だけれど、上記の例を無名クラスを使って実装すると、次のようになる。
WebDriverWait wait = new WebDriverWait(driver, 10);
WebElement element = wait.until(new ExpectedCondition(){
      @Override
      public WebElement apply(WebDriver d) {
      return d.findElement(By.id("id"));
}});

References

Selenium 2.xでHTML要素の非存在をチェックする

Selenium 2.X (確認したのは2.28) で指定したHTML要素が存在しないことをチェックするには、WebDriver#findElements(By)を使う。返値 (List) の長さが0なら、存在しない。例えば、hogeクラスが指定されたdiv要素が存在しないことをチェックするなら、下記のようになる。

if (driver.findElements(By.cssSelector("div.hoge")).size() == 0) {
  // hogeクラスが指定されたdiv要素は存在しない
}
このことは、WebDriver#findElements(By)じゃなくてWebDriver#findElement(By)のJavadocに書いてある。ちょっと回りくどい上に、使うメソッドのJavadocには特に何も書いてなくて、よく見失う。
findElement should not be used to look for non-present elements, use findElements(By) and assert zero length response instead.
WebDriver#findElement(By)
1.XのAPIだと、Selenium#isElementPresent(String)というそのものズバリのメソッドがある。後方互換性があるから今でも動作するけれど、それを使うのにはパッと思いつくだけでデメリットが3つある。というわけで、なるべく2.XのAPIで統一していきたい。
  • 1.XのAPIは要素の指定で、バグを作りやすい ("locatorType=argument"フォーマットのStringで、"locatorType="の省略時動作に暗黙のルールがある)
  • APIのスタイルが混在すると、リーダビリティが下がる
  • 古いAPIはいつまでも使い続けられるとは限らない

References

2013年1月13日日曜日

EclipseプロジェクトをEGitでGitHubにPushする

『JUnit実践入門』の演習問題を、手を動かして解いてみようと思ったのは良いのだけれど、そのときEclipse上のコードをGitHub上のリモートリポジトリに登録するのに手間取った。まだ未整理の部分があるけれど、次に似た作業をするまで間がありそうなので、メモを残しておく。

なお、バージョン情報は下記の通り。
  • Eclipse 3.8
  • EGit 2.2

下記の手順で、GitHub上のリモートリポジトリso-c/junit-introduction · GitHub以下に、Eclipse上の複数プロジェクト(チュートリアル用、各章の演習用)を登録できた。ただ、コミットログに残っている通り試行錯誤していたから、不正確な部分があるかもしれない。
  1. Eclipse上でプロジェクトを作成する。ここでは、"junit-practice"を使う。
  2. 作成したプロジェクトのコンテキストメニューから、[Team] > [Share Project]を選択する。
  3. "Share Project"ウィンドウが開くので、"Git"を選択して[Next]を押す。
  4. "Configure Git Repository"ウィンドウが開くので、下記の通り入力する。
    • 最初のプロジェクトでは下記を実行して、リポジトリを作成する。
      1. "Repository"横の[Create]ボタンを押す
      2. "Create a Git repository"ウィンドウが開くので、下記のように入力し、[finish]を押す。
        • Parent Directory: /home/so_c/git
        • name: junit-practice
    • 2つ目以降のプロジェクトでは、"Repository"で作成したリポジトリを選択する。
  5. "Configure Git Repository"ウィンドウのリストに作成したリポジトリが表示されるので、チェックし[finish]を押す。
これで、Eclipseのプロジェクトがローカルリポジトリで管理されるようになる。何かコミットしたとして、リモートリポジトリにPushするには、次の2種類の方法がある。空のリモートリポジトリを予め用意しておくこと。
  • Push: プロジェクトのコンテキストメニューの[Team] > [Remote] > [Push]から使用する。EGitメモ(Hishidama's Eclipse Git Memo) 新規プロジェクトの作成方法を参照。
  • Configure Push: プロジェクトのコンテキストメニューの[Team] > [Push to Upstream]から使用する。事前に設定が必要。EGit/User Guide - Eclipsepediaに書いてあると思うのだけれど気が付かず、端末からgit remote add
  • でリモートレポジトリ追加して、[Team] > [Remote] > [Consigure Push to Upstream]から設定してしまった。
なお、プロジェクトのルートをリポジトリのルートに対応させる場合は、4の手順が次のように変わるはず。通常はこちらの方をよく使うと思う。今回は、リポジトリ内で、チュートリアル・演習問題の各章を違うプロジェクトにしたかったから、1リポジトリ複数プロジェクト構成にした。
  1. "Use or create repository in parent folder of project"をチェック
  2. ローカルリポジトリの場所を入力し、[Create Repository]を押す (プロジェクトフォルダの親フォルダが既にリポジトリなら、不要のはず)
  3. [Next]を押す

References

2013年1月9日水曜日

"Object Calisthenics" (『オブジェクト指向エクササイズ』)

"Object Calisthenics" (『オブジェクト指向エクササイズ』) を読んだ。最初にルールだけを知ったときは、無茶を感じたけれど、そもそも何のためのルールで、各ルールがなぜ設定されているか、を知ったら納得できた。

"Object Calisthenics"のルールは、遵守すべきコーディング規約でも良い設計・コードのための原則でもない。オブジェクト指向のコンセプトを自分のものにして実践できるようにするためのエクササイズに使うギプスのようなものだ。それも、少なくとも自分にとっては、かなり強力なギプスだ。

ルールは全部で9つある。いずれのルールも、オブジェクト指向のコンセプトは知っている上で、実践に移すのを助けるためのものなので、かなり具体的。長くなるので、個々のルールには立ち入らないけれど、覚え書きを兼ねて()内に私訳を付けておく。
  1. One level of indentation per method(各メソッド、インデントは1段階)
  2. Don't use the ELSE keyword(else句は使わない)
  3. Wrap all primitives and Strings(プリミティブ型と文字列にはラッパークラスを作る)
  4. First class collections(コレクション型にもラッパークラスを作る)
  5. One dot per line(メソッドをチェーンさせない)
  6. Don't abbreviate(略語は使わない)
  7. Keep all entities small(クラスは50行以下、パッケージは10クラス以下を保つ)
  8. No classes with more than two instance variables(インスタンス変数は2つまで)
  9. No getters/setters/properties(ゲッター/セッターあるいはプロパティを使わない)
これらのルールは、いずれも凝集度を上げ結合度を下げ、最小化した責務を局所化し、情報隠蔽・ポリモーフィズムを実践を助けてくれる。1, 2はポリモーフィズムを推し進め、3, 4, 5, 9は問題領域外の情報を隠蔽し、6, 7, 8は責務の割り当てを最小化・局所化する。

その結果、再利用性もメンテナビリティも高いクラスになるので、保守・運用が容易になる。リーダビリティ・テスタビリティも上がるので、開発者にとっても嬉しい。

著者によると、100%これらのルールに従って20時間くらいかけて1000行ほどのコーディングすると、これまでの習慣(手続き指向)から抜け出せるとのこと。自分は割と本を読む方だけれど、どれだけ読んでも手を動かしていない内容は簡単に忘れてしまう。100%は無理そうだけれど、意識してみるつもり。

References


2013年1月2日水曜日

unittestの実行方法

Python 2.7.3のunittestでテストを実行する方法。実行方法には2種類ある。
  • コマンドラインインタフェース(CLI): テストモジュール名などを指定して実行
  • テストディスカバリ: テストモジュール名のパターンを指定して実行
CLIは、特定のモジュールのデバッグに便利。ディスカバリはコミット前などの回帰テストに便利。

CLIでは次のように、モジュール名・クラス名・メソッド名のいずれかを修飾子込みで指定する。
$ python -m unittest test_library.test_module
$ python -m unittest test_library.test_module.TestClass
$ python -m unittest test_library.test_module.TestClass.test_method
空白区切りで複数のテストを指定することもできる。
$ python -m unittest test_library.test_module1 test_library.test_module2

テストディスカバリでは、サブコマンドdiscoverを使用する。下記コマンドで、project_top_directory以下の"test*.py"にマッチするテストファイルが実行される。
$ cd project_top_directory
$ python -m unittest discover
対象ディレクトリやテストファイル名のパターンを変更したい場合は、オプション-s, -p, -tを用いる。
  • -s: テストモジュール検索開始ディレクトリ
  • -p: テストファイル名のパターン
  • -t: プロジェクトのトップディレクトリ

References