2011年7月11日月曜日

JMockit と Swing と無名クラス

JMockit を使った Swing の UnitTest を考えてみる。今日はとりあえず、最初の一歩。
参考資料はこれ → Behavior-based testing with JMockit

====
まずこんなコードから始めてみる。Swing に限らず、GUI フレークワークの入門書の最初に出てくるような、何もないウィンドウを単に開いてみるだけのもの。
public class Exercise1 {
  public static void main(String[] args) {
     JFrame frame = new JFrame();
     frame.setSize(500, 300);
     frame.setVisible(true);
  }
}

要するに 500×300のウィンドウがビジブルになれば良い訳で、以下のようなテストコードになる。
public class Exercise1Test {
  @Test public void main() {
     new Expectations() {
        @NonStrict JFrame frame; {
        frame.setSize(500, 300);times=1;
        frame.setVisible(true);times=1;
     }};
     Exercise1.main(new String[]{});
  }
}

ここで、ウィンドウのクローズイベントに対応するアプリケーション終了処理が無い事に気づき、例えば、以下のように追加したとする。
public static void main(String[] args) {
  JFrame frame = new JFrame();
  frame.addWindowListener(new WindowAdapter() {
     @Override public void windowClosing(WindowEvent e) {
        System.exit(0);
     }
  });
  frame.setSize(500, 300);
  frame.setVisible(true);
}
この無名クラスを含むコードをどうやってテストするか。

以下のような方針をとってみた。
  • System クラスをパーシャルモックして、exit(0) 呼び出しを expect する(変な日本語だが…)。
  • 無名インナークラスについては、次のようにする。
    • addWindowListener に渡された、WindowListener を保持しておく。
    • main() 実行の後で、保持しておいた WindowListener のwindowClosing()を明示的に呼び出す。

コードはこんな感じ。
public class Exercise1Test2 {
  @Mocked({"exit"}) System system;
 
  static class WindowListenerCapturer implements Delegate {
     WindowListener captured;
     void addWindowListener(WindowListener l) {
        captured = l;
     };
  }
 
  @Test
  public void main() {
     final WindowListenerCapturer delegate = new WindowListenerCapturer();
     new Expectations() {
        @NonStrict JFrame frame; {
        frame.addWindowListener((WindowListener)any); result = delegate;
        frame.setSize(500, 300);times=1;
        frame.setVisible(true);times=1;
     }};
     new Expectations() {{
        System.exit(0);
     }};
     Exercise1.main(new String[]{});
     delegate.captured.windowClosing(null);
  }
}

ここでは、実際の実行パスの流れと同じになるように、main() からの Expectation と、windowClosing() からの Expectation を分けて書いてみた。

WindowListenerCapturer を使わないで、result = new Delegate() { ... }として、「...」のところで、windowClosing() を呼ぶやり方も試してみたが、何故か Eclipse プラグインがテスト終了を認識しない。

まあ上で示したコードでも、キャプチャのためのコードが若干増えることになるとはいえ、実行時の流れを表していると言う点では、却って分かりやすいような気もする。

2011年7月7日木曜日

Effective Java から例外関連項目

以下、『Effective Java (2nd Edition)』 から、9章「例外」に関係する項目。

緑:推奨 黄:注意 赤:禁止

Item 57: Use exceptions only for exceptional conditions
ループとかに使うなという基本中の基本。パフォーマンスにも悪影響あり。

Item 58: Use checked exceptions for recoverable conditions and runtime exceptions for programming errors
まあ一応、教科書的な原則としてはこんな感じだろう。ただチェック例外の是非については、結構物議を醸す。そもそも、こんなの必要なのかと。Spring なんかも RuntimeException 派だし、C#や C++ でチェック例外が無いからといって困った事も全然ない。

Item 59: Avoid unnecessary use of checked exceptions
むやみにチェック例外を使うなと。例えば、「とりあえず呼んでみて例外が上がったらハンドリングする」なんてやり方より、「呼ぶ前にまずチェックする」やり方の方が、よりインテンショナルなコードになる場合がある。そんなメソッドのシグネーチャに例外を含めたって、誰の得にもならず負担が増えるだけ。

