愛と勇気と缶ビール

ふしぎとぼくらはなにをしたらよいか

Mockitoの新機能を使ったモダンげな使い方

Javaのmock用ライブラリは探せば色々あるが、一番よく使われてそう(自分調べ)ってことでMockitoを使っている。

http://mockito.org/

Mockitoはそのロゴからいっても「モキート」「モッキート」と読むのが正しいのだろうけど、心の中ではいつでも「モック伊藤」「モック伊藤」と呼んでしまう。

それはさておき、Mockitoについて日本語で書かれた記事は「mock()でモックしてverify()でベリファイするんやで〜」くらいで留まっているものが多そうに見受けられたので、新し目の機能を使ってこういう風に書くのもよさげかな、とここに記しておく。

参考: Mockito (Mockito 2.0.33-beta API)

mock()ではなく@Mock, spy()ではなく@Spyをつかう

以下のようにmock()メソッドを使ってmockを作ることも出来るのだが、

MyService service = mock(MyService.class);

Mockitoにはアノテーションでmockを作成する機能があるため、テストクラスの最初にフィールドとして以下のようにして書いておく。

public class MyServiceTest {
    @Mock
    MyService service;
}

こうすると、あるオマジナイを書いておくことでMockitoが各テストケースの前にmockを作成してくれるようになる。(オマジナイについては後述) spyについても同様のことが可能。

answerとかもちゃんと指定できる。

public class MyServiceTest {
    @Mock(answer = Answers.RETURNS_DEEP_STUBS;
    MyService service;
}

mock()やspy()を使っても同様のことが出来るのになぜこちらを選ぶのか。それはテストのコードを初めて見た人が「このクラスのテストではこいつらをmockする」とすぐに理解しやすいと考えるから。もちろん各テストケースの冒頭にmock(),mock()と繰り返し書いてあってもちゃんと読めば分かるが、クラスの先頭にまとめて書いてあった方が「テストケース共通でこいつらはmockされている」というのが分かりやすい。

@InjectMocksを使ってDIしてもらう (Whiteboxは使わない)

テスト対象のクラスが別のクラスのインスタンスに依存している、つまりprivateフィールドに別のクラスのインスタンスをコンポジットする感じになっている、というのはあるあるケースだと思う。

public class MyService {
    @Inject
    private OtherService otherService;
}

依存先クラスの単体テストは別に書くので、こいつもmockして置き換えたいなーと思うのが人情なのだが、これをMockito付属のWhiteboxというユーティリティクラスを使って書くとこんな感じになってしまう。

OtherService otherService = mock(OtherService.class);
Whitebox.setInternalState(service, "otherService", otherService);

フィールド名の文字列による指定がいただけない。これは@InjectMocksで置き換えられる。

public class MyServiceTest {
    @Spy
    @InjectMocks
    MyService service;

    @Mock
    OtherService otherService;
}

こう書くと、MyServiceのフィールドにOtherServiceクラスのmockされたインスタンスがDIされる。 これも先程と同様に、手続き的に行うよりも意図が分かりやすい。

どういった種類のインジェクションが可能なのかはInjectMocks (Mockito 2.0.33-beta API)を参照。

MockitoRuleを使う

以上の@Mock, @Spy, @InjectMocksを動作させるためにはオマジナイというか初期化処理が必要なのだが、それをJUnit4のRuleで行うためのMockitoRuleが用意されているのでそれを使う。

public class MyServiceTest {
    @Rule
    public MockitoRule mockito = MockitoJUnit.rule();

    @Spy
    @InjectMocks
    MyService service;

    @Mock
    OtherService otherService;
}

Matchersを使う

今まで書いたのとは若干毛色が違うが、「テキトーな値」を指定するためのMatcherが色々Mockitoには用意されているのでデータをわざわざ用意したくない場合はそれを使う。

doReturn(1).when(this.service).doSomething(anyString()); // どんなStringが渡ってきても1を返す

any(Class clazz)やisNull()などなど、おおむね想像通りのMatcherが用意されている。

Matchers (Mockito 2.0.33-beta API)

注意点として、Matcherを使った引数と通常の引数を混ぜて指定することは出来ない。 つまり以下のように書いてしまうとMockitoに怒られてエラーになる(ちなみにMockitoのエラーメッセージは結構親切だと思う)

doReturn(1).when(this.service).doSomething2(anyString(), 1);

それだと不便じゃね?と思うかもしれないが、そういう場合はeq()を使って引数をMatcherに統一すればよい。

doReturn(1).when(this.service).doSomething2(anyString(), eq(1));

AdditionalMatchersをインポートすれば、配列に対するMatcherなども使えるようになる。

AdditionalMatchers (Mockito API)

今までのまとめ

以上のことをまとめた上で更にちょっと付け足すと、テストクラスは大体以下の様な書き方になる。

public class MyServiceTest {
    // 初期化のためのおまじない
    @Rule
    public MockitoRule mockito = MockitoJUnit.rule();

    // テスト対象のクラス
    @Spy
    @InjectMocks
    MyService service;

    // テスト対象のクラスが依存している(フィールドに持っている)クラス
    @Mock
    OtherService otherService;

