JCUnitのBuilder APIをためす
以前、以下の記事で紹介した、JCUnitの開発がすすんでいます。(開発ブログ, ソース)
主な拡張としては、状態機械をモデリングしてテスト生成、ビルダーAPIの整備などなど*1
Spockからの利用サンプルをかきなおしてみました。ビルダーAPIを使用しています。
@Grab('com.github.dakusui:jcunit:0.5.4') @Grab('org.spockframework:spock-core:1.0-groovy-2.4') import com.github.dakusui.jcunit.core.factor.Factors import com.github.dakusui.jcunit.core.factor.Factor import com.github.dakusui.jcunit.generators.TupleGenerator import com.github.dakusui.jcunit.core.FactorField; import com.github.dakusui.jcunit.core.tuples.Tuple; import com.github.dakusui.jcunit.constraint.constraintmanagers.ConstraintManagerBase; import com.github.dakusui.jcunit.constraint.ConstraintManager; import com.github.dakusui.jcunit.exceptions.UndefinedSymbol; import spock.lang.* class HelloSpec extends Specification { static ConstraintManager closureConstraintManager(names, Closure clos) { return new ConstraintManagerBase() { @Override boolean check(Tuple tuple) throws UndefinedSymbol { Binding binding = new Binding() names.each { if (!tuple.containsKey(it)) { throw new UndefinedSymbol(it) } binding.setProperty(it, tuple[it]) } clos.delegate = binding clos.call() } } } static Collection genPairwiseTestData(Map factors, Closure constraint = null) { def builder = new TupleGenerator.Builder() if (constraint != null) { builder = builder.setConstraintManager(closureConstraintManager(factors.keySet(), constraint)) } builder.setFactors(new Factors(factors.collect{k,v -> new Factor(k, v)})) .build() .collect{ it.values() }; } static intLevels = [ 1, 0, -1, 100, -100, Integer.MAX_VALUE, Integer.MIN_VALUE ]; static stringLevels = [ "Hello world", "こんにちは世界", "1234567890", "ABCDEFGHIJKLMKNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz", "`-=~!@#\$%^&*()_+[]\\{}|;':\",./<>?", " ", "" ] @Unroll def "分配法則(a=#a,b=#b,c=#c)"() { expect: c*(a+b) == c*a + c*b where: [a,b,c] << genPairwiseTestData([a:intLevels, b:intLevels, c:intLevels]) } @Unroll def test1() { expect: c*(a+b) == c*a + c*b where: [a,b,c] << genPairwiseTestData([a:intLevels, b:intLevels, c:intLevels], { return a > 0 && b != c }) } @Unroll def test2() { expect: (a+b).size() == a.size() + b.size() where: [a,b] << genPairwiseTestData([a:stringLevels, b:stringLevels]) } @Unroll def test3() { expect: a+b == b+a where: [a,b] << genPairwiseTestData([a:[1,2,3], b:intLevels]) } @Unroll def test4() { expect: a == b || a != b where: [a,b] << genPairwiseTestData([a:MyBoolean.values() as List, b:MyBoolean.values() as List]) } } enum MyBoolean { True, False }
以前のようにアノテーションを使用せずとも使用できるようになりました。
気付いた点としては、
- 各型のレベルのデフォルト値をAPIクラスからうまく参照できなかったので自前で定義しています。 (上記の static intLevels = [ 1, 0, -1, 100, -100, Integer.MAX_VALUE, Integer.MIN_VALUE ]の部分)
- SpockのデータパイプAPIにもうちょっと柔軟性が欲しい。今の呼び出し方だとデータ指向テストの疑似変数名の指定に重複があるので、疑似変数名を取得もしくは設定できるAPIがあると良いのだが。Spock Extensionで触れるフックがあるかな。
など。楽しいです。
*1:他に、生成テストデータの保存、再生などもなされているようです。