Pitaliumを使ったデータ駆動テストの実装方法について

379 views
Skip to first unread message

Naoya Kojima

unread,
Aug 1, 2016, 5:08:27 PM8/1/16
to hifive User Group
はじめまして。小島と申します。

PitaliumのAssertionViewクラスが魅力的だった為、
自分のデータ駆動テストでも使ってみようとテストを書いたのですが、以下のエラーが出て上手くいかないため質問をさせて頂きました。

1.テストコード

自分のデータ駆動テストコードでうまくいかなかったので、自分のテストコードが悪いのか確認する為に、
以下のようなサンプルテストコードをPtlTestBaseを使って書いてみました。

package com.htmlhifive.pitalium.sample;

import static org.junit.Assert.assertEquals;
import java.util.Arrays;
import org.junit.Test;
import org.junit.runners.Parameterized.Parameters;
import com.htmlhifive.pitalium.core.PtlTestBase;


public class PtlSampleTest extends PtlTestBase {

@Parameters
public static Iterable<Object[]> data() {
return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } });
}

private final int fInput;
private final int fExpected;

public PtlSampleTest(int input, int expected) {
fInput = input;
fExpected = expected;
}

@Test
public void testFib() {
assertEquals(fExpected, fib(fInput));
}

private int fib(int x) {
// TODO: actually calculate Fibonacci numbers
return 0;
}
}

2.エラー内容

java.lang.Exception: Test class should have exactly one public zero-argument constructor
at org.junit.runners.BlockJUnit4ClassRunner.validateZeroArgConstructor(BlockJUnit4ClassRunner.java:147)
at org.junit.runners.Parameterized$TestClassRunnerForParameters.validateConstructor(Parameterized.java:226)
at org.junit.runners.BlockJUnit4ClassRunner.collectInitializationErrors(BlockJUnit4ClassRunner.java:103)
at org.junit.runners.ParentRunner.validate(ParentRunner.java:355)
at org.junit.runners.ParentRunner.<init>(ParentRunner.java:76)
at org.junit.runners.BlockJUnit4ClassRunner.<init>(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.Parameterized$TestClassRunnerForParameters.<init>(Parameterized.java:171)
at org.junit.runners.Parameterized.createRunnersForParameters(Parameterized.java:319)
at org.junit.runners.Parameterized.<init>(Parameterized.java:282)
at com.htmlhifive.pitalium.core.ParameterizedThreads.<init>(ParameterizedThreads.java:75)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at org.junit.internal.builders.AnnotatedBuilder.buildRunner(AnnotatedBuilder.java:29)
at org.junit.internal.builders.AnnotatedBuilder.runnerForClass(AnnotatedBuilder.java:21)
at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:59)
at org.junit.internal.builders.AllDefaultPossibilitiesBuilder.runnerForClass(AllDefaultPossibilitiesBuilder.java:26)
at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:59)
at org.junit.internal.requests.ClassRequest.getRunner(ClassRequest.java:26)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.<init>(JUnit4TestReference.java:33)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestClassReference.<init>(JUnit4TestClassReference.java:25)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestLoader.createTest(JUnit4TestLoader.java:48)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestLoader.loadTests(JUnit4TestLoader.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:444)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)


BlockJUnit4ClassRunner.validateZeroArgConstructorのチェックに引っかかっていることはソースを見れば理解できたのですが、
なぜこのようなチェックに掛かってしまうのかが分からず質問をさせて頂きました。

JUnit4の勉強をし始めたばかりということもあり、基本的な質問をしてしまっておりましたら申し訳ございませんが、ご教示ください。

Takuya Kashimura

unread,
Aug 2, 2016, 12:25:19 AM8/2/16
to hifive User Group
小島さん

ご質問ありがとうございます。

Parameterizedの仕組みとして、parameterへの値のsetの方法は、
大きく
 ①コンストラクタ
 ②フィールドへのinjection(フィールドに@Parameterをつける)
があります。

PitaliumはPtlTestBaseで②の方法を使って、capabilitiesの設定をしています。
そのため、上記のように書いても、JUnitはPtlTestBaseを見て②と判定してしまい、
その結果、引数なしのコンストラクタのみ許すようになっています。

したがって、コンストラクタではなく、フィールドに@Parameterをつけてやれば、動作すると思います。

ただし、前述の通り、capabilitiesの設定ですでにParameterizedの仕組みを使っているため、
子クラスでそれを使いたい場合は、capabilitiesの設定も一緒に渡して頂く必要があります。

以下、サンプルコードです。

public class SampleTest extends PtlTestBase {

@Parameters(name = "{0}, value={1}, expected={2}")
public static Iterable<Object[]> data() {
List<PtlCapabilities[]> capabilities = readCapabilities(); // capabilitiesを取得

Object[][] data = new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } };

List<Object[]> parameters = new ArrayList<>();

for (PtlCapabilities[] capability : capabilities) {
for (int i = 0; i < data.length; i++) {
parameters.add(new Object[] { capability[0], data[i][0], data[i][1] }); // 最初の要素はcapabilityとする
}
}
return parameters;
}

@Parameter(1) // Parameter(0)はcapability
public int fInput;

@Parameter(2)
public int fExpected;

@Test
public void testFib() {
assertEquals(fExpected, fib(fInput));
}

private int fib(int x) {
// TODO: actually calculate Fibonacci numbers
return 0;
}
}

Parameter(0)はcapabilitiesで使っているため、子クラスで用意するフィールドでは、Parameter(1)以降を使ってください。

上記のparametersの作成部分は、Pitaliumでもう少しカバーしようと思いますが、
現状は上記で実行して頂けますでしょうか。

よろしくお願いいたします。

Naoya Kojima

unread,
Aug 2, 2016, 5:51:09 PM8/2/16
to hifive User Group
Kashimura さん

さっそくご返信いただき、またご丁寧に教えて頂きましてありがとうございました。
@Parameterでインジェクションが出来ることは知りませんでした。
JUnitのGitHub(Parameterized test)を見ると書いてありましたね(^_^;)

この為、PtlTestBaseどうやってcapabilities.jsonをロードしているのか分からなかったのですが、おかげさまで謎が解けました。

また、サンプルコードを添付して頂きましたお陰で、capability毎に同じテストを繰り返し実行させる仕掛けも分かりました。
どのようにデータ駆動を実装するべきなのかも大変わかりやすかったです。(自分の環境で動かすことが出来ました)

Pitaliumの設計思想の一部を理解できましたので、この方法でPitaliumを使わせて頂きたいと思います。
ありがとうございました。


2016年8月2日火曜日 13時25分19秒 UTC+9 Takuya Kashimura:
Reply all
Reply to author
Forward
0 new messages