Groovy Script Structure
nGrinder 3.2부터 스크립트 언어로써 Groovy를 지원하기 시작했습니다. Groovy 스크립트는 JUnit에 기반하여 동작합니다.
- Groovy로 작성한 스크립트 예
import static net.grinder.script.Grinder.grinder
import static org.junit.Assert.*
import static org.hamcrest.Matchers.*
import net.grinder.plugin.http.HTTPRequest
import net.grinder.script.GTest
import net.grinder.script.Grinder
import net.grinder.scriptengine.groovy.junit.GrinderRunner
import net.grinder.scriptengine.groovy.junit.annotation.BeforeThread
import net.grinder.scriptengine.groovy.junit.annotation.BeforeProcess
import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith
import HTTPClient.HTTPResponse
/**
* A simple example using the HTTP plugin that shows the retrieval of a
* single page via HTTP.
*
* This script is auto generated by ngrinder.
*
* @author ${userName}
*/
@RunWith(GrinderRunner)
class Test1 {
public static GTest test;
public static HTTPRequest request;
// This method is executed once per a process.
@BeforeProcess
public static void beforeClass() {
test = new GTest(1, "${name}");
request = new HTTPRequest();
test.record(request);
grinder.logger.info("before process.");
}
// This method is executed once per a thread.
@BeforeThread
public void beforeThread() {
grinder.statistics.delayReports=true;
grinder.logger.info("before thread.");
}
// This method is continuously executed until you stop the test
@Test
public void test(){
HTTPResponse result = request.GET("${url}");
if (result.statusCode == 301 || result.statusCode == 302) {
grinder.logger.warn("Warning. The response may not be correct. The response code was {}.", result.statusCode);
} else {
assertThat(result.statusCode, is(200));
}
}
}
nGrinder Groovy 테스트를 위해서는 클래스 위에 @RunWith(GrinderRunner) 애너테이션을 꼭 붙여주어야 합니다. 이 부분은 GroovyRunner가 JUnit의 행동을 제어하며 JUnit에 grinder context를 마운트 합니다.
@Test 애너테이션이 붙은 메서드에는 각자 수행되어할 행동을 정의합니다. 이 메서드에선 JUnit assertion을 이용하여 결과를 단언(assert)할 수 있습니다. 만약 assertion이 실패한다면 해당 쓰레드는 failure로 처리됩니다.
JUnit이 제공하는 @BeforeClass, @Before, @AfterClass, @After 애너테이션 대신, nGrinder Groovy 테스트는 아래와 같은 애너테이션을 지원합니다.
애너테이션 | 설명 | 적용 메서드 형태 | 사용 예 |
@BeforeProcess | 프로세스가 호출되기 전 처리할 동작 정의 | static method | 쓰레드가 공유할 자원을 로드한다. GTest 와 타겟에 대하여 테스트할 레코드를 설정한다. |
@AfterProcess | 프로세스가 종료되기 전 처리할 동작 정의 | static method | 리소스를 반납한다. |
@BeforeThread | 각 쓰레드가 실행되기 전 처리할 동작 정의 | member method | 타겟 시스템에 로그인한다. 쿠키 핸들러와 같은 쓰레드 단위로 가질 데이터를 정의한다. |
@AfterThread | 각 쓰레드가 실행된 후 처리할 동작 정의 | member method | 타겟 시스템에서 로그아웃한다 |
@Before | 각각의 @Test 메서드가 수행되기 전 처리할 동작 정의 | member method | 테스트에 사용할 변수 세팅 |
@After | 각각의 @Test 메서드가 수행된 후 처리할 동작 정의 | member method | 잘 사용되지 않는다... |
@Test | 테스트할 동작 정의. 다중호출 됨 | member method | 테스트 |
- 실행 플로우
이제 위의 소스를 정의된 애너테이션을 따라 살펴보겠습니다
@BeforeProcess
public static GTest test;
public static HTTPRequest request;
// This method is executed once per a process.
@BeforeProcess
public static void beforeProcess() {
// Instead of Test in Jython, GTest is used here
// It's because the identifier "Test" is alredy used by the @Test
// GTest is the replacement to avoid the naming confliction.
test = new GTest(1, "test name");
request = new HTTPRequest();
test.record(request);
grinder.logger.info("before process.");
}
@BeforeProcess 애너테이션이 지정되었기에 프로세스가 생성 전 호출되며 모든 쓰레드가 공유할 데이터를 정의하기 좋은 곳입니다. 테스트 수집을 위한 GTest 인스턴스를 생성하고, HTTPRequest객체인 request 인스턴스를 생성합니다. test.record(request); 부분은 해당 HTTPRequest를 이용한 호출을 nGrinder가 수집하도록 설정하는 부분으로, 다수의 HTTPRequest에 대해 수집하고 싶다면 변수를 하나 더 추가하여 record 메서드에 전달하여 등록해주면 됩니다.
@BeforeThread
// This method is executed once per a thread.
@BeforeThread
public void beforeThread() {
grinder.statistics.delayReports=true;
grinder.logger.info("before thread.");
}
쓰레드가 시작되기 전 작업을 정의하는 곳입니다. 주로 타겟 시스템에 로그인을 하거나 쿠키 핸들링 등을 지정합니다. 만약 테스트하는 대상이 HTTPRequest가 아닌 @Test메서드 라면 이 메서드 내에서 아래와 같이 @Test에 대한 성능을 측정할 수 있습니다.
@BeforeThread
public void beforeThread() {
test1.record(this, "doTransaction1"); // Record current object’s doTransaction1 method into test1.
test2.record(this, "doTransaction2"); // Record current object’s doTransaction2 method into test2.
grinder.statistics.delayReports=true;
grinder.logger.info("before thread.");
}
@Test
public void doTransaction1(){
// Do transactions here!!
}
@Test
public void doTransaction2(){
// Do another transactions here!!
}
@Test
private boolean googleResult;
@Test
public void testGoogle(){
googleResult = false;
HTTPResponse result = request.GET("http://www.google.com");
if (result.statusCode == 301 || result.statusCode == 302) {
grinder.logger.warn("Warning. The response may not be correct. The response code was {}.", result.statusCode);
} else {
assertThat(result.statusCode, is(200));
}
googleResult = true;
}
@Test
public void testYahoo(){
if (!googleResult) {
grinder.logger.warn("Just return. Because prev google test is failed.");
return;
}
HTTPResponse result = request.GET("http://www.yahoo.com");
if (result.statusCode == 301 || result.statusCode == 302) {
grinder.logger.warn("Warning. The response may not be correct. The response code was {}.", result.statusCode);
} else {
assertThat(result.statusCode, is(200));
}
}
측정하려는 테스트의 몸체를 작성하는 부분입니다. JUnit는 각 테스트 별로 별도의 테스트 객체를 생성하지만, GrinderRunner는 쓰레드 당 한 개의 테스트 객체를 사용하기에, @Test 메서드들이 같은 멤버 변수를 참조할 수 있습니다. 그렇기에 위의 예시의 testYahoo 메서드는 testGoogle 메서드 결과가 반영된 googleResult 변수의 값에 따라 수행 방식이 달라집니다.