티스토리 뷰

개발/tools

테스트 명장, Apache JMeter

Jaeyeon Baek 2021. 1. 22. 01:26

서비스의 개발만큼 중요한게 API 단위 테스트이다. 테스트 tool은 네이버의 nGrinder, Apache JMeter등 다양하게 존재하지만 여기서는 명장, Apache Jmeter를 살펴본다.

 

필자는 얼마전까지 모든 API 테스트는 셸 스크립트로 충분하다고 생각해왔다. 아래처럼 말이다.

# curl.sh 
curl -X GET localhost:8000/products

 

위와 같은 파일을 터미널에서 아래와 같이 병렬로 처리하는 등 기교부리는 것을 좋아했다.

$ seq 400 | xargs -P 200 -I{} ./curl.sh

여기서는 꽤 간단하게 묘사되지만 sleep과 while, 다양한 조건까지 들어가면 꽤 그럴듯한 traffic generator tool 이 완성된다.

조금 더 헤비한 테스트가 필요하면 node.js 로 뚝딱뚝딱 만들어 사용했고 시간이 없으면 postman의 터미널 버전인 newman을 사용하는 것도 괜찮았다. 일전에 라디오 플랫폼을 서비스하는 회사에서 채팅 플랫폼을 개발한 적이 있었는데 그때 만든 traffic generator 는 다음처럼 동작했다. 1) 임의의 유저 N명을 생성해서 2) M개의 채팅방에 3) K만큼의 유저를 입장시켜서 3) P/sec 만큼 대화를 주고 받았다. node.js 로 작성했었는데 테스트 프로그램을 만드는 일이 너무 재미있어서 퇴근후에 만들정도로 열정이 넘쳤다. 사실 개발만큼 중요한게 테스트니까.

각설하고 서론이 길었는데 요즘은 생각이 많이 바껴서 잘 만들어놓은 툴을 가져다 쓰는게 더 경쟁력을 갖출 수 있다고 생각하게 됐다. 그렇다. 툴을 잘 쓰는 것도 경쟁력이다. 잡설로 이야기하자면 Traffic Generation 어플라이언스인 spirent사의 Avalanche, Ixia 머신 혹은 appium 같은 모바일 애플리케이션 테스트 자동화 툴(관련글 : 테스트 자동화 프레임워크 - appium 도입)을 잘 다룬다는 것은 테스트 엔지니어의 우대사항으로 취급되는데 개발자 채용으로 빗대면 프레임워크 사용경험 우대와 같다. 아무튼, 그럼 Jmeter를 이번 기회에 살펴보고 본인의 경쟁력을 높여보자.



JMeter는 Apache 재단의 오픈소스로 100% 순수 자바 애플리케이션으로 되어 있다. 1) http, https는 물론 FTP, LDAP, Mail 등 다양한 테스트 방식을 지원하며 2) multi-threading을 유연하게 제공해서 부하 테스트 가능, 3) 로그인, 상품 조회, 결제와 같은 시나리오를 세우고 트래픽을 흘릴 수 있다. 뭐 이정도면 서비스 시나리오 테스트 도구 = JMeter 라는 공식이 성립하겠다. JMeter GUI 버전으로는 heavy한 테스트는 약간 버겁지만 그게 JMeter의 한계라고 생각하면 안된다(JMeter에서 트래픽을 최대로 발생시켜도 받아주는 서버의 CPU는 널널하다던가). 스트레스 테스트가 필요하면 CLI 모드를 사용하면 된다. 클라우드의 분산된 환경에서 트래픽을 생성하는 것도 가능하기 때문에 엄청난 트래픽을 흘려보낼 수 있다(본 글에서는 다루지 않는다). 이번 글에서는 흔한 쇼핑몰의 간단한 시나리오를 만들고 HTTP request를 생성해보도록 한다. 시나리오는 다음과 같다.

- POST /login 로그인
- GET /products 상품 목록 조회

JMeter를 통해 통신할 서버는 쇼핑몰로 python fastapi로 만들었다. 안에 내용은 심플하다.
- login API 는 request body에 회원정보(email, password)를 받고 response로 회원의 id와 token을 내려준다.
- products API 는 헤더에 토큰 정보를 검사해서 토큰이 있으면 회원가(10% 할인)로 상품 정보를 내려주고 없으면 소비자가를 내려준다.

JMeter를 테스트하는 목적으로 코드는 심오하지 않다.

