Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

k6 테스트 스크립트를 추가한다. #36

Merged
merged 5 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ FROM openjdk:17-jdk
ARG PINPOINT_VERSION
ARG AGENT_ID
ARG APP_NAME
ENV JAVA_OPTS="-javaagent:/pinpoint-agent/pinpoint-bootstrap-${PINPOINT_VERSION}.jar -Dpinpoint.agentId=${AGENT_ID} -Dpinpoint.applicationName=${APP_NAME}"
ENV JAVA_PINPOINT_OPTS="-javaagent:/pinpoint-agent/pinpoint-bootstrap-${PINPOINT_VERSION}.jar -Dpinpoint.agentId=${AGENT_ID} -Dpinpoint.applicationName=${APP_NAME}"
ENV JAVA_OPTS="${JAVA_PINPOINT_OPTS} -Duser.timezone=Asia/Seoul -Dfile.encoding=UTF-8 -Dsun.jnu.encoding=UTF-8"
COPY ./build/libs/*SNAPSHOT.jar app.jar

CMD echo 'sleep for initialze hbase' && sleep 30 && java -jar ${JAVA_OPTS} app.jar
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ Numble Challenge - Banking API

### 테스트

- 테스트 할 API는 [API 문서](https://this-is-spear.github.io/hello-banking-api/src/main/resources/static/docs/index.html)에서 확인 할 수 있습니다.
- 서버는 `run.sh` 를 실행하면 됩니다.
-

> 간혹 pinpoint-hbase 이 정상 실행하기 전에 pinpoint-collector 가 실행되어 apm 이 정상적으로 실행되지 않는 경우가 존재합니다. 이런 경우 collector를 재실행 해주세요.

- 테스트는 `test.sh` 를 실행하면 됩니다.


### Development Environment

- Back-End : Spring-Boot, Spring-Security, JPA, MySQL, Testcontainers
- Fornt-End : Thymeleaf
- Front-End : Thymeleaf
- Cloud : AWS - RDS
- Infra : Docker
- Document : Rest Docs
Expand Down
303 changes: 303 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
import http from 'k6/http';
import {check, sleep} from 'k6';
import encoding from 'k6/encoding';

const users = [];
const BASE_URL = __ENV.BASE_URL || 'http://localhost';

function setup() {
// 사용자 등록 및 정보 저장한다. 이 떄 이름과 이메일은 임의 값을 부여한다.
for (let i = 0; i < 10; i++) {
let user = {
email: `${generateUUID().slice(1, 10)}@test.com`,
name: `User ${generateUUID().slice(1, 10)}`,
password: `password${i}`,
};

// 사용자 등록
let auth = `${user.email}:${user.password}`;
let registerResponse = http.post(`${BASE_URL}/members/register`, JSON.stringify(user),
{headers: {'Content-Type': 'application/json'}});
check(registerResponse, {
'registerResponse status is 200': (r) => r.status === 200,
});

let meResponse = http.get(`${BASE_URL}/members/me`, {
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${encoding.b64encode(auth)}`,
}
},);
check(meResponse, {
'meResponse status is 200': (r) => r.status === 200,
});
// 정보 저장
user.id = meResponse.json().id;

// 자신의 계좌 등록
let accountResponse = http.post(`${BASE_URL}/accounts`, null, {
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${encoding.b64encode(auth)}`,
'Idempotency-Key': generateUUID(),
}
});

check(accountResponse, {
'accountResponse status is 200': (r) => r.status === 200,
});
user.account = accountResponse.json().number;

users.push(user);
}
}

export default function () {
if (__ITER === 0) {
setup();
}

// 두 명의 사용자를 찾는다.
let randomUsers = getRandomUsers(users, 2);
let fromUser = randomUsers[0];
let toUser = randomUsers[1];

let fromUserAuth = `${fromUser.email}:${fromUser.password}`;
let toUserAuth = `${toUser.email}:${toUser.password}`;

let fromUserMeResponse = http.get(`${BASE_URL}/members/me`, {
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${encoding.b64encode(fromUserAuth)}`,
}
},);

check(fromUserMeResponse, {
'fromUserMeResponse status is 200': (r) => r.status === 200,
});

let toUserMeResponse = http.get(`${BASE_URL}/members/me`, {
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${encoding.b64encode(toUserAuth)}`,
}
},);

check(toUserMeResponse, {
'toUserMeResponse status is 200': (r) => r.status === 200,
});

// 두 명의 사용자가 친구 신청되어 있는지 확인한다. 응답 값은 friendResponses 배열에서 userId를 추출해 확인해야 한다.
let friendResponses = getFriends(fromUserAuth).map(request => request.userId).filter(userId => userId === toUser.id);

// 친구 신청이 되어 있지 않다면 진행
if (friendResponses.length === 0) {
// A가 B에게 친구 신청
let friendRequestResponse = http.post(`${BASE_URL}/members/friends/${toUser.id}`, null, {
headers: {
Authorization: `Basic ${encoding.b64encode(fromUserAuth)}`,
}
});

check(friendRequestResponse, {
'friendResponses status is 200': (r) => r.status === 200,
});

// B는 친구 신청을 확인
let requests = getFriendRequest(toUserAuth).map(request => {
return {
requestId: request.requestId,
userId: request.fromUserId
}
});

let requestId;

if (requests.length === 0) {
return;
}

requests.forEach(request => {
if (request.userId === fromUser.id) {
requestId = request.requestId;
}
});

if (requestId === undefined) {
return;
}

// B는 A의 친구 신청을 수락
let approveRequestResponse = http.post(`${BASE_URL}/members/friends/${requestId}/approval`, null, {
headers: {
Authorization: `Basic ${encoding.b64encode(toUserAuth)}`,
}
});
check(approveRequestResponse, {
'approveRequestResponse status is 200': (r) => r.status === 200,
});
}

// A는 자신의 계좌를 확인하고 임의로 선택
let accountsResponse = http.get(`${BASE_URL}/accounts`, {
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${encoding.b64encode(fromUserAuth)}`,
}
});
check(accountsResponse, {
'accountsResponse status is 200': (r) => r.status === 200,
});

let fromAccount = getRandomAccount(accountsResponse.json().map(account => account.number));

// A는 선택한 계좌에 돈을 10000원 입금
let depositResponse = http.post(`${BASE_URL}/accounts/${fromAccount}/deposit`, JSON.stringify({amount: 10000}), {
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${encoding.b64encode(fromUserAuth)}`,
'Idempotency-Key': generateUUID(),
}
});

check(depositResponse, {
'depositResponse status is 200': (r) => r.status === 200,
});

// A는 계좌 잔액 확인
let ABalanceResponse = http.get(`${BASE_URL}/accounts/${fromAccount}/history`, {
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${encoding.b64encode(fromUserAuth)}`,
'Idempotency-Key': generateUUID(),
}
});

check(ABalanceResponse, {
'ABalanceResponse status is 200': (r) => r.status === 200,
'ABalanceResponse account amount greater than 10,000': (r) => r.json("balance.amount") >= 10000,
});

// 이체 대상 조회
let transferTargetResponse = http.get(`${BASE_URL}/accounts/transfer/targets`, {
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${encoding.b64encode(fromUserAuth)}`,
}
});

check(transferTargetResponse, {
'transferTargetResponse status is 200': (r) => r.status === 200,
});

let transferTargetAccount = getRandomAccount(transferTargetResponse.json("targets").map(request => {
return {
account: request.accountNumber,
email: request.email,
}
}).filter(request => request.email === toUser.email)
.map(request => request.account.number)
);

// 10000원 이체
let transferResponse = http.post(`${BASE_URL}/accounts/${fromAccount}/transfer`, JSON.stringify({
amount: 10000,
toAccountNumber: transferTargetAccount
}), {
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${encoding.b64encode(fromUserAuth)}`,
'Idempotency-Key': generateUUID()
}
});

check(transferResponse, {
'transferResponse status is 200': (r) => r.status === 200,
});

// B는 계좌 잔액 확인
let BBalanceResponse = http.get(`${BASE_URL}/accounts/${transferTargetAccount}/history`, {
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${encoding.b64encode(toUserAuth)}`,
'Idempotency-Key': generateUUID()
}
});

check(BBalanceResponse, {
'BBalanceResponse status is 200': (r) => r.status === 200,
'BBalanceResponse account amount greater than 10,000': (r) => r.json("balance.amount") >= 10000,
});

// B는 10000원 출금
let withdrawResponse = http.post(`${BASE_URL}/accounts/${transferTargetAccount}/withdraw`, JSON.stringify({amount: 10000}), {
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${encoding.b64encode(toUserAuth)}`,
'Idempotency-Key': generateUUID()
}
});
check(withdrawResponse, {
'withdrawResponse status is 200': (r) => r.status === 200,
});

// 간격을 두고 실행
sleep(1);
}


function getFriends(auth) {
let friendsResponse = http.get(`${BASE_URL}/members/friends`, {
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${encoding.b64encode(auth)}`,
}
});
check(friendsResponse, {
'friendsResponse status is 200': (r) => r.status === 200,
});

return friendsResponse.json().friendResponses;
}

function getFriendRequest(auth) {
let request = {};
let friendRequestResponse = http.get(`${BASE_URL}/members/friends/requests`, {
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${encoding.b64encode(auth)}`,
}
});
check(friendRequestResponse, {
'friendRequestResponse status is 200': (r) => r.status === 200,
});

// 응답 값은 friendResponses 배열에서 user id와 requestId를 추출해 확인해야 한다.
return friendRequestResponse.json().askedFriendResponses;
}


// 랜덤으로 n명의 사용자 선택
function getRandomUsers(users, n) {
let randomUsers = [];
while (randomUsers.length < n) {
let user = users[Math.floor(Math.random() * users.length)];
if (!randomUsers.includes(user)) {
randomUsers.push(user);
}
}
return randomUsers;
}

// 랜덤으로 계좌 선택
function getRandomAccount(accounts) {
return accounts[Math.floor(Math.random() * accounts.length)];
}

function generateUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = Math.random() * 16 | 0,
v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
15 changes: 15 additions & 0 deletions test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/bash

script="test.js"

# 사용자에게 가상 사용자 수와 테스트 기간 입력 받기
read -r -p "Enter the number of virtual users (vuser): " vuser
read -r -p "Enter the test duration (e.g., 30s, 5m): " duration

# k6 실행 명령어
base_url="http://localhost"
k6_command="k6 run -e BASE_URL=$base_url -u $vuser -d $duration $script"

# k6 실행
echo "Running $script with $vuser virtual users for $duration..."
eval "$k6_command"
Loading