今、『Building and Testing with Gradle』(O'Reilly)という本を読んでいます。
Gradleのスローテスト問題への対応
さて、この本の一節に次のような記述がありました。
When JUnit tests reach a certain level of proliferation within a project, there is a motivation to run them in parallel to get the results faster. However, there would be a great overhead to running every unit test in its own JVM. Gradle provides an intelligent compromise in that it offers a maxParallelForks
that governs the maximum simultaneous JVMs that are spawned.
In the same area of testing, but with a different motivation, is the forkEvery
setting. Tests, in their quest to touch everything and exercise as much as possible, can cause unnatural pressure on the JVM's memory allocation. In short, it is waht Java developers term a "leak". It can merely be the loading of every class causing the problem. This isn't really a leak since the problem stems from the fact that loaded class definitions are not garbage collected but instead are loaded into permgen space. The forkEvery
setting causes a test-running JVM to close and be replaced by a brand new one after the specified number of tests have run under an instance.
まあ、訳すのが面倒なので、大雑把にまとめると、
maxParallelForks
… テストの並列実行数forkEvery
… JVMの再起動の頻度(OutOfMemoryException
を回避するためにJVMを再起動する。)
使い方はこんな感じになります。
build.gradle
apply plugin: 'java'
repositories {
mavenCentral()
}
dependencies {
testCompile 'junit:junit:4.8.2'
}
test {
maxParallelForks = 5
forkEvery = 30
}
この例ではテストが5個同時に実行されて、30個のテストクラスが実行される度に一度JVMが再起動されるということになります。
これにより並列でテストを行い、かつ
OutOfMemoryException
を回避して、スローテスト問題に対応してくれるということです。テストの準備
まあ、こういう本は実際動かしてみてなんぼですので、テストをやってみることにしましょう。まずはテストを強引に作ります。
CreateTest.groovy
import static groovyx.gpars.GParsPool.*;
def packagePath = 'C:/Users/mike/IDEA_Project/GradleSample/src/test/java/orz/mikeneck/gradle/sample/boxunbox/test'
def head = $/
package orz.mikeneck.gradle.sample.boxunbox.test;
import org.junit.Before;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
/$
def body = $/
public static final int SIZE = 400;
private List<Integer> intList;
private List<Long> longList;
@Test
public void testInteger() {
int[] array = new int[SIZE];
int position = 0;
for(Integer item : intList)
array[position++] = item;
for (int i : array)
assertThat(i, is(intList.get(i)));
}
@Test
public void testLong() {
long[] array = new long[SIZE];
int position = 0;
for (Long item : longList)
array[position++] = item;
position = 0;
for(long item : array)
assertThat(item, is(longList.get(position++)));
}
@Before
public void setUp() throws Exception {
Integer[] integers = new Integer[SIZE];
Long[] longs = new Long[SIZE];
for(int i = 0; i < SIZE; i++)
integers[i] = new Integer(i);
intList = Arrays.asList(integers);
for (int i = 0; i < SIZE; i++)
longs[i] = new Long(i + Integer.MAX_VALUE);
longList = Arrays.asList(longs);
}
}
/$
def numbers = []
(1..400).each {
numbers << it
}
withPool {
numbers.collectParallel { number ->
def className = "BoxUnboxTest${number}"
def name = "${className}.java"
def fileName = "${packagePath}/${name}"
def define = "public class ${className} {"
def content = new StringWriter()
content << head
content << define
content << body
println ' ---- '
println "now processing : $fileName"
println ' ---- '
new File(fileName).write(content.toString(), 'UTF-8')
assert new File(fileName).exists() == true
}
}
Groovyで書いていますが、まあヒアドキュメントで書かれているので、どういうテストかすぐにわかると思います。
大量(400 x 2 = 800個)のオブジェクト生成および基本型の
int
とlong
のボクシング・アンボクシングというコストのかかるようなテストを400個作ります。ちなみに単体でテストするとこれくらいの速度です。
ここから単純に計算すると 0.014s x 400 -> 5.6s くらいかかることが想定されます。
テストの実行(並列しない)
まずは並列実行しない場合のテスト
build.gradle
apply plugin: 'java'
repositories {
mavenCentral()
}
dependencies {
testCompile 'junit:junit:4.8.2'
}
実行結果
C:\Users\mike\IDEA_Project\GradleSample>gradle test
:buildSrc:compileJava UP-TO-DATE
:buildSrc:compileGroovy UP-TO-DATE
:buildSrc:processResources UP-TO-DATE
:buildSrc:classes UP-TO-DATE
:buildSrc:jar UP-TO-DATE
:buildSrc:assemble UP-TO-DATE
:buildSrc:compileTestJava UP-TO-DATE
:buildSrc:compileTestGroovy UP-TO-DATE
:buildSrc:processTestResources UP-TO-DATE
:buildSrc:testClasses UP-TO-DATE
:buildSrc:test UP-TO-DATE
:buildSrc:check UP-TO-DATE
:buildSrc:build UP-TO-DATE
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:test UP-TO-DATE
BUILD SUCCESSFUL
Total time: 6.945 secs
C:\Users\mike\IDEA_Project\GradleSample>
約6.9秒くらいですかね。
何回か実施しましたが、だいたい同じくらいの時間でした。
テストの実行(並列する)
並列実行( 3並列 : 50回に一回JVMをリロード )する場合。
build.gradle
apply plugin: 'java'
repositories {
mavenCentral()
}
dependencies {
testCompile 'junit:junit:4.8.2'
}
test {
maxParallelForks = 3
forkEvery = 50
}
実行結果
C:\Users\mike\IDEA_Project\GradleSample>gradle test
:buildSrc:compileJava UP-TO-DATE
:buildSrc:compileGroovy UP-TO-DATE
:buildSrc:processResources UP-TO-DATE
:buildSrc:classes UP-TO-DATE
:buildSrc:jar UP-TO-DATE
:buildSrc:assemble UP-TO-DATE
:buildSrc:compileTestJava UP-TO-DATE
:buildSrc:compileTestGroovy UP-TO-DATE
:buildSrc:processTestResources UP-TO-DATE
:buildSrc:testClasses UP-TO-DATE
:buildSrc:test UP-TO-DATE
:buildSrc:check UP-TO-DATE
:buildSrc:build UP-TO-DATE
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:test UP-TO-DATE
BUILD SUCCESSFUL
Total time: 7.943 secs
C:\Users\mike\IDEA_Project\GradleSample>
あれ、7.943sもかかっている!
何回か挑戦…
:buildSrc:test UP-TO-DATE
:buildSrc:check UP-TO-DATE
:buildSrc:build UP-TO-DATE
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:test UP-TO-DATE
BUILD SUCCESSFUL
Total time: 7.025 secs
C:\Users\mike\IDEA_Project\GradleSample>gradle test
:buildSrc:compileJava UP-TO-DATE
:buildSrc:compileGroovy UP-TO-DATE
:buildSrc:processResources UP-TO-DATE
:buildSrc:classes UP-TO-DATE
:buildSrc:jar UP-TO-DATE
:buildSrc:assemble UP-TO-DATE
:buildSrc:compileTestJava UP-TO-DATE
:buildSrc:compileTestGroovy UP-TO-DATE
:buildSrc:processTestResources UP-TO-DATE
:buildSrc:testClasses UP-TO-DATE
:buildSrc:test UP-TO-DATE
:buildSrc:check UP-TO-DATE
:buildSrc:build UP-TO-DATE
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:test UP-TO-DATE
BUILD SUCCESSFUL
Total time: 6.964 secs
C:\Users\mike\IDEA_Project\GradleSample>
う~ん、大して変わらないですね。
これはひょっとしてもっとテストケースを作らなければならないのかな?
とすると、今の手元にある環境ではちょっと実験できないので、
続きは家に戻ったらやってみます。
実験環境
OS : Windows 7
CPU : Intel Core i7 L640 (クアッドコア)
RAM : 8.00GB
おはようございます。
返信削除最初はなかなか上手くいかないものですよね。