간단한 서버인데 JMeter 를 이해하는데 큰 도움이 될 것으로 기대된다. 오늘(2021-01-20) 기준으로 구글에 검색되는 모든 글을 통틀어 시나리오를 설명하는 글은 없기 때문이다.

필자는 MacOS 유저로 국민 설치 프로그램인 brew를 사용했다.

$ brew install jmeter


혹시 (어떤 이유로) 이미 설치가 되어 있다면 업그레이드를 해주도록 하자.

$ brew upgrade jmeter


설치가 되었으면 터미널에서 jmeter를 실행한다. 앞에서 이야기 했던 것처럼 GUI 모드에서 부하 테스트를 하지 말라고 경고가 출력된다. CLI 모드를 권장하고 필요하면 Java Heap 메모리를 올리고 사용하란다.

JavaNativeFoudation 에러는 당장은 무시해도 상관 없다. 터미널에서 jmeter 커맨드를 입력하면 곧 아래와 같이 GUI로 jmeter가 실행된다.

JMeter는 테스트를 위해 계층 구조(Hierarchy)로 나뉘어져 있는데 세 가지를 기억하면 된다.

Test Plan : 전체 시나리오를 관장하는 글로벌한 설정 영역이다.
Thread Group : 시나리오 덩어리가 된다. 로그인해서 상품을 보고 결제하는 플로우를 묶어놓은 단위로 생각하면 좋다.
Sampler : 하나의 트랜잭션, 혹은 API 단위이다. 로그인, 상품 목록 조회, 주문내역 조회 등을 등록해서 사용하면 되겠다.


좌측 상단에 Test Plan 을 우클릭 해보자. 아래와 같은 설정들이 있다.


설명이 필요한 부분을 아래 정리한다.

Thread Group : 위에서 설명한 것처럼 시나리오를 담을 수 있는 그릇이다.
Config Element : http header, cache 등을 정의하고 관리한다
Listener : request를 받아 처리할 액션 모음이다.
Pre Processors : 발송하기 전에 request에 적용 가능한 것들이 있다. 타임아웃을 여기서 지정할 수 있다.
Post Processors : 발송이후 처리 모음이다. response 파싱을 여기서 하게된다.


Test Plan 에서는 사용자 지정 값을 설정해두자. 여기서는 HOST, PORT 정도를 지정했다. 두 개의 설정은 모든 Sampler에서 사용되는데 테스트 서버 주소가 변경되면 유연한 대응을 하기 위해서다. 이렇게 하지 않으면 모든 Sampler를 편집해야 하는 수고로움이 발생한다.


다음으로는 Thread Group (Add → Threads[Users] → Thread Group)을 생성해보자. 아래와 같이 생성되는데 부하를 조정하거나 에러 발생 시 액션을 정의할 수 있다 (무시하고 계속 트래픽을 흘릴건지, 테스트를 멈출건지 등)

Number of Threads (users) : 스레드의 개수, 즉 우리 쇼핑몰에 접속할 사람의 수를 정한다.
Ramp-up period (seconds) : 몇 초 동안 위에 thread를 순차적으로 실행 시킬지 설정이다.
Loop Count : Thread Group에 등록된 전체 Sampler를 몇 번 반복할지 설정한다.

각 스레드별로 초당 몇 개의 패킷을 전송할지 디테일한 설정은 불가능하다. 스레드가 실행되면 그저 최선을 다해서 발송할 뿐이다.

다음으로는 로그인 API 테스트를 위한 Smapler를 생성해보자. 여기서는 HTTP Request 를 사용한다.


생성하고 아래와 같이 설정 해준다. 위에 Test Plan에서 설정한 사용자 지정 값을 여기서 ${HOST}, ${PORT} 처럼 셸스크립트의 문법으로 사용하면 된다. HTTP 메소드는 POST, 경로는 /login 이다. 끝으로 Body Data를 선택하고 이메일과 패스워드를 입력해준다.


여기까지가 로그인 API를 호출하는 전부이다. 중앙 상단에 플레이 버튼만 누르면 로그인 요청이 시작된다. 하지만 우리는 여기서 멈추지 말고 다음 단계를 진행해보자. 전체적인 모니터링을 위해서 두 개의 Listener(View Result Tree, Summary Report)를 Test Plan 밑에 생성해 준다. Thread Group이나 Sampler 밑이 아니다. 다른쪽에 생성하면 Thread Group이나 Sampler 별로 매번 달아줘야 하는데 글로벌 모니터링을 만들어둔다고 생각하면 편하다. (필요에 따라서 Sampler 밑에 생성해야 하는 경우도 있지만 일반적이지 않다)

