사전학습 - 테스트코드 작성하기 실습

woply·2022년 1월 4일
1

TDD & CleanCode

목록 보기
1/3

  • 자바 플레이그라운드 with TDD, 클린코드 강의 내용을 실습한 내용입니다.
  • 0️⃣ 학습테스트 실습에 해당합니다.

TDD, 클린코드 실습은

  • GitHub을 활용해 미션 기반으로 학습하는 경험을 한다.
  • 컨벤션을 지키고, 읽기 좋은 코드 구현에 대한 필요성을 느끼고, 구현하는 경험을 한다.
  • 테스트 기반으로 리팩터링을 통해 점진적으로 클린코드를 구현하는 경험을 한다.

main method를 이용한 테스트의 문제점

  • Production code와 Test Code가 클래스 하나에 존재한다. 클래스 크기가 커짐. 복잡도 증가함.
  • Test Code가 실 서비스에 같이 배포됨.
  • main method 하나에서 여러 개의 기능을 테스트 함. 복잡도 증가.
  • method 이름을 통해 어떤 부분을 테스트하는지에 대한 의도를 드러내기 힘듦.
  • 테스트 결과를 사람이 수동으로 확인.

JUnit

  • main method를 활용해 테스트할 때 발생하는 문제점을 해결하기 위해 등장한 도구가 JUnit이다.

1. String 클래스에 대한 학습 테스트

  • src/test/java 폴더의 study.StringTest 클래스의 replace() 메서드를 실행해 테스트가 가능한지 확인한다.
package study;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

public class StringTest {
    @Test
    void replace() {
        String actual = "abc".replace("b", "d");
        assertThat(actual).isEqualTo("adc");
    }
}
  • StringTest에 다음 요구사항을 새로운 test case로 추가한다.

요구사항 1

  • "1,2"을 ,로 split 했을 때 1과 2로 잘 분리되는지 확인하는 학습 테스트를 구현한다.
  • "1"을 ,로 split 했을 때 1만을 포함하는 배열이 반환되는지에 대한 학습 테스트를 구현한다.

힌트

  • 배열로 반환하는 값의 경우 assertj의 contains()를 활용해 반환 값이 맞는지 검증한다.
  • 배열로 반환하는 값의 경우 assertj의 containsExactly()를 활용해 반환 값이 맞는지 검증한다.



  • 나의 풀이
    두 번째 힌트인 containsExactly()는 순서를 포함하여 정확하게 원소가 일치하는 것을 찾는다. 중복 값이 있어도 안되고 순서가 달라져도 안된다. 특정 자료 구조의 정확한 값을 테스트해야 할 때 이 메서드를 사용할 수 있다.
@Test
void split() {
    String[] actual1 = "1".split(",");
    assertThat(actual1).contains("1");
    System.out.println(Arrays.toString(actual1));

    String[] actual2 = "1,2".split(",");
    assertThat(actual2).contains("1","2");
    assertThat(actual2).containsExactly("1","2");
    System.out.println(Arrays.toString(actual2));
}

요구사항 2

  • "(1,2)" 값이 주어졌을 때 String의 substring() 메소드를 활용해 ()을 제거하고 "1,2"를 반환하도록 구현한다.
  • 나의 풀이
// 테스트 요구사항
// "(1,2)" 값이 주어졌을 때 String의 substring() 메소드를 활용해 ()을 제거하고 "1,2"를 반환하도록 구현한다.
@Test
void substring() {
    String actual = "(1,2)";
    actual = actual.substring(1);
    actual = actual.substring(0,3);
    System.out.println(actual);
    assertThat(actual).isEqualTo("1,2");
 }

substring()은 시작과 끝을 지정하여 문자열을 자를 수 있는 메서드다. ()안에 잘라낸 위치를 정할 수 있다.

String.substring(start) //문자열 start index부터 끝까지 문자열 자른다.
String.substring(start,end) //문자열 start index부터 end index까지 문자열을 발췌한다.

(1,2)라는 문자열은 index 0~4 까지 ( 1 , 2 )로 구성된 char 배열과 같다. 해당 위치를 index로 지정하여 자르는 것이 ( )에 해당하는 문자열을 잘라내는 가장 간단한 방법이라고 생각했다. 앞의 "("와 뒤의 ")"를 잘라내야 하기 때문에 두 번 작업이 필요하다.

첫번째 괄호는 0번을 잘라내고 1번 문자열부터 끝까지를 발췌하는 방법으로 제거하였고, 두번째 괄호는 0번부터 3번까지 문자열을 발췌하는 방법으로 제거하였다.



요구사항 3

  • "abc" 값이 주어졌을 때 String의 charAt() 메소드를 활용해 특정 위치의 문자를 가져오는 학습 테스트를 구현한다.
  • String의 charAt() 메소드를 활용해 특정 위치의 문자를 가져올 때 위치 값을 벗어나면 StringIndexOutOfBoundsException이 발생하는 부분에 대한 학습 테스트를 구현한다.
  • JUnit의 @DisplayName을 활용해 테스트 메소드의 의도를 드러낸다.

