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にもあげています。

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, 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にもあげています。

続き

今日はここまで!

31 Dec 2015, 22:46

REPL 駆動開発について(REPL Driven Development) 調べたメモ

REPL と REPL 駆動開発について調べたメモです.

REPL とは

Read-Eval-Print-Loop の略. 読んで、評価して、表示するを繰り返す.

対話的に開発するためのツール.

素早いフィードバックを得ることで、頭に浮かんだ考えを即実行できる!!

インタラクティブシェルとの違いがよくわからなかった. Lisp 系の言語で REPL という用語が利用され, スクリプト言語で インタラクティブシェルという用語が利用される??

各言語のサポート状況

スクリプト言語は大体できる.

  • Ruby … irb, pry
  • Python … iPhthon
  • Perl … perl -de 1

Lisp 系言語はもちろんできる.

  • Clojure .. lein repl
  • Scheme
  • Common Lisp

コンパイラ系言語でもサポートしようという取り組みがある.

REPL Driven Development

Repl によって開発を駆動する方法. 以下 RDD. 具体的には、 エディタと Repl を両側にならべて、以下のプロセスを繰り返す.

  1. 実装(S 式)を書いてみる
  2. 実装を 即座に Repl に読み込ませて評価
  3. 結果を確認しその実装が正しいことを確認する

この 記事によると、以下の 3 点から開放されることで、 開発に集中することができるとのこと.

  1. restarting the application … アプリケーションの再起動
  2. running something other than the application to verify behavior … 振る舞いの確認のためにアプリケーション以外を起動すること
  3. moving out of the source to execute arbitrary code… 任意のコードを実行するためにソースから離れること.

たとえば C#の場合は、アプリを止めて リビルドして、アプリを再起動するというサイクルが入るが、 このサイクルはとても時間がかかり受け入れがたい. REPL は瞬時にフィードバックが得られる.

RDD は TDD と組み合わせることもできる. テストを書くよりもより素早く フィードバッグを得られることが RDD の TDD に対するメリットだ. RDD の結果から, REPL の出力ずく pritty print をコピペすることで TDD のテストケースを書くことで、 リグレッションテストのメリットを享受できる.

資料

日本語

RDD 解説したスライド.

たいへん丁寧な解説。参考になる.

つまり、プログラムを作りながら、横で REPL を起動しておき、 その関数をロード(またはリロード)して実際に使ってみて、 さらに修正して、使ってみて、ということができる、ということです。

REPL 駆動開発を体験することができる.

Clojure は他の言語とは違ってファイルをベースに開発しません。 基本的に REPL の上に全てあるので、それを最大限に活かして開発出来るのが Clojure の利点であり他の言語に対するひとつの優位性でしょう

英語

REPL で評価した結果をコピペで TDD の期待値としてつかえば、 期待値を用意するのが楽。REPL の結果からテストが自動できればいいねという話.

所感

なにも REPL 駆動開発なんて、名前をつけるまでもなく、 つくったコードを動かして結果を確認するということは、当たり前のことだ. C 言語をはじめて勉強したときも、書いて、コンパイルして、実行をしていた.

REPL 駆動開発は、このフィードバッグを得るスピードが普通よりも抜群に速いことで、 開発により集中出きるのではと感じた. Emacs で C-x C-e をすることで、 Clojure や Emacs Lisp コードを評価して結果をパッと得られることは、快感でもある.

RDD は TDD と比較されることが多い. TDD はテストコードを書くのに、またメンテナンスすることに時間がかかることが問題視されることが多い. TDD は費用対効果を考えてやらなければならないというのが今の持論.

とくにプロトタイプなどは品質よりは速く成果をあげることが大事.そこで、RDD である. 先日ハッカーと画家を読んだが、そこでも速くアイデアを形にすることがハッカー気質だと学んだ.

より素早くフィードバッグを回してガンガンつくることに相性が良い.

REPL 駆動開発は、素早くプロトタイプや自分の考えを形にするときに有効な手段だと感じた.

11 Dec 2014, 15:30

JUnit のテストケースをステートフルで利用する

はじめに

JUnit で, 結合テスト (Integration test), シナリオテストを書きたい.

トランザクションごとに長いテストを書く必要がある.

複数のトランザクションを順に処理していったときに, オブジェクトの状態の変化を検証したい.

JUnit はテストケースが独立

はじめ, トランザクションごとにテストケースを書いていたが, うまく動かない.. オブジェクトの状態が初期化されてしまう.

これは, JUnit の設計思想だった.

つまり, 各テストケースは独立だということ. テストで使用するオブジェクトはテストケース内で生成されて, テストケース内で消滅する.

