[공부한 것] 테스트 코드 완전 써먹기! (feat. JUnit)

별의개발자커비·2023년 10월 21일
0

우테코 도전기

목록 보기
18/37

개요

프리코스 1주차에 TDD를 적용해보면서 테스트를 잘 하는 것이 생각보다 어렵다고 느꼈다.
따라서, 그동안 기본적인 assertThat, Thrown 정도만 사용하고 있었는데 테스트를 적극 활용해보기 위해 JUnit의 여러 테스트 메소드들과 이외의 테스트를 좀 더 활용할 수 있는 방법을 살펴보려고 한다!

@Test, assertThat, Thrown으로만 작성한 테스트 코드

1.조건을 통과하는 경우 2. 한 예외에 해당하는 여러 입력 경우 3. 공백인 경우 등을 테스트 할 때 이렇게 많은 테스트 메소드들이 필요했다.

하지만, 이걸 간략화 할 수 있는 Parameterized Tests와 기타 Junit의 테스트 메소드를 활용하는 방법을 알아보려고 한다!

📌JUnit 5 Parameterized Tests

1. @ValueSource

같은 결과가 나오는 여러개의 입력을 확인해보고 싶을 때!

예) 1,2가 아닌 입력인 a, 11, a A 등이 모두 예외 처리가 돼야함

이렇게 하면 똑같은 테스트 메소드를 입력만 다르게 여러개 쓸 필요가 없다.
예외 처리 확인할 때 유용할듯!

2. @CsvSource

여러개를 입력해서 각각 다른 결과가 나오는 걸 확인하고 싶을 때

예) Q를 입력하면 true, R을 입력하면 false

구분자를 지정할 수도 있음

3. @MethodSource

위와 동일한데, 입력이 객체 정도로 복잡한 경우

예) 1을 입력하면 RETRY, 2를 입력하면 Retry enum 객체의 END

이렇게 객체 등의 조금 복잡한 형태로 입력, 출력을 보려면 @MethodSource를 쓰면 된다!

4. @EmptySource (+null)

null이나 빈문자열이 들어왔을 때의 결과를 확인해보고 싶을 때!

📚 다른 사람들의 코드에서 배운점

1. validate는 from으로 테스트!

이렇게 하면 validate 메소드가 public일 필요도 없겠구나!

1-1. 그냥 from 테스트는 이렇게!

1-2. init의 경우 isEmpty로 초기화 확인!

경우에 따라, assertNotNull으로 new arrayList 생성 등 확인하는 방법도 있음!

2. assertDoesNotThrow

: 정상인 경우 테스트!

그동안은 테스트 코드 안에 정상 코드 껴놓고 넘어가면 된 걸로 했는데 이렇게 정상인 경우를 테스트 하는 코드가 있었다니...
의문이 들었을 때 그냥 지나가곤 했던 나를 반성한다...

2-2. assetJ 활용!

3. assertEquals

: equal 테스트를 짧게!

  static void assertEquals(Object expected, Object actual) {
      assertEquals(expected, actual);
  }

테스트 시, 생성된 객체가 필요할 때 mock!

이 코드를 해석해보면 실제 어떤 숫자, 문자의 객체가 들어와서 어떤 결과가 나오는지를 자세히 알 수는 없지만 그 메소드 안에 구현 부분이 잘 작동한다는 걸 테스트하는 것 같다.

void의 경우 테스트 수준

: 해당 클래스에서 해당 메소드가 작동하는지만 확인

그럼 이게 무슨 의미가 있냐. 라고 한다면

그 메소드 안의 구현 부분, 그러니까 조금 더 구체적인 메소드는 mock을 이용한 게 아니라 실제 값으로 테스트가 된 부분이라는 전제 조건 하이기 때문에,

속의 메소드가 제대로 동작하는 게 보장되었다면
그걸 이용한 메소드는 속의 보장된 메소드로 이어지는 부분이 잘 작동되었는지만 확인하면 되기 때문이다!

mock을 이용한 테스트 코드 쪼개보기

mock(MoveHistory.class)
: 이 코드는 MoveHistory 클래스의 mock(가짜) 인스턴스를 생성한다.
이 Mock 객체는 실제 객체처럼 동작하지 않고, 대신 테스트 중에 모의상태로 사용된다.

verify(moveHistory).mark(any(PlayerMove.class))
: MoveHistory 객체의 mark 메서드가 PlayerMove 클래스의 어떠한 인스턴스로 호출되었는지를 검증한다.

  • verify: 모의 객체에 대한 호출을 확인하고,
  • any(PlayerMove.class): PlayerMove 클래스의 임의의 인스턴스를 나타낸다.

- verify().method()

verify(mock).method(param);

해당 Mock Object 의 메소드를 호출했는지를 검증한다.

- any()

