-
테스트 코드란?
작성한 코드가 의도된 대로 작동하는지 검증하는 코드
테스트의 종류
단위 테스트 (Unit Test)
- 하나의 모듈을 기준으로 독립적으로 진행되는 가장 작은 단위의 테스트 (하나의 기능 또는 메소드)
- 어떤 기능일 실행되면 어떤 결과가 나올지 정도로 테스트한다.
통합 테스트 (Integration Test)
- 모듈을 통합하는 과정에서 모듈간의 호환성을 확인하기 위해 수행되는 테스트
테스트 코드의 장점
- 개발 과정 중 예상치 못한 문제를 미리 발견할 수 있다.
- 클라이언트를 통해 하나하나 기능을 작동하지 않고 테스트할 수 있다.
- 코드 수정이 필요한 상황에서 유연하고 안정적인 대응을 할 수 있게 해준다.
- 테스트 코드는 변경에 대한 사이드 이펙트를 줄이는 예방책이 된다.
- 코드 변경시, 변경 부분으로 인한 영향도를 쉽게 파악 할 수 있다.
- 문서로서의 역할이 가능하다.
- 테스트 코드는 개발자가 작성한 메소드가 어떻게 동작하고 어떤 결과 반환을 원하는지 알려준다.
테스트 코드의 단점
- Unit Test가 다른 객체들과 메세지를 주고 받아야할 경우 독립전인 테스트이기 때문에 다른 객체 역할을 하는 가짜 객체를 만들어서 정해진 결과르 반환받게 만들어야 한다.
좋은 테스트 코드란?
- 가독성이 좋은 코드
- 1개의 테스트 함수에 대해 assert 최소화
- 1개의 테스트 함수는 1가지 개념만을 테스트
- FIRST라는 5가지 규칙1
- Fast : 테스트는 빨그ㅔ 동작하여 자주 돌리 수 있어야한다.
- Independetn : 각각의 테스트는 독립적이며 서로 의존해서는 안된다.
- Repeatable : 어느 환경에서도 반복 가능해야한다.
- Self-Validating : 테스트는 성공 또는 실패로 bool 값으로 결과를 내어 자체적으로 검증되어야한다.
- Timely : 테스트는 적시에 즉, 테스트하려는 실제 코드를 구현하기 직전에 구현해야한다.
단위 테스트 작성 예시
라이브러리
단위 테스트 작성에는 크게 2가지 라이브러리가 사용된다.
- Junit5 : Java 단위 테스트를 위한 테스팅 프레임 워크
- AssertJ : Java 테스트르 돕기 위해 다양한 문법을 지원하는 라이브러리
given/when/then 패턴
단위 테스트는 주로 given-when-then 패턴으로 작성된다.
- given(준비) : 어떠한 데이터가 주어졌는지
- when(실행) : 어떠한 함수를 실행하는지
- then(검증) : 어떠한 결과가 나와야 하는지
※ 윈도우 기준 패키지에서 ctrl + shift + t를누르면 바로 test폴더에 test 파일을 생성 할 수 있다.
단위 테스트 예제
class MemberServiceTest { MemberService memberService; //DB가 아닌 로컬 메모리에 저장 MemoryMemberRepository memberRepository = new MemoryMemberRepository(); // 각테스트 실행전마다 실행 @BeforeEach public void beforeEach() { memberRepository = new MemoryMemberRepository(); memberService = new MemberService(memberRepository); } //각 테스트 실행 후마다 실행 @AfterEach public void afterEach() { memberRepository.clearStore(); } //테스트 코드 명시 @Test void join() { //given Member member = new Member(); member.setName("spring"); //when Long saveId = memberService.join(member); //then Member findMember = memberService.findOne(saveId).get(); //서로 비교해서 일치하면 통과 Assertions.assertThat(findMember.getId()).isEqualTo(member.getId()); } @Test //오류가 발생해야하는 케이스 public void duplicateMemberTest() { //given Member member1 = new Member(); member1.setName("spring"); //when Member member2 = new Member(); member2.setName("spring"); memberService.join(member1); //then //기대했던 오류메세지가 나오면 테스트 통과 IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2)); Assertions.assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다."); } }
통합 테스트 예제
@SpringBootTest @Transactional class MemberServiceIntegrationTest { @Autowired MemberService memberService; @Autowired MemberRepository memberRepository; @Test void join() { //given Member member = new Member(); member.setName("test"); //when Long saveId = memberService.join(member); //then Member findMember = memberService.findOne(saveId).get(); Assertions.assertThat(findMember.getName()).isEqualTo(member.getName()); } @Test public void duplicateMemberTest() { //given Member member1 = new Member(); member1.setName("spring3"); //when Member member2 = new Member(); member2.setName("spring3"); memberService.join(member1); //then IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2)); Assertions.assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다."); } }
단위테스트와 구성은 비슷하지만 실제 DB와의 상호작용도 고려하여 TEST
- @SpringBootTest 를 이용하면 모든 빈이 등록되고, 다른 컴포넌트 들과 연결되어 통합테스트가 된다.
- 불필요한 경우에는 사용하지 않는 경우가 좋다 -> 스프링 설정을 가져오기 때문에 순수 자바코드 테스트보다 느려짐
- @Transactional을 이용하면 Test가 transaction 단위로 실행되고 실제 DB에 반영되지않고 rollback 되어 여러번 테스트하여도 동일한 결과를 얻을 수 있게 해준다.
참고 문헌
- 인프런 - 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술
- https://velog.io/@ecvheo1/Test-Test-Code%EB%8A%94-%EC%99%9C-%EC%9E%91%EC%84%B1%ED%95%B4%EC%95%BC-%ED%95%98%EB%8A%94%EA%B0%80
- https://mangkyu.tistory.com/143
- https://mangkyu.tistory.com/145