힌트

  • AssertJ Exception Assertions 문서 참고

나의 풀이
IndexOutOfBoundsException 발생 시 Exception의 인스턴스 타입이 일치하면 메시지를 출력한다.

// 테스트 요구사항 3
// "abc" 값이 주어졌을 때 String의 charAt() 메소드를 활용해 특정 위치의 문자를 가져오는 학습 테스트를 구현한다.
// String의 charAt() 메소드를 활용해 특정 위치의 문자를 가져올 때 위치 값을 벗어나면
// StringIndexOutOfBoundsException이 발생하는 부분에 대한 학습 테스트를 구현한다.
// JUnit의 @DisplayName을 활용해 테스트 메소드의 의도를 드러낸다
@Test
@DisplayName("예외 발생 테스트")
void charAt() {
    assertThatThrownBy(() -> {

        String actual = "abc";
        System.out.println(actual.charAt(2));

     throw new IndexOutOfBoundsException("boom!");

    }).isInstanceOf(IndexOutOfBoundsException.class)
            .hasMessageContaining("인덱스 범위를 초과했습니다.");
}

자주 발생하는 Exception의 경우 Exception별 메서드 제공하고 있음.

  • assertThatIllegalArgumentException()
  • assertThatIllegalStateException()
  • assertThatIOException()
  • assertThatNullPointerException()

2. 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의 크기를 확인하는 학습테스트를 구현한다.

나의 풀이

  • setUp()가 @beforeEach 되어 있으므로, 모든 테스트 메서드 실행 전 실행된다.
  • setUp()이 실행되면서 생성된 numbers 인스턴스의 size를 출력하여 확인했다.
public class SetCollectionTest {
    // Set Collection에 대한 학습 테스트
    // 다음과 같은 Set 데이터가 주어졌을 때 요구사항을 만족해야 한다.

        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
    @DisplayName("Set 사이즈 확인")
    void setSize() {
        System.out.println(numbers.size());
    }
}

요구사항 2

  • Set의 contains() 메소드를 활용해 1, 2, 3의 값이 존재하는지를 확인하는 학습테스트를 구현하려한다.
  • 구현하고 보니 다음과 같이 중복 코드가 계속해서 발생한다.
  • JUnit의 ParameterizedTest를 활용해 중복 코드를 제거해 본다.

나의 풀이
@ParameterizedTest를 사용하면 여러개의 파라미터 값을 순차적으로 테스트 할 수 있다.
파라미터에 넣을 값은 @ValueSource에 배열의 형태로 넣으면 된다.

// 요구사항 2
// Set의 contains() 메소드를 활용해 1, 2, 3의 값이 존재하는지를 확인하는 학습테스트를 구현하려한다.
// 구현하고 보니 다음과 같이 중복 코드가 계속해서 발생한다.
// JUnit의 ParameterizedTest를 활용해 중복 코드를 제거해 본다.

@DisplayName("assertThat 중복 코드 제거하기")
@ParameterizedTest
@ValueSource(ints = {1,2,3})
void contains(int number) {
    assertThat(numbers.contains(number)).isTrue();
}

요구사항 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);
}

나의 풀이
CsvSource를 이용해 예상되는 결과 값을 쌍으로 준비했다.
테스트 메서드의 파리미터로 CsvSource에 쌍으로 들어가 있는 input:expected 값을 순차적으로 받는다.
Contain()을 이용해 numbers Set에 input에 해당하는 값이 포함되어 있는지 확인한다. 문자열을 숫자로 타입 캐스팅했다.
assertEquals()를 이용해 expected 값과 실제 값을 비교했다.

// 요구사항 3
// 요구사항 2는 contains 메소드 결과 값이 true인 경우만 테스트 가능하다. 입력 값에 따라 결과 값이 다른 경우에 대한 테스트도 가능하도록 구현한다.
// 예를 들어 1, 2, 3 값은 contains 메소드 실행결과 true, 4, 5 값을 넣으면 false 가 반환되는 테스트를 하나의 Test Case로 구현한다.
// 힌트
// Guide to JUnit 5 Parameterized Tests 문서에서 @CsvSource를 활용한다.
@ParameterizedTest
@CsvSource(value = {"1:true", "2:true", "3:true", "4:false", "5:false"}, delimiter = ':')
void contains123(String input, String expected) {
    boolean actualNum = numbers.contains(Integer.parseInt(input));
    System.out.println(actualNum);
    assertEquals(Boolean.parseBoolean(expected), actualNum);
}
profile
7년간 마케터로 일했고, 현재는 헤렌에서 백엔드 개발자로 일하고 있습니다. 고객 가치를 설계하는 개발자를 지향하며, 개발, 독서, 글쓰기를 좋아합니다. 업이 심오한 놀이이길 바라는 덕업일치 주의자입니다.

0개의 댓글