しかしここでも、いっそチェック例外なんて止めりゃいいじゃんなんて思いが、やはり沸いてきたりする…

Item 60: Favor the use of standard exceptions
例外に限らず全ての Java 標準APIに言えるけど、よく調べ、よく理解して、使い倒していくと、いろんな意味で効率的。低リスクで高い生産性が得られる。

Item 61: Throw exceptions appropriate to the abstraction
よくAPI なんかについて、低レベルとか高レベルとか言うけど、例外についてもレベルに合わせて使い分けよう、必要なら変換もしようという話。

Item 62: Document all exceptions thrown by each method
メソッドのドキュメントで、例外の説明をちゃんとしとけと。特に、RuntimeException だけで押し通す方針を選択した場合、チェック例外の面倒くささから解放される分、このドキュメント化だけはちゃんとしないと単なる手抜きになる。

Item 63: Include failure-capture information in detail messages
せっかく例外投げるんだから、ちゃんと原因究明の手がかりも含めようという、まっとうな話。まあ、面倒くさいし省かれがちだけど、これが正論。

Item 64: Strive for failure atomicity
例外送出による実行中断のおかげで、システムの状態が中途半端になったりしないように、例外発生時には呼び出し前の状態を復元しようという項目。

こういうのを考えると、注意喚起力が強いチェック例外って、やっぱアリかなあなんて思えてくる。少なくともドキュメントには、やはり可能性のある例外を列挙しておきたい。まあキャッチしたとしても、状態の復元はいつも簡単には行かないだろうけど。

Item 65: Don't ignore exceptions
これについては、前に書いた。驚くべき事に、例外を基底クラスでキャッチしてモミ消した挙句、鼻高々という面白い人種が、業界の底辺には多数棲息しているが、これらのおバカさん達についてもレポートした。

2011年7月4日月曜日

JMockit

数日前のポストで、この2・3年に普及してきた、Java の Mocking / Isolation フレームワークに触れた。今日は、そのうちの一つ JMockit を使って、以下のお題について解を考えてみる。

クラス TestTarget があり、Collaborator オブジェクトへの関連と、メソッド methodA() を持っている。

public class TestTarget {
   private final Collaborator collaborator = new Collaborator();
   public String methodA(String pattern) {
      int hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
      return String.format(pattern, collaborator.methodB(hour));
   }
}

methodA() では、Collaborator オブジェクトのメソッド methodB() を呼んでいるが、methodB() の振る舞いについて分かっている事は、24時間制の「時」を表す整数を与えると、その時刻を表す単語("morning"や"夕方"など)を返すと言う事だけである。

さて、ここで methodA() における、Collaborator オブジェクトとの協調を含む、TestTarget の振る舞いをテストするコードはどのようなものになるか。

見ての通り、methodA() の振る舞いは、
  1. Calendar から現在時刻の「時」の部分を取得し
  2. これを Collaborator.methodB に渡して戻り値を受け取り
  3. 更にこれを書式化して返すというものになる。

テスティングポイントは以下のようになる。
  1. Calendar から、正しく「時」を取得している事
  2. collaborator に渡された「時」が、上記1で取得したものである事
  3. collaborator から返された文字列を正しく用いて、メソッドの戻り値を作っている事

また、以下のような事に留意する必要がある。
  • Collaborator.methodB() の現時点の挙動には依存しない事。
    時間の区切りがどうなっているか、言語が何であるかなどは、今後変わる可能性があるが、TestTarget クラスの責任範囲外である。
  • Collaborator.methodB() の実コードを呼ばない事。
    Collaborator は外部サービスが起動している事に依存していて、実行時間も無視できない。また TestTarget のテストで Collaborator コードまで呼ばれてしまうと、コード・カバレッジが実際を正しく反映しなくなると言う問題もある。
  • このテストをどの時間帯に実行しても結果が変わらない事。
    つまり、Calendar が返す時刻に依存してはいけない。

この UnitTest を、従来の dynamic proxy ベースの ツール(jmock など)を使ったり、ましてや状態ベースの普通の JUnitテストで書こうとすると、難しいテストコーディングになったと思う。

