2. Test
테스트
2.1. UserDaoTest 다시 보기
UserDaoTest는 DAO가 의도한 대로 정확히 동작하는지를 직접 실행을 통해 빠르게 검증하기 위한 초기 테스트 코드입니다. main() 메서드를 이용해 UserDao의 add(), get() 기능을 수행하고 결과를 콘솔로 확인하는 방식으로 작성되었습니다.
이 테스트는 자동화 수준은 낮지만 DAO 개발 초기에 기능 검증과 리팩터링 안정성을 확보하는 데 중요한 역할을 하게됩니다.
2.1.1. 테스트의 유용성
초기 UserDao 테스트는 아래와 같은 방식으로 진행됩니다.
main() 메서드를 이용해 테스트 생성
스프링 컨테이너에서 UserDao 빈을 가져옴
User 객체를 생성해서 add() 호출
다시 get()으로 조회해 결과를 콘솔로 출력.
이 과정을 반복 실행하면서 기능이 정상 동작함을 확인했습니다.
2.1.2. UserDaoTest의 특징
특징은 아래와 같습니다.
자바에서 가장 간단히 실행 가능한
main()메서드를 사용테스트 대상인 UserDao를 직접 호출
테스트에 필요한 User 객체를 코드로 직접 생성
테스트 결과를 콘솔 출력으로 확인
각 단계가 문제없이 끝나면 성공 메시지를 출력
가장 눈에 띄는 특징은 테스트 실행과 검증이 전적으로 개발자의 눈에 의존한다는 점입니다.
웹을 통한 DAO 테스트 방법의 문제점
실무에서는 DAO를 테스트하기 위해 웹 화면을 띄우고 폼에 값을 입력한 뒤 버튼을 눌러 결과를 확인하는 경우가 많습니다.
이 방식은 다음과 같은 심각한 문제를 가집니다.
DAO 테스트를 위해 서비스, 컨트롤러, JSP까지 모두 필요
어느 계층에서 오류가 발생했는지 파악하기 어려움
DB 문제인지, SQL 문제인지, MVC 설정 문제인지 구분이 힘듦
테스트 실패 시 원인 분석에 많은 시간 소요
서버 설정, 파라미터 전달, 화면 로직까지 테스트에 영향
결국 정말 확인하고 싶은 것은 UserDao인데, 다른 계층의 코드와 환경이 테스트를 방해하게 됩니다.
작은 단위의 테스트
테스트 대상이 명확하다면, 가능한 한 그 대상만 집중해서 테스트하는 것이 바람직합니다. 이를 위해서는 테스트 단위를 최대한 작게 가져가야 합니다.
UserDaoTest의 경우 다음과 같은 특징을 가집니다.
웹 서버 불필요
MVC, 서비스 계층 불필요
IDE 또는 콘솔에서 즉시 실행 가능
오류 발생 시 UserDao 또는 DB 접근 로직으로 원인 범위가 좁혀짐
이처럼 작은 단위로 코드의 동작을 검증하는 테스트를 단위 테스트(Unit Test)라고 합니다. 여기서 단위란 클래스 하나일 수도 있고, 메서드 하나일 수도 있으며 관심사를 집중해서 검증 가능한 최소 범위를 의미합니다.
자동수행 테스트 코드
UserDaoTest의 또 다른 중요한 특징은 테스트 데이터 준비와 실행이 모두 코드로 자동화되어 있다는 점입니다.
매번 화면에서 값을 입력할 필요 없음
DB에 값을 넣고 조회하는 과정이 자동 수행
실행 시간 짧음
반복 실행 부담 없음
이러한 테스트는 다음과 같은 장점을 제공합니다.
코드 수정 후 즉시 전체 테스트 실행 가능
리팩터링 시 기존 기능이 깨지지 않았음을 빠르게 확인 가능
테스트 실행 자체가 개발 속도를 늦추지 않음
지속적인 개선과 점진적인 개발을 위한 테스트
초기 DAO 구현 단계에서는 설계가 완벽하지 않아도 괜찮습니다. 중요한 것은 작동하는 최소한의 코드 + 이를 검증하는 테스트를 먼저 만드는 것입니다.
테스트가 있다면 다음이 가능해집니다.
구조 개선 시 기능 유지 여부 즉시 확인
설계 변경에 대한 심리적 부담 감소
작은 변경을 반복하며 코드 품질 향상
만약 테스트 없이 스프링 설정, DI, 구조 설계를 한 번에 모두 적용했다면 오류 발생 시 원인을 찾는 데 훨씬 많은 시간이 들었을 것입니다.
2.1.3. UserDaoTest의 문제점
UserDaoTest는 분명 장점이 있지만, 한계도 존재합니다.
■ 수동 확인 작업의 번거로움
테스트 결과를 사람이 직접 콘솔 출력으로 확인해야 함
기대값과 실제값 비교를 코드가 해주지 않음
작은 실수도 놓칠 가능성 존재
■ 실행 작업의 번거로움
테스트 클래스가 많아질수록
main()실행이 반복됨테스트 결과를 체계적으로 관리하기 어려움
성공/실패 이력을 자동으로 기록하지 못함
결국 main() 메서드 기반 테스트는
초기 검증에는 유용하지만, 체계적인 테스트 방법으로는 한계가 있습니다.
2.2. UserDaoTest 개선
2.1. 에서 만든 UserDaoTest는 DAO 기능을 빠르게 검증하지만, 결과를 직접 확인해야하는 불편함이 있었고, 반복 실행과 관리가 어려웠습니다.
이제는 테스트 결과 검증을 코드가 하도록 개선하고,
main() 기반 테스트를 JUnit 테스트로 전환해 효율적으로 실행 및 관리하게 됩니다.
2.2.1. 테스트 검증의 자동화
기존 UserDaoTest의 가장 큰 문제는 테스트 성공 여부를 사람이 확인하는 것입니다.
콘솔 출력에 의지하는 것은 휴먼에러가 발생할 수도 있고, 반복에 적합하지도 않습니다.
테스트 결과의 종류
테스트 결과는 다음 세 가지로 나뉩니다.
테스트 에러 테스트 수행 중 예외가 발생해 정상적으로 끝나지 않은 경우
테스트 실패 에러는 없지만 결과가 기대와 다른 경우
테스트 성공 에러도 없고 결과도 기대와 일치하는 경우
기존 테스트 코드는 에러만 확인 가능했고, 성공과 실패를 코드 차원에서 구분하지 못했습니다.
검증 로직 추가
기존에는 get() 결과를 단순 출력만 했습니다.
이를 기대값과 실제값을 비교하는 코드로 변경합니다.
이제 테스트 코드는 다음을 보장합니다.
add()로 저장한 값이get()으로 정확히 조회되는지필드 단위로 자동 검증
이 검증은 get()뿐 아니라 add()의 정상 동작까지 함께 검증합니다.
만약 add()가 실패했다면 get() 검증도 통과할 수 없습니다.
자동 테스트의 의미
이제 테스트는 다음 과정을 모두 자동으로 수행합니다.
테스트 실행
테스트 대상 코드 호출
기대값과 결과 비교
성공 / 실패 판단
개발자가 할 일은 마지막 출력이 “테스트 성공”인지 확인하는 것뿐입니다. 이 단계에서 UserDao는 언제든지 반복 실행 가능한 자동 테스트 대상이 됩니다.
2.2.2. 테스트의 효율적인 수행과 결과 관리
main() 기반 테스트는 자동 검증까지는 가능해졌지만, 여전히 다음과 같은 문제가 남아 있습니다.
테스트가 많아질수록 실행이 번거로움
테스트 결과를 한눈에 파악하기 어려움
테스트 실패 원인을 체계적으로 알기 어려움
이 문제를 해결하기 위해 테스트 전용 프레임워크가 필요합니다.
JUnit 테스트로의 전환
JUnit은 자바 진영에서 가장 널리 사용되는 단위 테스트 프레임워크입니다. JUnit을 사용하면 다음이 가능해집니다.
테스트 실행 자동화
테스트 성공 / 실패 자동 판별
실패 위치와 원인 명확한 출력
IDE, 빌드 도구와 자연스럽게 연동
테스트 메서드 전환
기존 main() 메서드 테스트는 JUnit에 적합하지 않습니다. 따라서 테스트 코드를 일반 메서드로 옮기고 @Test를 붙입니다.
Kotlin + JUnit 테스트 메서드 예시
검증 코드의 변화
기존 if/else 검증은 다음과 같이 바뀝니다.
조건이 맞지 않으면
AssertionError발생예외가 발생하지 않으면 테스트 성공
성공 메시지를 출력할 필요 없음
JUnit은 테스트 결과를 프레임워크 차원에서 관리합니다.
JUnit 테스트 실행
JUnit 테스트는 IDE, 빌드 도구, 또는 코드로 실행할 수 있습니다.
실행 결과 예시:
실패 시에는 다음 정보가 자동 출력됩니다.
실패한 테스트 메서드
기대값과 실제값
실패 지점의 코드 위치
호출 스택
이는 main() 테스트로는 얻기 어려운 수준의 피드백입니다.
JUnit 테스트의 의미
JUnit을 적용한 UserDaoTest는 이제 다음을 만족합니다.
테스트 결과 자동 판별
반복 실행 부담 없음
코드 변경 후 즉시 검증 가능
실패 원인 즉시 파악 가능
이로써 UserDao는 안전하게 리팩터링할 수 있는 코드가 됩니다. 앞으로 구조를 변경하거나 기술을 교체하더라도, 이 테스트를 통과한다면 기능은 여전히 정상이라는 확신을 가질 수 있습니다.
2.3. 개발자를 위한 테스팅 프레임워크 JUnit
JUnit은 표준 단위 테스트 프레임워크로 취급되며, main() 메서드와 콘솔 출력에 의존하던 테스트를 자동 실행, 자동 검증, 자동 보고 형태로 발전시켜줍니다.
2.3.1. JUnit 테스트 실행방법
JUnit 테스트를 실행하는 가장 단순한 방법은 JUnitCore를 이용하는 것이지만, 테스트 개수가 많아질수록 관리가 어려워집니다. 따라서, 실무에서는 IDE가 제공하는 JUnit 실행 기능을 사용하는 것이 일반적입니다.
테스트를 실행하는 방식:
@Test 가 붙은 클래스 또는 메서드 선택
Run As -> JUnit Test 실행
테스트 결과를 JUnit 전용 뷰로 확인
JUnit뷰에서 확인할 수 있는 정보:
전체 테스트 실행 시간
실행된 테스트 개수
에러 수/실패 수
실패한 테스트 메서드와 원인
실패 지점의 코드 위치
패키지 단위, 프로젝트 전체 단위로도 테스트를 한번에 실행할 수 있습니다.
2.3.2. 테스트 결과의 일관성
JUnit을 적용하면서 가장 먼저 체감하는 문제는 테스트 결과가 매번 달라질 수 있다는 점입니다. 특히 DB를 사용하는 테스트에서는 다음 문제가 발생한다.
이전 테스트 실행에서 남은 데이터로 인해 실패
테스트 실행 전에 매번 수동으로 DB 초기화 필요
테스트 순서에 따라 성공/실패가 달라짐
코드가 변경되지 않았아면 테스트 결과는 항상 같아야합니다. 이를 해결하기 위해 UserDao에 두 가지 기능을 추가합니다.
deleteAll과 getCount 추가
위 두 메서드를 이용하면 테스트 시작 전에 항상 DB 상태를 동일하게 만들 수 있습니다.
일관된 결과를 보장하는 테스트
이제 테스트를 몇번을 실행해도 항상 같은 결과를 보장하게 되며, 실행 순서도 영향을 주지 않습니다.
2.3.3. 포괄적인 테스트
실제 코드에서는 정상 동작뿐만 아니라 예외 상황도 중요합니다.
여러 사용자를 순차적으로 추가하면서 count가 정확히 증가하는지 검증합니다.
존재하지 않은 id로 조회했을때의 동작도 정의하고, 예외를 던지는 방식도 선택합니다.
조건: USER 테이블이 비어있음
행위: 존재하지 않는 id로 get() 호출
결과: EmptyResultDataAccessException 발생
테스트를 통과시키기 위한 코드 수정
테스트가 요구하는 동작에 맞게 코드를 수정하고, 테스트가 성공하면 해당 기능이 올바르게 구현되었음을 확신할 수 있습니다.
2.3.4. 테스트가 이끄는 개발(TDD)
여기서 중요한 흐름이 드러납니다.
원하는 동작을 먼저 테스트로 표현
테스트 실패 확인
테스트를 통과하도록 코드 수정
테스트 성공확인.
이 방식이 TDD입니다.
테스트는 단순한 검증 수단이 아니라 기능 요구사항을 가장 정확하게 표현하는 코드가 됩니다.
테스트는 조건, 행위, 결과를 모두 담고 있음
테스트 코드는 기능 명세와 동일한 역할 수행
테스트가 성공하면 구현과 검증이 동시에 완료됨
2.3.5. 테스트 코드 개선
테스트 코드도 코드이기 때문에 리팩터링 대상입니다.
UserDaoTest에는 방복되는 준비 코드가 많습니다.
이를 해결하기 위해 @Before를 사용합니다.
@Before 메서드는 각 테스트 메서드 실행 전에 호출됨
매 테스트마다 새로운 테스트 객체가 생성됩니다.
테스트 간 상태 공유로 인한 영향 제거
공통 준비 코드 제거로 가독성 향상
이렇게 티스트에 필요한 정보와 객체를 픽스처(fixture)라고 부릅니다.
2.4. 스프링 테스트 적용
JUnit을 이용해 테스트 구조를 많이 개선했지만, 애플리케이션 컨텍스르를 매 테스트마다 새로 생성한다는 문제가 남아있습니다. 애플리케이션 컨텍스트는 생성 비용이 크고, 내부적으로 다양한 빈을 초기화하기 때문에 테스트가 많아질수록 실행 시간이 증가합니다.
2.4.1. 테스트를 위한 애플리케이션 컨텍스트 관리
기존 테스트에서는 @Before 메서드 안에서 매번 다음과 같은 코드가 실행되었습니다.
이 방식은 아래와 같은 문제를 가지고 있습니다.
테스트 메서드 개수만큼 컨텍스트 생성
빈 초기화 비용 반복 발생
테스트 실행 시간이 누적 증가
컨텍스트 내부 상태 변경 시 다음 테스트에 영향 가능성
애플리케이션 컨텍ㄷ스트는 불변에 가깝게 사용되며, DAO 테스트에서는 컨텍스트 내부 상태를 변경할 이유가 없습니다. 따라서 컨텍스트는 한번만 만들고 공유하는게 바람직합니다.
스프링 테스트 컨텍스트 프레임워크 적용
스프링은 JUnit과 연동되는 테스트 컨텍스트 프레임워크를 제공합니다. 이를 사용하게 된다면:
테스트 실행 전에 컨텍스트를 한 번만 생성
테스트 클래스 단위로 컨텍스트 캐싱
테스트 간 컨텍스트 공유
DI를 테스트 코드에서도 자연스럽게 사용
적용 방법:
@RunWith: JUnit 실행 방식을 스프링 테스트 러너로 확장
@ContextConfiguration: 테스트에 사용할 애플리케이션 컨텍스트 파일 지정
이 두 어노테이션만으로도 스프링이 테스트 실행 전체를 관리하게 됩니다.
ApplicationContext 자동 주입
스프링 테스트 환경에서는 ApplicationContext 자체도 DI 대상입니다.
이를 통해 테스트 코드에서 컨텍스트를 직접 생성할 필요가 없어집니다. 컨텍스트는 테스트 실행 전에 스프링이 준비해두고 자동으로 주입합니다.
이제 @Before 메서드에서는 다음과 같이 단순화 할 수 있습니다.
컨텍스트 공유 확인
@Before에서 context와 this를 출력해보면 아래를 확인할 수 있습니다.
this (테스트 클래스 인스턴스)는 매번 새로 생성됨. 테스트 객체는 격리
context는 모든 테스트에서 동일한 인스턴스. 애플리케이션 컨텍스트는 공유
라는 구조가 만들어집니다. 이로 인해 테스트 간 간섭 없이도 성능을 개선할 수 있습니다.
2.4.2. DI와 테스트
스프링 테스트의 핵심 장점은 DI를 테스트 코드에서도 동일하게 적용할 수 있다는 것입니다.
이제 테스트 코드에서는 더 이상 getBean()을 호출할 필요가 없고, 운영 코드와 동일한 방식으로 의존성을 주입 받습니다. 장점들은:
테스트 코드가 운영 코드와 동일한 설정을 사용
설정 변경 시 테스트 코드 수정 불필요
DI 구조 검증까지 함께 가능
타입 기반 자동 주입
@Autowired는 기본적으로 타입 기준으로 빈을 찾습니다.
동일 타입의 빈이 하나면 자동 주입
둘 이상이면 예외 발생
이름이 필요할 경우 추가 설정 필요
이 방식은 테스트 코드에서도 동일하게 적용합니다.
테스트에서 DataSource 교체
테스트 환경에서는 운영 DB가 아닌 테스트 전용 DB를 사용해야합니다. 이를 위한 전략은
1. 테스트 코드에서 직접 DI
장점:
XML 설정 수정 불필요
테스트 상황에 맞는 객체 직접 구성 가능
단점:
스트 코드가 설정 책임까지 가짐
컨텍스트 공유 구조와 충돌 가능
이 경우 반드시 @DirtiesContext를 사용해야합니다.
@DirtiesContext
해당 테스트 클래스가 컨텍스트 상태를 변경했음을 스프링에 알립니다
테스트 종료 후 컨텍스트 폐기
다음 테스트에서 새로운 컨텍스트 생성
이는 예외적인 상황에서만 사용해야하고, 남용하면 컨텍스트 캐싱의 장점이 사라집니다.
2. 테스트 전용 설정 파일 사용
가장 권장되는 방법은 테스트 전용 애플리케이션 컨텍스트 설정 파일을 사용하는 것입니다.
이 설정 파일에는 다음만 포함됩니다.
테스트용 DataSource
테스트에 필요한 최소 빈
이 방식의 장점은:
운영 설정과 테스트 설정 분리
컨텍스트 공유 가능
@DirtiesContext 불필요
테스트 코드 단순화
3. 컨테이너 없는 DI 테스트
마지막으로 스프링 컨테이너 없이도 DI 테스트는 가능합니다.
이 방식의 특징은:
스프링 의존성 없음
테스트 코드가 매우 단순
빠른 실행 속도
단점은:
스프링 설정 검증 불가
실제 운영 환경과 차이 발생 가능
DI 테스트 방법 선택 기준
DI 테스트에는 정답이 하나만 있는게 아니라, 상황에 따라 아래 기준들로 선택하는 것이 합리적입니다.
컨테이너 없이 테스트 가능한 구조인지 확인
설정 검증이 필요하다면 스프링 테스트 컨텍스트 사용
테스트 환경과 운영환경이 다르다면 테스트 전용 설정 파일 사용
불가피한 경우에만 @DirtiesContext 사용
컨텍스트(ApplicationContext)란 무엇인가
요약 & 정리
**스프링에서 말하는 컨텍스트(ApplicationContext)는 “애플리케이션이 사용하는 모든 객체(빈)와 그 관계를 한 번에 만들어서 보관해두는 공장 + 저장소”**입니다.
객체를 언제, 어떻게, 무엇으로 만들지 정해둔 설계도
그 설계도대로 실제 객체들을 생성해서 들고 있는 공간
DI는 이 컨텍스트 안에서만 일어납니다
테스트에서 문제가 되는 이유는 👉 이 컨텍스트를 “몇 번 만들고, 누가 쓰고, 언제 버리느냐” 때문입니다.
먼저 비유로 이해하기
❌ 컨텍스트를 모를 때의 이미지
“뭔가 스프링이 관리하는 객체 묶음 같은데…”
“DAO랑 DataSource가 들어있는 박스?”
✅ 정확한 비유
ApplicationContext = 공장 + 창고 + 조립 설명서
공장: 객체를 직접 만들어줌
창고: 만들어진 객체를 보관
설명서: 어떤 객체를 어떤 객체와 연결할지 정의 (XML, 설정 클래스)
코드로 보면 바로 이해된다
이 한 줄에서 실제로 일어나는 일은 다음과 같습니다.
applicationContext.xml을 읽는다<bean>정의를 전부 분석한다각 bean에 대해
객체를 생성하고
의존성을 주입하고
완성된 객체들을 메모리에 들고 있는다
이 “객체들을 들고 있는 녀석”이 바로 ApplicationContext입니다.
컨텍스트가 들고 있는 것들
예를 들어 UserDao 기준으로 보면
객체를 “만들기만” 하는 게 아닙니다
객체 사이의 연결 관계까지 이미 끝낸 상태입니다
그래서 우리는 이렇게 씁니다.
→ “야 컨텍스트야, 네가 만들어둔 userDao 하나 줘”
중요한 오해 하나 정리
❌ 컨텍스트 = 설정 파일 ❌ 컨텍스트 = DI 그 자체
✅ 설정 파일은 컨텍스트를 만들기 위한 재료 ✅ 컨텍스트는 ‘이미 만들어진 객체들의 세계’
테스트에서 컨텍스트가 왜 문제인가
이제 2.4에서 왜 난리가 났는지 보겠습니다.
이 XML을 읽으면 스프링은 다음 일을 순서대로 수행합니다.
dataSource라는 이름의 빈을 만든다 →SimpleDriverDataSource객체 생성 → DB 접속 정보 주입userDao라는 이름의 빈을 만든다 →UserDao객체 생성 →dataSource프로퍼티에 위에서 만든 dataSource 객체를 주입이 두 객체를 ApplicationContext 안에 보관한다
기존 테스트 코드
이 코드는 테스트 메서드가 3개면:
컨텍스트 3번 생성
userDao 3번 생성
dataSource 3번 생성
DB 연결도 3번 초기화
👉 컨텍스트 생성은 매우 비쌉니다
그래서 나온 해결책
“컨텍스트는 한 번만 만들고, 테스트들은 그걸 공유하자”
이걸 스프링 테스트 프레임워크가 대신 해줍니다.
스프링 테스트에서 컨텍스트는 이렇게 동작한다
이제 흐름이 바뀝니다.
JUnit이 테스트 시작
SpringJUnit4ClassRunner가 개입
테스트 클래스당 컨텍스트를 한 번 생성
그 컨텍스트를 캐시에 저장
각 테스트 메서드 실행 시
새로운 테스트 객체 생성
같은 컨텍스트를 주입
즉,
테스트 객체: 매번 새로
컨텍스트: 한 번만
이걸 눈으로 확인한 게 책의 예제
출력 결과:
this→ 매번 주소가 다름context→ 항상 동일
👉 컨텍스트는 공유, 테스트 객체는 격리
그럼 컨텍스트 안의 객체를 바꾸면?
여기가 진짜 핵심입니다.
이 코드는
“테스트 객체의 dao”를 바꾸는 게 아니라
컨텍스트 안에 있는 userDao 빈 자체를 바꿉니다
왜냐하면 dao는 컨텍스트가 만든 공유 객체이기 때문입니다.
그래서:
테스트 A에서 바꾸면
테스트 B도 바뀐 걸 받게 됩니다
이게 2.4에서 계속 경고하는 이유입니다.
그래서 선택지가 갈린다
이제 “컨텍스트”를 기준으로 3가지 방식이 자연스럽게 갈립니다.
컨텍스트 기준으로 다시 정리
1️⃣ 컨텍스트를 만들고 → 테스트에서 내부 상태를 바꿈
컨텍스트: 공유
내부 빈 상태: 변경됨
위험 →
@DirtiesContext필요
2️⃣ 테스트 전용 컨텍스트를 처음부터 따로 만듦
컨텍스트: 테스트 전용
내부 상태: 안정
가장 권장
3️⃣ 컨텍스트를 아예 안 씀
컨텍스트: 없음
객체: 테스트가 직접 생성
가장 빠름
세 방식이 왜 나뉘는가
테스트에서 가장 흔히 바꾸고 싶은 대상은 DatSource입니다.
운영: 운영에 붙는 DataSource
테스트: 테스트DB(로컬 testDB 등)에 붙은 DataSource
문제는, 스프링 컨텍스트를 한 번 만들어 공유하면 빠르지만, 테스트 코드가 그 컨텍스트 안의 빈 연결관계를 "중간에 바꿔버리면" 다른 테스트에 영향이 생길 수 있습니다.
1) 스프링 컨텍스트를 쓰면서, 테스트 코드에서 DI를 바꿔 끼우는 방식
동작 원리
스프링이
applicationContext.xml로 컨텍스트를 만들고,UserDao빈도 생성합니다.그런데 테스트 코드(
@Before)에서dao.setDataSource(testDataSource)처럼 런타임에 주입값을 바꿉니다.
즉, “스프링이 만든 빈”을 가져다 놓고 테스트 코드가 상태를 바꾸는 방식입니다.
왜 이렇게 하게 되는가
XML을 복사해서 테스트 전용 설정을 만드는 게 번거롭거나
특정 테스트에서만 임시로 다른 DataSource를 써야 하거나
빠르게 실험하고 싶을 때
문제점(트레이드오프)
스프링 테스트는 컨텍스트를 캐싱해서 여러 테스트가 공유합니다.
그런데 테스트 A가 dao.setDataSource(testdb)로 바꿔버리면,
테스트 B는 원래 운영 설정대로 테스트하고 싶어도 이미 바뀐 dao를 그대로 받게 될 수 있습니다.
그래서 이런 상황을 막으려고 @DirtiesContext를 붙입니다.
의미: “이 테스트가 컨텍스트를 더럽혔으니, 다음엔 새 컨텍스트 만들어라”
결과: 안정성은 올라가지만, 컨텍스트를 다시 만들게 되어 속도는 크게 떨어질 수 있음
정리
장점: 설정 파일 손대지 않고 테스트 코드에서 즉석 변경 가능
단점: 컨텍스트 공유와 충돌 → 보통
@DirtiesContext필요 → 느려짐언제 쓰나: “딱 이 테스트만 특별한 DI가 필요” 같은 예외 케이스
2) 테스트 전용 설정 파일로 스프링 컨텍스트를 구성하는 방식 (권장)
동작 원리
운영용:
applicationContext.xml테스트용:
test-applicationContext.xml
테스트는 이렇게 실행합니다.
@ContextConfiguration(locations = ["test-applicationContext.xml"])
즉, 처음부터 테스트 환경에 맞는 컨텍스트를 별도로 만들고, 그 안에서 테스트를 수행합니다.
왜 이렇게 하게 되는가
테스트에서 바꿔야 하는 설정이 DataSource처럼 “환경 차이”라면,
런타임에 억지로 바꾸는 것보다 설정 자체를 분리하는 게 안전합니다.
장점(효과)
스프링이 만든 컨텍스트는 테스트 동안 변하지 않습니다.
같은 설정 파일을 쓰는 테스트들은 컨텍스트를 캐싱해서 공유합니다. (빠름)
테스트 코드에서
dao.setDataSource(...)같은 위험한 조작이 없어집니다.@DirtiesContext없이도 일관된 테스트 결과를 유지할 수 있습니다.
단점
설정 파일을 하나 더 관리해야 합니다.
운영 설정과 테스트 설정이 너무 많이 벌어지면 유지보수가 어려울 수 있습니다. 그래서 보통은 운영 설정을 복사한 뒤 차이가 필요한 부분만 최소 변경합니다(주로 DataSource).
정리
장점: 가장 안정적, 컨텍스트 캐싱 이점 유지, 테스트 코드 단순
단점: 설정 파일 관리 비용
언제 쓰나: “스프링 설정까지 포함해서 통합적으로 테스트”하고 싶을 때, 특히 DB 테스트
3) 스프링 컨테이너 없이 순수 객체로 테스트하는 방식
동작 원리
스프링을 전혀 띄우지 않습니다.
테스트가 직접 객체를 만들고, setter/생성자로 주입합니다.
예:
val dao = UserDao()dao.setDataSource(testDataSource)
이 방식의 본질은 IoC 컨테이너를 쓰지 않고도 DI는 할 수 있다는 점입니다. DI는 “기술”이 아니라 “설계”에 가깝기 때문에 가능합니다.
왜 이렇게 하게 되는가
테스트를 가장 빠르게 돌리고 싶을 때
스프링 설정이 맞는지보다 “DAO 로직 자체”가 맞는지가 더 중요할 때
작은 단위(Unit) 테스트를 하고 싶을 때
장점(효과)
컨텍스트 생성 비용이 0 → 매우 빠름
테스트가 단순해지고, 실패 원인도 좁혀짐
스프링 프레임워크에 대한 의존이 줄어듦
단점(트레이드오프)
스프링 설정 파일이 올바른지(빈 등록/주입)가 검증되지 않습니다.
실제 운영에서 스프링이 주입하는 방식과 달라 문제가 숨어 있을 수 있습니다.
정리
장점: 가장 빠름, 가장 단순, 단위 테스트에 적합
단점: 스프링 설정/통합 관점 검증 불가
언제 쓰나: “순수 로직 검증”이 목적일 때, 또는 스프링과 분리된 설계를 지향할 때
세 방식을 한 문장으로 구분하기
1번: 스프링을 띄운 뒤, 테스트 코드가 런타임에 빈 상태를 바꾼다
2번: 스프링을 테스트 전용 설정으로 띄워서, 처음부터 테스트 환경을 만든다
3번: 스프링을 아예 띄우지 않고, 테스트가 객체를 직접 만들고 주입한다
Last updated