본문 바로가기

java

[Java] 테스팅 툴 - JMeter

앞선 글(모니터링 툴 - VisualVM)에서 JVM 기반의 서버 자원에 대한 모니터링 방법을 알아보았습니다. 이 때 클라이언트의 요청을 전달하기 위해 IDE(IntelliJ)에서 직접 수동으로 http 요청을 보내 서버가 request를 처리하도록 만들었습니다. 만약 동시에 얼마나 많은 요청을 처리할 수 있는지를 알아보기 위해서는 어떻게 요청을 보내야 할까요. http 요청을 여러번 수동으로 클릭하거나, 테스트 코드를 만들어 해당 요청을 여러 번 보낼 수 있도록 코드를 작성해야 할까요.

이러한 테스트를 자동화하기 위한 툴들이 있습니다. 이번 글에서는 Apache에서 오픈소스로 만든 'JMeter'의 활용에 대해서 살펴보도록 하겠습니다.

 

JMeter

JMeter는 application의 성능(performance)을 측정하기 위한 테스팅 툴입니다. 이전 글의 VisualVM이 application server의 입장에서 서버의 성능이나 자원을 모니터링하기 위해 사용되었다면, JMeter는 client의 입장에서 application server가 클라이언트의 요청을 얼마만큼 수행할 수 있는지 테스트하기 위해 사용됩니다.

따라서 JMeter는 다양한 요청 타입을 처리할 수 있는 대상(appication, server, protocol)에 대한 테스트를 지원합니다. Web HTTP(S), FTP, JDBC를 이용한 데이터베이스, Mail, Java Objects 등을 처리하는 대상을 테스트할 수 있습니다.

client가 요청을 보내는 시나리오를 'Test Plan'이라고 부릅니다. 해당 시나리오는 여러가지 요소로 구성됩니다. Test Plan의 요소에는 Thread Group, Controllers(Samplers or Logic), Listeners 등이 있습니다.

Thread Group은 모든 테스트들의 시작점입니다. JMeter가 가용한 자원을 가지고 테스트 시나리오를 직접 수행하는 역할을 합니다. Controller는 Thread Group에게 어떤 target server에 어떤 request를 보내야 할지 알려주고, 그 세부 사항을 설정할 수 있습니다. JMeter에 내장된 Samplers는 FTP, HTTP, JDBC 등 여러 요청 처리에 대한 기본 가이드를 제공합니다. Listener는 JMeter가 테스트를 수행하면서 수집한 데이터들을 정제, 분류하여 다양한 형태의 정보로 표현해줍니다. Test Plan 요소의 자세한 내용은 공식 문서(jmeter.apache.org/usermanual/test_plan.html)를 참고바랍니다.

 

Templates

JMeter에서 다양한 타입의 테스트를 지원하기 위해 기본적으로 제공하는 template들이 존재합니다. 특정 요청 타입의 테스트를 하기 위한 기본적인 틀을 제공합니다. 이번 글에서는 다음의 세가지 테스트 template에 대해서 설명하겠습니다.

1. Simple HTTP Request

간단한 HTTP 요청을 테스트하는 템플릿입니다. 이전 글에서 http 요청을 보내는 예시를 들었는데, 이 템플릿을 이용해 다수의 요청을 보내는 테스트를 해볼 수 있습니다. local로 실행시킨 application 서버에 요청을 보낼 수도 있고, http 기반의 요청이라면 인터넷 환경에서의 요청도 테스트할 수 있습니다.

2. Recording Tests

외부 서버에서 application이 실행되고 있는 경우 target server에 대한 요청 테스트를 할 수 있습니다. local server에 JMeter proxy 서버를 실행시켜 요청을 보내 응답 받는 데이터를 수집합니다. 예를 들어 https://www.myserver.com에 요청을 보낸다면, 특정 브라우저(chrome, firefox 등)를 통해 요청을 보낼 때 해당 requests를 JMeter proxy 서버가 가로채서 분석하는 방식입니다.

3. Distributed Testing

여러 JMeter 서버를 두어 요청을 보내 테스트하는 방식입니다. Controller 노드는 Worker Node들에게 target server로 요청을 보내라고 명령을 내리고, worker node들이 실제 target server에 요청을 보내 데이터를 수집하는 방식입니다.(수집된 데이터는 Controller Node에 병합되어 보여집니다)

