[우테코 6기 프리코스] NsTest 코드 분석해보기 - 2탄: 땅끝까지 분석!

별의개발자커비·2023년 10월 22일
11

우테코 도전기

목록 보기
17/37

개요

이번 1주차 ApplicationTest 클래스에는 assertRandomNumberInRangeTest, assertSimpleTest가 사용되고 있다. 앞으로 프리코스 동안 우테코의 NsTest 속 테스트 메소드를 계속 사용할텐데 이 메소드들을 뿌리까지 이해하면 다른 테스트 코드 이해에도 도움이 될 것 같다는 생각이 들었다!

🚨 본 테스트 코드를 분석하며 한 층 아래, 그 아래, 또 그 아래를 파고 들어갑니다. 깊게 들어감 주의!

1. assertRandomNumberInRangeTest

Assertions 클래스 내로 들어와 assertRandomNumberInRangeTest 메소드를 살펴봤다.

1F: assertRandomNumberInRangeTest 메소드의 인자 부분

메소드의 인자로 있는

  • Executable 타입의 executable과
  • value, values가 뭔지 더 들어가보자.

2F: Executable 타입

실행 인터페이스로 Executable 인터페이스의 로직을 정의해서 쓴다.

Executable은 JUnit5에 정의되어 있는 함수형 인터페이스이다.
Runnable을 예외를 던질 수 있도록 재정의한 클래스
---중략---
함수를 실행하되 예외를 던질 수 있게 하도록 Executable을 사용한 것임을 알 수 있다.

2F: value, values

value, values은 일단 메소드 내부로 전달하기 때문에 다음 층에서 알아보자.

여기까지 적용해보자면...

@Test
void 게임종료_후_재시작() {
	assertRandomNumberInRangeTest(
    // ecexutable → 246, 135 등을 입력해서 runMain을 실행하겠다. 단, 함수를 실행하되 예외를 던질 수 있게 되어있음.
    () -> {
    	run("246", "135", "1", "597", "589", "2");
        assertThat(output()).contains("낫싱", "3스트라이크", "1볼 1스트라이크", "3스트라이크", "게임 종료");
        },
        // value, values...
        1, 3, 5, 5, 8, 9
	);
}

1F: assertRandomNumberInRangeTest 메소드 내부

assertRandomTest를 아래의 4개 인자를 갖고 실행하러 들어간다.

  • () -> Randoms.pickNumberInRange(anyInt(), anyInt())
  • executable : 246, 135 등을 입력해서 runMain을 실행하겠다.
  • value, values: 1, 3, 5, 5, 8, 9

잠깐, anyInt()는 뭐지?
: 모든 int 또는 null이 아닌 정수를 반환한다.

assertRandomTest로 한 층 더 들어가보자!

2F: assertRandomTest 메소드 인자 부분

위 부분에 아래와 같이 들어왔다.
한 눈에 이해가 안가는 부분은 Vertification 타입으로 들어온 () -> Randoms.pickNumberInList(anyList())이었다.

Vertification 타입은 뭘까? 객체 안으로 들어가보자.

3F: Vertification 타입

MockedStatic 인터페이스 안의 Verification 인터페이스로 들어왔다.

이거로만으로는 이해가 안가서 검색을 통해 이해해보았다.
여기에 들어온 함수의 호출이 이루어졌는지 확인하는 verify 기능을 하는 객체라는 걸 알았다.

verify 기능이란?
: 조금 더 견고하고 정확한 테스트를 진행하기 위해서 가끔은 해당 테스트 안에서 특정 메소드를 호출했는지에 대해서 검증을 할 필요가 있습니다. 이를 위해 Mockito 에서는 verify() 라는 함수를 지원해줍니다.

다시 2F로: assertRandomTest 메소드 인자 부분

아래의 메소드 인자로 위에서 넣은 값으로 해석해보자면

  • Verification verification = () -> Randoms.pickNumberInRange(anyInt(), anyInt())
    : pickNumber 이 함수가 호출되었는지 확인하고,
  • executable : 246, 135 등을 입력해서 runMain을 실행하겠다.
  • value, values: 1, 3, 5, 5, 8, 9

이제 이 인자들을 갖고 assertRandomTest 메소드 내부를 실행해보자!

2F: assertRandomTest 메소드 내부