JMockit を使うと以下のような感じになる。
public class TestTargetTest {
   @NonStrict Collaborator collaborator;
   @Mocked({"getInstance", "get(int)"}) Calendar mockCalendar;
   @Test public void methodA() {
      final int HOUR = 7; 
      new Expectations() {{
         collaborator.methodB(HOUR); result = "morning";
         Calendar.getInstance(); result = mockCalendar;
         mockCalendar.get(Calendar.HOUR_OF_DAY); result = HOUR;
      }};
      String actual = new TestTarget().methodA("good %s!");
      Assert.assertEquals("good morning", actual);
   }
}
コードの意味は以下のようなもの
  • Calendar.getInstance() が モックの Calendar を返すように、getInstance() コードを差し替えた
  • Calendar のモックは、今、何時なのか尋ねられる事を期待し、またその際、固定値7 を返すよう記述した。
  • Collaborator のモックが、固定値7 で methodB()が呼ばれることを期待し、その際、固定値"morning"を返すよう記述した
  • 上記設定で、TestTarget.methodA()に"good %s!"が与えられ、"good morning"が得られる事を検証するよう記述した。


一度グリーンにしてから、いろいろコードを変えてみて期待通りにレッドになることを観察してみた。

せいぜい jmock の延長くらいかと思っていたら、使用感はかなり異なっていて、若干戸惑う。ただし、状態変化ベースだけではなく、相互作用ベースの UnitTest まで理解していれば、それほど高いハードルではない。DynamicProxy 系のテストを書いていていろいろ悩んだり困った経験のある人ほど、理解が早いと思う。

2011年6月30日木曜日

技術者と英語:「読める」だけで嬉しい三つの事

TOEIC 800点前後というと、レベルBに当たる。

TOEIC が提示している相関表の説明によると、「どんな状況でも適切なコミュニケーションができる素地を備えている」、「日常会話は完全理解」、「業務上も支障なし」とされている。

なんて言っても、実は「話す」「書く」が未経験でも「聴く」「読む」がソコソコできれば、割と取れる点数だったりする。自分もだいぶ前に 800を越えたが、恥ずかしながら全然喋った事なんかない。

つうか話す相手も機会も今のところ一切無いし、従って興味も沸かず努力のしようもないんだけど、ソフトウェア開発技術者としては、「読める」だけで結構役に立つ事がある。

以下の3つ。

◆ 英語でググれる
言うまでもなくインターネットは日本語情報より英語情報の方が圧倒的に多いし、技術情報は特に英語圏発のものが時間的にも先行している事が多い。

英語が読めると、Google の search settings で、English を設定しておいても別に問題ないから、情報量の少ない日本語サイトを避けて効率的に検索できる。

技術系BBSでのディスカッションやトラブルシューティングでも、海外の方が盛んで検索にもヒットしやすい。


◆ 技術系の洋書が読める
最近は、少しだけ改善したようだけど、一昔前の邦訳書は、かなり読みにくいものが多かった。APIリファレンス的なものはまだマシな方だけど、ちょっとユーモアを交えた語り口になったり、思想的に難解な話になったりすると、途端に日本語がおかしくなる。

邦訳書の日本語に対する文句は至る所で聞くけど、最初から原書にしておけば、無駄なイライラをせずにすむ。つうか、無駄な変換レイヤーが無いという点で情報処理の視点からもある意味合理的なわけで、技術者ならなおさら原書を選択すべき。


◆ ソースコードが英語に見えてくる
意外と意識されていない事が多いが、ほとんどの高級プログラミング言語は英語ベースだったりする。

まず単語の面で、言語のキーワードにおいても、各種 API の識別子においても、そのほとんどは英語の語彙、つまり何百年も前からアングロサクソンが日常のコミュニケーションの中で使ってきた言葉で構成されている。

また文法の面でも、意識的にか無意識的にか知らないけど、メソッド呼び出しの文が、「subject.verb(object)」みたいな感じで、だいたい英語の語順に従うようになっていたりする。

なので、上手に書いたプログラムは自然言語としての英語に近くなり、下手なコメントやダイアグラムなんかよりも、書き手の意図を読者に、より効率的、より直截に伝える高い表現力を持つ事ができる。