たとえば

たとえば, このテストは失敗する. number は 0 が入っている.

import static org.junit.Assert.*;

import org.junit.Test;

public class MemoryTest {

    static int number;

    @Test
    public void test () {
        number = 1;
    }

    @Test
    public void test2 () {
        assertEquals (1, number);
    }
}

static を利用する

テスト間で状態を引き継ぐためには, 変数に static 修飾子をつける.

こうすると, 各テストケースの独立を破ることができる.

package test;

import static org.junit.Assert.*;

import org.junit.Test;

public class MemoryTest {

    static int number;
    static Foo foo;
    static Foo bar; 
    static Foo pee;     

    @Test
    public void test () {
        number = 1;
        foo = new Foo (1);
        bar = new Foo (foo);
        pee = new Foo ();       
    }

    @Test
    public void test2 () {
        assertEquals (1, number);
        assertEquals (1, foo.i);
        assertEquals (1, bar.foo.i);        
        assertEquals (2, pee.foo.i);
        foo.plus (3);
    }

    @Test
    public void test3 () {
        assertEquals (4, foo.i);
    }

}

class Foo {
    public int i;
    public Foo foo;

    public Foo (int i) {
        this.i = i;
    }

    public Foo () {
        this.foo = new Foo (2);
    }

    public Foo (Foo foo) {
        this.foo = foo;
    }

    public void plus (int i){
        this.i += i;
    }
}

テストの実行順序を制御する

JUnit のテストが実行される順番はランダム.

これだと, ステートフルなテストには不向きだ.

テストの実行順序を指定するには, 以下の方法がある

  • @FixMethodOrder (MethodSorters.NAME_ASCENDING) をクラスの頭に設定
  • メソッド名を 実行したいものから abc 順に変更.
import org.junit.Test;
import org.junit.runners.MethodSorters;
import org.junit.FixMethodOrder;

@FixMethodOrder (MethodSorters.NAME_ASCENDING)
public class MemoryTest {

    static int number;
    static Foo foo;
    static Foo bar; 
    static Foo pee;     

    @Test
    public void test () {
        number = 1;
        foo = new Foo (1);
        bar = new Foo (foo);
        pee = new Foo ();       
    }

    @Test
    public void test2 () {
        assertEquals (1, number);
        assertEquals (1, foo.i);
        assertEquals (1, bar.foo.i);        
        assertEquals (2, pee.foo.i);
        foo.plus (3);
    }

    @Test
    public void test3 () {
        assertEquals (4, foo.i);
    }

}

07 Dec 2014, 12:45

Haskell の xUnit ツール HUnit を試す

はじめに

Haskell でテストコードを書くツールをしらべてみた.

メジャーなものは以下

  • doctest
  • QuickCheck
  • HSpec
  • HUnit

各ツールの特徴

doctest

コメントにテストを書くスタイルのツール.

Python の doctest を haskell に移植したものだとか.

QuickCheck

ランダムなテストデータによって関数の性質をテストする.

xUnit とは異なるコンセプトをもつ.

HSpec

xSpec ライクなテストツール.

Ruby の RSpec にインスパイヤされたらしい.

記法が BDD 的.

HUnit

xUnit ライクなテストツール. JUnit ライク.

HUnit を試す

JUnit になじみがあるので, HUnit を試してみた.

Install

$ cabal install HUnit

Usage

Test.HUnit をインポート.

import Test.HUnit

テスト対象コード

import Data.List
import Data.Char
import Unsafe.Coerce

data Nat = Zero
         | Succ Nat
         deriving Show

natToInteger (Succ n) = natToInteger n + 1
natToInteger Zero = 0

テストコード

tests = TestList
        [ "natToInteger 1" ~: natToInteger Zero ~?= 0
        , "natToInteger 2" ~: natToInteger (Succ Zero) ~?= 1
        , "natToInteger 3" ~: natToInteger (Succ (Succ Zero)) ~?= 2
        ]

テスト実行

runTestTT (テスト関数名) でテスト実行.

$ runTestTT tests
Cases: 3  Tried: 3  Errors: 0  Failures: 0
Counts {cases = 3, tried = 3, errors = 0, failures = 0}

わざと失敗させてみる.

*Main> runTestTT tests
### Failure in: 2:natToInteger 3
expected: 1
 but got: 2
Cases: 3  Tried: 3  Errors: 0  Failures: 1
Counts {cases = 3, tried = 3, errors = 0, failures = 1}

Bookmarks

05 Apr 2014, 18:15

Excel VBAで テスト駆動開発してみる

2年前に、VB Lite Unitをいじっていたことがあるが、 より未来派ハッカーに近づいたいま、改めていじってみる。