assertRandomTest 내부는 낯설고 꽤 복잡하다... 특단의 조치!

  1. assertTimeoutPreemptively 메소드
  2. assertTimeoutPreemptively의 인자인 executable 부분
  3. try 인자 부분
  4. try 실행 부분

이렇게 4부분으로 나눠서 들어가보려고 한다!

3F: assertTimeoutPreemptively 메소드

assertTimeoutPreemptively(Duration timeout, Executable executable)
------
assertTimeoutPreemptively(Duration.ofSeconds(10L), () -> {})

3F: assertTimeoutPreemptively의 인자인 executable 부분

try 절을 실행하게 한다.
그럼 try의 인자로 들어온 부분을 우선 살펴보자!

3F: try 인자

4F 왼쪽: MockedStatic<Randoms> mock

Randoms을 MockedStatic이란 애가 감싸고 있는 변수가 있다. 얘는 Randoms의 클래스를 대상으로 mockStatic이라는 걸 실행해서 그 MockedStatic로 반환해주고 있다.

4F 오른쪽: mockStatic(Randoms.class)

공식 문서를 살펴보았다.

번역은 아래와 같다!

지정된 클래스 또는 인터페이스의 모든 static 메서드에 대한 스레드 로컬 mock 컨트롤러를 만듭니다.

여기서 만든다는 Mock은 뭘까? Mock에 검색해 이해해보려고 했다!

5F: Mock 객체란?

이 포스팅에서 발췌해 온 부분으로 Mock을 이해할 수 있다.

다시 3F: try 인자

다시 3F try 인자 부분으로 돌아와 위의 부분들을 정리해보자면

Randoms에 속한 모든 static 메소드에 대해서는, 임의로 조작하는 mocking을 만들어 사용할 것이다. ⇒ 변수 mock

라고 이해할 수 있다.

3F: try 실행 부분

인자 부분에서 정리한 걸 여기에서 실행을 하는 단계이다.

(참고) 인자 부분

  • verification
    : () -> Randoms.pickNumberInRange(anyInt(), anyInt())
    : pickNumber 이 함수가 호출되었는지 확인하고,
  • executable
    : 246, 135 등을 입력해서 runMain을 실행하겠다.
  • value, values
    : 1, 3, 5, 5, 8, 9

위의 인자를 .을 기준으로 나누어 적용해보면

// Randoms에 속한 모든 static 메소드에 대해서는 임의로 조작할 수있게 만든 mock 객체에 대해서 
mock

// Randoms.pickNumberInRange 함수를 호출했는지 verify를 통과하면
.when(verification)

// 들어온 value + values를 Randoms.pickNumberInRange의 실행결과로 반환해라
.thenReturn(value, Arrays.stream(values).toArray());

// 그 랜덤값을 갖고 executable 속의 run 등을 실행해라.
executable.execute();

다시 처음 1F로 돌아가서 정리

1. verify
: run 실행 안에 Randoms.pickNumberInRange가 호출되었는지 확인도 하고
2. 랜덤 결과 주입
: Randoms.pickNumberInRange의 실행결과는 value, values로 대신 반환하게 하고
3. execute
: run을 그 입력한 반환값을 랜덤 결과로 하여 실행해라!

2. assertSimpleTest

이름값에 맞게(?) assertRandomNumberInRangeTest보다는 구조가 간단하다!

이 위의 코드를 갖고 아래의 assertSimpleTestrunException메소드 구조를 적용해보면

runException("1234")
: "1234"를 넣어 runMain()을 실행하되, NoSuchElementException 예외는 catch한다.
: 근데 1234의 경우 길이 초과로 IllegalArgumentException로 처리했을 것이므로 catch되지는 않을 것이고,

isInstanceOf(IllegalArgumentException.class)
: ThownBy로 테스트를 통과하게 될 것 이다!

assertThatThrownBy(() -> runException("1234"))
	 .isInstanceOf(IllegalArgumentException.class)

3. runMain()

가장 바깥의 사용자가 만든 테스트 클래스에서 NsTest를 extends했기 때문에
만들어져있는 NsTest의 추상 메소드를 overide해서 사용하게 된다.

extends 받은 클래스해서 overide해서 여기에 실행시키고 싶은 클래스의 메소드를 넣어주면 된다.

run 함수에서 runMain을 실행하게 내부에 되어있기에 결과적으로 이 runMain이 실행된다.

0개의 댓글