2015年8月2日日曜日

JUnitのRunner (Enclosed, Theories, Categories) を併用したときの動き

JUnit4の次のRunnerの関係を整理してみた。なお、JUnitのバージョンは4.12。
  • Enclosed
  • Theories
  • Categories

論理的にテストクラスを分類しつつ、並列化するための分類もしたくなることがあるので、JUnitの仕組みでどこまでできるか知っておきたくて。

では、早速。

EnclosedとTheoriesは併用できる。外部クラスEnclosingTheoryをJUnit実行すれば、内部クラスEnclosedTheoryがJUnit実行される。これでTheoriesを使いたいけれど、テストクラスを分けたくない場合は大丈夫。
@RunWith(Enclosed.class)
public class EnclosingTheory {
    @RunWith(Theories.class)
    public static class EnclosedTheory {
        @Theory
        public void testTheory(Fixture f) throws Exception {
            // test method.
        }
    }
}

EnclosedとCategoriesは併用できない。外部クラスEnclosingTheoryにカテゴリOuterを付けて、Categoriesを使ったテストスイートを実行しても、テストが見つからない。
@RunWith(Enclosed.class)
@Category(Outer.class)
public class EnclosingTheory {
    @RunWith(Theories.class)
    public static class EnclosedTheory {
        @Theory
        public void testTheory(Fixture f) throws Exception {
            // test method.
        }
    }
}
@RunWith(Categories.class)
@IncludeCategory(Outer.class)
@SuiteClasses(EnclosingTheory.class)
public class CategorizedTestSuite {
    // NoTestsRemainException is thrown.
}

ただ、内部クラスEnclosedTheoryを直接指定すれば、実行させられる。Enclosedの甲斐がないと見るか、互いに独立した分類が使えると見るか、悩ましい。
@RunWith(Enclosed.class)
public class EnclosingTheory {
    @RunWith(Theories.class)
    @Category(Inner.class)
    public static class EnclosedTheory {
        @Theory
        public void testTheory(Fixture f) throws Exception {
            // test method.
        }
    }
}
@RunWith(Categories.class)
@IncludeCategory(Inner.class)
@SuiteClasses(EnclosedTheory.class)
public class CategorizedTestSuite {
    // run testTheory.
}

上記でテストが実行されるので、CategoriesとTheoriesは併用できることが分かる。

調べて見ると、CategoriesとEnclosedはそれぞれSuiteのサブクラスだった。併用できないのもさもありなん。

以下は、調べながら考えたことをつらつらと。

こうして調べて見ると、Categoryアノテーションで並列化のための分類をするのは筋が悪い気がしてきた。MECEに分割したいのだけれど、アノテーションの付け忘れやテストスイートへの追加忘れがありそう。何並列にするかによるけれど、上位のパッケージ構成でざっくり割っちゃった方が安全かなぁ。

CIとの相性も考える必要がある。AntのJUnitタスクから実行するなら、パッケージ構成やファイル命名規約に加えてFileSetで色々できる(開発端末上では使えないけれど)。一方、MavenはCategoryにも対応している。そろそろAntから卒業した方がいい気がしてきた……。

なお、調べるために書いたコードはso-c/junit4.12-categories-configuration-sampleにアップしてある。ここに書いたスニペットよりゴチャゴチャしているけれど、ちゃんと動くのでこれはこれで。

2015年8月1日土曜日

GitHubにPJを作成し、Eclipse (EGit) でCloneし、Mavenプロジェクトにする

EclipseプロジェクトをEGitでGitHubにPushするの手順がいまいち使いにくかったので、EclipseをMarsにしてMavenを使ったやり方を整理してみた。まだ気になるところはあるけれど、一旦こんなところで。

GitHub上にリポジトリを作る

GitHubにログインして、右上の"+"メニューから"New Repository"を選び、リポジトリ作成画面を表示させる。必要な項目を入力して、"Create repository"ボタンを押すと、リポジトリが作られる。

入力内容によってREADME.mdなんかが出来たり出来なかったりするけれど、この投稿はファイルができているケースを想定して続ける。

Eclipseとリポジトリを連携する

httpsプロトコルで連携する。sshでも連携できるけれど、その場合は事前に公開鍵を作成しGitHubに登録しておく必要がある。

Windows > Show View > Git Repositoryを選び、そのビューを表示させる。"Clone a Git Repository"を選び、Location > URIにGitHubリポジトリの"HTTPS Clone URL"を入力する(リポジトリ初期表示時はHTTPSではなくSSHなので、HTTPSリンクを押して切り変える)。Hostなどが自動的に埋められるので、AuthenticationにGitHubのユーザとパスワードを入力し、"Next"ボタンを押す。Local Destinationを選んでFinishを押すと、そこにリポジトリのCloneができる。

リポジトリからEclipseにプロジェクトをimportする

EclipseのGit Repositoryビューで、リポジトリを右クリックして、"Import Projects"を選ぶ。"Import as general project"を選んで、"Next"ボタン、"Finish"ボタンを順に押していくとimportされる。

