(우테코 2주차에 대한 내용이 포함되어 있어 비공개로 올려두었다가 2주차 과제 마감일인 1일 이후에 공개로 전환하였습니다)
이전에 Mock을 사용하는 방법에 대해 간단하게 알아봤다.
좀 더 잘 사용하는 방법이 궁금해서 고민하다가 며칠 전 테코톡 깃허브사용법을 보고 도움이 많이 됐던 기억이 났다.
우테코 공통 피드백에서도 테코톡을 많이 활용해서 공부하면 좋을 것 같다고 하셔서 이번에 테코톡을 몇 개 보면서 정리해보려고 한다.
1. JUnit5 사용법 - 바다님
https://www.youtube.com/watch?v=EwI3E9Natcw
- 이 영상에서는 JUnit4와 JUnit5의 차이점을 중심으로 사용법을 알려주신다. JUnit에 대해 조금 지식이 있다면 더 유용하게 들을 수 있다
(1) JUnit이란?
- JUnit은 자바개발자의 93%가 사용하는 단위테스트 프레임워크다
(2) JUnit5의 구조
- JUnit4: 하나의 jar파일로 의존성을 불러와서 다른 라이브러리를 참조해서 사용하는 구조였다
- JUnit5는 그 자체로 모듈화되어있음
- Platform위에 Jupiter랑 vintage올릴 수 있는 구조(세부모듈)
- Platform: JUnit으로 작성한 테스트코드를 실행해주는 런처 제공
- Jupiter, Vintage: JUnit플랫폼이 제공하는 TestEngine api 구현체
- Jupiter: JUnit5 지원 구현체
- JUnit4, JUnit3 지원
- 런처실행/ 43작성 테스트는 빈티지로 테스트 진행됨~
(3) 제공하는 어노테이션
@Test
- 테스트 메서드임을 나타내는 메서드
- Jupiter에서는 속성 선언이 없다
생명주기 어노테이션
- @BeforeAll: 해당 클래스에 위치한 모든 테스트 메서드 실행 전 딱 한 번 실행되는 메서드
- @AfterAll: 해당 클래스에 위치한 모든 테스트 메서드 실행 후 딱 한 번 실행되는 메서드
- 테스트가 조건에 대해 어떤 변경도 하지 않는다 확신 -> BeforeAll, AfterAll
- @BeforeEach: 모든 테스트 메서드 실행 전에 실행
- @AfterEach: 모든 테스트 메서드 실행 후 실행
@Disabled
- 테스트 하고싶지 않으면 사용
- 문제가 있는 코드의 경우 문제해결까지 테스트를 중단해야함 -> 주석의 역할
@DisplayName
- 테스트의 이름을 지정
- 많이 사용함 - 공백, 이모지, 특수문자 모두 지원
@RepeatedTest
@RepeatedTest(value = 10, name = "{displayName} 중 {currentRepetition} of {totalRepetitions}"
@DisplayName("반복 테스트")
void repeatedTest2() {
...
}
- 특정 테스트를 반복시키고 싶을 때 사용하는 어노테이션
- 반복횟수, 반복 테스트 이름 설정가능함 -> 커스텀해서 반복 출력 가능!
@ParameterizedTest
@ParameterizedTest
@CsvSource(value = {"ACE,ACE:12", "ACE,ACE,ACE:13", "ACE,ACE,TEN:12"}, delimeter = ':')
@DisplayName("에이스 카드가 여러 개일 때 합 구하기")
void calculateCardSumWHenAceIsTwo(final String input, final int expected) {
final String[] inputs = input.split(",");
for (final String number : inputs) {
final CardNumber cardNumber = CardNumber.valueOf(number);
dealer.receiveOneCard(new Card(cardNumber, CardType.CLOVER));
}
assertThat(dealer.calculateScore()).isEqualTo(expected);
}
- 반복적으로 수행해야할 테스트의 데이터를 정의할 수 있는 어노테이션
- 테스트에 여러 다른 매개변수를 대입해가며 반복 실행할 때 사용
- 반복문보다 가독성이 향상되고, 테스트 성격과 목적을 잘 드러낼 수 ㅇ
@Nested
class InputValidatorTest {
private InputValidator inputValidator = new InputValidator();
@DisplayName("자동차 이름 검증")
@Nested
class ValidateCarNames {
@Test
@DisplayName("정상동작")
void good_case() {
//when
String goodCarNamesInput = "카,붕붕카,타요버스,시내버스,토마스버스,'";
//then
assertDoesNotThrow(() -> {
inputValidator.validateCarNames(goodCarNamesInput);
});
}
@ParameterizedTest
@CsvSource(value = {"'붕붕카,타요버스,'", "'붕붕카,'", "',붕붕카'", "'붕붕카,,타요버스'"})
@DisplayName("쉼표구분 예외 발생")
void 쉼표구분_예외_발생(String input) {
assertThatThrownBy(() -> inputValidator.validateCarNames(input))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("자동차 이름의 길이는 1이상 5이하여야 합니다");
}
}
}
- 테스트 클래스 안에서 이너클래스 정의, 테스트를 계층화할 때 사용
- 부모클래스 멤버 필드 접근가능
- Before/After와 같은 생명주기 관계 메서드들도 계층에 맞춰 동작한다
- 테스트는 이렇게 묶여서 보인다
(4) Assertions
- test case의 수행결과를 판밸하는 메서드이다
- 모든 JUnit Jupiter Assertions는 static메서드라고 한다
이런 식으로 static 임포트를 사용하면
깔끔하게 보인다
assertAll(executables ...)
- JUnit5에서는 assertion문 전체를 실행할 수 있는 기능이 추가되었다
- assertAll을 사용하여 매개변수로 받는 모든 테스트코드를 한 번에 실행
- 오류가 나도 끝까지 실행한 뒤 한 번에 모아서 출력한다
assertThrows(expectedType, executable)
- 예외 발생을 확인함
- executable의 로직을 실행하는 도중 expectedType의 에러를 발생시키는지 확인한다
- 예외의 내부상태 검증 기능 추가되어, 기대하는 예외 기술방식으로 테스트 수행할 수 있게 되었다고 한다
- 예외 발생하지 않는 경우도 확인 가능하다
assertTimeout(duration, executable)
- 특정 시간 안에 실행이 완료되는지 확인한다
- Duration: 원하는 시간
- Executable: 테스트할 로직
- (@Rule은 JUnit에서 테스트 클래스에서 동작 방식을 재정의 하거나 쉽게 추가하는 것을 가능하게 하는 어노테이션이라고 한다)
(5) Assumption
- 전제문이 true라면 실행하고, false라면 실행을 종료한다
- assumeTrue: false일 때 이후 테스트 전체가 실행 x
- assumingThat: 파라미터로 전달된 코드블럭만 실행되지 않고, 나머지는 실행됨
- 특정환경에서 테스트를 수행해야하는 경우에 유용하게 사용할 수 있겠다
2. 단위테스트 - 제이님
https://www.youtube.com/watch?v=mIO4Rbe_M74
- 단위테스트에 대해 전혀 모르는 사람들은 이 강의가 더 쉽게 다가올 것 같다
(1) 단위테스트란?
- 개별적인 코드 단위가 의도한대로 동작하는 지 확인하는 것
- 개별적인 기능이 의도대로 잘 작동하는지 확인한다
(2) 단위 테스트 사용이유?
- 결과보기까지 과정이 많아진다면 내 코드의 동작을 테스트하기 쉽지 않다
- 어쩔 수 없이 System.out.print를 사용했던 순간들..
- 결과를 빨리 보려면.. 단위의 기능을 테스트하면 된다!
- 장점
- 원하는 부분만 테스트 -> 결과 빠르게 보기 (코드 일부분 빠른 검증)
- 미리 작성한 기반으로 프로덕션 코드의 리팩토링을 안정적으로 할 수 있다
- 실패지점에서 문제점 빠르게 찾기 가능
(3) 단위테스트 예시
(예시가 자동차경주 기능 테스트관련이라 이번주차 우테코과제에 대한 조금의 스포가 있지만, 내 기능구현은 모두 끝난 상태였고, 단위테스트 예시가 궁금해서 들었다)
이런식으로 distanceResult가 orderedCount와 같은지 테스트한다
(4) 단위 테스트 꿀팁: given - when - then 패턴
- given-when-then 패턴은 테스트코드 작성의 하나의 표현방식이다
- given: 테스트를 준비하는 과정. 테스트레 사용하는 값을 정의
- when: 테스트하고자 하는 기능을 실행
- then: 기능을 실행한 후 결과를 검증
- 더 쉽게 읽을 수 있고 유지보수하기 좋다
- 필수는 아니고, 선택이다
예시코드(제가 작성한 코드입니다)
@ParameterizedTest
@CsvSource(value = {"0", "1", "5", "10", "100", "1000"})
@DisplayName("자동차 이동상태 출력")
void print_car_state(int path) {
//given (주어진 값)
List<CarStateDto> dtoList = new ArrayList<>();
dtoList.add(new CarStateDto("붕붕카", path));
//when (기능 작동)
output.printCarsState(dtoList);
//then (기능 작동 검증)
assertThat(getOutput()).contains("붕붕카 : " + "-".repeat(path));
}
(5) 단위 테스트 꿀팁: @ParameterizedTest
- @ParameterizedTest를 사용하여 여러 값을 테스트할 수 있다
- 값만 다르고 같은 기능을 테스트할 때 중복을 줄일 수 있다
예시코드(1주차 저의 코드입니다)
@DisplayName("3스트라이크 테스트")
@Test
void threeStrikeTest() {
//given
Comparator comparator = new Comparator();
//when
String computer = "345";
String user = "345";
//then
assertThat(comparator.getResult(computer, user).get("strike")).isEqualTo(3);
assertThat(comparator.getResult(computer, user).get("ball")).isEqualTo(0);
}
@DisplayName("2스트라이크 테스트")
@Test
void twoStrikeTest() {
//given
Comparator comparator = new Comparator();
//when
String computer = "345";
String user = "348";
//then
assertThat(comparator.getResult(computer, user).get("strike")).isEqualTo(2);
assertThat(comparator.getResult(computer, user).get("ball")).isEqualTo(0);
}
- 정말 동일한 내용인데 값만 달라졌습니다
- 저는 @ParameterizedTest를 몰라서 그냥 중복 코드를 9개를 만들었습니다
- 어우.. 왜그랬지
@ParameterizedTest
@CsvSource(value = {"'붕붕카,붕붕카를타는토마스'", "'붕붕카를타는토마스,타요버스'", "'붕붕카, "})
@DisplayName("길이 예외 발생")
void 길이_예외_발생(String input) {
assertThatThrownBy(() -> inputValidator.validateCarNames(input))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("자동차 이름의 길이는 1이상 5이하여야 합니다");
}
- @ParameterizedTest를 사용하여 여러 개의 입력에 대해 하나의 메서드로 테스트할 수 있습니다
(6) FIRST원칙
- '클린 코드' 책의 저자 Bob Martin이 제시함
- 효율적이고 좋은 단위 테스트를 하기 위한 5가지 요소로 이뤄진 규칙
F(Fast): 단위테스트는 빨라야한다
I(Independent): 테스트는 독립적으로 동작해야한다(이전 테스트 영향 -> 실패원인 찾기 힘듦)
R(Repeatable): 어떤 상황에서든 예상한 대로 테스트 결과가 나와야함
S(Self-Validating): 출력 혹은 로그가 아닌 테스트 자체적으로 결과가 나와야함
T(Timely): 적시에 테스트를 철저하게 작성
단위테스트를 수행하면 FIRST원칙의 F를 만족할 것이고, static메서드나 객체의 속성값이 변하지 않도록 조심하면 I를, assert문을 잘 사용하면 S, 기능을 잘 개발하였다면 R을 만족할 수 있다. 그리고 기능을 개발하면서 단위테스트 코드를 작성한다면 T를 만족할 것 같다.
느낀점
힝싱 단위테스트는 어렵다고만 생각했는데, 사용법 몇 개만 알면 단위테스트를 작성하는 건 어려운 일이 아니었다! 그리고 단위테스트를 하면서 내 코드의 문제점을 고쳐나가는게 상당히 재밌는 일이었다. 이제부터 단위테스트를 계속 작성하면서 기능을 개발하는 연습을 해야겠다
다음 글
'프로그래밍 언어 > Java' 카테고리의 다른 글
[우테코] 2주차 Test코드 분석 - NsTest, assertj, JUnit (0) | 2023.10.30 |
---|---|
[JAVA] 문자열 양쪽 공백을 제거하는 trim과 strip 비교 (0) | 2023.10.29 |
[JAVA] 단위테스트 공부1: Mock 알아보기 (1) | 2023.10.29 |
[JAVA] public private 메소드 정렬 순서 결정하는법 (0) | 2023.10.28 |
[JAVA] StringBuilder 비우기(초기화하기) (1) | 2023.10.28 |