View Results Tree : Request와 Response의 생김새를 살펴볼 수 있다. 크롬 개발자모드 Network 탭을 생각하면 된다.
Summary Report : 생성한 트래픽에 대한 통계지표를 살펴볼 수 있다.

View Results Tree를 먼저 살펴보면 다음과 같이 Sampler result, Request, Response data를 확인할 수 있다. 서버에서 http status 값으로 200이 아닌 다른 값을 줬을 때 디버깅하는 용도로 사용하면 편하다. 내용 확인을 위해 상단 플레이 버튼을 눌러줬다.


Summary Report에서는 아래와 같은 통계를 볼 수 있다. Average는 ms 단위로 평균 2ms 응답 시간을 보내는 것을 볼 수 있다. Max 값을 통해 latency가 튀는 경우가 발생하는지 모니터링하고 Error 값을 통해 서버의 상태를 점검해보자. Throughput이 기준 이하여도 서버 점검이 필요하다.


이제 다시 Thread Group으로 돌아가서 비회원 상품 조회 Sampler를 아래와 같이 생성하도록 하자. login API 처럼 ${HOST}, ${PORT} 를 사용해줬고 HTTP 메소드는 GET을 사용해줬다. 파라미터는 필요하지 않다. 


다음으로는 회원 상품 조회 Sampler를 등록하기 전에 login API 에서 내려준 token값을 사용할 수 있도록 설정해줘야 한다. 회원/비회원 구분은 토큰 여부로 결정되기 때문이다. login Sampler 밑에 Post Processor에 JSON Extractor 을 등록해주자. JSON 형태의 response에서 원하는 값을 추출할 수 있다.


아래처럼 사용할 변수 이름으로 token을 지정했고 json 경로는 token을 지정해줬다. response가 복잡하면 정규표현식을 통해 JSON의 특정 key를 가져오는게 가능하다.


회원 상품 조회 API는 비회원 Sampler를 복사해서 사용하도록 하자. 아래처럼 Duplicate가 가능하다.


생성된 회원 상품 조회 Sampler에 헤더를 등록하고 token정보를 넘겨줘야 한다. 아래처럼 products (members) Sampler 밑에 Config Element에 HTTP Header Manager를 만들어준다.


앞서 Sampler 에서 HOST, PORT를 사용했던 것처럼 ${token} 정보를 헤더에 실어준다. 테스트에 필요한 헤더 정보가 더 있다면 여기에 같이 넣어주면 된다.


모든 과정이 끝났다. 하지만 플로우가 뭔가 좀 이상하니 순서를 조정해주도록 하자. 1) 비회원 상품 조회 2) 로그인 3) 회원 상품 조회, 이 플로우가 자연스럽다. 마우스 drag and drop으로 순서를 조정한다. 최종 모습은 아래와 같다.


이제 상단에 플레이 버튼을 누르고 결과를 확인해보자. 우선 View Products Tree에서 비회원의 상품 가격을 살펴보고,


회원의 상품 가격을 살펴보자. 10% 할인이 정상적으로 되었나? 그렇다면 login의 response에서 token이 정상적으로 잘 파싱 됐다는거다.


다음으로는 Summary Report를 살펴보자. 각 Sampler 별로 통계를 살펴볼 수 있다.



# 마무리

지금까지 간단한 쇼핑몰의 예제를 통해 JMeter 사용법을 5% 정도 알아봤다. 살펴본걸 JMeter의 전부로 생각하면 그저그런 도구에 지나지 않겠지만 깊이 파낼수록 강력함을 맛볼 수 있으리라. 이번 글에서는 다양한 기능보다는 시나리오에 초점을 맞췄다. 서론에서 언급한 것처럼 인터넷에 JMeter를 설명하는 글은 많지만 시나리오로 엮어서 설명하고 있는 글은 단 한개도 찾을 수 없었기 때문이다. 여기서 끝내기는 아쉬운가? 그렇다면 부하테스트 도구(JMeter)와 설정은 준비 됐으니 서버의 프레임워크(Django, flask)나 언어를 바꿔가면서 테스트해보는건 어떨까? 위에서 사용한 fastapi 코드와 JMeter 설정은 GitHub 에서 올려뒀으니 필요하신 분은 참고하시면 되겠다.


# 참고

터미널에서 커맨드를 통해 실행된 JMeter는 커맨드가 종료되면 설정이 모두 없어진다. 주기적으로 저장해서 바닥치는 일이 없도록 하자.

 

댓글
댓글쓰기 폼