정글에서 온 개발자
1/14 TIL 데이터베이스 테스트 본문
전제조건
- 데이터베이스 스키마를 형상관리로 관리
- 단순 인스턴스로 관리하는 것에 비해 과거 시점으로 돌릴 수 있는 등 장점이 있다.
- 형상관리 외에서는 스키마를 수정하면 안된다.
- 개발자마다 별도의 데이터베이스 인스턴스 사용
- 테스트 중 운영과 간섭이 없도록 함
- 데이터베이스 배포에 마이그레이션 기반 적용
- 상태 기반 방식은 병합 충돌 처리가 수월
- 마이그레이션은 데이터 모션 문제 해결이 수월
- 모션 문제 해결이 병합충돌 해결보다 중요하다.
* 병합 충돌 : 마이그레이션 도중 사용자가 데이터를 수정할 수 있음..?
스키마
- 테이블, 부, 인덱스, 저장프로시저 등 SQL 스크립트로 표현된다.
- 데이터베이스에 저장된 데이터라도 변하지 않는 데이터 (참조 데이터) 도 스키마다! (SQL INSERT 형태로 저장)
마이그레이션
- 마이그레이션이 커밋 된 후에는 수정하지 말고, 잘못되면 되돌리지 말고 새 마이그레이션을 생성
트랜잭션 관리
- 읽기 연산 중에는 여러 트랜잭션을 열어도 된다.
- 중간에 데이터 변경이 포함된다면 원자성을 위해 트랜잭션을 쓴다.
- 업데이트할 데이터 , 업데이트 유지 또는 롤백 여부에 대한 책임을 나눈다.
리포지토리 - 데이터에 대한 접근과 수정을 가능하게 하는 클래스
트랜잭션 - 데이터 업데이트를 완전히 커밋하거나 롤백하는 클래스
- 리포지토리는 수명이 짧고, 트랜잭션은 연산 동안 존재한다.
- 리포지토리에 트랜잭션을 주입한다!
public UserConroller( //UserController 생성자
Transaction transaction,
..)
{
_transaction = transaction;
_userRepository = new UserRepository(transaction);
}
public class Transaction : IDisposable
{
public void Commit() {...}
public void Dispose() {...}
}
- ORM에서 지원하는 작업단위 패턴을 사용하면 '작업 지연'을 통해 SQL 호출을 줄일 수도 있다.
- gorm에서 쓰는 tx.begin()이 따로 없는 것, Controller가 시작하자마자 transaction을 넣는 것이 생소한데, 어떻게 적용해야 할까?
- gorm에서는 작업단위 패턴을 어떻게 지원할지도 의문이다.
* 비관계형 데이터베이스에는 트랜잭션이 없다. 그래서 한번에 여러 도큐먼트를 수정하지 않도록 도큐먼트 구조 자체를 연산 단위로 설계한다.
통합테스트에서 트랜잭션
- 운영과 동일하게 트랜잭션을 쪼개야한다.
- 따라서 준비, 실행, 검증 각각 트랜잭션을 사용해야 한다.
데이터 생명 주기
데이터 정리
- 테스트 전 데이터베이스 백업 복원 - 느리다.
- 테스트 종료 시점에 데이터 정리하기 - 테스트 도중 서버 중단되면 정리 안됨
- 데이터베이스 트랜잭션에 각 테스트를 래핑하고 커밋 안하기 - 운영과 다르게 트랜잭션이 운용된다.
- 테스트 시작 시점에 데이터 정리하기 - 가장 좋음!
public abstract class IntegerationTests
{
protected IntegrationTests()
{
ClearDatabase();
}
private void ClearDatabase()
{
string query =
"DELETE FROM dbo.User; .."
...
}
}
인메모리 데이터베이스 사용하지 않기
- 운영 DB와 환경이 다르게 될 수 있기 때문에
테스크 구절 코드 재사용
- 준비문 -> 오브젝트 마더
- 실행문 -> 대리자
- 검증 -> 헬퍼메서드와 플루언트 api (클래스 확장을 통해)
위와 같이 정리하면서 트랜잭션이 늘어나 성능 저하가 있을 수 있지만, 유지보수를 위한 감수 가능한 절충이다.
* 오브젝트 마더 : 주로 테스트 코드에서 사용(하고 여기서 쓰는 용어) - 복잡한 객체 반복 생성을 피하기 위해 미리 객체를 생성해두고 테스트에 필요한 객체를 편리하게 생성.
* 오브젝트 마더가 빌더 패턴으로 되면 빌더. (선택적 인수가 있으면 오브젝트 빌더가 더 좋을수도)
기타
읽기 테스트
읽기는 도메인 모델이 필요가 없다. -> 단위 테스트가 필요 없다. -> 테스트 하고 싶으면 통합테스트를 하자
변경사항이 없으므로 캡슐화도 필요 없다. -> ORM도 필요 없고 일반 SQL을 쓰는 것이 좋다. (기능 얘기인지, 테스트 얘기인지?)
레포지토리 테스트
리포지토리가 도메인 객체를 데이터베이스에 어떻게 맵핑하는지 정도는 유닛 테스트를 할 수 있다. 그러나 가치가 크지 않다.
ORM을 사용하면 데이터베이스 상호작용과 맵핑을 분리하는 것도 불가능하다.
통합테스트의 일부로써 다루는 게 좋다.
내 생각
대망의 데이터베이스 테스트다. 이거 보려고 여기까지 읽었는데!
트랜잭션 부분은 이해하기 어려웠다. 기존에 서비스(책에서는 컨트롤러)에서 트랜잭션을 사용하도록 구현하는게 어렴풋이 맞는 것 같다. 이렇게 생각해보니 사실상 도메인 로직을 쓰는 모델 계층이 없는 것 같다는 생각이 들었다. 책을 보고 모델이 어떤 역할을 수행했는지 다시 봐야할 것 같다.
레포지토리 부분도 어려웠다. 좀 더 예제가 필요하다. Go로 책에 나온 패턴을 구현해봐야 할 것 같다. ORM과 레포지토리를 같이 쓰는 경우도 생각이 더 필요하다.
Go는 진정한 생성자가 없고 오브젝트 마더만 있는 것 같다는 생각이 들었다. 그러나 오브젝트 마더는 테스트에서 주로 쓰는 용어라고 한다.
읽기 테스트를 하지 말라고 하는데, 나는 사실 간단한 읽기가 아닌 리스트형 읽기를 테스트하고 싶다. 쿼리를 최적화하려고 수정하는 중 LEFT JOIN 조건 등이 잘못되어 회귀되는 경우가 종종 있었기 때문이다. 이 방법은 책에 안나오는 것 같으니 따로 공부해야 할 것 같다.
이쯤 되면 내가 만드는 시스템이 통합테스트정도만 필요한 복잡하지 않은 시스템인가 싶고 (기능만 많은) 도대체 유닛 테스트가 필요한 복잡한 로직의 시스템은 어떤게 있다는 건지 궁금하다.
다른 웹 프로젝트들을 봐야할 때인가보다.
'TIL' 카테고리의 다른 글
Go가 탄생한 이유 (0) | 2025.01.19 |
---|---|
1/15 TIL 단위 테스트 안티패턴 (0) | 2025.01.16 |
1/13 TIL 목 처리 (0) | 2025.01.13 |
1/12 TIL 통합테스트 (1) | 2025.01.12 |
1/11 TIL 가치 있는 단위 테스트를 위한 리팩토링 (0) | 2025.01.12 |