공식 문서의 Best Practices의 예를 보면 스레드 갯수를 올바르게 설정하라고 되어 있습니다. 이는 JMeter 서버가 보내는 요청도 해당 서버의 성능에 의해 영향을 받을 수 있기 때문입니다. JMeter 서버를 실행시킨 시스템의 성능이 좋지 않다면 요청을 보내는 순간부터 성능 저하가 발생하여 정확한 테스트를 할 수 없을 것입니다. large scale testing을 한다면 여러 JMeter 서버를 두어 분산 테스트를 하는 것이 권장됩니다.

 

테스트 환경 구성

스프링 부트 WEB 기반으로 실제 MySQL 데이터베이스에 접근하여 Post(게시글) 데이터를 저장하고 응답하는 간단한 서버 환경을 구성했습니다. 대략적인 구성은 다음과 같습니다.

@RequestMapping("/api/post")
@RestController
@RequiredArgsConstructor
public class PostController {

    private final PostService postService;

    @PostMapping
    public ResponseEntity<Post> save(@RequestBody Post post) {
        return ResponseEntity.ok(postService.save(post));
    }
}

@Service
@RequiredArgsConstructor
public class PostService {

    private final PostRepository postRepository;

    @Transactional
    public Post save(Post post) {
        return postRepository.save(post);
    }
}

@Repository
public interface PostRepository extends JpaRepository<Post, Long> {}

@Entity
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Post {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;
    private String content;

    public Post(String title, String content) {
        this.title = title;
        this.content = content;
    }
}

 

Simple HTTP Request

좌측 상단의 templates - Simple HTTP request 템플릿을 선택합니다. create를 누르면 method, body, contentType, url을 명시하게 되어 있는데, 이후 설정에서 입력할 수 있기 때문에 우선은 기본 포맷으로 생성하겠습니다.

 

Test Plan이 생성되었고, User Defined Variables, Thread Group, View Results Tree가 보입니다.

User Defined Variables

이름과 코멘트를 달 수 있고, Name을 변수명으로 하여 Value 값을 할당해 다른 설정 정보에서 사용할 수 있도록 작성합니다. 요청을 보내야 하는 url, contentType, http method, body는 json 타입으로 Post 테이블 insert 시 필요한 정보를 명시했습니다.

HTTP Request

위에서 설정한 user-defined-variable로 요청 정보를 설정합니다. POST method로 url에 요청을 보냅니다. body에는 json 형식의 데이터가 담깁니다.

참고로 HTTP Header Manager에는 request header 정보를 명시합니다. 위에서 설정한 contentType도 header 정보에 포함되어 있습니다.

 

Thread Group

해당 요청을 실제로 실행할 Thread Group을 설정합니다. Number of Threads, 요청을 보낼 스레드수(유저수)를 설정합니다. ramp-up period는 1초, 반복수는 10번을 하도록 합니다.

이외에도 Sampler가 에러가 났을 때 어떤 action을 할지, thread creation에 대한 delay, thread lifetime(duration(얼마동안 실행할지), startup delay(언제 시작할지)) 등을 지정할 수 있습니다.

 

Test Run

상단의 Start 버튼을 클릭하거나 단축키(command + r)를 이용해 테스트를 실행시킵니다. 그 전에 Test Plan을 jmx 확장자 파일로 저장하여 필요 시 다시 load 할 수 있습니다.

테스트 결과 5개의 thread가 10번 반복하였으므로, DB에도 실제로 50개의 데이터가 insert 되었습니다.

-- result 50
SELECT COUNT(*) FROM POST:

 

Test Result Listener

내장 listener 또는 listener plugin을 설치하면 다양한 형태로 테스트 결과를 볼 수 있습니다.

View Results Tree

실제 보낸 요청을 tree 형태의 구조로 보여주고, 각 요청당 sampler result, request data, response data를 볼 수 있습니다. sample result에서 load time, connect time, latency, size 정보들을 확인할 수 있습니다.

 

jpgc - Standard Set

일례로 jpgc plug-in을 활용해보았습니다. 초당 트랜잭션 처리 갯수를 그래프 형태로 볼 수도 있고, ms 당 응답시간을 볼 수도 있습니다. 공식 문서의 Best Practice에서는 가급적 적은 수의 Listener를 사용하라고 나와 있으니 large scale test를 할 때는 listener 설정도 무분별하게 사용해서는 안됩니다.

 

Recording Tests / Distributed Testing

recording, distributed 테스트는 추후 다시 포스팅하도록 하겠습니다.