Mockito Argument Matchers - any() - 공식문서 번역
때때로 우리는 주어진 유형의 인수에 대한 동작을 mocking하고 싶을 때가 있습니다. 이 경우 Mockito Argument Matchers(인자 일치자)를 사용할 수 있습니다. org.mockito.ArgumentMatchersMockito 인수 메서드는 클래스에 static 메서드로 정의됩니다.

이 markHistory나 mark 메소드는 작은 단위의 테스트이므로 실제 값으로 테스트를 마친 메소드들이라 이렇게 쓸 수 있는 것이다!

verify의 횟수 검증

아래와 같은 메소드를 활용하면 호출 횟수도 검증할 수 있다.
단, 횟수 관련 메소드를 파라미터에 입력하지 않으면 기본적으로 1번 호출하는지를 검증한다.

4. when~ thenReturn

: 테스트에 필요한 메소드 결과를 미리 주입해놓기

getVictoryStatus가 Success인 경우를 테스트하는 코드가 만들어진 과정은 다음과 같다.

  1. 메소드가 속한 Player 객체를 모의로 만들어야하고

  2. 메소드 속이 moveHistory.isAllMoved(true) 이렇게 되어있어야한다.
    이 때 이렇게 when(메소드).thenReturn(기대반환값)이 쓰일 수 있다!

단, when~을 쓸 때에는 해당 인자가 mock 객체여야한다.

5.set.add

: 중복없는 랜덤 뽑히는지 확인할 때!

set.add()는 중복되지 않은 값이 add되면 true를, 이미 있던 값이 add 되면 false를 반환한다.
이를 이용해 중복없는 랜덤 뽑기가 정상적으로 작동하는지를 테스트할 수 있다!

6. isBetween

: 범위 내 숫자 검증할 때!

위와 같이 범위 내 숫자 검증을 isBetween으로 구현할 수 있다!

공식문서를 살펴보니 활용할 수 있는 더 다양한 메소드들이 있어서 다음에 써봐야겠다!

7. when + verify

    @Test
    void isPlayerMoved는_움직인_경우_true응답한다() { 
    // 메소드를 호출하기 위해 player를 mock 객체들을 이용해 생성한다.
        TrialCount trialCount = mock(TrialCount.class);
        MoveHistory moveHistory = mock(MoveHistory.class);
        Player player = Player.fromTest(trialCount, moveHistory);

 	// player.isPlayerMoved 메소드에서 실행될 moveHistory.isPlayerMoved의 반환값을 true로 주입해놓음
 		when(moveHistory.isPlayerMoved()).thenReturn(true);
        boolean isMoved = player.isPlayerMoved();
        
	// 위의 메소드 실행 후, moveHistory 객체에서 isPlayerMoved가 실행되었는지 확인
        verify(moveHistory).isPlayerMoved();
   // 매소드 실행 결과 일치 확인
        assertTrue(isMoved);
    }

8. enum의 경우 from으로 validate!

enum은 생성자가 따로 없거나 생성자로 생성되는 게 아니기 때문에 입력을 통해 enum으로 변환되어야 하는 경우 validate 위치가 애매하다고 생각했는데,

이 때 from 메소드 안의 stream에 orElseThrow 안에 넣는 방법이 있었다!

filter로 validate 내용을 넣어주고 해당되지 않는 예외 경우들을 다 orElseThrow로 던져주면 되는 방식이다.

9. Predict 타입

: 스트림 filter를 간단히!

Predicate란?
Predicate는 함수형 인터페이스로, 조건을 검사하는 데 사용됩니다. Predicate는 일반적으로 함수형 프로그래밍 또는 Java 8의 람다 표현식과 함께 사용됩니다.
Predicate를 사용함으로써, 해당 조건을 명시적으로 정의하고 필터링할 수 있으며, 이는 함수형 프로그래밍에서 코드를 간결하게 작성하고 가독성을 향상시키는 데 도움이 됩니다.

원래는 이렇게 boolean 메소드로 작성할 수도 있겠지만

Predicate 메소드로 사용하면 스트림 안에서 작성된 코드가 간결해지는 효과가 있다.

물론 간단한 경우에는 그냥 boolean을 사용하는 것이 더 낫다!

@고민: mock을 안쓴다면

공부를 위해 mock을 안써보라는 프리코스 채널의 답변을 보고 mock을 대체하기위해,

함수형 인터페이스의 추상메소드가 하나인 속성을 이용해 number -> ~~ 이렇게 알아서 인자를 넣어주고 결과로 간주하는 방법을 적용하고 싶었다.

하지만 그러면 generate가 3,4,5를 불러오는 게 아니라 3을 갖고 intstream을 다 돌아버렸다.

그래서 일단 mock을 쓰는 버전으로 테스트를 구현해놓은채로 고민에 빠졌다.. to be continue...

0개의 댓글