블로그

Java Spring 기반의 Test와 TDD

Test테스트는 항상 통과해야하며, 예외를 포함한 최대한 많은 케이스를 다룰수록 좋다.테스트 종류Unit Test단위적인 기능을 개별적으로 테스트하는 것Mock행위 검증; 메소드의 리턴 값으로 판단할 수 없는 경우, 특정 동작을 수행하는지 확인Stub상태 검증; 객체의 상태를 확인하여 올바르게 동작했는지를 확인Fake객체의 행위를 모킹하는 것이 아닌 객체자체를 모킹하는 방식인메모리DB, Map 더미 데이터 등을 사용Integration Test상호의존적 환경 내에서 전반적인 흐름을 테스트 TDD 실습1단계 : 테스트 코드 작성 후 테스트 실행(실패)JPA Repository 또는 Fake Repository 생성Test@ExtendWith(SpringExtension.class) class BoardServiceTest { @Mock BoardRepository boardRepository; @Test void boardSave(){ // given long boardId = 1l; BoardRequestDto boardRequestDto = BoardRequestDto.builder().build(); // when BoardService boardService = new BoardService(); BoardResponseDto boardResponseDto = boardService.save(boardRequestDto); // then assertThat(boardId).isEqualTo(boardResponseDto.getBoardId()); } class BoardService { BoardResponseDto save(BoardRequestDto boardRequestDto){ return null; } } }BoardRequestDto 생성@Getter @Builder @NoArgsConstructor public class BoardRequestDto { private long writerId; private long categoryId; private String title; private String content; }2단계 : 비즈니스 로직 필요만큼 추가 후 테스트 실행(통과)Test@ExtendWith(SpringExtension.class) class BoardServiceTest { @Mock BoardRepository boardRepository; @Test void boardSave(){ // given long boardId = 1l; BoardRequestDto boardRequestDto = BoardRequestDto.builder().build(); // when BoardService boardService = new BoardService(); BoardResponseDto boardResponseDto = boardService.save(boardRequestDto); // then assertThat(boardId).isEqualTo(boardResponseDto.getBoardId()); } class BoardService { BoardResponseDto save(BoardRequestDto boardRequestDto){ BoardEntity boardEntity = BoardEntity.builder() .writerId(boardRequestDto.getWriterId()) .build(); BoardEntity savedBoard = boardRepository.save(boardEntity); return new BoardResponseDto(savedBoard); } } }  3단계 : 반복categoryId 검증과 같은 필수 로직을 하나씩 추가하며 테스트를 반복한다.4단계 : 확장도메인 메서드로 분리하거나 중복 로직을 메서드화하며 리팩터링한다.

TDDtest

<도서정리> 테스트 주도 개발 시작하기 - 최범균

TDD 단계: 레드 - 그린 - 리팩토링 테스트 선 작성 후 테스트를 통과시킬 만큼의 코드를 작성한다.(기능을 구현한다)마지막으로 리팩토링을 통해 코드를 다듬는다.테스트를 통해 개발의 범위가 정해진다. 테스트가 진행될수록 검증하는 범위가 넓어지면서 기능 구현이 점점 완성되어간다. TDD는 개발 과정에서 코드를 지속적으로 정리하므로 코드품질을 높이고 유지보수 비용을 낮춘다.코드 수정 후 테스트가 진행되므로 향후 올바르지 않은 코드가 배포되는 것을 방지할 수 있다.   테스트 코드 작성 순서 쉬운 경우(넓은 범위)에서 어려운 경우로 진행 예외적인 경우에서 정상인 경우로 진행 한 번에 많은 코드를 만들면, 그 사이 발생한 버그를 잡기 위해 많은 비용이 들어간다. 정해진 값 리턴 값 비교를 통해 정해진 값 리턴 다양한 경우 추가하면서 구현을 일반화 테스트를 먼저 작성하고 return 상수로 테스트 통과시킨 후에 비즈니스 로직 하나씩 추가하면서 테스트 매번 실행   대역 MockitomemoryRepository implements 리포지토리 인터페이스 - Map 사용하기@TempDir   기능명세 테스트할 기능을 생성하면서 클래스, 메서드, 파라미터 구성결과 검증하면서 리턴값 구성-> 테스트하면서 설계까지 진행됨클래스, 메서드, 변수명은 주석보다 코드 이해에 효과적이게 구성하기필요한만큼만 설계하기예외적인 상황, 복잡한 상황은 최대한 많이 기획자와 상의하고 대비하는 것이 효율적이고 완성도 높은 개발을 이끌어냄   리팩토링 기능 개발을 다 끝낸 후에 리팩토링하기또는 커밋 후에 리팩토링하기리팩토링은 클래스 분리, 메서드 추출, or문, count 로직 등 이용...파라미터 개수가 3개 이상이면 객체화하기 꿀팁 Assert 메서드를 테스트 클래스에 별도 생성해서 응용도 가능하다.로직을 중복 호출하지 않고, Assert 메서드에 로직을 넣어버려서 검증만 진행하도록 할 수도 있다. 중간에 예외처리를 하면, 조건문 중복 등 코드가 복잡해지기 때문에초반에 예외처리를 하는 것이 시스템 운영 중에 NPE를 만나지 않는 방법이다. before setUp으로 데이터를 미리 생성해놓는 것은 중복이 제거되는 듯이 보이지만,각 테스트마다 필요한 데이터가 항상 일정하지 않을 수 있음으로 각 테스트에서 데이터 생성하는 것을 추천한다. 검증 시 굳이 변수나 필드명으로 검증하는 것보다 기댓값을 실제값으로 넣는 것도 좋다.테스트에서는 직관적으로 확인할 수 있는 것이 좋기 때문이다. given으로 파라미터를 넘길 때는 값보다 타입으로 넘겨주자.(범용성) update와 같은 내부구현 검증이 필수적일 때는 내부구현 검증이 필요하다.하지만 웬만하면 실행결과 검증 중심으로 진행할 것.테스트 실패의 경우를 줄이기 위해서 검증해야할 부분을 명확히 하기 위해서이다.

TDDtest최범균junit도서