25 Nov 2017, 11:11

Logbackのログを見やすくする方法(ファイルをgrep, Lilith)

仕事で、Javaのロギングツールとして slf4j + logbackを使っている。

このログなのだが、IntelliJの出力画面から見ると、みにくい。

できれば、一連のログをgrepしたいのだが、それがIntellJではできないのだ。

そこで、出力されたログをフィルタする方法を調べたところ、2つ方法が見つかった。

ファイルに書き出してgrepする

まずは、簡単な方法から。ログをファイル出力して出力結果をキーワードでfilterする。

filterの方法は、さくらエディタだったり、秀丸エディタだったりの機能を使えば良い。

では、ファイルにログを出力する方法だけれども、以下をlogback.xmlに追加。

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/app.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logs/app.%d{yyyy-MM-dd}.log.tar.gz</fileNamePattern>
            <maxHistory>7</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="FILE"/>
    </root>
</configuration>

これで logs/app.logにログが出力される。

ログイベントビューアの Lilithをつかう

Lilithは OSSのlogback用ログビューア。

Lilithのために、以下のツールをインストールする。build.gradeのdependenciesに以下を追加。

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <appender name="LogbackClassic" class="ch.qos.logback.classic.net.SocketAppender">
        <RemoteHost>localhost</RemoteHost>
        <Port>4560</Port>
        <ReconnectionDelay>170</ReconnectionDelay>
        <IncludeCallerData>true</IncludeCallerData>
    </appender>

    <appender name="FILE2" class="ch.qos.logback.core.FileAppender">
        <file>logs/classic.lilith</file>
        <encoder class="de.huxhorn.lilith.logback.encoder.ClassicLilithEncoder">
            <IncludeCallerData>true</IncludeCallerData>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="LogbackClassic"/>
        <appender-ref ref="FILE2"/>
    </root>
</configuration>

Lilithを立ち上げておいて、アプリを実行すると、Lilithにログが表示される。

18 Nov 2017, 13:53

JUnitとdbUnitを使ってPostgresのDBテストを実施する

はじめに

データベースの知識がなくって、DBのテスト方法がわからない。

そこで、データベースのテストを自動化するためのツール、dbUnitを調べてみた。

dbUnitをつかうと以下のようなことができる。

  1. JUnitのテスト実施前に、データをデータベースに投入。
  2. テスト実施後に、期待のデータと変更後のデータベースの値を比較。

データはxmlデータ, Excelデータ, csvデータが利用できる。

インストール

ビルドツールにgradleを利用しているので、gradleの設定を書く。

以下から、最新のバージョンをクリックして、gradleのコードをdependenciesに書く。

dependencies {   
    compile group: 'org.dbunit', name: 'dbunit', version: '2.5.4'
}

データのテーブル用意

データのテーブルを用意する。ここでは、flywayを使った。

buildscript {
    dependencies {
        classpath 'com.h2database:h2:1.4.191'
    }
}

plugins {
    id "org.flywaydb.flyway" version "4.2.0"
}

flyway {
    url = 'jdbc:postgresql://localhost:5434/sampleDB'
    user = 'postgres'
    password = 'postgres'
}

本題ではないので、ここでは参考リンクを貼って詳細はそちらに譲る。

以下のようなテーブルをflywayMigrateで作成。V1__create_table.sql

CREATE TABLE HOGE (
    ID INT,
    NAME VARCHAR(255),
    STATE VARCHAR(10)
);

投入データと期待データの準備

データはxml形式のものを用意する。テスト前、テスト後のデータを用意。ID=3の stateを normal から errorに書き換える。

Before.xml

<?xml version="1.0" encoding="UTF-8"?>
<dataset>
    <HOGE ID="1" STATE="normal" NAME="name#1"/>
    <HOGE ID="2" STATE="normal" NAME="name#2"/>
    <HOGE ID="3" STATE="normal" NAME="name#3"/>
    <HOGE ID="4" STATE="normal" NAME="name#4"/>
    <HOGE ID="5" STATE="normal" NAME="name#5"/>
