-
[spring] tdd와 단위 테스트spring 2023. 5. 22. 23:26
tdd 사이클 1. tdd 사이클
- 실패하는 케이스의 테스트 구현
- 테스트가 성공하도록 프로덕션 코드를 구현
- 테스트 코드와 프로덕션 코드를 리팩토링
※ 프로덕션 코드 : 프로그램 구현을 담당하는 부분으로 사용자가 실제로 사용하는 소스 코드
※ 리팩토링 : 새로운 기능을 만들지 않고 코드를 개선하는 체계적인 프로세스
※ 리팩토링 주요 기법
Extract
Method– 그룹으로 묶을 수 있는 코드의 메서드 추출 – 결합도 감소 Extract
Class– 두 개의 클래스 업무를 신규 클래스로 이동 – 응집도증가
– 결합도 감소Move
Method– 정의된 클래스보다 타 클래스 다수 호출 시 이동 – 응집도 증가
– 결합도 감소Rename
Method– 메서드 이름이 목적을 드러내도록 이름 변경 – 가독성 향상
– 유지보수 향상Replace Temp with Query – 임시 변수 참조 시 메서드 호출로 교체 – 응집도 증가
– 결합도 감소Full up
Field– 서브 클래스 동일 필드 보유 시 슈퍼 클래스로 이동 – 중복 제거 Full up
Method– 동일 기능 메서드를 슈퍼 클래스로 이동 – 중복 제거 - 결합도 : 모듈간의 상호 의존도. 모듈의 상대적인 독립성 정도. 모듈 간 관계 (약할수록 좋음)
- 응집도 : 한 모듈 내에 있는 구성요소의 기능적 관련성. 모듈의 상대적인 함수적 강도. 모듈 내 관계 (강할수록 좋음)
※ 리팩토링과 리엔지니어링 차이
항목 리팩토링 리엔지니어링 관점 모듈의 정제 시스템 개선 적용 범위 클래스, 메소드 단위 시스템 등 대단위 반복성 대다수 반복 반복 미 발생 적용 사례 메소드 다형성 적용 시스템 전반적 모듈화 code smell : 심각한 문제를 일으킬 수 있는 코드의 특징 ▶ switch 문의 경우 다른 케이스를 추가하고자 하면 수정이 힘들고 버그 발생 위험이 존재하므로 자제
1) 게으른 클래스(Lazy Class): 자식 클래스와 부모 클래스 차이 없으면 합침
2) 추측성 일반화(Speculative Generality): 추측만으로 생성한 클래스는 제거
3) 임시필드(Temporary Field): 파라미터를 줄이기 위한 필드, 메서드는 클래스화
4) 메시지 체인(Message Chain): 특정 객체를 얻기위한 다수 객체는 간소화
5) 미들 맨(Middle Man): 다른 클래스로 위임하는 역할만 담당하는 클래스 검토
6) 부적절한 친밀(Inappropriate Intimacy): 불필요 데이터를 가지고 있는 경우
7) 불완전 라이브러리(Incomplete Library): 불완전 시 필요 부분 추가 구성
8) 거부된 유산(Refused Bequest): 부모클래스의 I/F 거부 시 부모 객체 호출
9) 주석(Comment): 코드 내부 주석은 메서드로 추출해서 설명2. tdd 정의
- TDD에서는 제품의 기능 구현을 위한 코드와 별개로, 해당 기능이 정상적으로 움직이는지 검증하기 위한 테스트 코드를 작성
- 테스트가 실패할 경우, 테스트를 통과하기 위한 최소한으로 코드를 개선
- 최종적으로 테스트에 성공한 코드를 리팩토링
- 즉, 테스트 코드를 우선 작성한 후 프로덕션 코드를 작성
3. tdd의 효과
일반적으로 "인수 테스트" 수행
인수테스트란 이미 배치된 시스템을 대상으로 클라이언트가 의뢰한 소프트웨어가 사용자 관점에서 사용할 수 있는 수준인지 체크하는 과정이다.
이미 완성되어있는 코드나 마찬가지이므로 문제가 발생해도 원인을 파악하기 어려움- tdd를 통해 문제의 원인을 빠르게 찾을 수 있음
- 리팩토링이 편함
- 디버깅 시간 단축
- 테스트 커버리지가 높아짐
- 오버엔지니어링 방지
- 설계에 대한 피드백이 빠름
※ 테스트 커버리지 : "테스트 대상의 전체 범위에서 테스트를 수행한 범위"를 의미합니다. 즉, 테스트 대상을 얼마만큼 테스트했나를 정의, 테스트의 정확성을 판단하는 하나의 척도가 될 수 있음
※ 오버엔지니어링 : 계획하지 않았던 코드를 추가
4. tdd를 실패하는 이유
코드가 이루고자 하는 가치나 기능을 테스트하기보다 구현을 테스트
▶ 테스트들이 구현체 안에 있는 implementation 내부에 존재하는 구현체들을 테스트
▶ 여러 모양의 도형을 네모 도형으로 리팩토링
▶ 이와 같은 상태에서 리팩토링하게 되면 테스트 코드도 변경해야 함
▶ 테스트 케이스들이 구현체와 결합도가 높아짐
▶ 내부가 변경되더라도 test는 변경되선 안됨
5. tdd의 단점
- 생산성 저하
6. 테스트 범위와 종류
- 인수테스트 : 고객이 명세한 요구사항을 충족했는지 검증하는 테스트
- 부하 테스트 : 퍼포먼스 테스트, 시스템 특정 지점의 반응 시간 지연, 실패하는지 테스트
- 기능 테스트 : 공개된 API의 가장 바깥쪽에서 코드 검증
- 통합 테스트 : 여러 작업 단위가 연계된 워크 플로우 테스트(객체 간, 서비스 간, 시스템 간)
- 단위 테스트 : 하나의 기능 또는 메소드를 기준, 독립적으로 수행하는 가장 작은 단위의 테스트
테스트 속도는 통합 테스트보다 단위 테스트가 빠르기 때문에 가능하면 단위 테스트에서 다양한 상황을 다루고,
통합 테스트나 기능 테스트는 주요 상황에 초점을 맞춰야 함
그래야 테스트 실행 시간이 증가해 피드백이 느려지는 것을 방지7. 단위 테스트 사용 이유
- 코드의 일부분을 빠르게 검증
- 코드 리팩토링이 안정적
- 코드의 문제점을 빠르게 찾을 수 있음
- 품질 향상
- 코드의 문서화
8. 사용 예
@Test public void 회원가입() throws Exception { //Given Member member = new Member(); member.setName("hello"); //When Long saveId = memberService.join(member); //Then Member findMember = memberRepository.findById(saveId).get(); assertEquals(member.getName(), findMember.getName());
given : 테스트를 위해 준비하는 과정, 테스트에 사용하는 값을 정의
when : 테스트 하고자하는 기능을 실행
then : 기능을 실행한 후 결과를 검증9. @parameterizedTest
- JUnit에는 이렇게 여러 개의 테스트를 한번에 작성하기 위한 @ParameterizedTest 라는 어노테이션을 제공
- 기본적인 사용 방법은 @Test 대신 @ParameterizedTest라는 어노테이션을 사용하는 것 외에는 동일
- 기존 test code
private Set<Integer> numbers; @BeforeEach void setUp() { numbers = new HashSet<>(); numbers.add(1); numbers.add(1); numbers.add(2); numbers.add(3); } @Test void set_contains_test() { assertThat(numbers.contains(1)).isTrue(); assertThat(numbers.contains(2)).isTrue(); assertThat(numbers.contains(3)).isTrue(); ... }
▶ 여러 개의 값을 테스트하려면 코드를 여러번 작성해야 함
10. @ValueSource
- 리터럴 값의 배열에 대한 액세스를 제공
- 지원되는 유형에는 shorts(), bytes(), ints(), longs(), floats(), doubles(), chars(), booleans(), strings()및 가 포함
- 하나의 유형일때만 사용
@ParameterizedTest @ValueSource(ints = {1, 2, 3}) void set_contains_test(int element) { assertThat(numbers.contains(element)).isTrue(); }
11. @CsvSource
입력값에 따라 결과값이 다른 경우
콤마(default)를 기준으로 CSV를 구분해서 읽음
구분자는 커스터마이징이 가능
@DisplayName("true, false 값을 같이 리턴한다") @ParameterizedTest @CsvSource(value = {"1:true", "2:true", "3:true", "4:false", "5:false"}, delimiter = ':') void set_contains_true_false(int element, boolean expected) { assertThat(numbers.contains(element)).isEqualTo(expected); }
12. 단위테스트의 5가지 원칙
first원칙
fast 단위테스트는 빨라야한다.
independent 테스트는 독립적으로 동작해야 한다. 이전 테스트에 영향을 받으면 실패 원인을 찾기 어렵다.
repeatable 어떤 상황에서든 예상한 대로 테스트 결과가 나와야한다.
self-validating 출력 혹은 로그가 아닌 테스트 자체적으로 결과가 나와야 한다. 성공 or 실패를 무조건 반환해야함,개발자가 주관적으로 판단할 수 있음
timely 적시에 테스트를 철저하게 작성해야 한다. 실제 코드를 구현하기 전에 작성해야함'spring' 카테고리의 다른 글
[spring] spring과 mysql 연결 (0) 2023.05.08 [spring] 프록시 (Proxy) 패턴 (0) 2023.05.02 [spring] MyBatis와 Hibernate의 차이 (0) 2023.03.28 [spring] IntelliJ Cannot resolve symbol 'persistence' (1) 2023.03.27 [spring] GDSC spring study 2주차 오답노트 (0) 2023.03.21