英語が「読める」ようになるだけでも、人間同士が意思疎通するコトバという観点から見たプログラムの良し悪しがより分かるようになり、ソースの読み書き両面に良い効用が出てくる。

====
ちなみに、逆に英語が全然読めない人の場合、英文としての表現力さえ備えるに至った良質なコードを前にしても、解読を要する意味不明な記号の羅列か、もしかすると ASCII アート的な図形にしか見えていないのでは、と疑わしくなる場面がよくある。

こういう人達って、「ソースを読む」ではなく「ソースを解析する」という言い回しを好む。「ソースが言葉である」なんて事は脳内ですら成立したためしがないから、「読める」コードなんか自分でも書ける訳もなく、必然的に無駄なコメントだらけの残念なコードを書いてしまうことになる。

2011年6月27日月曜日

java の static メソッドの mocking

しばらく .net 案件とかやってた間に、いつの間にか Java でもstatic メソッドに mocking が適用できるようになっていたらしい。

ググってみると、今のところ PowerMock と JMockit が、どうやら使えそうな感じ。

前に Java をやっていた頃、mocking には jMock(いわゆる「流れるようなインターフェイス」が何とも素敵な奴だったのだが)を使っていたが、この jMock の制約のせいで java では static メソッドは迂回もすり替えもできないと諦めていた(final 外しはあったが)。

考えてみれば、.net の Mole も TypeMock も instrumentation を使っていたわけだけど、Java だって 5.0 からは instrumentation があったんだから、とっくにできていて不思議はなかった。

しかし、どっちを使えば良いか悩む。

つうか、昔と同じく jMock を使うという選択肢も捨てきれないから、3択になる。いや、PowerMock は EasyMock か Mockito と組み合わせるからもっと選択肢が増える(jMock との組み合わせもできないことないらしいが、ちょっと不安)。

カバレッジツールとの相性とか、微妙な使い勝手の優劣とかもあるだろうし、急いで決めねばならない状況ではあるんだけど、拙速な決断は危ない。

うーん、もっと早く気づいていればなあ…

2011年6月24日金曜日

Fedora 15/Jenkins/Maven3/Subversion/Helios

空きマシンに入れていた Fedora 12を Fedora 15 Lovelock に入れ替えて、自宅環境の筆頭開発マシンに昇格させ、ついでにその他開発ツールもセッティングした。その作業の超ザックリメモ。

====

Helios: Fedora の「ソフトウェアの追加と削除」から GUIでインストール。Fedora 12の時は Galileo だったが、Fedora 15 では Helios (Eclipse 3.6)になっていた。

Maven 3: apache のダウンロードサイトからバイナリを落としてきて適当に展開。README.txtを見ながら適当に設定、"mvn --version" で 3.0.3 が入ったことを確認。

Subversion: これも「ソフトウェアの追加と削除」でインストール。バージョン 1.6.16 が入ってきた。"svnadmin create" で適当にリポジトリを作っておく。差し当たり file://でアクセスするだけなので、これ以上の設定は保留。

m2eclipse: Helios の「新規ソフトウェアのインストール...」で、更新サイトに[このURL]を指定してインストール。

Subclipse: Helios の「新規ソフトウェアのインストール...」で、更新サイトに[このURL]を指定して、Subclipse, Client Adapter, JavaHLを選択してインストール。

Jenkins: [ここ]を見てインストール。"sudo /etc/init.d/jenkins start"で起動し、ブラウザで localhost:8080 を開いて確認。で、とりあえず stop しておく。

テスト用プロジェクト作成:

  • Helios に戻る。
  • maven-archetype-quickstart を指定して新規 Maven Projectを作成。
  • pom.xml で JUnit のバージョンを4.8.2に変更して、依存性を更新。
  • App.java を書き換える
    public class App {
        public String getMessage() {
            return "Hello World!";
        }
    }
  • AppTestを書き換える
    public class AppTest {
       @Test
          public void testGetMessage() {
          String message = new App().getMessage();
          Assert.assertEquals("Good-Bye World!", message);
       }
    }
  • Maven package を実行して、テストでビルドが失敗するのを確認。
  • 上で作ったリポジトリを指定して"プロジェクトの共用"を実行し、適当にファイルを選んでバージョン管理に追加して、敢えてテスト失敗版をコミット。