    // テストケース間で共通のデータ
    long userId = 1L;

    @Before
    public void setUp() {
        // テストケース間で共通のモック処理はここでやっておく
        when(this.otherService.foo(anyString()).thenReturn("");
        when(this.otherService.bar(any(MyBean.class)).thenReturn(1);
    }

    @Test
    public void testDoSomething() throws Exception {
        // 各テストケースで固有のモック処理はここでやる
        doReturn(1).when(this.service).doSomething(anyString(), eq(1));

        // assertion
    }
}

好みの問題もあるだろうが、各テストケース内に色んなものをダラダラっと書くよりは「このクラスのテストではこうします」という意図は伝わりやすくなるかと思う。

JavaでiPhoneからアップロードされた画像を(再)回転させる

iPhoneからアップロードした画像をPCで表示すると思ったのと違う向きで表示されるという事象は、よく知られているように、iPhoneが本来横向きの画像にexifのorientationをかました上で縦に表示しているから発生する。

blog.isao.co.jp

これに真面目に対応する場合は以下のようなコードを書くのが正解っぽいが、

mumumu.github.io

多少サボったコードでもいいならThumbnailator (https://github.com/coobird/thumbnailator) を通してしまうのが一番楽かと思う。

Thumbnails.of(inputStream).scale(1).toFile(file);

Vimを使って複数の文字列を一度で置換する

Vimについて書くのとか久しぶりすぎる。

例えば、テキスト中の

  1. FFFF00をFFFFFFに
  2. FFFFFFをFFFF00に

一発で置き換えたい。これを1 => 2の順でやると1の後で全部FFFFFFになってしまうので、アカン。

perlとかでコマンドラインからやってもいいけど、まあVimからやりたいとしよう。

おそらく色々方法はあるのだろうけど、一番楽なのは tpope/vim-abolish · GitHub を入れて

:%Subvert/{FFFF00,FFFFFF}/{FFFFFF,FFFF00}

かな、と思った。

jOOQ (Java Object Oriented Querying) の使い方の紹介

jOOQ (http://www.jooq.org/) についてのメモ。 jOOQは、type safeかつdatabase orientedなクエリビルダーである。 database orientedなので、「アプリケーションからDBのことなんて全然意識したくないねん」的な思想に基いて作られた類のプロダクトとは異なり、思い通りのSQLを生成できるようになっている。

以下gradle前提で。version等はよしなに。

gradle

dependenciesはこんな感じ

compile 'org.jooq:jooq:3.7.1'
compile 'org.jooq:jooq-meta:3.7.1'

テーブル等に相当するクラスをgradle taskから生成するためにbuildscriptのdependenciesにも以下をかいておく

classpath 'org.jooq:jooq-codegen:3.7.1'

んで、以下のようなコードをbuild.gradleに書く。MySQL前提です。細かい部分は自分の環境に合わせてよしなに。

def genXml = { db, writer ->
    new groovy.xml.MarkupBuilder(writer).configuration('xmlns': 'http://www.jooq.org/xsd/jooq-codegen-3.6.0.xsd') {
        jdbc() {
            driver("com.mysql.jdbc.Driver")
            url("jdbc:mysql://localhost:3306/")
            user("root")
            password("")
        }
        generator() {
            database() {
                inputSchema(db)
            }
            generate() {
            }
            target() {
                packageName("com.foo.bar.jooq." + db)
                directory("src/main/java")
            }
        }
    }
}

def generateJooqSources = { db -> 
    def writer = new StringWriter()
    def xml = genXml(db, writer)
    org.jooq.util.GenerationTool.generate(
        javax.xml.bind.JAXB.unmarshal(new StringReader(writer.toString()), org.jooq.util.jaxb.Configuration.class)
    )
}

で、以下のようにデータベース毎にtaskを定義する、と。

task("jooq-generate-example") << {
    generateJooqSources(name)
}

若干DRYではないがここでは気にしない。

初期化

単にクエリを発行するだけなら、ConnectionやDataSourceからDSLContextクラスのオブジェクトを作ればよい。

DSLContext ctx = DSL.using(dataSource, SQLDialect.MYSQL);

クエリの書き方

SQLでやりたいことは大体出来る。たまにめんどくさい書き方をする必要があるが...

以下、userというtableからコード生成して、適宜static importしている前提。

単品SELECT

UserRecord record = ctx.selectFrom(USER)
    .where(USER.ID.equal(userId)
    .fetchOneInto(UserRecord.class); 

カラムを指定して単品SELECT

返り値を制御する関係で、結果として二回カラム名を指定する必要がある

String name = ctx.select(USER.NAME)
    .from(USER)
    .where(USER.ID.equal(userId)
    .fetchOne(USER.NAME); 

複数SELECT

List<UserRecord> userRecords = ctx.selectFrom(USER)
    .where(USER.NAME.equal(name)
    .fetchInto(UserRecord.class);

特定のカラムの値をkeyにしたMapを取得

PerlのDBIでいうところのselectrow_hashref的なことも出来る。1対1のマップ。

Map<Integer, UserRecord> userRecords = ctx.selectFrom(USER)
    .where(USER.NAME.equal(name)
    .fetchMap(USER.ID, UserRecord.class);

特定のカラムの値をkey、keyの値でグルーピングされたレコードのListをvalueにしたMapを取得

PerlのDBIでいうところの…なんだっけ。あったっけか。1対nのマップ。

Map<Integer, List<UserRecord>> userRecords = ctx.selectFrom(USER)
    .where(USER.ID.in(userIds))
    .fetchGroups(USER.STATUS, UserRecord.class);

INSERT

ctx.insertInto(USER, USER.NAME, USER.STATUS)
    .values(name, 0)
    .execute();

batch INSERT

List<UserRecord> userRecords = new ArrayList<UserRecord>();
...
ctx.batchInsert(userRecords)
    .execute();

UPDATE

ctx.update(USER)
    .set(USER.NAME, name)
    .where(USER.ID.equal(userId))
    .execute();

DELETE

ctx.deleteFrom(USER)
    .where(USER.ID.equal(userId))
    .execute();

トランザクション

ctx.transaction(new TransactionalRunnable() {
    @Override
    public void run(Configuration configuration) throws Exception {
        // ここの中
    }
})

where条件の動的な組み立て

SelectQuery<UserRecord> query = ctx.selectFrom(USER).getQuery();

for (User u : users) {
    Condition condition = USER.NAME.equal(userName).and(USER.STATUS.equal(1));
    query.addConditions(Operator.OR, condition);
}

List<UserRecord> userRecords = query.fetchInto(UserRecord.class);

DAO

使ったことはないが生成できるらしい

http://www.jooq.org/doc/2.6/manual/sql-execution/daos/

で、実際どうなん?

たまにかゆい所もあるけど、一通りのことは出来てまあまあ使いやすいです。

ドキュメントが結構充実しているのと、開発者がstackoverflowでこまめにレス付けてる所を見ると、気合の入っていることが分かります。

JDBCによるJavaデータベースプログラミング 第2版

JDBCによるJavaデータベースプログラミング 第2版

「神聖喜劇」に寄す

大西巨人「神聖喜劇」を読んだ。

神聖喜劇〈第1巻〉 (光文社文庫)

神聖喜劇〈第1巻〉 (光文社文庫)

「神聖喜劇」とは何のことやらと思うかもしれないが、これはダンテの「神曲」(La Divina Commedia) の直訳である。

全五巻に渡り、非常にきびきびとした文章で主人公・東堂太郎の対馬における軍隊生活が綴られる。その文章の生硬さもさることながら、主人公の設定(=超人的な記憶力の持ち主)から来る膨大な引用、出来事および引用それ自体を契機とした数十ページに渡る逸脱、言葉遣いや表現の綾に対する主人公の偏執的とも言えるこだわり等々、ことばそのものへの尋常ならざる執着を楽しむことがほぼ主眼となっているような小説でもある。

「戦争の出てくる小説」といえばついつい反戦的なテーマだとか、あるいは軍隊生活の厳しさ、そこにおける上官の理不尽さなどのありきたりな主題を誰しも想定してしまうだろう。この小説においてもそれらの要素自体は含まれているものも、典型的なそれを期待して読むといずれの場面でも肩透かしを食うことになるだろう。批判的に戦争や軍隊を眺める眼の中にもいわゆる反戦はなく、理不尽な軍隊生活の中にも捩れたロジックがあり、粗暴・質朴な人間の中にも決して単純には断じきれない深淵がある。これはそういう小説なのである。

こういう風に書くとひたすらお堅い文学青年御用達の小説のように思えるかもしれないが、神聖喜劇には読みながら声を出して笑ってしまうような場面が頻発する。「笑ってはいけない◯◯」というシリーズがダウンタウンのガキの使いにあるが、当人たちが大真面目であり、また真面目に振る舞わなくてはいけないとされている場面の僅かな歪みで我々の笑いは噴出する。軍隊こそは、大真面目に滑稽なことが行われ、また真面目の極みを求められるが故にまさに神聖な「喜劇」の場になり得るのだ。

現代のグローバル資本主義のありようを「戦争」、会社を「軍隊」と例えるほど僕はアナクロではないし鈍感でもない。だが神聖喜劇の中で時折爆発する笑いの震源について一度考えてみるならば、我々がその日常において大真面目であることを強いられ、それが故に滑稽さを免れない状況をもたらすところの会社もまた「喜劇」の場の一つであることはどうやら間違いがなさそうである。

そう、神聖喜劇は、軍隊でのみ演じられるのではない。人間の作るあらゆる組織と、その組織が大真面目に行うゲームからそれは生まれるのである。

今調べたら漫画版も出ているようだが、おすすめはしない。先ほどから書いているようにこれは文章そのものをとことん楽しむための小説であり、また一般に優れた文学作品というのはテーマや話の筋を追っただけではその半分も味わえない。文章を読む時間そのものが体験であり、快楽であり、小説である。そうしたものなのだ。

神聖喜劇 第一巻

神聖喜劇 第一巻

万人向けのやさしい小説ではないが、歯ごたえのあるものを食べたい向きにはお勧めする。