</dataset>

After.xml

<?xml version="1.0" encoding="UTF-8"?>
<dataset>
    <HOGE ID="1" STATE="normal" NAME="name#1"/>
    <HOGE ID="2" STATE="normal" NAME="name#2"/>
    <HOGE ID="3" STATE="error" NAME="name#3"/>
    <HOGE ID="4" STATE="normal" NAME="name#4"/>
    <HOGE ID="5" STATE="normal" NAME="name#5"/>
</dataset>

テスト対象コード

テスト対象コードをいかに示す。やっていることは、SQL文を実行しているだけ。

package sample;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;

public class DbUnitSample {
    private static final String SQL = "update HOGE set state = 'error' where name = 'name#3';";

    public void execute() {
        try(Connection conn = DriverManager.getConnection(
                "jdbc:postgresql://localhost:5434/sampleDB", "postgres", "postgres");
        ) {
            PreparedStatement stmt = conn.prepareStatement(SQL);
            stmt.executeUpdate();
        } catch(Exception e) {
            System.out.println("failed.");
        }
    }
}

テストコード

テストコードをいかに示す。

package sample;

import org.dbunit.Assertion;
import org.dbunit.IDatabaseTester;
import org.dbunit.JdbcDatabaseTester;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.ITable;
import org.dbunit.dataset.SortedTable;
import org.dbunit.dataset.xml.FlatXmlDataSetBuilder;
import org.dbunit.operation.DatabaseOperation;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.File;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;

public class DbUnitSampleTest {
    private static IDatabaseTester databaseTester;
    private DbUnitSample sample;

    @Before
    public void setUp() throws Exception {
        databaseTester = new JdbcDatabaseTester("org.postgresql.Driver",
                "jdbc:postgresql://localhost:5434/sampleDB",
                "postgres",
                "postgres",
                "public");

        IDataSet dataSet = new FlatXmlDataSetBuilder().build(
                new File("src/test/resources/db/dbunit/Before.xml"));
        databaseTester.setDataSet(dataSet);
        databaseTester.setSetUpOperation(DatabaseOperation.CLEAN_INSERT);
        databaseTester.onSetup();

        sample = new DbUnitSample();
    }

    @After
    public void tearDown() throws Exception {
        databaseTester.setTearDownOperation(DatabaseOperation.NONE);
        databaseTester.onTearDown();
    }

    @Test
    public void state更新() throws Exception {
        sample.execute();
        IDataSet expectedDataSet = new FlatXmlDataSetBuilder().build(
                new File("src/test/resources/db/dbunit/After.xml"));
        ITable expectedTable = expectedDataSet.getTable("HOGE");

        IDataSet databaseDataSet = databaseTester.getConnection().createDataSet();
        ITable actualTable = databaseDataSet.getTable("HOGE");
        ITable sortedTable = new SortedTable(actualTable, new String[]{"id"});

        Assertion.assertEquals(expectedTable, sortedTable);
    }

    @Test
    public void state更新2() throws Exception {
        sample.execute();
        IDataSet databaseDataSet = databaseTester.getConnection().createDataSet();
        ITable actualTable = databaseDataSet.getTable("HOGE");

        String actualState = "normal";
        for(int i=0; i < actualTable.getRowCount(); i++) {
            if (actualTable.getValue(i, "name").toString().equals("name#3")) {
                actualState = actualTable.getValue(i, "state").toString();
                break;
            }
        }
        assertThat("error", is(actualState));
    }
}

ちょっと注意するところは、sqlのupdateを実行すると、updateしたid=3が、rowの一番最後になってしまうところ。なので、テスト1では、わざわざソートして対応、テスト2では、for文で名前をキーにして検索をしている。

        ITable sortedTable = new SortedTable(actualTable, new String[]{"id"});

おわりに

手動でデータベースを書き換えてテストするのではなくて、flywayやらdbunitやら、使えるものは使って効率よく開発すべき。

しかし問題は、テストを書く時間すら今は確保できないくらい追い詰められていること。

