トップ «前の日記(2005-03-24) 最新 次の日記(2005-03-26)» 編集

日々の破片

Subscribe with livedoor Reader
著作一覧

2005-03-25

_ モックとしてのインナークラス

やっとここまできた。

っていうわけで、最近は、EasyMockとか利用しなくてもインナークラスでいいじゃんという気がしていたり。こんな感じ。

int evidence;
public void testUpdate() throws Exception {
    evidence = 0;
    testTarget.setDao(new DAO() {
        public DAO.Record read(int key1, int key2) throws SQLException {
            assertEquals(0, evidence++);
            assertEquals(10, key1);
            assertEquals(11, key2);
            return new DAO.Record() {
                int column1 = 111;
                public int getColumn1() {
                    return column1;
                }
                public void setColumn1(int newValue) {
                    column1 = newValue;
                }
                String column2 = "ABC";
                public String getColumn2() {
                    return column2;
                }
                public void setColumn2(String newValue) {
                    column2 = newValue;
                }
            };
        }
        public void update(DAO.Record rec) throws SQLException {
             assertEquals(1, evidence++);
             // このrecは上で返したモックと==が真。検証するには無名インナークラスじゃだめだけど。
             assertEquals(112, rec.getCoulumn1());
             assertEquals("CBA", rec.getColumn2());
        }
    });
    testTarget.foo(new FormData() {
        public int getKeyData1() {
            return 10;
        }
        public int getKeyData2() {
            return 11;
        }
    });
    assertEquals(2, evidence);
}

DIを利用するようにアプリケーションクラスを作るから、テストプログラムからは利用するDAO(ここでは文字通りDAOというクラス名としている)をセットできる。そこで、そのモックを無名インナークラスとして設定してやる。この中でAssertメソッド群を呼び出せるのは、インナークラスだからエンクローズドクラスであるTestCase派生のテストクラスを利用できるからだ。呼び出しシーケンスはevidenceという名前のフィールドで検証する。それによってモックの呼び出しが行われたことを最終的に検証する。

こういうやり方をすると、いくつかのコーディングルールが必要になってくる。たとえば、finalクラスは基本的に不可(上の例だとDAOとかDAO.Recordは継承している)。単なる値オブジェクトであってもゲッタ、セッタを用意する(上の例だとDAO.Recordの実装。この例ではセッタではアサートをかけていないので単に書き込み可能なpublicフィールドにしてもいいけど余り応用がきかなくなる)。など。追記:あと、パッケージプライベートも重要だ。上の例だとDAO.Recordなんてクラスは意味的にはDAOクラス内からしか本来生成するはずがないからコンストラクタをプライベートにしたくなるわけだが(無意味なメソッドの公開は良くないので、それはそういうものだ)、パッケージプライベートを利用することで同一パッケージに配置したテストプログラムからの生成を許すようにしておく。いずれにしろ、部外者はパッケージの外だから関係ないわけだし。

あまり良い方法とは思えないが(なんか本末転倒に思える)、DI抜きにしても動くように考えても良いかも。

class TestTarget {
    DAO dao;
    public void setDao(DAO newDao) {
        dao = newDao;
    }
    public void foo(FormData data) {
        if (dao == null) {
            // インジェクションされていなければ
            dao = new product.DAO();
        }
        ...
    }
}

_ JUnitの有効性

たとえば、StackだとかCalcだとかのテストであれば、呼び出しパラメータと戻り値の組でほとんどのテストが可能なわけだ。

ところが、ほとんどの業務アプリケーションって何かデータを貰って、それでデータベースをいじくることがほとんどで、ちょっと事情が異なる。

それでも情報取得系の処理なら、なんらかの戻り値があるはずだから(たとえば、Actionからmode.execute(param1, param2);と呼び出した後に、request.setAttribute("resultList", model.getViewDataList());とかしたり、あるいは直接request.setAttirubute("viewData", model);とかできるのなら、getViewDataList()の戻り値が実行結果と見なせる)どうにかなるかも知れない。

でも、更新系だとそうはいかない。

そこで、アサートするためには、メソッドの内部に入り込んで呼び出しを検証しなければならないということになる。与えたパラメータと、更新に利用する読み出したデータを与えてやって、更新のために生成された値を検証するという方法だ。

それを実行するには、モックを利用したエンドゥテストが必要となるわけだ。

もちろん、データベースを直接使うという手段はあるし、それはそれで良い方法だとは思う。setUpのたびにConnectionを繋ぐオーバーヘッドが気にならなければ。あるいは、テストプログラム内でコネクションプールを使うとか。そうすれば、呼出し後のテーブルから更新結果を読み出して検証することはできるからだ。

でも、DIを自由に利用するようになると、データベースへのアクセスはDAOを使うほうがいろんな意味ですっきりする。かつ、それによってエンドゥテストが可能となる。データベースとのインターフェイスはDAOに任せることができるからだ。で、任せているからモックすることも可能になるということだ。


2003|06|07|08|09|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|

ジェズイットを見習え