책 읽고 기억하고 싶은 구절 메모 📖
- 테스트를 작성하는 과정에서 구현을 생각하지않는다.
- 테스트를 추가하고 그때까지 추가한 테스트를 통과할 만큼의 코드만 구현한다.
- 테스트코드가 추가되면서 검증하는 범위가 넓어지고 구현도 점점 완성된다.
- 필요할 것으로 예측해서 미리 코드를 만들지 않는다.
즉, 이렇게 테스트가 개발을 주도해나간다.
- ex) 모든 규칙을 충족하는 경우 조건을 충족하지 않는 경우
- 쉬운 경우 -> 어려운 경우로 진행
- 예외적인 경우 -> 정상인 경우로 진행
- 이유는 해당 테스트를 통과시키기 위해서 한 번에 구현해야 할 코드가 많아진다.
- 다양한 예외 상황은 복잡한 if-else 블록을 동반할 떄가 많다.
- 예외 상황을 전혀 고려하지 않은 코드에 예외 상황을 반영하려면 코드의 구조를 뒤집거나 코드 중간에 예외 상황을 처리하기 위해 조건문을 중복해서 추가하는 일이 벌어진다.
- 버그발생가능성 up, 코드복잡성 up
- 한번에 얼마만큼의 코드를 작성할 것인가? (교재 p.66 참조)
- 정해진 값을 리턴
- 값 비교를 이용해서 정해진 값을 리턴
- 다양한 테스트 추가하면서 구현을 일반화
하지만 단순한 로직이거나 TDD가 익숙해지면 1번 제외하고 바로 구현해도됨 그러나 한 번에 구현을 시도했는데 안 되면 한발물러서서 1번부터하자!
- 뒤에 어떻게 테스트가 가능하게 바꾸는지 알아봄 (p180 참조~)
-
(before) 하드코딩된 ip주소, 포트번호 포함
-
(after) 하드코딩된 상수를 생성자나 메서드 파라미터로 받기
- 세터를 이용해서 교체 가능하게 함으로써 테스트가 쉬워짐
- (after) 의존대상 주입받기
- (after) 외부 라이브러리는 직접 사용하지 말고 감싸서 사용하기!
- 대역으로 대체하기 어려운 외부 라이브러리가 있다면 외부 라이브러리를 직접 사용하지 말고 외부 라이브러리와 연동하기 위한 타입을 따로 만든다. 테스트 대상 트 대상 코드는 새로 분리한 타입을 사용함으로써 외부 연동이 필요한 기능을 쉽게 대역으로 대체할수있다.
- 예시
- ex1) LocalDate.now()와 같이 실행결과가 달라지는 경우가 있다.
- 어제까지는 문제 없이 성공하던 테스트가 오늘은 깨질 수 가 있다.
- ex2) Random을 이용해서 임의값을 사용하는 코드도 마찬가지 실행시점에 따라 달라진다.
- ex1) LocalDate.now()와 같이 실행결과가 달라지는 경우가 있다.
- (after) 테스트하고싶은코드를 분리하기
- p188
- 기능 테스트는 사용자입장에서 시스템이 제공하는 기능이 올바르게 동작하는지 확인한다.
- 이 테스트를 수행하려면 시스템을 구동하고 사용하는데 필요한 모든 구성요소 필요
- ex, 회원가입 기능이 올바르게 동작하는지 확인하려면 웹서버,db,브라우저 필요
- 모든구성요소를 논리적으로 완전한 하나의 기능으로 다룸
- QA 조직이 주로 기능 테스트함
- 예를들어, 회원가입 폼을 통해서 가입했을때 해당 정보로 로그인이 잘 되는지, 회원 정보를 조회할때 가입할때 입력한 데이터가 올바르게 표시되는지 등을 확인한다.
즉, 사용자와 동일한 방식으로 기능을 검증해야한다.
- 통합테스트는 시스템의 각 구성 요소가 올바르게 연동되는지 확인한다.
- 기능테스트는 사용자입장에서 테스트
- 통합 테스트는 소프트웨어의 코드를 직접테스트
- ex)모바일앱
- 기능테스트 - 앱을 통해 가입 기능 테스트
- 통합 테스트 - 서버 회원가입코드를 직접 테스트
- 단위테스트는 개별 코드나 컴포넌트가 기대한대로 동작하는지 확인한다
- 한 클래스나 한 메서드와 같은 작은 범위를 테스트한다.
- 일부 의존 대상은 스텁이나 모의 객체등을 이용해서 대역으로 대체한다.
- 차이정리 p199 참조
- 통합테스트는 db, 캐시 서버와 같이 연동대상 구성
- 기능테스트는 모바일 앱을 폰에 설치할수도
- 테스트 상황을 만들기위해 많은 노력필요
- 단위 테스트는 테스트 코드를 빼면 따로 준비할것없다.
- 통합테스트는 테스트 실행속도를 느리게하는 요인많다.(db연결, 소켓통신, 스프링 컨테이너 초기화)
- 기능테스트는 추가로 브라우저나 앱을 구동하고 화면의 흐름에 따라 알맞은 상호작용해야함
- 단위테스트는 서버 구동하거나 db 준비할필요 x -> 의존하는 기능을 대역으로 처리하면됨
- 실행속도 빠름
- 통합테스트나 기능테스트로는 상황을 준비하거나 결과확인이 어렵거나 불가능할때가있다.
- (특히, 외부 시스템연동)
- 이런경우에는 단위테스트와 대역을 조합해서 상황을 만들고 결과확인해야함
통합 테스트하기 어려운 대상이 외부 서버다.
이럴때 WireMock 을 사용하면 서버 API를 스텁으로 대체할수있다.
(P.204)
내부구현을 검증하는 것은 나쁜건아니다.
예를들어,
//PasswordChecker#checkPasswordWeak() 메서드 호출여부 검사
BDDMockito.then(mockPasswordChecker)
.should()
.checkPasswordWeak(Mockito.anyString());
- 구현을 조금만 변경해도 테스트가 깨질 가능성이 커진다.
- 내부 구현은 언제든지 바뀔 수 있다. 따라서 테스트코드는 내부 구현보다 실행 결과를 검증 해야한다.
- but, 이미 존재하는 코드에 단위 테스트를 추가하면 어쩔 수 없이 내부 구현을 검증해야 할 때도있다.
- 기능이 정상적으로 동작하는지 확인할 수단이 구현 검증밖에 없다면 모의 객체를 사용해서
- 테스트코드를 작성해야하지만 일단 테스트 코드를 작성한뒤에는 점진적으로 코드를 리팩토링해서 구현이 아닌 결과를 검증할 수 있도록 시도해야한다.
- 그렇게 함으로써 향후에 사소한 구현 변경으로 인해 테스트가 꺠지는 것을 방지할 수 있고 또한 코드의 테스트 가능성도 높일 수 있다.
@BeforeEach
void setUp(){
changeService = new ChangeUserService(memoryRepository);
}
모든 테스트 메서드에 동일한 상황을 적용한다.
- setUp을 하면 유지보수 힘들어질 수도 있다.
- 나중에 코드를 볼때 메서드를 이해하기 위해서 위아래로 이동하면서 확인해야한다.
- 테스트메서드가 자체적으로 검증하는 내용을 완전히 기술하고 있어야 좋다.
- 또한, 다른 메서드 결과에 영향미칠 가능성도 있다.