String 클래스에 대한 학습 테스트
요구사항 1
- "1,2"을 ,로 split 했을 때 1과 2로 잘 분리되는지 확인하는 학습 테스트를 구현한다.
- "1"을 ,로 split 했을 때 1만을 포함하는 배열이 반환되는지에 대한 학습 테스트를 구현한다.
힌트
- 배열로 반환하는 값의 경우 assertj의 contains()를 활용해 반환 값이 맞는지 검증한다.
- 배열로 반환하는 값의 경우 assertj의 containsExactly()를 활용해 반환 값이 맞는지 검증한다.
@DisplayName("String 1과 2를 split으로 나누고 결과 값이 예상한 결과와 일치하는지 확인")
@Test
void split1() {
String[] result = "1,2".split(",");
assertThat(result).containsExactly("1","2");
}
@DisplayName
- Junit을 import 하면 사용할 수 있다.
- 테스트 메소드의 의도를 명확하게 정의하는 용도로 사용
- Declares a custom display name for the test class or test method. Such annotations are not inherited.
containsExactly
- array에 존재하는 값과 내가 예상한 값이 일치하는지 확인할 때 사용
- erifies that the actual iterable/array contains exactly the given values and nothing else in order
요구사항 2
- "(1,2)" 값이 주어졌을 때 String의 substring() 메소드를 활용해 ()을 제거하고 "1,2"를 반환하도록 구현한다.
@DisplayName("(1,2) 값이 주어졌을 때 String의 substring() 메소드를 활용해 ()을 제거하고 1,2를 반환")
@Test
void split2() {
String result = "(1,2)".substring(1,4);
assertThat(result).isEqualTo("1,2");
}
isEqualTo
- 앞서 살펴보았던 containsExactly와 동일하게 값을 비교해주는 것이지만, containsExactly는 어레이값을 살펴 볼 때 확인하고 isEqualTo 는 객체가 참조하는 값을 확인한다.
요구사항 3
- "abc" 값이 주어졌을 때 String의 charAt() 메소드를 활용해 특정 위치의 문자를 가져오는 학습 테스트를 구현한다.
- String의 charAt() 메소드를 활용해 특정 위치의 문자를 가져올 때 위치 값을 벗어나면 StringIndexOutOfBoundsException이 발생하는 부분에 대한 학습 테스트를 구현한다.
- JUnit의 @DisplayName을 활용해 테스트 메소드의 의도를 드러낸다.
힌트
@DisplayName("String의 charAt() 메소드를 이용하여 특정 위치의 문자열을 가져오는 테스트를 진행")
@Test
void split3() {
assertThatThrownBy(()->{
char result = "abc".charAt(4);
}).isInstanceOf(IndexOutOfBoundsException.class)
.hasMessageContaining("String index out of range: 4");
}
assertThatThrownBy()라는 예외처리를 가독성 있게 테스트할 수 있는 함수가 존재한다.
람다 표현식을 이용하여 사용이 가능하며
char result = "abc".charAt(4); 를 실행하게 되면 index가 벗어난 곳을 찾고 있기 때문에 outofBoudsException이 발생한다.
isInstanceOf 메소드를 이용하여 내가 예상한 에러가 발생하는지
hasMessageContaining 메소드를 이용하여 예상한 에러 메세지가 발생하는지를 검증할 수 있다.
Set Collection에 대한 학습 테스트
- 다음과 같은 Set 데이터가 주어졌을 때 요구사항을 만족해야 한다.
public class SetTest {
private Set<Integer> numbers;
@BeforeEach
void setUp() {
numbers = new HashSet<>();
numbers.add(1);
numbers.add(1);
numbers.add(2);
numbers.add(3);
}
// Test Case 구현
}
요구사항 1
- Set의 size() 메소드를 활용해 Set의 크기를 확인하는 학습테스트를 구현한다.
// Test Case 구현
@DisplayName("Set의 size() 메소드를 활용해 Set의 크기를 확인")
@Test
void checkSize(){
assertThat(numbers.size()).isEqualTo(3);
}
String 클래스에 요구사항2에 대한 학습 테스트와 동일하다.set은 중복을 허용하지 않기 때문에 number이라는 set에는 3개의 값이 존재한다.
요구사항 2
- Set의 contains() 메소드를 활용해 1, 2, 3의 값이 존재하는지를 확인하는 학습테스트를 구현하려한다.
- 구현하고 보니 다음과 같이 중복 코드가 계속해서 발생한다.
- JUnit의 ParameterizedTest를 활용해 중복 코드를 제거해 본다.
@Test
void contains() {
assertThat(numbers.contains(1)).isTrue();
assertThat(numbers.contains(2)).isTrue();
assertThat(numbers.contains(3)).isTrue();
}
@ParameterizedTest
@ValueSource(strings = {"", " "})
void isBlank_ShouldReturnTrueForNullOrBlankStrings(String input) {
assertTrue(Strings.isBlank(input));
}
아래와 같이 구현하였다.
@DisplayName("Set에서 특정 값의 존재 여부를 확인 (Parameterized Tests 방법)")
@ParameterizedTest
@ValueSource(ints = {1,2,3})
void contains2(int input) {
assertThat(numbers.contains(input)).isTrue();
}
contains 메소드에는 중복된 코드가 나타난다.
이를 보완하기 위해 parameterized Test를 사용한다.
사용법은 간단하다. valuesource 어노테이션에 사용할 값을 넣어준다.
valuesource에 존재하는 각각의 값은 contains2 메소드에 존재하는 input argument 에 할당이 되며
assertThat을 이용하여 input 값과 내가 예상한 값의 일치 여부를 확인할 수 있다.
value source를 사용할 때는 몇가지 제약 조건이 존재한다.
1. value source 는 아래와 같은 타입만 사용할 수 있다.
- short (with the shorts attribute)
- byte (bytes attribute)
- int (ints attribute)
- long (longs attribute)
- float (floats attribute)
- double (doubles attribute)
- char (chars attribute)
- java.lang.String (strings attribute)
- java.lang.Class (classes attribute)
2. 한개의 메소드에는 한개의 argument만 올 수 있다.
3. null값은 사용할 수 없다.
요구사항 3
- 요구사항 2는 contains 메소드 결과 값이 true인 경우만 테스트 가능하다. 입력 값에 따라 결과 값이 다른 경우에 대한 테스트도 가능하도록 구현한다.
- 예를 들어 1, 2, 3 값은 contains 메소드 실행결과 true, 4, 5 값을 넣으면 false 가 반환되는 테스트를 하나의 Test Case로 구현한다.
힌트
- Guide to JUnit 5 Parameterized Tests 문서에서 @CsvSource를 활용한다.
@ParameterizedTest
@CsvSource(value = {"test:test", "tEst:test", "Java:java"}, delimiter = ':')
void toLowerCase_ShouldGenerateTheExpectedLowercaseValue(String input, String expected) {
String actualValue = input.toLowerCase();
assertEquals(expected, actualValue);
}
valueSource의 문제점을 보완하기 위해 나온것으로 보인다.
기존의 ValueSource는 한가지의 argument만 사용 가능하였다면, CsvSource는 여러가지 argument를 사용할 수 있다.
@DisplayName("Set에서 특정 값의 존재 여부를 확인 (CsvSource 이용)")
@ParameterizedTest
@CsvSource(value = {"1,true", "2,true", "3,true", "4,false"})
void contains3(String input, boolean expected) {
boolean actualValue = numbers.contains(Integer.parseInt(input));
assertEquals(expected, actualValue);
}
@DisplayName("Set에서 특정 값의 존재 여부를 확인 (CsvSource 이용)")
@ParameterizedTest
@CsvSource(value = {"1:true", "2:true", "3:true", "4:false"}, delimiter = ':')
void contains4(String input, boolean expected) {
boolean actualValue = numbers.contains(Integer.parseInt(input));
assertEquals(expected, actualValue);
}
contains3와 contains4는 동일한 테스트 방법이다.
delimeter를 이용하여 구분자를 지정할 수 있다.
요약
assertThat 을 이용하면 개발자의 의도를 보다 명확하게 드러낼 수 있는 큰 장점을 얻어갈 수 있음.
assertThat을 이용하여 두개의 값을 비교하였으며, DisplayName 어노테이션을 이용하여 메소드의 의도를 구체화 함.
assertThatThrownBy 예외처리를 가독성 있게 테스트할 수 있는 함수이다.
어떤 예외가 발생하는지, 어떤 에러 메세지가 발생하는지 등을 비교할 수 있음.
ParameterizedTest (ValueSource , CsvSource) 를 이용하여 중복된 테스트를 한줄로 줄일 수 있다. (for 문과 같은 기능을 한다.)
궁금증
- try catch를 이용하여 예외를 잘 다루고 있는지만 확인하면 충분할 것 같은데,
어떤 예외가 발생하고, 에러가 발생하였을 때 어떤 메세지가 뜨는지 까지 확인을 하는게 일반적인 것인가?
현재 연습하시는 부분에 대해서는 예외가 잘 발생하는지 확인만 하면 충분합니다.
하지만 어떤 예외에 따라서 어떤 메세지가 뜨는지가 만약 서비스를 호출한 사용자에게 노출된다거나
아니면 그 메세지 혹은 Exception에 선언된 다른 정보들 (예를들어 에러코드 등)에 따라서
다른 대응이 필요하다면 테스트 과정에서 Exception에 따른 값들이 정상적으로 반환 되는지
확인은 필요합니다
'강의 > TDD, Clean Code with Java 12기' 카테고리의 다른 글
4단계 자동차경주 (우승자) (8일차) (0) | 2021.07.27 |
---|---|
3단계 자동차경주 - 피드백반영 (6일차, 7일차) (0) | 2021.07.25 |
3단계 자동차 경주 (5일차) (0) | 2021.07.24 |
2단계 문자열 계산기 피드백 반영 (4일차) (0) | 2021.07.23 |
2단계 문자열 계산기 (2,3 일차) (0) | 2021.07.22 |