Jenkins に Jobを追加:

  • "java -jar /usr/lib/jenkins/jenkins.war"で起動する。(init.d/jenkins startだと、file:// で SVNリポジトリにアクセスできなかったので、とりあえず war を直接起動。)
  • ブラウザから開いて、[Manage Jenkins]/[Configure System] から、右のように Maven の設定 → Install Automatically をアンチェックして、上でインストールした Maven のパスをMAVEN_HOMEに指定。
  • Jenkins の[New Job] で [Build a maven2/3 project] を選択。"Source Code Management" で Subversion を選択し、上で作ったリポジトリを指定。

Build 確認:

  • [Build Now] を実行し、テストでビルドが失敗している事を確認。
  • Helios に戻ってテスト・メソッドの"Good-Bye"をHelloに変えて、SVNコミット。
  • 再び[Build Now] でビルド。今度はテストを含むビルドが成功している事を確認。

====

TODO

  • svn+ssh でのリポジトリアクセス
  • サービス化
  • Trac の導入とSVNとの連携
  • SVNコミットをトリガーとする自動ビルド
  • FishEye導入
  • Emma 導入

2011年6月21日火曜日

アジャイルの資格

アジャイル関連の資格について調べてみた。

アジャイルと認定資格って、なんとなく相容れないイメージで、開発者の間でも物議を醸しているらしいが、最近は結構、動きが活発になってきている。

まあ他の資格と同じで、保有しているからといって実務能力があるって事にはならないだろうが、少なくとも会話が成立するレベルの語彙を持っている事の表明くらいにはなると思う。もちろん実践で成功している人なら、我流に偏らないお墨付きの正統な知識も持っているという事になり、鬼に金棒で言う事が無い。

====
◆ Scrum Alliance: 認定スクラムマスタ 等
すでに日本でも取得者が増え始めている、Scrum Alliance の認定資格。

トレーニングコースに参加する必要があり、日本開催時の受講体験記をネットでも散見する。ただ、これが結構高い。ざっと検索したところ、同時通訳付きで20万、通訳なしで15万といったところか。会社の金ではなく個人で受けるとすると、ちょっとした決断が必要になる。

なんか昔のイメージだと、お金を払って2日間の講習に座ってるだけで取れるって感じだったけど、調べてみると、今は一応、インターネット経由の試験で知識を証明する必要があるらしい。

ちなみに Master 以外には、ProductOwner, Developer, Professional, Trainer, Coach といったものがある。


◆ Scrum.org: Professional Scrum Master 等
URLはここ

Professional Scrum Master I (Fundamental) :
トレーニング・コースも提供されているが必須ではなく、自信があればいきなりネット試験を受験してもよく、そこで 85%とれば合格らしい。

Professional Scrum Master II (Intermediate):
これもネット試験だが、小論文もあるらしい。費用もやや高額。

Professional Scrum Developer:
これはコース受講と試験の両方が要るらしいが、ただトレーニング・コースの方は欧米中心の開催。アジアでは今のところインドだけっぽい。

Professional Scrum Product Owner:
これもコース受講とネット試験。


◆ PMI: PMI-Agile Certified Practitioner

先月、Pilot Programが始まったばかりの PMIのアジャイル認定試験。

以前、PMP の勉強をしていたとき、使っていた問題集でも参考書でも、道路の延伸工事プロジェクトだとか、小売業の店舗拡大プロジェクトだとか、予想外にソフトウェア開発と離れた設問ばかりで、PMBOKってそう言うものだったのかと、ちょっと驚いた。

でも、同じ PMI の資格でも、PMI-Agile のFAQを見ると、馴染みのあるアジャイル開発の用語ばかりで、ソフトウェア開発者としてちょっとテンションが上がってくる。


◆ ICAgile:
ユースケースで有名なコーバーンとかが創立した、ICAgile なる組織でも、認定試験を提供する計画らしい。

組織自体が発足したてで、試験などはまだまだ形になっていないようだけど、この記事を読む限り、結構おもしろそうではある。