계속 업데이트 예정 😎
📌 jest Unit Test
단위 테스트란
동작/기능을 단위로 하여 검증하고, 빠르게 수행하고, 격리된 방식으로 처리하는 테스트
- 기본적인 비지니스 로직과 예외 사항들
- 인프라 구성 요소 대신 가짜 또는 모의 개체로 알려져 있는 제작된 구성 요소를 사용
단위 테스트의 목표
- 테스트에 드는 노력은 최소, 그에 따른 이득은 최대화
- 코드는 점점 나빠지는 경향이 있다. 변경이 생길 때마다 무질서도가 높아진다. 따라서, 지속적인 관리를 위해 테스트 코드는 필수적이다.
- 코드 변경시 테스트를 수행하고, 제품 코드를 리팩토링할 때 테스트도 리팩토링한다.
성공적인 unit test suite
- 테스트는 자동으로 확인할 수 없다! 따라서, 개발 주기에 통합되어 있어야한다.
- 가장 중요한 부분 (비지니스 로직, 도메인 모델)을 대상으로 해야한다.
jest 사용기
- 각 메서드 안에서 사용하는 함수를 재정의해주는 것이 핵심! Mocking이라고 한다. Model, function 모두 원래 함수의 동작을 모방하되 테스트용으로 return value를 지정해주면 된다.
jest.fn(() => returnValue)
📌 supertest Integration Test
통합 테스트란
동작/기능을 단위로 하여 검증하고, 빠르게 수행하고, 격리된 방식으로 처리하는 테스트
- 단위 테스트는 하나의 메서드가 잘 동작한다는 것은 보장할 수 있지만, 그들이 결합되었을 때도 잘 작동한다는 보장을 할 수 없기 때문에, 통합 테스트를 해야한다.
- 주요 흐름과 단위 테스트가 다루지 못하는 edge case
- 앱이 프로덕션 환경에서 사용하는 실제 구성 요소를 사용
더 많은 코드와 데이터 처리가 필요
- 테스트용 데이터베이스를 만들어준다.
통합 테스트의 목표
jest supertest 사용기
1) 로그인 상태를 유지해야한다. 즉, 토큰을 담은 cookie들을 계속해서 지니고 있어야한다.
- 이를 위해, accessToken과 refreshToken을 login API에서 발급하면 이를 다른 객체에 저장해주었다.
- 이를 auth-middleware를 통과하는 API들에 대해 header에 cookie를 직접 넣어주었다.
const response = await supertest(app)
.post(`/api/worldcup`)
.set("Authorization", `Bearer ${userData.accessToken}`)
.set("refreshtoken", `${userData.refreshToken}`)
.send(createWorldcupRequestBodyParams);
이 방식을 사용하면 매번 test를 진행할때마다 요청이 새롭게 생성된다.
2) agent()
를 사용하여 요청을 지속시킬 수 있다.
- agent는 브라우저가 쿠키를 기본으로 보내는 것처럼 비슷한 행동을 할 수 있는 메서드이다.
describe('POST /api/auth/login', () => {
const agent = request.agent(app);
beforeEach((done) => {
agent.post('/api/auth/login')
.send({
nickname: userData.nickname,
password: userData.password,
email: userData.email,
})
.end(done);
});
test("POST /api/worldcup", async () => {
const createWorldcupRequestBodyParams = {
title: worldcupData2.title,
content: worldcupData2.content,
choices: worldcupData2.choices,
};
const response = await agent
.post(`/api/worldcup`)
.send(createWorldcupRequestBodyParams);
expect(response.status).toEqual(201)
expect(response.body).toMatchObject({
newWorldcup: {
worldcup_id: worldcupData2.worldcup_id,
user_id: userData.user_id,
title: createWorldcupRequestBodyParams.title,
content: createWorldcupRequestBodyParams.content,
choices: createWorldcupRequestBodyParams.choices,
},
});
});
});
📌 Artillery Load Test
부하 테스트란
임계치의 한계에 도달할 때까지 부하를 꾸준히 증가시키며 진행하는 테스트. 즉, 서버가 얼마만큼의 요청을 견딜 수 있는지 테스트하는 것이다.
- 내 코드가 실제로 배포되었을 때, 어떤 문법적, 논리적 문제가 있을지는 유닛 테스트와 통합 테스트를 통해 어느 정도 확인할 수 있지만, 내 서버가 몇 명의 동시 접속자나 일일 사용자를 수용할 수 있는지 예측하는 것은 매우 어렵다.
- 코드에 문법적, 논리적 문제가 없더라도 서버의 하드웨어 제약으로 인해 서비스가 중단될 수 있다.
- 대표적인 것이 OOM(out of memory) 문제인데, 이는 서버가 접속자들의 정보 저장을 위해 각 사용자마다 일정한 메모리를 할당할 때, 메모리의 양이 계속 증가하다가 결국 서버의 메모리 용량을 넘어서게 되면 발생하는 문제이다.
- 이런 것들을 부하 테스트를 통해 어느 정도 예측할 수 있다.
Artillery 사용기
사용 방법 포스팅
📌 TDD: Test-Driven Development
TDD와 기존 프로세스의 차이
TDD란
- 애자일 방법론 중 하나인 eXtream Programming (XP)의 'Test-First' 개념에 기반을 둔, 테스트 주도 개발이다.
- XP: 미래에 대한 예측을 최대한 하지 않고, 지속적으로 프로토타입을 완성하는 애자일 방법론 중 하나이다. 이 방법론은 추가 요구사항이 생기더라도, 실시간으로 반영할 수 있다.
Red: 실패하는 테스트 코드 작성
Green: 테스트 코드를 성공시키기 위한 실제 코드 작성
Refactor: 중복 코드 제거, 일반화 등의 리팩토링
- 핵심
1) 실패하는 테스트 코드를 작성할 때까지 실제 코드를 작성하지 않는 것
2) 실패하는 테스트를 통과할 정도의 최소 실제 코드를 작성하는 것
TDD로 개발해야 하는 케이스 = 불확실성이 높을 때
- 처음 해보는 프로젝트 수행시 (내부적인 불확실성)
- 고객의 요구조건이 바뀔 수 있는 프로젝트 수행시 (외부적인 불확실성)
- 개발하는 중에 코드를 많이 바꿔야한다고 생각되는 경우
- 내가 개발하고 나서, 누가 유지보수를 할 지 모르는 경우 (외부적인 불확실성)
TDD의 장점
- 튼튼한 객체 지향적인 코드 생산 - TDD는 코드의 재사용을 보장하여 기능별 철저한 모듈화가 이루어진다.
- 재설계 시간의 단축 - 개발자가 무슨 코드/로직을 짜야하는지 분명히 정의하고 개발을 시작할 수 있다.
- 디버깅 시간의 단축 - 유닛 테스트의 이점, 어떤 레이어에서 문제가 발생하는지 파악하기 용이하다.
- 추가 구현의 용이함 - 추가 기능 구현시 기존 코드에 영향을 미치는 것에 조심해야 하는데, TDD는 자동화된 유닛 테스팅을 전제하므로 테스트 기간을 단축할 수 있다.
TDD의 단점
- 생산성 저하: 개발 속도가 느려질 수 있다. 실제, 일반적인 개발 방식보다 10~30%정도 시간이 늘어난다. 따라서, SI 프로젝트의 경우 TDD를 잘 채택하지 않는다.