๐ ์ ๊ท ํ์ ๊ฐ์ ์ด๋ฒคํธ๋ฅผ ์ ์ ํ ์ด์
์ ๊ท ํ์ ๊ฐ์ ์ด๋ฒคํธ๋ฅผ ์คํํ๋ ๊ฒ์ ๋๋ถ๋ถ์ ํํ ํฌ ํ์ฌ์์ ํ๋ ์ด๋ฒคํธ์ด๊ธฐ ๋๋ฌธ์ ์ ๊ท ํ์ ๊ฐ์ ์ด๋ฒคํธ๋ก ์ ์ ํ๊ฒ ๋์๋ค.
๐ ๊ธฐ๋ํ๋ ๋ชฉํ
์ด ์ด๋ฒคํธ๋ก ์ธํด ๋ฐ์ํ๋ ํธ๋ํฝ ๋ฌธ์ ๋ฅผ ์ฒ๋ฆฌํด๋ณด๊ณ , ์ข ๋ ํจ์จ์ ์ธ ๋ฐฉ๋ฒ์ ์ฐพ์๋ณด๊ณ ์ถ๋ค.
๋ํ ํธ๋ํฝ์ ์ฒ๋ฆฌํ๋ ๊ณผ์ ์ ๊ธ๋ก ์์ฑํจ์ผ๋ก์จ, ๊ฐ๋ฐ ์ง๊ตฐ์ ๋ชจ๋ ๋ถ๋ค์๊ฒ ์ ์๋ฏธํ ์ ๋ณด๋ฅผ ์ ๊ณตํ๊ณ ์ถ๋ค.
๊ทธ๋ผ ์ถ๋ฐํด๋ณด์!!
๐ nGrinder๋ก ๋ถํ ํ ์คํธ ํด๋ณด๊ธฐ
nGrinder๋ก ๋ก์ปฌํ๊ฒฝ์์ ์์ฒญ์ด ์ ๊ฐ๋์ง ํ ์คํธ ํด๋ณด๋ ค๊ณ ํ๋ค. ํ์๊ฐ์ ์ ํ๋ API๋ฅผ ํ ์คํธ ํ๊ณ ์ ํ๋ค.
์ฐธ๊ณ ๋ก ๋ก์ปฌ ํ๊ฒฝ์์ ํ ์คํธ ํ๊ธฐ ์ํด์ , URL ์ ๋ ฅ๋์ localhost๊ฐ ์๋ 127.0.0.1์ ์ ๋ ฅํด ์ฃผ์ด์ผ Invalid Input์๋ฌ๊ฐ ๋ฐ์ํ์ง ์๋๋ค.
127.0.0.1์ ํฌํธ๋ฒํธ๊น์ง ํฌํจํด์, 127.0.0.1:8080์ ์ ๋ ฅํด์ฃผ์ด์ผ ํ๋ค.(ํฌํธ ๋ฒํธ๊น์ง ๊ฐ์ด ์ ๋ ฅํด์ฃผ์ด์ผ ํ๋ค.)
ํ์๊ฐ์ ํ๋ api๋ฅผ ํธ์ถํ๋๋ฐ ์ฑ๊ณตํ๋ค.
์งํ๋ฅผ ๋ถ์ํด๋ณด์..!!
Total Vusers : ํ ์คํธํ ์ ์ ์
TPS : ์ด๋น ํธ๋์ญ์
๊ฐ์(Transaction Per Second)
Peak TPS : ์ต๊ณ ์ TPS
Mean Test Time : ๊ฐ๋ณ ํ ์คํธ ์๊ฐ, ์์ฑํ ์คํฌ๋ฆฝํธ์ @Test ํจ์์ ํ๊ท ์ํ์๊ฐ์ ์๋ฏธํ๋ค.
Executed Tests : ์คํํ ํ ์คํธ ์
Successful Tests : ์ฑ๊ณตํ ํ ์คํธ ์
Errors : ์๋ฌ ๋ฐ์ ์
Run time : ์คํ ์๊ฐ
testํจ์๋ ๋ค์๊ณผ ๊ฐ๋ค.
๐จ ๋ฐ๋ก ๊ณต๋ถํ ๋ถ๋ถ
QPS : Query Per Second
QPS๊ฐ ๊ธ๊ฒฉํ๊ฒ ์ฆ๊ฐํ๋ฉด ๋ฐ์ดํฐ๋ฒ ์ด์ค๊ฐ ๋ ๋ง์ ํธ๋์ญ์ ์ ์ฒ๋ฆฌํด์ผ ํ๊ธฐ ๋๋ฌธ์, CPU๋ฅผ ํฌํจํ ๋ฆฌ์์ค ์ฌ์ฉ๋๋ ๊ธ์ฆํ๊ฒ ๋๋ค.
QPS๊ฐ ์ฆ๊ฐํ๋ฉด ์ด๋ ํ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋๊ฐ?
CPU ์ฌ์ฉ๋ ๊ธ์ฆ, ๋ณ๋ชฉ ํ์, IO ๋ฐ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ์ฆ๊ฐ, Lock ๋ฐ Deadlock ์ ๊ฐ์ ๋ฌธ์ ๋ค์ด ๋ฐ์ํ ์ ์๋ค.
QPS๊ฐ ์ฆ๊ฐํจ์ผ๋ก ์ธํด ๋ฐ์ํ๋ DB CPU ์ฆ๊ฐ ๋ฌธ์ ๋ฅผ ์ด๋ป๊ฒ ํด๊ฒฐํ ๊ฒ์ธ๊ฐ?
๋ค์๊ณผ ๊ฐ์ ๋ฐฉ๋ฒ์ด ์กด์ฌํ๋ค.
(1) ์ฟผ๋ฆฌ ์ต์ ํ - ์ฟผ๋ฆฌ ๋ถ์, ๋ณต์กํ ์กฐ์ธ ์ต์ํ, ๋ฐฐ์น ์ฒ๋ฆฌ
(2) ์ธ๋ฑ์ค ํ๋ - ์ ์ ํ ์ธ๋ฑ์ค ์ค๊ณ, ๊ณผ๋ํ ์ธ๋ฑ์ค ์ ๊ฑฐ
(3) ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ค๋ฉ - ์ํ๋ถํ (DB ํธ๋ํฝ ๋ถ์ฐํ๊ธฐ)
(4) ์บ์ฑ - ์บ์ฑ ๋ ์ด์ด ์ถ๊ฐ, DB ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ ์บ์ฑ
(5) ๋ฐ์ดํฐ๋ฒ ์ด์ค ํด๋ฌ์คํฐ๋ง ๋ฐ ๋ฆฌํ๋ฆฌ์ผ์ด์ - ์ฝ๊ธฐ ์ ์ฉ ์๋ฒ ์ถ๊ฐ, ๋ก๋ ๋ฐธ๋ฐ์ฑ
(6) DBMS ์ ๊ทธ๋ ์ด๋
๐ ์ฟผ๋ฆฌ๋ฌธ DB ๋ฐ์์ด ์ ๋๋์ง ํ์ธ
nGrinder๋ฅผ ์คํํ ํ, ์ฟผ๋ฆฌ๋ฌธ์ด ์ ๋ ๋ผ๊ฐ๋์ง ํ์ธํด๋ณด์๋ค.
์ฟผ๋ฆฌ๋ฌธ์ด ํจํจ ์ ๋ ์๊ฐ๋ค. ๐
DB์ ์ ์ฅ์ด ์ ๋์๋์ง ํ์ธ!
๊ณ์ ์ด ์ค๋ณต๋์ ์ ์ฅ๋๋ ๋ฌธ์ ๋ฐ์๐ซ
๐ ๋ก๊ทธ์ธ ์ค๋ณต ๋ฌธ์ ํด๊ฒฐํ๊ธฐ
์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ณ ์ ์ค๋ณต๋ ๊ณ์ ์ ํ์ธํ๋ ๋ก์ง์ ์ถ๊ฐํด์ฃผ์๋ค.
์๋ ์ฝ๋๋ ํ์ ๊ฐ์ ์ ์ฌ์ฉํ๋ ๋ก์ง์ด๋ค.
public Long createMember(final MemberCreateRequest request) {
Member member = request.toEntity();
if(checkAlreadyExistedUser(member)) {
throw new AlreadyExistedUserException();
}
return memberRepository.save(member).getId();
}
์๋ ์ฝ๋๋ ์ ์ ์ ์ด๋ฉ์ผ์ด DB์ ์ด๋ฏธ ์กด์ฌํ๋์ง ํ์ธํ๋ ๋ก์ง์ด๋ค.
private boolean checkAlreadyExistedUser(Member member) {
return memberRepository.findByEmail(member.getEmail()).isPresent();
}
postman์ผ๋ก ๊ฒฐ๊ณผ๊ฐ ์ ๋ฐ์์ด ๋์๋์ง ํ์ธํด๋ณด์
์ฒซ๋ฒ์งธ API ์์ฒญํ๊ธฐ
๋๋ฒ์งธ API ์์ฒญํ๊ธฐ
๐ nGrinder๋ก ํ์๊ฐ์ ์์ฒญ์ ๋ฐ์ํ๋ ๋ฌธ์
๊ธฐ์กด์ nGrinder์ ์ฌ์ฉํ script๋ ๋์ผํ ๊ณ์ ์ผ๋ก ํ์๊ฐ์ api๋ฅผ ํธ์ถํด์ฃผ๋ ๋ฐฉ์์ด์๋ค.
ํ์๊ฐ์ ์ค๋ณต์ ๋ฐฉ์งํ๊ธฐ ์ํด ๋ก์ง์ ์ถ๊ฐํด์ฃผ์์ผ๋ฏ๋ก, nGrinder๋ฅผ ํตํ apiํธ์ถ์ ์ฒซ๋ฒ์งธ api ํธ์ถ๋ง ์ฑ๊ณตํ ํ, ๋๋จธ์ง api ํธ์ถ์ ์คํจํ๊ฒ ๋ ๊ฒ์ด๋ค.
nGrinder Test๊ฒฐ๊ณผ
๐ nGrinder ํ์๊ฐ์ ์์ฒญ์ ๋ฐ์ํ๋ ๋ฌธ์ ํด๊ฒฐํ๊ธฐ
import static net.grinder.script.Grinder.grinder
import static org.junit.Assert.*
import static org.hamcrest.Matchers.*
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 org.ngrinder.http.HTTPRequest
import org.ngrinder.http.HTTPRequestControl
import org.ngrinder.http.HTTPResponse
import org.ngrinder.http.cookie.Cookie
import org.ngrinder.http.cookie.CookieManager
/**
* 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
public static Map<String, String> headers = [:]
public static String body = "{\n \"email\": \"abc@abc.com\",\n \"password\": \"abc123\",\n \"name\": \"abc\"\n}"
public static List<Cookie> cookies = []
@BeforeProcess
public static void beforeProcess() {
HTTPRequestControl.setConnectionTimeout(300000)
test = new GTest(1, "127.0.0.1")
request = new HTTPRequest()
// Set header data
headers.put("Content-Type", "application/json")
grinder.logger.info("before process.")
}
@BeforeThread
public void beforeThread() {
test.record(this, "test")
grinder.statistics.delayReports = true
grinder.logger.info("before thread.")
}
@Before
public void before() {
request.setHeaders(headers)
CookieManager.addCookies(cookies)
grinder.logger.info("before. init headers and cookies")
}
@Test
public void test() {
HTTPResponse response = request.POST("http://127.0.0.1:8080/api/members", body.getBytes())
if (response.statusCode == 301 || response.statusCode == 302) {
grinder.logger.warn("Warning. The response may not be correct. The response code was {}.", response.statusCode)
} else {
assertThat(response.statusCode, is(200))
}
}
}
ํ์ฌ ๋ฐ์ํ๋ ๋ฌธ์ ์ ์ฃผ์ํ ์์ธ์ test ๋ฉ์๋๋ฅผ ํธ์ถํ ๋๋ง๋ค ๋์ผํ ์ด๋ฉ์ผ, ํจ์ค์๋, ์ด๋ฆ์ ์ฌ์ฉํ์ฌ ํธ์ถํด์ฃผ๊ณ ์๋ค.
public static String body = "{\n \"email\": \"abc@abc.com\",\n \"password\": \"abc123\",\n \"name\": \"abc\"\n}"
์ด ๋ถ๋ถ์ test๋ฉ์๋๋ฅผ ํธ์ถํ ๋๋ง๋ค ๋ณ๊ฒฝ๋๋ ์์์ ๊ฐ์ผ๋ก ์ ์ฉ์์ผ์ฃผ์!
UUID๋ฅผ ์ฌ์ฉํด ์๋์ ๊ฐ์ด test์ฝ๋๋ฅผ ๋ณ๊ฒฝํด์ฃผ์๋ค.
@Test
public void test() {
String uniqueEmail = "user" + UUID.randomUUID().toString() + "@example.com";
String uniqueName = "user" + UUID.randomUUID().toString();
String uniquePassword = UUID.randomUUID().toString(); // ๊ฐ๋จํ ๋ฌด์์ ๋ฌธ์์ด ์์ฑ
String body = String.format("{\"email\": \"%s\", \"name\": \"%s\", \"password\": \"%s\"}", uniqueEmail, uniqueName, uniquePassword);
HTTPResponse response = request.POST("http://127.0.0.1:8080/api/members", body.getBytes())
if (response.statusCode == 301 || response.statusCode == 302) {
grinder.logger.warn("Warning. The response may not be correct. The response code was {}.", response.statusCode)
} else {
assertThat(response.statusCode, is(200))
}
}
๐ nGrinder๋ก ํ ์คํธ๊ฐ ์ ์คํ๋๋์ง ํ์ธํ๊ธฐ
๊ณ์ ์ ํ์ธํด์ฃผ๋ ๋ก์ง ํ๋๋ง ์ถ๊ฐํ์ ๋ฟ์ธ๋ฐ, TPS๊ฐ 267 โก 110์ผ๋ก ๋จ์ด์ง๊ฒ ๋์๋ค.
ํ์ ๊ฐ์ ์ ํ๊ธฐ ์ํ ๋งค๋ฒ์ ์ฟผ๋ฆฌ ์์ฒญ๋ง๋ค ๊ฐ์ ํ๊ณ ์ ํ๋ ๊ณ์ ์ ๋ํ ์ ์ ์ ์กด์ฌ์ฌ๋ถ๋ฅผ ํ์ ํ๊ธฐ ์ํด select ์ฟผ๋ฆฌ๋ฌธ์ ๋ ๋ ค์ฃผ๊ณ ์๋ค.
๋ก์ปฌ ํ๊ฒฝ์์ nGrinder๋ก ์คํธ๋ ์ค ํ ์คํธํด๋ณด๊ธฐ ์๋ฃ..!
'nGrinder' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
nGrinder์ ์นํด์ ธ๋ณผ๊น(feat.๊ณต์๋ฌธ์) (2) | 2024.09.01 |
---|