회사에서는 마이바티스를 사용하는데 다른 부서에서는 마이바티스를 통해 테스트 코드를 작성하고 있다.
현재 내 프로젝트도 주력이 마이바티스이기에 이번에 토이프로젝트를 진행하면서 마이바티스를 사용하기로 결정했다.
PR를 날리기 위해서는 테스트 커버리지 80% 이상이라는 목표가 있어서 이번에 통합 테스트를 작성하게 되었는데 마이바티스만의 구축 방법이 있어서 이를 기록으로 남기고자 작성하게 되었다.
이번에 테스트 대상은 MemberMapper이다. 단순한 쿼리만 들어있는 상태라고 보면 된다.
<!-- 회원 정보 삽입 -->
<insert id="insertMember" parameterType="SignUpRequestDto">
INSERT INTO member (id, password, email, phone_number, nickname, age, profile_image, regist_date)
VALUES (#{id}, #{password}, #{email}, #{phoneNumber}, #{nickname}, #{age}, #{profileImage}, #{registDate})
</insert>
<!-- 로그인 -->
<select id="selectMember" parameterType="String" resultType="LoginResponseDto">
SELECT member_pk,
id,
password,
email,
phone_number,
nickname,
age,
profile_image
FROM member
WHERE id = #{id}
</select>
<!-- 아이디 중복체크 -->
<select id="checkIdDuplicate" parameterType="String" resultType="int">
SELECT COUNT(*)
FROM member
WHERE id = #{id}
</select>
<!-- 이메일 중복체크 -->
<select id="checkEmailDuplicate" parameterType="String" resultType="int">
SELECT COUNT(*)
FROM member
WHERE email = #{email}
</select>
등등...
기존에는 Mockito를 이용해서 모킹 테스트만 진행했었는데 하나의 쿼리를 모킹 해서 테스트하려고 했으나 사실상 의미가 없는 테스트라는 걸 깨닫고 통합 테스트로 변경하였다.
(쿼리의 응답을 1로 모킹해놓고 성공이라고 테스트를 만들면 이는 무조건 성공할 수밖에 없다. 그래서 테스트 의미가 없어지는 것)
결국 실제 DB에 데이터를 넣고 테스트를 진행해야 하는데 이때 MySQL과 H2를 놓고 고민하게 되었다.
MySQL을 사용한다면 기존에 만들어놓은 테이블이랑 데이터를 이용할 수 있다. 또한 개발과 동일한 환경에서 테스트가 가능하다는 장점이 있다.
H2 인메모리 디비를 사용한다면 네트워크 통신이 발생하지 않는다. 또한 인메모리 디비이기에 매번 테스트를 실행할 때마다 내부 데이터 초기화가 진행된다.
위 두 가지를 비교했을 때 H2 인메모리 디비를 사용해서 빠르게 구축하는 것이 더 효율적이라고 판단했다.
실제로 MySQL과 H2의 속도 비교를 진행했던 글이 있는데 흥미로운 글이다.
[Test Code] 테스트 DB의 In-Memory H2와 로컬 MySQL 속도 차이 비교하기
지난번에 "In-Memory H2 사용해서 Spring Boot 테스트 코드 작성하기" 라는 글에 이어서 실제로 MySQL과 In-Memory H2 데이터 베이스의 속도 차이를 비교해보겠습니다.
velog.io
우선 test를 위해서 test용 yml을 따로 만들어주었다.
test / resources / application-test.yml
spring:
datasource:
url: jdbc:h2:mem:test
driver-class-name: org.h2.Driver
username: sa
password:
h2:
console:
enabled: true
sql:
init:
mode: always
sql.init.mode를 always로 설정해서 매번 초기화를 진행하도록 설정.
JPA는 테스트를 진행하면 기본적으로 entity 설정을 통해 테이블을 생성한다. 그러나 마이바티스는 그러한 개념이 없기 때문에 조금 색다른 방법으로 테이블 초기화를 진행한다.
test/resources/data.sql
test/resources/schema.sql
위 두 가지 파일을 읽어서 테이블, 초기 데이터 설정을 진행할 수 있다.
schema.sql
CREATE TABLE member
(
member_pk BIGINT NOT NULL AUTO_INCREMENT,
id VARCHAR(30) NOT NULL UNIQUE,
password VARCHAR(100) NOT NULL,
email VARCHAR(30) NOT NULL UNIQUE,
phone_number VARCHAR(13) NOT NULL UNIQUE,
nickname VARCHAR(20) NOT NULL UNIQUE,
age INTEGER NOT NULL,
profile_image VARCHAR(200) NOT NULL,
regist_date DATETIME,
PRIMARY KEY (member_pk)
);
CREATE TABLE login_history
(
login_history_pk BIGINT NOT NULL AUTO_INCREMENT,
member_pk BIGINT NOT NULL,
login_date DATETIME NOT NULL,
ip VARCHAR(20) NOT NULL,
PRIMARY KEY (login_history_pk),
FOREIGN KEY (member_pk) REFERENCES member (member_pk)
);
data.sql
INSERT INTO member(id, password, email, phone_number, nickname, age, profile_image, regist_date)
VALUES ('qkrrlgus114', '1234', 'test@naver.com', '010-8615-6570', '박기현', 28, 'http://default.url', NOW());
이렇게 설정해 놓고 테스트를 실행하면 자동으로 schema.sql을 읽어서 테이블 생성을 진행하고 data.sql을 읽어서 초기 데이터 설정을 진행한다.
이제 테스트를 진행할 Test 클래스를 하나 만든다.
@SpringBootTest
@ActiveProfiles("test")
class MemberMapperTest {
@Autowired
private MemberMapper memberMapper;
이때 @SpringBootTest를 이용해서 실제 서비스와 동일하게 빈을 생성한다.
@ActiveProfiles("test")를 이용해서 위에서 만든 test.yml을 가리킬 수 있도록 설정해 준다.
이제 실제로 테스트할 쿼리 중 회원가입 성공 쿼리만 예시로 살펴보자.
<insert id="insertMember" parameterType="SignUpRequestDto">
INSERT INTO member (id, password, email, phone_number, nickname, age, profile_image, regist_date)
VALUES (#{id}, #{password}, #{email}, #{phoneNumber}, #{nickname}, #{age}, #{profileImage}, #{registDate})
</insert>
코드는 아래처럼 작성했다.
@Test
@DisplayName("유저 회원가입 쿼리 성공")
@Transactional
void insertMemberSuccess() {
SignUpRequestDto signUpRequestDto = SignUpRequestDto.builder()
.id("test")
.password("testtest")
.age(15)
.nickname("테스트")
.profileImage("http://default.url")
.phoneNumber("010-1234-1234")
.email("tes123@naver.com")
.build();
int result = memberMapper.insertMember(signUpRequestDto);
Assertions.assertEquals(1, result);
}
파라미터로 signUpRequestDto를 넘겨주고 insert 결과를 int 타입으로 받는다.
이때 result의 값이 1이면 insert 성공, 0이면 insert 실패를 의미한다.
성공의 경우를 테스트하니 1이 나와야 하는 것.
디버깅을 통해 확인해 보면 실제로 MapperProxy를 주입받아서 진행되는 것을 확인할 수 있다.
(즉, 빈 주입이 잘 이루어지고 있다는 것)
이제부터 하나씩 테스트 코드를 짜고 있는데 생각보다 머리도 많이 써야 하고 노가다 느낌이 물씬 풍긴다.
그리고 어떤 건 모킹을 하고 어떤 건 모킹을 하지 않으니 그걸 구분 짓는 것도 꽤 머리 아픈 일이다.
그래도 테스트 코드를 잘 작성해 두면 내부 로직이 변경됐을 때 만들어놓은 테스트를 통해 로직 검증을 빠르게 진행할 수 있다는 장점이 있기에 이번에 적극 도입해 보고 리뷰해보려고 한다.(앞으로 많은 테스트 관련 글이 올라올 수 있으면 좋겠다.)
'기타' 카테고리의 다른 글
크롬 다중 세션을 쉽게 이용해보자(feat 인간 수동 테스트) (1) | 2025.01.25 |
---|---|
특정 사용자의 IP만 허용시키기 위한 IP 필터 제작하기 (0) | 2024.11.06 |
PK를 설정하는 방법(AutoIncrement, UUID) (2) | 2024.09.29 |
API에 대한 로깅을 어떻게 찍는 게 좋을까?(인터셉터, 필터, AOP) (0) | 2024.09.15 |