クラスモジュールでないとテストできない

VBAには、ユーザフォーム、標準モジュール、クラスモジュールの3種類のコードの種類がある。 VBLiteUnitでは、クラスモジュールに対してテストを実施する。

クラスモジュールっなんだっけ?という僕たちには、以下のリンクが参考になる。

テスト実行用マクロをつくる

以下のようなSubプロシージャを用意することで、テスト実行をマクロにする。

Public Sub RunAllTests
        Runtests New Tst_Data
End Sub

テスト実行用のVBSをつくる

コマンドラインからRunAllTestsを実行したい。

以下を参考にして、Excelのマクロをコマンドラインから実施するためのVBSを作成。


Dim objExcelApp ,objExcelBook
Dim macro_path

macro_path = "C:\cygwin\home\TSUNEMICHI\repo\vba-study\sample\test.xlsm"

Set objExcelApp = CreateObject("Excel.Application")
Set objExcelBook = objExcelApp.Workbooks.Open(macro_path, , True)

objExcelApp.Run "'" + macro_path + "'!ThisWorkbook.reloadModule"
objExcelApp.Run "'" + macro_path + "'!ThisWorkbook.runAllTests"

objExcelBook.Saved = True
objExcelBook.Close False
Set objExcelBook = Nothing
Set objExcelApp = Nothing

テスト実行用のRakefile作成

TDDっぽくするために、rakefileを作成してみた。rake testでテストを実行する。


require 'rake/clean'

FILE_PATH  = "./test.xlsm"

task :default => "open"

task :open do
  `cygstart  #{FILE_PATH}` 
end

task :test do
  `cygstart test.vbs` 
end

テストコード作成

src/testディレクトリ配下にそれぞれ作成。

完成品

<div class="outline-text-2" id="text-1">
  <p>
    githubにアップ。
  </p>

  <ul class="org-ul">
    <li>
      <a href="https://github.com/tsu-nera/ExcelVBA_TDD_Sample">https://github.com/tsu-nera/ExcelVBA_TDD_Sample</a>
    </li>
  </ul>
</div>

<div id="outline-container-sec-1-1" class="outline-3">
  <h3 id="sec-1-1">
    Special Thanks
  </h3>

  <div class="outline-text-3" id="text-1-1">
    <ul class="org-ul">
      <li>
        <a href="http://rsh.csh.sh/text-scripting-vba/">http://rsh.csh.sh/text-scripting-vba/</a>
      </li>
      <li>
        <a href="http://vb-lite-unit.sourceforge.net/">http://vb-lite-unit.sourceforge.net/</a>
      </li>
    </ul>
  </div></p>
</div></p>

19 Feb 2014, 15:13

Rubyのテストコード自動生成!レガシーコードをrspec-kickstarterで切り崩す

RSpec絶賛勉強中! t-wadaさんの記事を参考にして写経してる。

RSpecについての情報を探してネット上を徘徊していたら、 rspec-kickstarterというおもしろそうなツールを発見したので試す。

rspec-kickstarterとは

githubのREADMEによると、 既存コードからRSpecのスケルトンコードを自動生成するツールのようだ。

  • インストール
    gem install rspec-kickstarter
    

つかってみる

写経用教材をrspec-kickstarterにかけてみると・・・

require 'spec_helper'
require 'message_filter'

describe MessageFilter do

  # TODO auto-generated
  describe '#new' do
    it 'works' do
      word = double('word')
      result = MessageFilter.new(word)
      expect(result).not_to be_nil
    end
  end

  # TODO auto-generated
  describe '#detect?' do
    it 'works' do
      word = double('word')
      message_filter = MessageFilter.new(word)
      text = double('text')
      result = message_filter.detect?(text)
      expect(result).not_to be_nil
    end
  end

end

あっという間に自動生成。自動生成なのでテストがDRYではあるが、それでも素晴らしい。 いきなり最終回だ。

テストのリズム

オプション -f をつけると、 既存のspecコードに足りないメソッドのサンプルを追加してくれる。

このオプションをつかうと、以下のようなTDDのリズムが考えられる。

  1. メソッドを定義
  2. メソッドに対応するスケルトンを生成し、テストを実装
  3. テスト失敗
  4. メソッド実装
  5. テスト成功

普通ならば、このあとリファクタリングをする。

まだ、お試しなので、もう少し使ってみて有用性を見極めてみる。

一からスクラッチを書くコードよりも、レガシーコードにたいして絶大な効果がありそう。

また、テストファイルをとりあえず生成することができるので、初めの一歩をkickするためだけの用途でも使えそう。