general projectを選んでいるのは、リポジトリを作成するときにファイルが作られているから。こうしてからこの後にMavenプロジェクトにするのと、完全に空にしておいてこの時に新しいMavenプロジェクトとして作成するのとどちらがベターなんだろう。

ここで.gitignoreにgitignore/Global/Eclipse.gitignoreを追加しておくと後から誤コミットをしなくて済む。.classpathなどなくてもこの後Maven化するので問題ない。

インポートしたプロジェクトをMavenプロジェクトにする

importしたプロジェクトを右クリックして、Configure > Convert to Maven Projectを選ぶ。

J2SEに関するWarining "Build path specifies execution environment J2SE-1.5. There are no JREs installed in the workspace that are strictly compatible with this environment."が出る。pom.xmlに下記を追記して、Maven > Update Projectで解消すいる(参考:Maven Project を作成すると警告が出る - 電卓片手に)。
<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>2.3.2</version>
      <configuration>
        <source>1.8</source>
        <target>1.8</target>
      </configuration>
    </plugin>
  </plugins>
</build>

Mavenで依存するライブラリを追加する

右クリックして Maven > Add Dependenciesを選び、からライブラリを検索して追加すると、Maven Dependenciesに追加される。

あとは

好きなようにコードを書けばOK。ただし、Mavenのarchitypeを使っていないので、ソースフォルダなどは手で作らないといけない。GitHubのリポジトリは空にして、新しいプロジェクトを作ってimportすればarchitype使えそうだけれどどちらが楽だろうか。

参考

2015年2月18日水曜日

継続的テスト、継続的インテグレーション、継続的デリバリー

最近、テストとビルドの関係について考え直している。『継続的デリバリー』を読み始めて少しだけまとまってきたので、整理を兼ねてメモしておく。

結論から言うと、テストとビルドの結合はこれまで考えていたよりずっと密だった。継続的インテグレーション/継続的デリバリーは継続的テストを含んでいる。スピードを上げるには〈デプロイメント・パイプライン〉を実装する必要がある。

なお〈デプロイメント・パイプライン〉は『継続的デリバリー』の中核となるパターンで、今のところ、インスペクション、コンパイル、ユニットテスト、ビルド、デプロイ、インテグレーションテスト、システムテスト、リリースなど一連のプロセスをうまく組み合わせることだと理解している。

ただ、組み合わせる範囲が広過ぎて、どこからどう手を付けたらいいのやら。『継続的デリバリー』の続きにヒントがあると期待しているのだけれど。

さて、以降はこの考えに至るまでに考えたあれやこれや。

テストとビルドは切っても切り離せない。そもそも正しい構成のテストがテスト環境にデプロイされていないと、テストする意味が無い。『ソフトウェアテスト 293の鉄則』でも、ビルドに関する鉄則が出てくる。
鉄則170 ビルドスケジュールを調整せよ
鉄則171 ビルド引き渡し前のプログラマの仕事を把握せよ
鉄則172 ビルドの受け入れ準備は怠るな
鉄則173 ときにはビルドのテスト受け入れを拒否することも大切だ
鉄則174 まずはスモークテストで判断せよ

一方で、テスターがビルドしているイメージはない。ビルドしてデプロイするのは別の役割に思える。テスターがするとしても全員ではないだろう。テストレベルが上がれば、テスターの人数が試験環境数を上回る。

〈テスト自動化〉と言ったときも、ビルドの自動化までは想像しないことが多いと思う。Javaでよく使うツールでいうとJUnitやSeleniumとかが真っ先に思い浮かんで、Antやmavenまではなかなか辿り着かない。

そのせいか、〈継続的インテグレーション〉とか〈継続的デリバリー〉と聞くと、ビルドやデプロイのことが真っ先に思い浮かんで、今度はテストがなおざりになる。

でも〈継続的インテグレーション〉について改めて『継続的インテグレーション入門』を読み返したりしてみると、テスティングやインスペクションまで含んでいる。継続的インテグレーションのベストプラクティスにも、Make the build self-testingが並んでいる。正しくビルドできたことを、テストで確認する必要があるからだと思う。

つまり、テストから見ても、ビルドから見ても、お互いに強く必要としあっている。

という論理的な帰結は論理的な帰結として、実際的な問題としてインスペクションしてコンパイルしてユニットテストしてビルドして、データベースにデータ準備して実行環境を構築して、そこにデプロイしてインテグレーションテストやシステムテストしてリリースしようなんて、膨大なスキルセットを持つユニコーンか、せめてそれぞれのエキスパートと全体を見渡すアーキテクト的なリーダを務められる人が集まらなきゃできない。

それぞれどれも大変なのに、一貫した効率的なデプロイメント・パイプラインなんて、どうしたらうまく実装できるんだろうか。

References

2014年7月30日水曜日

テストピラミッド

テスト戦略やテストアプローチについて調べていたら、テストピラミッドというコンセプトに行き当たった。自分の考えを整理するのにも、他人に考えを伝えるのにも使えそうなのでメモしておく。

考えたのは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年4月2日水曜日

UbuntuへのRのインストール

Ubuntu 13.10にR3.0.3とRStudioをインストールした時の手順を残しておく。

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プログラマへの助言として
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