스크립트(Groovy) 작성법
Groovy란?
Groovy는 자바에 파이썬, 루비, 스몰토크 등의 특징을 더한 동적 객체 지향 프로그래밍 언어이다. JVM에서 동작하고 자바의 강점 위에서 파이썬, 루비, 스몰토크 등의 프로그래밍 언어에 영향을 받은 특징 및 장점이 있다. 자바 기반이기 때문에 자바 프로그래머들이 많은 학습을 하지 않아도 프로그래밍을 할 수 있다는 점과 단순화된 문법을 지원하여 코드를 읽고 유지보수하기 편하다는 장점이 있다.
자바와의 비교
그루비의 문법체계는 자바를 계승하고 발전시켰다. 자바에 없는 간편 표기법을 지원하는 것 외에 LIST, MAP, 정규식을 위한 구문을 제공함으로서 프로그래밍을 쉽고 간결하게 해준다. JVM 상에서 동작하는 동적 스크립트 언어인 Jython, Jruby 등과 비교해도 손색이 없다. 자바는 소스를 컴파일해야만 사용할 수 있지만, 그루비 소스는 스크립트 파일 그대로 실행시킬 수 있고 자바처럼 컴파일하여 사용할 수도 있다. 대부분의 자바 소스는 파일 확장자만 변경하면 수정 없이 그루비에서도 사용할 수 있다.
Java
public class StdJava
{
public static void main(String argv[])
{
for (String it : new String [] {"Rod", "Carlos", "Chris"})
if (it.length() <= 4)
System.out.println(it);
}
}
Groovy
["Rod", "Carlos", "Chris"].findAll{it.size() <= 4}.each{println it}
스크립트(Groovy) 작성법
테스트를 실행할 수 있는 스크립트는 Groovy, Jthyon, Groovy Maven Project 총 3가지가 있지만 이 중 Groovy를 중심으로 설명
등장 배경
Groovy는 nGrinder 3.2부터 지원했다. Groovy 스크립트는 Jython 스크립트와 다르게 JUnit 기반으로 동작하도록 되어 있다. nGrinder 개발에 참여한 NHN 개발자들의 대부분이 JUnit을 사용한 적이 있고 대부분의 IDE에서 JUnit을 지원하기 때문에 개발을 했다는 얘기가 있다. Groovy Maven Project는 Groovy 스크립트에 Maven 구조를 더한 형태이다. 만약 Maven 구조 없이 단독으로 Groovy 스크립트 형태로 작성했다면, nGrinder에서 제공한느 스크립트 에디터에서 편집 검증을 해야 하지만, Groovy Maven Project 구조를 사용했다면 이클립스같은 개발환경으로 해당 프로젝트를 import 후 로컬에서 테스트를 작성할 수 있다.
nGrinder Groovy 스크립트 기본 생성 소스
먼저 Groovy 스크립트를 생성하거나, Groovy Maven Project에 묶어서 사용해도 스크립트 자체의 형태는 아래와 동일하다.
package org.ngrinder;
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.plugin.http.HTTPPluginControl;
import net.grinder.script.GTest
import net.grinder.script.Grinder
import net.grinder.scriptengine.groovy.junit.GrinderRunner
import net.grinder.scriptengine.groovy.junit.annotation.BeforeProcess
import net.grinder.scriptengine.groovy.junit.annotation.BeforeThread
// import static net.grinder.util.GrinderUtils.* // You can use this if you're using nGrinder after 3.2.3
import org.junit.Before
import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith
import HTTPClient.HTTPResponse
import HTTPClient.NVPair
/**
* A simple example using the HTTP plugin that shows the retrieval of a
* single page via HTTP.
*
* This script is automatically generated by ngrinder.
*
* @author admin
*/
@RunWith(GrinderRunner)
class TestRunner {
public static GTest test
public static HTTPRequest request
@BeforeProcess
public static void beforeProcess() {
HTTPPluginControl.getConnectionDefaults().timeout = 6000
test = new GTest(1, "Test1")
request = new HTTPRequest()
test.record(request);
grinder.logger.info("before process.");
}
@BeforeThread
public void beforeThread() {
grinder.statistics.delayReports=true;
grinder.logger.info("before thread.");
}
@Test
public void test(){
HTTPResponse result = request.GET("http://please_modify_this.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));
}
}
}
nGrinder Groovy 스크립트 내의 클래스에는 JUnit을 nGrinder에 맞게 처리할 수 있도록 @RunWith(Grinder Runner)를 사용한다.
@RunWith(): 스프링의 테스트 컨텍스트 프레임워크의 JUnit 확장기능 지정
JUnit은 각각의 테스트가 서로 영향을 주지 않고 독립적으로 실행하는 것을 기본으로 하기에 각 테스트 클래스마다 매번 오브젝트를 생성한다. 그러므로 각 테스트 클래스를 지정한 ApplicationContext도 매번 새로 생성되는 상황이 발생한다. 이를 방지하기 위해 @RunWith annotation은 각 테스트 별로 오브젝트가 생성되더라도 싱글톤의 ApplicationContext를 보장하는 역할을 한다.
@Test로 annootation이 붙은 method는 쓰레드 내에서 반복 실행된다. 이 대 일반적인 JUnit Assertion을 사용하여 테스트 결과를 검증할 수 있다. Assertion에 실패할 경우, 해당 쓰레드에서 실행한 마지막 테스트가 실패 처리된다.
@Test: @Test annotation이 선언되면 해당 method는 테스트 대상임을 뜻한다.
Assertion: 해당 문장이 실행 될 경우, 이 문장은 참이라고 단언할 수 있는 문장이다. 즉 개발자가 개발한 프로그램에서 가정하고 있는 사실이 올바른 지 검사할 수 있도록 도와주는 기능이다. 여기서 Assertion이 아니더라도 try/catch문이나 if/else문으로 에러 검사 기능을 처리할 수 있다. 여기서 차이점은 예외는 주로 프로그램을 실행하는 도중 예상하지 못한 상태를 처리하는데 사용한다. 예를 들어, 파일을 열려고 하는데 열리지 않거나, 네트워크의 연결이 끊어지는 현상 등의 상태를 처리할 때 주로 사용된다. 반면 Assertion은 개발자가 참이라고 가정하는 상태를 명시하기 위해 사용된다. 에를 들어 어떤 method가 파라미터로 양수만 입력 받아야 한다고 확신한다면, Assertion을 사용해 해당 사실(파라미터가 양수라는 것)을 명시할 수 있다.
다시 말해, 예외는 프로그램의 코드가 실행되는 도중 발생하는 예외인 비정상적인 상태를 처리한다. 하지만 Assertion은 프로그램이 올바르게 수행될 수 있는 조건을 명시해주어 해당 조건을 ㅁ나족하는 경우에만 코드가 실행될 수 있도록 한다.
nGrinder Groovy 테스트에서는 기존에 많이 사용되던 JUnit의 @BeforeClass, @Befor, @AfterClass, @After 대신 다음과 같은 annotation을 사용한다.
설명 | 적용 | 사용 예 | |
@BeforeProcess | 프로세스가 생성될때 실행해야 하는 동작 정의 | static method |
- 프로세스 내 쓰레드가 공유할 리소스 파일 로드 - GTest를 사용한 테스트 항목Instrumentation |
@AfterProcess | 프로세스가 종료하기 직전에 실행해야 하는 동작 정의 | static method |
리소스파일 닫기 |
@BeforeThread | 각 쓰레드가 실행된 전에 실행해야 하는 동작 정의 | member method |
- 테스트 대상 시스템 로그인. - 쓰레드 별 쿠키 핸들러 설정 |
@AfterThread | 각 쓰레드가 종료하기 직전에 실행해야 하는 동작 정의 | member method |
- 테스트 대상 시스템 로그아웃 |
@Before | 모든 @Test 메소드가 실행되기 전에 실행해야 하는 동작 정의 | member method |
- 여러 @Test 메소드가 공유하는 로직 - 설정 검증 |
@After | 모든 @Test 메소드가 종료된 이후 실행해야 하는 동작 정의 | member method |
- 거의 사용 안함 |
@Test | 테스트 동작 정의 | member method |
Test body |
Groovy 스크립트 실행 흐름도
어노테이션 별 분석
@BeforeProcess
public static GTest test;
public static HTTPRequest request;
@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, "test1");
request = new HTTPRequest();
test.record(request);
grinder.logger.info("before process.");
}
}
모든 쓰레드가 공유할 데이터를 정의하기에 좋은 곳이다. @BeforeProcess가 붙은 static method는 각 테스트 통계를 수집할 때 사용되는 GTest 인스턴스를 정의하고, request 인스턴스를 바이트 코드 조작(record method)를 통해 레코딩하도록 한다.
request 인스턴스에 대해 method 호출을 하게 되면 테스트 별로 TPS를 증가 시킨다. 만약 다수의 HTTPRequest 객체를 레코딩해야 한다면 해당 객체를 test.record(request2)와 같이 처리하면 된다.
@BeforeThread
@BeforeThread
public void beforeThread() {
grinder.statistics.delayReports=true;
grinder.logger.info("before thread.");
}
해당 함수는 쓰레드가 시작되기 전 실행되어야 할 부분을 적는다. 보통 로그인 같은 테스스 사전 처리 코드를 넣는다. 다음 실제 테스트를 작성한다. 아래 @Test와 같이 @Test를 추가해 여러 test 함수를 정의할 수 있다.
@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));
}
}
첫 번째 testGoogle() 테스트 함수를 실행한 다음, testYahoo()를 실행한다. 그런데 googleResult라는 멤버 변수를 사용하여 testGoogle() 함수의 실행결과를 참조한다. 이러한 방식은 JUnit에서는 각 테스트 별로 각각의 테스트 객체를 생성하기 때문에 불가능하다. 그러나 GrinderRunner는 이와 같은 제약을 수정하여, 쓰레드 당 한개의 테스트 객체만을 사용한다. 따라서 googleResult와 같은 멤버 변수 참조도 가능하다.
'Study > WEB' 카테고리의 다른 글
[WEB] 프록시(Proxy)란? (0) | 2023.02.12 |
---|---|
[Java] 빠져나올 수 없는 null 처리의 늪 - 2 (0) | 2023.02.08 |
[Java] 빠져나올 수 없는 null 처리의 늪 - 1 (2) | 2023.02.06 |
[JAVA] 예외 던지기(throw) & 예외 연결(Chained Exception) (0) | 2023.01.29 |
[WEB] HTTP Method (0) | 2023.01.26 |