🎁[속보] 인프런 내 깜짝 선물 출현 중🎁

블로그

김태영

[인프런 워밍업 클럽 3기 BE 클린코드 & 테스트 스터디 ] 발자국 4주차

[4주차] 섹션7Mockito로 Stubbing하기외부 서비스를 테스트하려면 실제로 수행하거나, Mocking하여 가짜 객체를 만들어서 사용Mocking하여 Mock 객체의 응답을 정해줌으로써, 외부 서비스와의 독립성을 유지외부 서비스가 있어도 간단하게 서버 로직에 대한 검증 가능Test DoubleDummy : 아무것도 하지 않는 깡통 객체Fake : 단순한 형태로 동일한 기능은 수행하나, 프로덕션에서 쓰기에는 부족한 객체Stub : 테스트에서 요청한 것에 대해 미리 준비한 결과를 제공하는 객체 그 외에는 응답하지 않는다.Spy : Stub이면서 호출된 내용을 기록하여 보여줄 수 있는 객체, 일부는 실제 객체처럼 동작시키고 일부만 Stubbing할 수 있다.Mock : 행위에 대한 기대를 명세하고, 그에 따라 동작하도록 만들어진 객체Stub -> 상태 검증, Mock -> 행위 검증@Mock, @Spy, @InjectMocksMock은 정해진 행위를 하는 객체를 만드는 것Spy는 실제 객체의 메서드를 수행하지만, 특정 메서드만 mocking한 것InjectMocks는 Mock과 Spy 객체를 자동 주입해주는 것BDDMockito행동주도개발TDD -> when().thenReturn(), BDD -> given().willReturn()Classicist VS MockistClassicist : Mock을 통한 검증은 완전함을 보장할 수 없다.Mockist : Mock을 통해서 각각 테스트를 성공했으면 된다. 섹션 8한 문단에 한 주제완벽하게 제어하기현재 날짜, 현재 시간 등 제어할 수 없는 값은 파라미터로 넘겨서 상위 계층에서 관리테스트 환경의 독립성을 보장하자테스트가 실패하는 부분은 when절 또는 then절이어야 한다. given 절에서 실패하면 안된다.given 절에서 객체를 생성할 때는 순수한 생성자 또는 builder 기반으로 만들면 좋다.테스트 간 독립성을 보장하자테스트 간 공유 자원을 사용하면 안된다.공유 자원을 사용하면 테스트 간 순서에 따라 성공, 실패가 갈린다.한 눈에 들어오는 Test Fixture 구성하기@BeforeEach를 사용하여 설정할 경우, 각 테스트 입장에서 봤을 때, 아예 몰라도 테스트 내용을 이해하는데 지장이 없어야 한다.뇌의 메모리를 적게 사용Test Fixture 클렌징deleteAll, deleteAllInBatch@ParameterizedTestenum 클래스의 각 값에 대하여 테스트를 하고 싶을 때, ParameterizedTest를 수행하면 좋다.ValueSource() 파라미터가 1개일 때NullSOurce, EmptySource, NullEmptySourceCsvSource() csv 형식으로 여러개의 파라미터 사용여러 개의 파라미터 값을 테스트하고 싶을 때 사용@DynamicTest한 문단에 한 주제, 하지만 연속된 상황을 검증하고 싶다면?연속된 상황을 부여하여, 일련의 시나리오를 테스트할 수 있다.테스트 수행도 비용이다. 환경 통합하기테스트 환경이 달라지면, 서버가 각 테스트 클래스마다 새로 부팅한다.통합 테스트 클래스를 만들어서, 통합 테스트 클래스가 실행될 때 1번 서버가 실행되도록 한다.MockBean, SpyBean 등 환경이 달라지면 통합 테스트 클래스를 상속받아도 서버가 새로 부팅된다.MockBean, SpyBean을 통합 테스트 클래스에 옮겨놓으면 1번으로 유지 가능DataJpaTest를 repository 테스트를 할 때 쓴다면 이것도 환경이 달라졌으므로, 서버를 새로 부팅WebMvcTest, SpringBootTest, DataJpaTest 각각 통합 테스트 클래스를 만들어주면 3번만 부팅private 메서드의 테스트는 어떻게 하나요?private 메서드는 테스트할 필요 X외부로 노출되지 않는 기능까지는 알 필요가 없다.단위 테스트 검증 시에 어차피 검사하게 된다.그래도 하고 싶다면, 객체 분리를 통해서 public으로 만든 후 검증테스트에서만 필요한 메서드가 생겼는데 프로덕션 코드에서는 필요 없다면?만들어도 되지만, 지양하자 [회고]벌써 마지막 주차가 되어버렸다. 1달 동안 클린코드와 테스트코드에 대해 전혀 무지했던 내가 개념을 익히고 실습을 하며어떻게 하면 더 깔끔하게, 더 읽기 편하게 적을 수 있을까를 고민하게 되었고, 어떻게 검증해야 할까, 무엇을 검증해야 할까를 고민할 수 있게 되었다.1달이라면 길다면 길고, 짧다면 짧은 시간 동안 한층 더 나아갔고, 더 우수한 사람이 되기 위해 노력했다. 한달 전의 나보다 성장했기에 뿌듯하고, 앞으로 성장할 나를 위해 다시금 노력하자워밍업 클럽을 통하여 궁금했던 질문도 하였고, 앞으로 나아갈 길과 동기를 얻을 수 있었다. 다음 기수에도 신청하고 싶다  [출처]인프런 워밍업 클럽 : https://inf.run/Y4cf2강의 : https://www.inflearn.com/course/readable-code-%EC%9D%BD%EA%B8%B0%EC%A2%8B%EC%9D%80%EC%BD%94%EB%93%9C-%EC%9E%91%EC%84%B1%EC%82%AC%EA%B3%A0%EB%B2%95/dashboard

test

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도서

채널톡 아이콘