テスト戦略やテストアプローチについて調べていたら、テストピラミッドというコンセプトに行き当たった。自分の考えを整理するのにも、他人に考えを伝えるのにも使えそうなのでメモしておく。
考えたのはMike Cohnという人。見覚えがあると思ったら、『アジャイルな見積りと計画づくり』の著者だった。初出は書籍"Suceeding with Agile"。まだ読めていないけれど、Martin FowlerのTestPyramidをとっかかりに調べて分かった範囲で書く。
要は、「Unit test > Subcutaneous Test (サービス層のテスト) > End-to-end test」とピラミッド状になるようにしろよという話。
メソッドを直接叩ける高速で柔軟なUnit testじゃないと、ビジネスロジックの複雑な組み合わせをテストしきれない。GUI操作を経由する低速で融通が利かない (≒目的のテストを書き辛い) End-to-endテストでは、効率が悪すぎる。Unit testの必要性については、アジャイル開発において、技術と品質の重要性は不可欠だ(後編)。Agile Japan 2013 - Publickeyに詳しい。
将来、End-to-endテストも高速になるだろうし、融通が利かないという見方には「レコーディングツールを使えば簡単にテストを書ける」という反論が考えられるけれど、Recordedテストは壊れやすく、壊れてしまったら再レコーディングするハメになる。「レコーディングツールで最初は簡単にテストを書けるという特徴」は、Ice Cream Cornというアンチパターンを招きがちという意味で、危険でさえある。なお、テストの維持コストやRecorded Testの問題については、"xUnit Test Patterns"が詳しい。その上、仮に、End-to-endテストが高速になって、保守性の高いテストを書けたとしても、Non-determinism problemsがあると言っている。
各テストレベルのテスト目的は異なるはずなので、各テストレベルのバランスの取れたテスト戦略・テストアプローチ(Test portfolio) を取るのが健全だと言っている。(少なくとも最初は)簡単だからという理由でEnd-to-endテストの代わりに、Martin FowlerはSubcutaneous test (ユーザーインターフェースのすぐ下から操作するテスト)を推奨している。RESTインタフェースを備えたWebアプリケーションの場合、ブラウザを操作してフォームに入力してパラメータを組み立てる代わりに、REST APIを叩くイメージだろうか。テストコード中でこれを使えば、End-to-endテストのテスト目的 (Test Object) をほぼ満たしつつ、UIに起因する問題を回避できるとしている。
高レベルテストは第二防衛ラインなので、もし失敗したらプロダクトコードを直すだけでなく、ユニットテストを追加すべきだと言っている。
2014年7月30日水曜日
2014年4月2日水曜日
UbuntuへのRのインストール
Ubuntu 13.10にR3.0.3とRStudioをインストールした時の手順を残しておく。
CRANのインストール手順に従って、Rをインストールする。
まず/etc/apt/sources.list に下記のような1行を追記して、リポジトリを追加する。
ただし、<my.favorite.cran.mirror>はをミラーサイト一覧参照して適当なミラーサイトに置き換える。
また、末尾の(saucy/)はUbuntuのバージョンに合わせたコードネームに書き換える。コードネームは下記コマンドで確認できる。
例えば、筑波のミラーからUbuntu 13.10 (saucy)にインストールする場合、次の様になる。
リポジトリへのアクセスに必要な公開鍵を取得する。
リポジトリからRをインストールする。
インストール確認。次のコマンドでRが起動したらOK。
続けて、推奨Rパッケージもインストールしておく。
CRANのインストール手順に従って、Rをインストールする。
まず/etc/apt/sources.list に下記のような1行を追記して、リポジトリを追加する。
deb http://<my.favorite.cran.mirror>/bin/linux/ubuntu saucy/
ただし、<my.favorite.cran.mirror>はをミラーサイト一覧参照して適当なミラーサイトに置き換える。
また、末尾の(saucy/)はUbuntuのバージョンに合わせたコードネームに書き換える。コードネームは下記コマンドで確認できる。
cat /etc/lsb-release
例えば、筑波のミラーからUbuntu 13.10 (saucy)にインストールする場合、次の様になる。
deb http://cran.md.tsukuba.ac.jp/bin/linux/ubuntu saucy/
リポジトリへのアクセスに必要な公開鍵を取得する。
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E084DAB9
リポジトリからRをインストールする。
sudo apt-get update sudo apt-get install r-base
インストール確認。次のコマンドでRが起動したらOK。
R
続けて、推奨Rパッケージもインストールしておく。
sudo apt-get install r-cran-*
References
2013年12月9日月曜日
Javaのアサーション (assert文) の使いどころ
Javaのアサーションについて、少し調べたことをメモしておく。『言語設計者が考えること』の「12章 Java」で、Javaプログラマへの助言として
アサーションを使用したプログラミング (Java言語仕様) によると、使いどころは次の3つ。リンク先には具体例がもあるので、これを見るとおおよその想像がつく。
この内、3つ目の「事前条件、事後条件、およびクラスの不変条件」のさらに「事前条件」には「publicメソッドの引数チェックに使ってはいけない」という但し書きが付く。publicメソッドの引数が満たすべき事前条件はAPI仕様の一部だというのがその理由。『Java セキュアコーディングスタンダード 』の「MET01-J. メソッドの引数の検証にassertを使わない」に詳しい。
「適切な例外をスローする」とあるけれど、例外は使わないで通常の制御フローを前提にアサーションを使うのか、それともアサーション以外の例外を使うのか、一般解は存在しない。例外設計における大罪や『Effective Java 2nd Edition』の「項目57: 例外的状態にだけ 例外を使用する」が参考にはなるけれど、最後は作ろうとしているソフトウェアがどう振る舞うべきか、自分で考えるしかなさそう。
というわけで、指針は掴めたけれど、簡単な話ではない。
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への追加を想定している。使用したバージョンは次の通り。
サンプルには含まれていないけれど、一行目にXML宣言を追加しておく方がベターだと思う。
CIするためにAntタスクとして定義したい。
ここまでやって、Quizのコードスニペットを含むクラスでFindBugsを実行してみたら、期待通り動作した。めでたし、めでたし。
以下は、覚え書き。
このチュートリアルの"Debugging"はまだ書かれていない。テストケースの簡単な説明はFindbugsTestCasesにあるけれど、ここを読むだけではちょっと分りそうにない。実際のテストケースを見ながら、試行錯誤が要りそう。
チュートリアルではなぜこうするのか分らないけれど、Plugins4FindBugsで補完できる。Detector Pluginの概要が記載されている。
Eclipse Pluginに特化した内容として、コメントでFindBugs In Eclipse Tutorialが紹介されているのに、後から気がついた。ただ、FindBugs 1.3.9よちょっと情報が古い。
- 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
- FindBugs: 公式サイト
- FindBugs 第 1 回: コード品質を改善する, FindBugs 第2回: カスタムのチェック機能を書く: IBM Developper Worksの関連エントリィ
- FindBugsのバグパターンを実装する - eller's blog: ノウハウが簡潔にまとまっている
2013年8月21日水曜日
Javaのデコンパイラと難読化ツール
Javaのクラスファイルをデコンパイルしたソースコードは読みやすいと聞いて、「どれくらいソースを再現できるのか?」と「難読化したらどれくらいデコンパイル結果が読み辛くなるのか?」について試してみたくなった。というわけで、まずはデコンパイラ (decompiler) と難読化ツール (obfuscator) ついて調べてみた。今回はツールの調査だけで、実行方法についてはおいおい。なお、調査方法は、GoogleとStack Overflow頼み。
デコンパイラはJava Decompilerでいいのかな。デコンパイラとして真っ先に思い浮かんだJADは、更新が止まっていた。Java DecompilerもEclipseプラグイン・JD-Eclipseが最近のEclipseだと動かないようだけれど、少なくとも、Mchr3k - JDEclipse-Realignというフォークがメンテナンスされている。
難読化はProGuardが良さそう。予備知識が無いので手探りだけれど。こちらは現在もアクティブな様子。最近はAndroidアプリの難読化に力を入れているよう。
ところで、難読化すると、クラスやメソッドなどの名前が変わるから、リフレクションAPIを使っていると実行時例外が送出される。けれど、ProGuardのIntroduction > Reflectionによると、典型的なリフレクションAPIに対応しているみたい。
デコンパイラはJava Decompilerでいいのかな。デコンパイラとして真っ先に思い浮かんだJADは、更新が止まっていた。Java DecompilerもEclipseプラグイン・JD-Eclipseが最近のEclipseだと動かないようだけれど、少なくとも、Mchr3k - JDEclipse-Realignというフォークがメンテナンスされている。
Java Decompiler (Yet another Fast Java decompiler) has:ともあれ、こちらはすんなり使えた。クラスファイルからほぼソースそのままにデコンパイルできてビックリ。Live Demoもある。
decompiler - How do I "decompile" Java class files? - Stack Overflow
- explicit support for decompiling and analyzing Java 5+ “.class” files.
- a nice GUI:
難読化は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#)に使用例として「メソッドが複数の値を返すようにする場合に便利」だなんて書かれている。同時に扱いたい複数の値があるなら、クラスとしてまとめた方が読みやすくないだろうか。原則として、入力を与えたら戻り値が返ってくる関数として考えたい。
これに真っ向から対立しているのが、Out parameter。状態を変えるために渡す可変オブジェクトのことを指す。Webを検索してみると、アンチパターンとして紹介されている記事が見つかる。
- Javaプログラマが知るべき9のこと - @katzchang.contexts
- privateメソッドは不要、出力パラメータに御用心 - kagamihogeのblog
- 技術トピックス|やってはいけないJavaプログラミング(4/4) | ウルシステムズ株式会社 | UL Systems, Inc.
でも、"Collectiong parameter"として『実装パターン』や『パターン指向リファクタリング入門』で紹介されていたりする。また、『パターン指向リファクタリング入門』では、その延長線上に"Visitor Pattern"を適用したパターンも紹介されている。
- Collecting Parameter(コレクティングパラメータ) - Strategic Choice
- Collecting Parameterによる累積処理の書き換え - Strategic Choice
- Visitorによる累積処理の書き換え - Strategic Choice
確かにこちらの方が合理的なケースもありそう。でも、基本的にはあまり使わない方がよさそう。コマンドとクエリは分離するのが原則だ。使うなら、Javadocに明記するなりしないと混乱を呼びそう。C#のoutキーワードに相当するものがJavaにはないから、引数の状態を変えるかどうか宣言だけから読み取れない。
ところで、out (C#)に使用例として「メソッドが複数の値を返すようにする場合に便利」だなんて書かれている。同時に扱いたい複数の値があるなら、クラスとしてまとめた方が読みやすくないだろうか。原則として、入力を与えたら戻り値が返ってくる関数として考えたい。
References
2013年7月6日土曜日
モンスターメソッドを解体する
『レガシーコード改善ガイド』の第22章「モンスターメソッドを変更する必要がありますが、テストを書くことができません」を自分なりに整理して、モンスターメソッドへの対処方法を要約してみる。
次に、モンスターメソッドを読み込んで、狙いを定める。どこまで狙いを絞り込めるかは、モンスターメソッドの作りと必要な変更によってケースバイケースになる。少なくとも自分にとっては、技芸の世界で、筋道立った説明をできない。
狙いが定まったら、検証方法を考える。この時、検証対象が一意に識別できなかったり (例: 別のブランチから同じ結果が返ってくる) 、テストから見えなかったりしたら、〈検出用変数〉または〈クエリメソッド〉を導入する。この時点では、これ以上の編集は避けた方が無難だと思う。やり過ぎると〈編集して祈る〉のと変わらない。
メソッドを抽出するときには、抽出後に残るメソッドの形を想像しておくと、どういう単位で抽出すればいいか方針を決めやすい。残る形には、〈骨組みメソッド〉と〈処理シーケンス〉の2種類がある。
〈骨組みメソッド〉は制御構造だけを持っていて、条件式や各条件下の処理はメソッドに任せてしまう。『実装パターン』だとConditionalに対応する。『デザインパターン』で例えるとFacadeやMediatorのイメージになる。
もう一つの〈処理シーケンス〉は単に順番にメソッドを呼び出していく。『リーダブル・コード』の無関係の下位問題の抽出を行うイメージ。「いちいちメソッドにするほどの問題ではないんじゃ?」と思っても、メソッドにして意図に即した名前を付けておくとリーダビリティが上がるはず (『実装パターン』のExplaining Message)。
どちらが適しているかは、モンスターメソッドの元々の役割によって変わる。大抵の場合、モンスターメソッドは、実装コストが低いところに追加が集中した結果であって、最初からモンスターメソッドとして生まれたわけではない。処理の振り分けが役割だったなら骨組みメソッド、抽象度の高いAPI提供が目的だったなら処理シーケンスを残す方が自然だと思う。
どういう形を残す決めたら、メソッドを抽出する前に、抽出したメソッドのテストを書く。先にテストを書けばレガシーコードは生まれない。アジャイルじゃなくたって、TDDした方がいいと思う。
何をどこまでテストすれば?
まず、どういう種類のテストを書こうとしているか考える。それには、第13章「変更する必要がありますが、どんなテストを書けばよいのかわかりません」が役に立つ。そこでは、〈仕様化テスト〉と〈狙いを定めたテスト〉が紹介されている。〈狙いを定めたテスト〉を書くのが現実的だろう。モンスターメソッドの振る舞いは多様だから、いきなり全体を対象とした〈仕様化テスト〉は書けないことが多そう。次に、モンスターメソッドを読み込んで、狙いを定める。どこまで狙いを絞り込めるかは、モンスターメソッドの作りと必要な変更によってケースバイケースになる。少なくとも自分にとっては、技芸の世界で、筋道立った説明をできない。
狙いが定まったら、検証方法を考える。この時、検証対象が一意に識別できなかったり (例: 別のブランチから同じ結果が返ってくる) 、テストから見えなかったりしたら、〈検出用変数〉または〈クエリメソッド〉を導入する。この時点では、これ以上の編集は避けた方が無難だと思う。やり過ぎると〈編集して祈る〉のと変わらない。
テストを書く
ここまでで、何のためにどの範囲でどんな検証をしないといけないか、具体的になってくるはず。ここまで来たら、テストを書き始める。書き始めたはいいが、モンスターメソッドを持っているクラスをインスタンス化できなかったり、モンスターメソッドの引数をインスタンス化できなかったりすることが多いと思う。その場合は、別の章を参照して対処する (ここでは深入りしない)。- 第09章 このクラスをテストハーネスに入れることができません
- 第10章 このメソッドをテストハーネスで動かすことができません
変更とメソッド抽出
無事にテストが書けたら、実行してパスすることを確認する。この後、必要な変更を行うか、〈メソッドの抽出〉を行うかは、悩ましいところ。変更が局所的なら先に変更してもいいと思う。逆に変更がモンスターメソッドのあちこちに散らばるようなら、先にメソッドを抽出しておかないと後が苦しくなる。いずれにせよ、ここでは変更については触れずに、メソッドの抽出についてまとめる。メソッドを抽出するときには、抽出後に残るメソッドの形を想像しておくと、どういう単位で抽出すればいいか方針を決めやすい。残る形には、〈骨組みメソッド〉と〈処理シーケンス〉の2種類がある。
〈骨組みメソッド〉は制御構造だけを持っていて、条件式や各条件下の処理はメソッドに任せてしまう。『実装パターン』だとConditionalに対応する。『デザインパターン』で例えるとFacadeやMediatorのイメージになる。
もう一つの〈処理シーケンス〉は単に順番にメソッドを呼び出していく。『リーダブル・コード』の無関係の下位問題の抽出を行うイメージ。「いちいちメソッドにするほどの問題ではないんじゃ?」と思っても、メソッドにして意図に即した名前を付けておくとリーダビリティが上がるはず (『実装パターン』のExplaining Message)。
どちらが適しているかは、モンスターメソッドの元々の役割によって変わる。大抵の場合、モンスターメソッドは、実装コストが低いところに追加が集中した結果であって、最初からモンスターメソッドとして生まれたわけではない。処理の振り分けが役割だったなら骨組みメソッド、抽象度の高いAPI提供が目的だったなら処理シーケンスを残す方が自然だと思う。
どういう形を残す決めたら、メソッドを抽出する前に、抽出したメソッドのテストを書く。先にテストを書けばレガシーコードは生まれない。アジャイルじゃなくたって、TDDした方がいいと思う。
さらに分割
ここまで来たら、細かい粒度でテストできるようになっている。でも、モンスターメソッドを持っているオブジェクトは、得てしてGod Objectだったりする。抽出したメソッドを〈メソッドオブジェクト〉として取り出して、委譲するように修正することも考えていく。つまり、『レガシーコード改善ガイド』の〈メソッドオブジェクトの取り出し〉、『リファクタリング』のReplace Method with Method Objectを行う。References
登録:
投稿 (Atom)