第4回Jenkins勉強会に行って来ました。
さて、内容の詳細ですが、
ブログまとめ職人の@shinyaa31さんが素晴らしいものを書いていますので、
そっち読んでください。
あと、トゥギャッターもまとめられているので、読んでみてください。
togetter - 10月15日 第4回Jenkins勉強会(東京都)
2011/10/15_第4回Jenkins勉強会( #jenkinsstudy )
当日のUST録画もありますので、興味のある方は御覧ください。
以上、報告終わり!
(´・ω・`)
(ω)
(m)
(w)
(M)
GroovyですよProhibitedTweet.groovy
@Grab(group='org.twitter4j', module='twitter4j-core', version='2.2.4')
import twitter4j.*
import twitter4j.auth.*
import twitter4j.conf.*
class Result {
int id
long status
String expected
String result
String showResult(){
return "success : ${expected == result}, id : ${id}, expected : ${expected}, result : ${result}"
}
@Override
String toString() {
return "<id : ${id}, expected : ${expected}, result : ${result}, status : ${status}>"
}
}
def consumerKey = 'consumerKey'
def consumerSecret = 'consumerSecret'
def accessToken = 'accessToken'
def accessTokenSecret = 'accessTokenSecret'
def conf =
new ConfigurationBuilder()
.setOAuthConsumerKey(consumerKey)
.setOAuthConsumerSecret(consumerSecret)
.setOAuthAccessToken(accessToken)
.setOAuthAccessTokenSecret(accessTokenSecret).build()
def twitter = new TwitterFactory(conf).getInstance()
def results = []
(65..90).each {
def tweets = "(${it as char})"
try{
def result = twitter.updateStatus(tweets)
results << new Result(id : it, expected : tweets, result : result.text, status : result.id)
}catch(TwitterException e) {
results << new Result(id : it, expected : tweets, result : '', status : -1)
}
}
results.each { println it.showResult()}
success : true, id : 65, expected : (A), result : (A)
success : true, id : 66, expected : (B), result : (B)
success : true, id : 67, expected : (C), result : (C)
success : false, id : 68, expected : (D), result : (C)
success : true, id : 69, expected : (E), result : (E)
success : false, id : 70, expected : (F), result : (E)
success : false, id : 71, expected : (G), result : (E)
success : true, id : 72, expected : (H), result : (H)
success : true, id : 73, expected : (I), result : (I)
success : true, id : 74, expected : (J), result : (J)
success : true, id : 75, expected : (K), result : (K)
success : false, id : 76, expected : (L), result : (K)
success : false, id : 77, expected : (M), result : (K)
success : true, id : 78, expected : (N), result : (N)
success : true, id : 79, expected : (O), result : (O)
success : true, id : 80, expected : (P), result : (P)
success : true, id : 81, expected : (Q), result : (Q)
success : true, id : 82, expected : (R), result : (R)
success : false, id : 83, expected : (S), result : (R)
success : true, id : 84, expected : (T), result : (T)
success : true, id : 85, expected : (U), result : (U)
success : true, id : 86, expected : (V), result : (V)
success : false, id : 87, expected : (W), result : (V)
success : true, id : 88, expected : (X), result : (X)
success : true, id : 89, expected : (Y), result : (Y)
success : true, id : 90, expected : (Z), result : (Z)
(D)、(F)、(G)、(L)、(M)、(S)、(W)、(d)、(f)、(g)、(l)、(m)、(s)、(w)
for (int i = 0; i < 100; i++ ) {
Thread.sleep(10);
}
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 = 100;
private List<Integer> intList;
private List<Long> longList;
@Test
public void testInteger() throws InterruptedException {
int[] array = new int[SIZE];
int position = 0;
for(Integer item : intList) {
array[position++] = item;
Thread.sleep(10);
}
for (int i : array)
assertThat(i, is(intList.get(i)));
}
@Test
public void testLong() throws InterruptedException {
long[] array = new long[SIZE];
int position = 0;
for (Long item : longList) {
array[position++] = item;
Thread.sleep(10);
}
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..300).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
}
}
build.gradle
apply plugin: 'java'
repositories {
mavenCentral()
}
dependencies {
testCompile 'junit:junit:4.8.2'
}
OS : Windows 7
CPU : Intel Xeon X3460 (8Core) (64bit)
RAM : 8.00GB
c:\Users\mike\IdeaProjects\TestingGradle>gradle build
: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
:jar UP-TO-DATE
:assemble UP-TO-DATE
:compileTestJava
:processTestResources UP-TO-DATE
:testClasses
:test
:check
:build
BUILD SUCCESSFUL
Total time: 15 mins 42.983 secs
build.gradle
apply plugin: 'java'
repositories {
mavenCentral()
}
dependencies {
testCompile 'junit:junit:4.8.2'
}
test {
maxParallelForks = 6
forkEvery = 30
}
maxParallelForksは並列するスレッド数。forkEveryは指定した回数テストを実行するとJVMを再起動して、OutOfMemoryExceptionを回避する仕組みです。
c:\Users\mike\IdeaProjects\TestingGradle>gradle build
: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
:check
:build
BUILD SUCCESSFUL
Total time: 2 mins 42.544 secs
15min43sec / 6(core) = 2min37sec
GradleのmaxParallelForksを使うともれなくスローテスト問題が解決できる。1.テストのみの状態のMoneyTest.java1.コンパイルが通るようになったMoneyTest.javaとDollar.java1.当然落ちるMoneyTest.java1.強引にテストを通過させるDollar.java1.重複を取り除いたDollar.java2.新たな振る舞いを記述したMoneyTest.java2.解決策が思いつかないのでVariableを導入したMoneyTest.java2.とりあえずコンパイルエラーをなくしたDollar.java2.正しいと思われるコードに修正して動きを確認できたDollar.java3.全てにたいする等価性を検証するMoneyTest.java3.等価性の仮実装Dollar.java3.不安を三角測量で表すMoney.java3.一般化の実施Dollar.java4.情報が豊富になってきたのでリファクタリングしたMoneyTest.java4.さらにインライン化したMoneyTest.java4.Dollar.javaオブジェクトだけがamountフィールドを扱えるようになったのでprivate化する5.フランのテストと実装なにテキストで公開して欲しい?本を買うか、図書館で借りてください。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
}
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
}
}
intとlongのボクシング・アンボクシングというコストのかかるようなテストを400個作ります。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>
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>
: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>
MailControllerとMailControllerTestにしておきます。MailControllerTestでは、すでにリダイレクト先を変更しておきます。MailController
package orz.mikeneck.mail;
import org.slim3.controller.Controller;
import org.slim3.controller.Navigation;
public class MailController extends Controller {
@Override
public Navigation run() throws Exception {
return null;
}
}
MailControllerTest
package orz.mikeneck.mail;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;
import org.slim3.tester.ControllerTestCase;
public class MailControllerTest extends ControllerTestCase {
@Test
public void run() throws Exception {
tester.start("/mail/mail");
TweetController controller = tester.getController();
assertThat(controller, is(notNullValue()));
assertThat(tester.isRedirect(), is(true));
assertThat(tester.getDestinationPath(), is("/mail/"));
}
}
MailController
package orz.mikeneck.mail;
import org.slim3.controller.Controller;
import org.slim3.controller.Navigation;
public class MailController extends Controller {
@Override
public Navigation run() throws Exception {
return redirect(basePath);
}
}
e_mailに入るerr_e_mailに入るMailControllerTest
package orz.mikeneck.mail;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;
import org.slim3.tester.ControllerTestCase;
public class MailControllerTest extends ControllerTestCase {
// 省略
@Test
public void testEMailShouldBeFilled() throws Exception {
HttpServletRequest request = tester.request;
request.setAttribute("e_mail", "");
tester.start("/mail/mail");
TweetController controller = tester.getController();
assertThat(controller, is(notNullValue()));
assertThat(request.getAttribute("err_e_mail"), is(notNullValue()));
assertThat(tester.isRedirect(), is(true));
assertThat(tester.getDestinationPath(), is("/mail/"));
}
}
e_mailに対して値が設定されていることのValidationを加える。err_e_mailにエラーメッセージを加える。MailController
package orz.mikeneck.mail;
import org.slim3.controller.Controller;
import org.slim3.controller.Navigation;
public class MailController extends Controller {
@Override
public Navigation run() throws Exception {
Validators valid = new Validators(request);
valid.add("e_mail", valid.required());
return redirect(basePath);
}
}
e_mailに対して値が設定されていることのValidationを加える。err_e_mailにエラーメッセージを加える。MailController
package orz.mikeneck.mail;
import org.slim3.controller.Controller;
import org.slim3.controller.Navigation;
public class MailController extends Controller {
@Override
public Navigation run() throws Exception {
Validators valid = new Validators(request);
valid.add("e_mail", valid.required());
if(valid.validate()){
// TODO When valid
} else {
Errors errors = valid.getErrors();
request.setAttribute("err_e_mail", errors.get(key));
}
return redirect(basePath);
}
}
ifとか付いているし…MailControllerTest
package orz.mikeneck.mail;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;
import org.slim3.tester.ControllerTestCase;
public class MailControllerTest extends ControllerTestCase {
// 省略
@Test
public void testEMailAddressShouldBeValid() throws Exception {
HttpServletRequest request = tester.request;
request.setAttribute("e_mail", "hoge");
tester.start("/mail/mail");
TweetController controller = tester.getController();
assertThat(controller, is(notNullValue()));
assertThat(request.getAttribute("err_e_mail"), is(notNullValue()));
assertThat(tester.isRedirect(), is(true));
assertThat(tester.getDestinationPath(), is("/mail/"));
}
}
^([a-zA-Z0-9])+([a-zA-Z0-9\._-])*@([a-zA-Z0-9_-])+([a-zA-Z0-9\._-]+)+$MailController
package orz.mikeneck.mail;
import org.slim3.controller.Controller;
import org.slim3.controller.Navigation;
public class MailController extends Controller {
@Override
public Navigation run() throws Exception {
Validators valid = new Validators(request);
valid.add("e_mail", valid.required(),
validation.regexp("^([a-zA-Z])+([a-zA-Z0-9\\._-])*@([a-zA-Z])+([0-9a-zA-Z\\._-])+[a-z]+$"));
if(valid.validate()){
// TODO When valid
} else {
Errors errors = valid.getErrors();
request.setAttribute("err_e_mail", errors.get(key));
}
return redirect(basePath);
}
}
MailControllerTest
package orz.mikeneck.mail;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;
import org.slim3.tester.ControllerTestCase;
public class MailControllerTest extends ControllerTestCase {
// 省略
@Test
public void testEMailValid() throws Exception {
HttpServletRequest request = tester.request;
request.setAttribute("e_mail", "hoge@hoge.com");
tester.start("/mail/mail");
TweetController controller = tester.getController();
assertThat(controller, is(notNullValue()));
assertThat(request.getAttribute("err_e_mail"), is(nullValue()));
assertThat(tester.isRedirect(), is(true));
assertThat(tester.getDestinationPath(), is("/mail/"));
}
}
InterfaceにStringや、intの定数を持たせることができるのは知っていましたが、java.util.Map<K, V>も持たせられるのですね。
public interface AlarmDate {
public static final String KEY_YEAR = "YEAR";
public static final String KEY_MONTH = "MONTH";
@SuppressWarnings("serial")
public static final Map<String, Utility> KEY_ENUM =
Collections.unmodifiableMap(new HashMap<String, Utility>(){{
put(KEY_YEAR, Utility.ATTRIBUTE_YEAR);
put(KEY_MONTH, Utility.ATTRIBUTE_MONTH);
}});
enum Utility {
ATTRIBUTE_YEAR {
@Override
public String attribute() {
return KEY_YEAR;
}
}, ATTRIBUTE_MONTH {
@Override
public String attribute() {
return KEY_MONTH;
}
};
abstract public String attribute();
}
}
enumからStringは簡単にアクセスできたのですが、Stringからenumにどうやってアクセスできるか悩んでいたんですよ。switch文を書くのも嫌だし(というか、switch文を書かないために戦略的enumパターンを使っている)、どうしようか悩んでいたので、すっきりしました。