進捗ヤバメです。(´・ω・`)

code:

18 Nov 2017, 08:27

PowerMockを使ってJavaコードをテストしてみた

はじめに

仕事で JavaのTesting Frameworkとして Spockを利用する予定があったので、一生懸命勉強していたが、政治的な理由によって Spockではなく JUnitが採用されてしまった!

というわけで、急遽 JUnitで利用できそうな Mockフレームワークを調査。

PowerMockというのがなかなかいけているので、それを調べた。

今回のテスト対象コードは、以前書いたSpock Frameworkのときのと同じです。

package sample;

public class MockSample {
    private MessageManager mgr;

    public void setMgr(MessageManager mgr) {
        this.mgr = mgr;
    }

    public MockSample() {
        mgr = new MessageManagerImpl();
    }

    public void sendMsg(String msg) {
        mgr.send(msg);
    }

    public int sendMsg2(String msg) {
        return mgr.send2(msg);
    }

    public void sendMsg3(String msg) {
        MessageManager mgr2 = new MessageManagerImpl();
       mgr2.send(msg);
    }

    public static int sendMsg4(String msg) {
        return MessageManagerStatic.send(msg);
    }

    private void sendMsg5(String msg) {
        mgr.send(msg);
    }
}
package sample;

public interface MessageManager {
    void send(String msg);
    int send2(String msg);
}
package sample;

public class MessageManagerImpl implements MessageManager {
    @Override
    public void send(String msg) {
        System.out.println(msg);
    }

    @Override
    public int send2(String msg) {
        System.out.println(msg);
        return 0;
    }
}
package sample;

public class MessageManagerStatic {
    public static int send(String msg) {
        System.out.println(msg);
        return 0;
    }
}

PowerMock Install w/ gradle

Gradleをビルドツールで利用しているので、gradleの方法を書く。

公式wikiには、Mavenのやり方が書いてあるので、そちらを参照。

build.gradleの dependenciesに以下を追加する。

もっと最新バージョンが出ているらしいが不安定だとwikiに書いてあったので、安定バージョンを。

dependencies {
    testCompile 'junit:junit:4.12'
    testCompile 'org.powermock:powermock-module-junit4:1.6.2'
    testCompile 'org.powermock:powermock-api-mockito:1.6.2'
}

テスト!テスト!テスト!

テストする内容は、比較のためSpock Frameworkと同じにしてます。

まずは、お決まりのおまじないアノテーション2つを書く

  • @RunWith(PowerMockRunner.class)
  • @PrepareForTest({MockSample.class, MessageManagerStatic.class})

    • MockSample.class … メソッド内のインスタンスモック用
    • MessageManagerStatic … メソッド内のスタティックメソッドモック用

      呼び出し引数と回数をチェック

  • times で 呼び出し回数をチェック

  • Mockito.veirfy(spy).send(“Hello”) で引数にHelloが来たかチェック

@Test
public void 呼び出し引数をチェック() {
    MessageManager spy = PowerMockito.spy(new MessageManagerImpl());

    sample.setMgr(spy);
    sample.sendMsg("Hello");
    sample.sendMsg("Hello");

    Mockito.verify(spy, times(2)).send("Hello");
}

戻り値を返す

  • when(spy.send2(“Hello”)).thenReturn(1) で 1を返す.
@Test
public void 戻り値を返す() {
    MessageManager spy = PowerMockito.spy(new MessageManagerImpl());
    when(spy.send2("Hello")).thenReturn(1);
    sample.setMgr(spy);

    int ret = sample.sendMsg2("Hello");

    Mockito.verify(spy, times(1)).send2("Hello");
    assertThat(1, is(ret));
}

メソッド内で生成されるインスタンスをモックに置き換える

  • PowerMockit.whenNiewでMessageManagerImplがnew されたときに、作成したmockオブジェクトにすり替える。

この機能は、いろんなフレームワークを試しきたが、PowerMock/Mockitoで初めて見た。

@Test
public void メソッド内で生成されるインスタンスをモックに置き換える() throws Exception {
    // https://github.com/powermock/powermock/wiki/MockConstructor
    MessageManagerImpl mock = PowerMockito.mock(MessageManagerImpl.class);
    PowerMockito.whenNew(MessageManagerImpl.class).withNoArguments().thenReturn(mock);

    sample.sendMsg3("Hello");

    Mockito.verify(mock).send("Hello");
}

staticなメソッドをテストする

オブジェクト内のメソッドからstaticメソッドを呼んでいたって、モックを利用することで、戻り値を書き換えることができる。

@Test
public void staticなメソッドをテストする() {
    // https://github.com/powermock/powermock/wiki/Mockito#mocking-static-method
    PowerMockito.mockStatic(MessageManagerStatic.class);
    Mockito.when(MessageManagerStatic.send("Hello")).thenReturn(1);

    int ret = sample.sendMsg4("Hello");

    assertThat(1, is(ret));
}

例外を発生させる

モックのメソッドが呼ばれたら例外を発生させることもできる。 PowerMockito.doThrow(new Exception).when(mock).send(“Hello”)で実現している。

@Test
public void 例外が発生しないことを確認する() {
    try {
        sample.sendMsg("Hello");
    } catch (Exception e) {
        fail(e.getMessage());
    }
}

@Test(expected = IllegalStateException.class)
public void 例外が発生したこと確認する() throws Exception {
    MessageManagerImpl mock = PowerMockito.mock(MessageManagerImpl.class);
    PowerMockito.doThrow(new IllegalStateException()).when(mock).send("Hello");
    PowerMockito.whenNew(MessageManagerImpl.class).withNoArguments().thenReturn(mock);

    sample.sendMsg3("Hello");
}

おわりに

PowerMock, メソッド内で生成されるインスタンスをモックに置き換える機能がとても強力。

しかし、最大の問題点は、ドキュメントが少ないことだ。いろいろネットで情報を漁ってみたものの、情報量が少ない。

やっぱりSpockを使いたいなあ。

今回のコードはgithubにもあげています。

01 Oct 2017, 08:43

Java開発でJenkinsを個人運用するためのメモ

はじめに

仕事でJava開発をすることになったのだけれども、

コードの品質をGUIで可視化すると便利だなと思って、

Jenkinsをつかってみることにしました。

あくまで可視化するためのグラフィカルな便利cronとしての役割のみ求めているので、複雑なことや難しいことはしません。

やりたいこと

  • gradle 連携
  • FindBugsの指摘を可視化
  • CheckStyleの指摘を可視化
  • ステップ数を数える
  • TODO 管理
  • spock テスト実行

version

  • Jenkins 2.73.1
  • StepCounter Plugin 2.0.0
  • FindBugs Plugin 4.71
  • CheckStyle Plug-in 3.49
  • Task Scanner Plug-in 4.52

Jenkinsのインストール

お試しなので、自PCのUbuntu 16.04 LTSにインストールします。手順は以下。

仕事では、CentOS 7.4にインストールします。

ポート番号の変更をします。デフォルトでは8080です。

/etc/default/jenkinsを編集して、12345とか適当なものに変更します。

- HTTP_PORT=8080
+ HTTP_PORT=12345

Jenkinsを再起動。

$ sudo systemctl daemon-reload
$ sudo service jenkins restart

http://localhost:12345 にアクセスして、画面が表示されることを確認。

手順にしたがって、初期設定をする。オススメプラグインをインストールする。

リポジトリをチェックアウトしてビルドしてみる

とりあえず以下のgithub repoを利用します。ビルドツールはgradleです。

設定手順

  • General -> プロジェクト名に “hello_jenkins”を入力
  • ソースコード管理 -> git にチェックして、リポジトリURLに https://github.com/tsu-nera/java_spock_playground.git を入力
  • ビルド -> ビルド手順の追加 -> Invoke gradle script を選択

手動でビルドを実行すると、githubからcloneして、gradleコマンドが叩かれる。

プラグインでやりたいことを実現

ステップ数を数える

以下のプラグインを追加。

  • StepCounter Plugin
  • 設定 -> ビルド後の処理 -> ビルド後の処理の追加を選択
  • Step Counter を選択
  • ファイルの種類: java,
  • 解析するファイルパターン **/*.java を入力

FindBugsの設定

以下のプラグインを追加。

  • FindBugs Plug-in
  • 設定 -> ビルド後の処理 -> ビルド後の処理の追加を選択
  • Findbugs警告の集計を選択
  • build/reports/findbugs/main.xmlを入力。
  • build.gradleに以下を追加
apply plugin: 'findbugs'

findbugs {
    reportsDir = file("./build/reports/findbugs")
    ignoreFailures = true
}

CheckStyleの設定

  • CheckStyle Plug-in
  • 設定 -> ビルド後の処理 -> ビルド後の処理の追加を選択
  • CheckStyle警告の集計を選択
  • build/reports/checkstyle/main.xmlを入力。
  • build.gradleに以下を追加
apply plugin: 'checkstyle'

checkstyle {
    reportsDir = file('./build/reports/checkstyle')
    configFile = file('./sun_checks.xml')
    ignoreFailures = true
}
  • ここから、checkstyleの設定ファイルを取得してプロジェクトルートに置く。 https://github.com/checkstyle/checkstyle/tree/master/src/main/resources
    • google_checks.xml
    • sun_checks.xml
  • 設定-> ビルド -> ビルド手順の追加 -> Invoke gradle script を選択
  • Use Gradle Wrapperを選択。Tasksのところに以下を入力。

    checkstyleMain findbugsMain

TODO を表示

  • TaskScanner Plug-in
  • 設定 -> ビルド後の処理 -> ビルド後の処理の追加を選択
  • 未解決タスクの集計を選択
  • 集計対象に **/*.javaを入力

spock テスト実行

  • 設定 -> ビルド後の処理 -> ビルド後の処理の追加を選択
  • JUnitテスト結果の集計を選択
  • テスト結果xmlに **/*Spec.xmlを入力
  • 設定-> ビルド -> ビルド手順の追加 -> Invoke gradle script を選択
  • Use Gradle Wrapperを選択。Tasksのところに以下を入力。

    clean test

ジョブの実行

エラーするので以下をプロジェクトルートで叩いた。

sudo gradle wrap

01 Oct 2017, 06:13

Java開発でIntelliJ IDEAにとりあえず入れたプラグインたちのメモ

はじめに

仕事でIntelliJ IDEAを使って Java開発することになったので、

とりあえず便利そうなプラグインをみつくろって入れてみたので、そのメモ。

IntelliJはデフォルトでいろんなプラグインがすでにインストールされているのが嬉しい。

プラグイン

静的解析

  • FindBugs-IDEA
  • CheckStyle-IDEA

FindBugsと CheckStyleは定番な静的解析ツールなので、とりあえず入れておく。

ちなみに、InteliJでは、Ctrl + Shift + Alt + L で コードのフォーマット整形をしてくれる。

テスト

Spock Frameworkを使うので、サポートツールを入れる。ハイライトするだけかな?

  • Spock Framework Enhancements

ステップカウント

進捗はステップ数で報告することになっているので、

ステップカウンタ(LOC)がIDEから使えると便利。

  • Statistics

日毎のステップ数が差分でわかればいいのだが、これはエクスポート機能がみつからない。

ステップ数については、別のツールを使ったほうがいいかな。Jenkinsをかませるか?

保管アクション

保管をトリガにして、いろいろな機能を動かす。

  • Save Action

設定 -> その他の設定 -> 保管アクションから各種設定を有効にする。

これはある意味、チーム開発しているときは、

勝手にいろいろな部分を修正してしまう恐れがあって危険だな。

Lombok

  • Lombok Plugin

LombokをIntelliJで補完したりするプラグイン。

Lombokについては、以下の記事を参照。

その他

あまり入れるようなプラグインはなかったな。オススメプラグインはない。

それはデフォルトでIntelliJ IDEAがとても便利ということだ。

思いついたら、追記するようにする。

24 Sep 2017, 13:39

Spock で モック を interfaceを用意せずにつくる方法

はじめに

前回、spockの mock 機能を利用するために、interfaceを用意していた。

というのも、interfaceからでないとMockを作成できないと思っていたからだ。

しかし、interfaceを用意しなくても、classからモックを作成できることがわかったので、紹介。

byte-buddy

class からモックを作ろうとすると、以下のようなエラーがでる。

org.spockframework.mock.CannotCreateMockException:Cannot create mock for class sample.Calculator. Mocking of non-interface types requires a code generation library. Please put byte-buddy-1.6.4 or cglib-nodep-3.2 or higher on the class path.

注目すべきは、Please put byte-buddy-1.6.4 or cglib-nodep-3.2 or higher on the class path.

なんだ、byte-buddyとは?ということで、検索。

どうやら、コードを自動生成するようなライブラリらしい。早速インストール。gradleをつかっているので、以下を build.gradleのdependencies

に追加。他のビルドツールでの追加方法は、byte-buddyのサイトを参照してください。

testRuntime "net.bytebuddy:byte-buddy:1.7.5"

すると、interfaceを実装していないクラスからでもMockがつくれた!

実列

以下のクラスのモックを作る。

class Calculator {

    int add(int a, int b) {
        return a + b
    }
}

テストコードは以下。

def "interfaceなしでMockをつくる" () {
    setup:
    def calc = Mock(Calculator)
    calc.add(_, _) >> 4

    expect:
    calc.add(1,2) == 4
}

これを走らせると、テストが成功する。

何が起こっているのかわからないけれども、おそらくクラスからインタフェースを自動生成しているのかな?とりあえず、便利になった。

コードは以下です。

今日はここまで。

24 Sep 2017, 08:48

Javaでsetter/getter自動生成するLombokが便利。IntelliJ+gradleでの設定

はじめに

Lombokという Javaのライブラリが便利。

アノテーションを使うことで、getter, setter, toStoring, コンストラクタなどの、

定型的なコードを自動生成することができる。

gradleでのインストール

gradleでは、build.gradleのdependenciesに以下の行を追加する。

dependencies {
    compileOnly 'org.projectlombok:lombok:1.16.18'
}

IntelliJ IDEAでの設定

まずは、プラグインを入れる。ツールバーのFile -> Preferences -> Plugins から、

Lombokを検索して、インストール。

次に、コンパイル時に、アノテーションからコードを自動生成する機能を有効にする。

Preferences – Build, Execution, Deployment – Compiler – Annotation Processors を開き

Enable annotation processing をチェック。

ここまでできたら、IntelliJを再起動。

使用例

いろいろと便利な機能があるのだが、つまみぐいして紹介。

@Getter/@Setter

このアノテーションを使うと、getter/setterを自動生成してくれる。

class Parson {
    private @Getter @Setter String name;
}
  • getName()
  • setName()

@ToString

このアノテーションを使うと、toStringを自動生成してくれる。

@ToString
class Parson {
    private @Setter String name;
}

Person(name=mikan) とか表示される。

@Data

getter/setter/コンストラクタ/toString/equals/hashCode 自動生成。すごい。

@Data
class Group {
    private String name;
}

@AllArgsConstructor

フィールドを持つ変数を引数にするコンストラクタを自動生成。

@AllArgsConstructor
class Group {
    private String name;
    private int id;
}
  • Group(String, int)

24 Sep 2017, 05:57

Spockでモックとスタブを使ってJavaコードをテストする

はじめに

前回の続きです。

前回は、基本的な文法を確認しました。今回は、モック機能を使ってみます。

テスト対象コード

想定としては、MockSampleが自分が開発しているコード。MessageManagerが他人が開発しているコードとします。

MessageManagerクラスの開発は遅延しているので、インタフェースだけ先に提供されているものとします。

こんなとき、自分が作ったMockSampleクラスをMessageManagerの依存関係をうまく扱ってテストすることを目指します。

  • MockSample.java
package sample;

public class MockSample {
    private MessageManager mgr;

    public void setManager(MessageManager mgr) {
        this.mgr = mgr;
    }

    public void sendMsg(String msg) {
        mgr.send(msg);
    }

    public int sendMsg2(String msg) {
        return mgr.send2(msg);
    }
}
  • MessageManager.java
package sample;

public interface MessageManager {
    void send(String msg);
    int send2(String msg);
}

テストコード

モックとスタブの言葉の定義は人それぞれあって混乱するのだけれども、ここでは

  • モック: 呼びだされたときに与えられるパラメータチェックと呼び出し回数をチェックするダミークラス
  • スタブ: 呼びだされた時に戻り値を返すダミークラス

とします。

モック

まずは、モックから。Mockを生成するには、

def mgr = Mock(MessageManager)

とするか、

MessageManager mgr = Mock()

で宣言します。

import sample.MessageManager
import sample.MockSample
import spock.lang.Specification

class MockSampleSpec extends Specification {

  def "呼び出し引数をチェック(Mocking)"() {
      setup:
      def sample = new MockSample()
      def mgr = Mock(MessageManager)
      sample.setManager(mgr)

      when:
      sample.sendMsg("hello")
      sample.sendMsg("hello")

      then:
      2 * mgr.send("hello")
  }
}

以下で2回呼び出しを期待して、呼び出し引数は”hello”を期待しています。

2 * mgr.send("hello")

スタブ

次にスタブです。以下の宣言で、戻り値をオブジェクトに指定します。

mgr.send2(_) >> 1
def "戻り値を返す(Stubbing)" () {
    setup:
    def sample = new MockSample()
    def mgr = Mock(MessageManager)
    mgr.send2(_) >> 1
    sample.setManager(mgr)

    expect:
    sample.sendMsg2("hello") == 1
}

例外をチェックするテストコード

戻り値の代わりに例外をスタブで発生させることもできます。

def "例外が発生したことを確認する" () {
    setup:
    def sample = new MockSample()
    def mgr = Mock(MessageManager)
    mgr.send(_) >> {throw new IllegalArgumentException()}
    sample.setManager(mgr)

    when:
    sample.sendMsg("hoge")

    then:
    thrown(IllegalArgumentException)
}

また、なにも例外が発生しなかったことを確認することもできます。

def "例外が発生しないことを確認する" () {
    setup:
    def sample = new MockSample()
    def mgr = Mock(MessageManager)
    sample.setManager(mgr)

    when:
    sample.sendMsg("hello")

    then:
    noExceptionThrown()
}

コードはgithubにもあげています。

参考

今日はここまで!

21 Sep 2017, 13:54

Spock をつかってJavaコードをテストしてみた

はじめに

仕事で spockをつかうことになりそうなので、まずはHello, World的な簡単なサンプルを実行することにしました。

環境構築

環境情報

  • JDK 1.8
  • IntelliJ IDEA 2017.2.4
  • spock 1.1(groovy2.4)

IntelliJで プロジェクト作成

IntelliJ のツールバーより、

  • [ファイル] -> [新規] を選択。
  • Gradleプロジェクトを選択。
  • Java, Groovyにチェックを入れる。

あとは、デフォルトのままにOKを押していく。

build.gradleの修正

spockをダウンロードするようにbuild.gradleを修正。

version '1.0-SNAPSHOT'

apply plugin: 'groovy'
apply plugin: 'java'

sourceCompatibility = 1.8

repositories {
    mavenCentral()
}

dependencies {
    compile 'org.codehaus.groovy:groovy-all:2.3.11'
    testCompile "org.spockframework:spock-core:1.1-groovy-2.4"
}

code

テスト対象コード

以下のクラスをテストします。src/main/java/sample/Calculator.java

package sample

class Calculator {
    int add(int a, int b) {
        return a + b
    }
}

テストクラス

以下のテストクラスを用意します。 test/groovy/CalculatorSpec

package spock

import sample.Calculator
import spock.lang.Specification

class CalculatorSpec extends Specification {

    def '足し算1'() {
        setup:
        Calculator calc = new Calculator()

        expect:
        calc.add(1,1) == 2
    }
}
  • setup: ・・・ 前処理でやりたいことを書く
  • expect: ・・・期待するテスト結果を書く == でAssertする。

CalculatorSpecを選択して、実行する。テストが成功します。

テストクラスその2

こんどはわざと失敗するテストを書いてみます。

def '足し算1'() {
    setup:
    Calculator calc = new Calculator()

    expect:
    calc.add(1,2) == 4
}

以下のようにわかりやすいエラー表示が出力されます。

Condition not satisfied:

calc.add(1,2) == 4
|    |        |
|    3        false
sample.Calculator@5e316c74

予想 :4

実際   :3

リファクタリング

前処理、後処理をまとめる関数が用意されています。

  • 前処理: setup(){}
  • 後処理: cleanup(){}
package spock

import sample.Calculator
import spock.lang.Specification

class CalculatorSpec extends Specification {
    def calc

    def setup() {
        calc = new Calculator()
    }

    def cleanup() {
        calc = null
    }

    def '足し算1'() {
        expect:
        calc.add(1,1) == 2
    }

    def '足し算2'() {
        expect:
        calc.add(1,2) == 3
    }
}

データ駆動テスト

where:を使うことで、データテーブルを使ったテストがかけます。

def "足し算:データ駆動テスト"() {
    expect:
    calc.add(a, b) == c

    where:
    a|b|c
    1|1|2
    2|3|5
    3|4|7
}

その他

コードはgithubにもあげています。

続き

今日はここまで!

18 Sep 2017, 02:24

Java本格入門(モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで)を読んだ

仕事でJavaをつかうことになった。

2年間Javaを使ってなかったので、忘れてしまった。

さびついた頭脳に磨きをかけるために、Java本格入門を読みました。アクロ本というらしい。

特徴

Javaの文法がコンパクトにまとまっている

Javaの文法を外観できます。

Java8まで対応しているので、ラムダ式やストリームまでカバーしています。

また、各Javaのバージョンごとの説明が詳しいです。

裏タイトル: 35歳からのJava入門

この本の裏タイトルは、35歳からのJava入門です。

世の中には、プログラマ35歳定年説というものがありますが、私はこれは嘘だと思います。

その根拠は、新人研修で習った知識で10年開発をやってきて、

時代の流れについていけなくなった人を指すのだと思います。

学び続ける人に定年はないです。そして、この本は、Java5,6を勉強して、

そのまま月日が経ってしまった、人に対する視点で書かれています。

新しくなったJava7,8の仕様がふんだんに紹介されています。

Javaの開発について書かれている

Javaの文法のみに終始している本とは違い、この本には、開発でつかうための知識が書かれています。

具体的には、(ビルド、javadoc, JUnit, FindBugs, Jenkins, JSON, Log…)

などの知識がてにはいります。実践的なJavaの入門書です。

ミッションクリティカルな配慮

秀逸なのは、例外についての説明や、並列処理についての説明です。

ここは、ミッションクリティカルなプロジェクトの経験を元に、とても丁寧に解説されています。

どこでどんな例外を捕捉するべきか、どんなライブラリを使うとスレッドせーフなプログラムになるか、

読んでいて知らないことが多いこのあたりの領域は目にウロコです。

感想

Java7,8の知識が知りたかったので、この本は当たり。

Java7,8ではこういう書き方があるということが書かれている。

自分は、一応 Java8 SE Silverの資格と2年間のJavaの開発経験があるので、

初心者ではない。そんな自分にとっては優しすぎる本は物足りない。

この本は、初心者以上の中級者をターゲットにして書かれているので、

自分にはちょうどよい難易度だった。

また、Java8で追加されたラムダ式やストリームについてもよくまとまっているので、

何度も参照して使いこなしたいところだ。

デザインパターンとか載っているけれども、これはおまけかな。

本格的に学ぼうとするなら、他の書籍をあたった方がいい。

とにかく、Javaに関する知識が盛りだくさんなので、

机の脇においておいて、困ったらいつでも参照できるようにしたい。