외부활동/우아한테크코스 [프리코스]

우아한테크코스 NsTest,Assertions 톺아보기

softmoca__ 2024. 10. 27. 23:11
목차

NsTest,Assertions는 우아한테크코스 미션에서 사용되는 테스트 API들이다.
사용하기 이전에 하나하나 톺아보자 !

 

2주차 자동차 경주 미션에는 위와 같은 테스트 파일이 먼저 제공되며 NsTest를 상속한 ApplicationTest가 구현되어 있다.

 

NsTest

Junit을 활용한 테스트 환경에서 콘솔 입출력을 테스트하기 위해 설계된 추상 클래스이다.

 

 

PrintStream standardOut;

테스트 수행 후, 시스템 출력(System.out)을 원래 상태로 복구하기 위해 기존 표준 출력 스트림을 저장한다.

OutputStream captor;

테스트 중 발생한 콘솔 출력을 임시로 저장하기 위한 스트림이다.

 

 

@BeforeEach protected final void init()

 

역할: 각 테스트 실행 전에 호출되어 초기화를 수행하며, 테스트 중 발생하는 출력 결과를 캡처하기 위해 표준 출력 스트림을 임시로 변경한다.

  1. 현재 표준 출력 스트림을 standardOut에 저장한다.
  2. 새로운 ByteArrayOutputStream 객체를 생성하고 이를 captor에 할당한다.
  3. 표준 출력(System.out)을 새롭게 생성된 PrintStream 객체(captor)로 변경한다.

 

@AfterEach protected final void printOutput()

 

역할: 각 테스트 실행 후 호출되어 출력 스트림을 원래 상태로 복구하고 테스트 결과를 출력하며, 테스트 완료 후 콘솔 출력 상태를 복구하고, 테스트 중 캡처된 결과를 콘솔에 출력한다.

  1. 표준 출력 스트림(System.out)을 초기 값(standardOut)으로 복원합니다.
  2. captor에 저장된 출력 내용을 output() 메서드를 통해 출력합니다.

 

 

output

 

역할: 테스트 중 캡처된 콘솔 출력 결과를 문자열로 반환하며,  테스트가 콘솔에 출력한 결과를 확인할 수 있도록 제공한다.

  1. captor.toString()을 호출하여 저장된 출력 내용을 문자열로 변환합니다.
  2. .trim()을 호출하여 문자열 앞뒤의 공백을 제거합니다.

 

run

 

역할: 입력 데이터를 설정하고 테스트 대상 프로그램의 main 메서드를 실행한다.

  1. command(args)를 호출하여 콘솔 입력 스트림(System.in)을 설정한다.
  2. 추상 메서드 runMain()을 호출하여 실제 프로그램 실행을 담당한다.
  3. 테스트 종료 후, Console.close()를 호출하여 리소스를 정리한다.
  4. 예외 처리: try-finally 블록을 사용하여 프로그램 실행 중 예외가 발생하더라도 리소스 정리가 보장된다.

command

역할: 콘솔 입력 데이터를 설정한다.테스트 중 실제 입력 대신 사전 정의된 데이터를 입력으로 사용할 수 있도록 한다.

  1. 입력 데이터(args)를 줄바꿈 문자(\n)로 연결하여 문자열로 만든다.
  2. 해당 문자열을 바이트 배열로 변환하여 ByteArrayInputStream에 저장한다.
  3. 표준 입력 스트림(System.in)을 새로 만든 ByteArrayInputStream으로 설정한다.

runMain

 

역할: 테스트 대상 프로그램의 실행 진입점을 정의하기 위한 추상 메서드이다.

구체 클래스에서 구현되며, 일반적으로 테스트 대상 프로그램의 main 메서드를 호출한다. 즉, Application.main인 애플리케이션 실행 함수를 빈 문자열 배열과 함께 실행한다.

 

runEception

 

 

 

역할: 예외 발생 가능성이 있는 테스트를 실행한다.테스트 중 예외가 발생하는 상황에서도 프로그램이 중단되지 않도록 한다.

  1. run(args)를 호출한다.
  2. NoSuchElementException 예외를 발생시키는 경우 이를 무시한다.
  3. 그 외의 예외는 그대로 터트리며 외부에서 assertThatThrownBy로 확인하도록 한다.

 

 

Assertions

JUnit과 Mockito를 활용하여 테스트를 지원하는 유틸리티 클래스로서 테스트에서 랜덤 값 생성 및 시간 관련 기능을 다루기 때문에, 테스트의 안정성을 위해 이러한 동작을 제어하고 검증하는 메서드를 제공한다.

 

static final Duration SIMPLE_TEST_TIMEOUT

간단한 테스트의 실행 시간 제한을 설정합니다.

static final Duration RANDOM_TEST_TIMEOUT

랜덤 값 또는 시간 의존적인 테스트의 실행 시간 제한을 설정합니다.

 

 

assertSimpleTest

 

역할: 간단한 테스트를 실행하며, 실행 시간이 SIMPLE_TEST_TIMEOUT을 초과하지 않도록 보장하며, 테스트가 너무 오래 걸리지 않도록 보장한다.

파라미터: Executable executable: 테스트 실행 로직(람다식이나 메서드 참조로 전달).

구현 : assertTimeoutPreemptively 메서드를 사용하여 시간 제한을 검증한다.

 

 

 

assertRandomNumberInListTest

 

역할: Randoms.pickNumberInList 메서드를 사용하는 테스트를 실행하며, 테스트 동안 반환값을 value와 values로 제한하며, 랜덤 값 생성을 제어하여 테스트의 예측 가능성을 높인다.

파라미터:

  • Executable executable: 테스트 실행 로직.
  • Integer value: 첫 번째 반환값.
  • Integer... values: 이후 반환값들.

구현 : 내부적으로 assertRandomTest를 호출하여 동작을 캡슐화한다.

 

 

assertRandomNumberInRangeTest

 

역할: Randoms.pickNumberInRange 메서드를 사용하는 테스트를 실행하며, 반환값을 value와 values로 제한힌다.

구현 : assertRandomTest를 활용하여 랜덤 숫자 생성 동작을 모의(Mock)한다.

 

assertRandomUniqueNumbersInRangeTest

 

역할: Randoms.pickUniqueNumbersInRange 메서드를 사용하는 테스트를 실행하며, 반환값을 value와 values로 제한한다.

구현 : 동일한 방식으로 assertRandomTest를 호출한다.

 

assertShuffleTest

 

역할: Randoms.shuffle 메서드를 사용하는 테스트를 실행하며, 반환값을 value와 values로 제한힌다.

파라미터:

  • List<T> value: 첫 번째 반환값 리스트.
  • List<T>... values: 이후 반환 리스트.

구현:

  • 제네릭 메서드로 구현되어, 다양한 타입의 리스트를 다룰 수 있다.
  • assertRandomTest를 호출하여 Randoms.shuffle 동작을 제어한다.

 

assertRandomTest

 

역할: 랜덤 값 생성 메서드를 모의(Mock)하고 테스트를 실행하며, 랜덤 값 생성 동작을 테스트에서 제어 가능하게 만들어 테스트의 재현성을 보장한다.

 파라미터:

 

  • Verification verification: 모의(Mock)할 메서드를 정의하는 인터페이스.
  • Executable executable: 테스트 실행 로직.
  • T value: 첫 번째 반환값.
  • T... values: 이후 반환값들.

구현:

  • mockStatic(Randoms.class)를 호출하여 Randoms 클래스의 정적 메서드를 모의(Mock)한다.
  • 반환값(value 및 values)을 설정한 후 테스트를 실행한다.
  • 테스트 완료 후 MockedStatic 객체를 자동으로 닫아 리소스를 정리한다.

assertNowTest

 

역할: DateTimes.now 메서드를 사용하는 테스트를 실행하며, 반환값을 value와 values로 제한하며,  시간 관련 동작을 제어하여 특정 시간 시점에서의 테스트를 재현 가능하게 만든다.

 

파라미터:

  • LocalDateTime value: 첫 번째 반환값.
  • LocalDateTime... values: 이후 반환값들.

구현:

  • mockStatic(DateTimes.class)를 호출하여 DateTimes.now 메서드를 모의(Mock)한다.
  • 지정된 반환값을 설정하고 테스트를 실행한다.
  • 테스트 완료 후 MockedStatic 객체를 닫는다.

 

 

assertTimeOutPreemptively

JUnit의 시간 제한(Time-out) 기능을 제공하며 지정된 timeout 기간 내에 executable이 실행을 완료해야 하며, 이를 만족하지 못하면 테스트가 실패한다.

 

 

  1. 시간 제한 적용:
    • 지정된 시간(Duration) 안에 executable이 실행을 완료해야 한다.
    • 실행이 시간 제한을 초과하면 테스트는 실패로 간주된다.
  2. 즉시 중단(Preemptive):
    • 테스트 실행 중 시간이 초과되면 즉시 해당 실행을 중단한다.
    • 이 점에서 일반 assertTimeout과 다릅니다. assertTimeout은 테스트가 완료될 때까지 기다린 후 시간 초과 여부를 확인하지만, assertTimeoutPreemptively는 초과 시 즉시 중단한다.
  3. 입력 파라미터:
    • Duration timeout: 시간 제한 값입니다. 예를 들어 Duration.ofSeconds(5)는 5초를 의미한다.
    • Executable executable: 실행할 테스트 로직입니다. 주로 람다식이나 메서드 참조로 전달된다.
  4. 기능 구현:
    • 내부적으로 AssertTimeoutPreemptively.assertTimeoutPreemptively 메서드를 호출하여 기능을 제공하며 JUnit 라이브러리에서 제공하는 핵심 동작을 수행한다.

 

 

 

 

 

실제 예시 테스트에서 적용

게임 로직에서 랜덤 값이 주어졌을 때 결과를 검증하며, 랜덤 값이 4 이상일 경우 전진하고, 3 이하일 경우 정지하는 로직을 검증하려고 한다.

 

 

  1. assertRandomNumberInRangeTest 호출:
    • 랜덤 숫자를 반환하는 Randoms.pickNumberInRange 메서드 호출을 제어한다.
    • Verification: Randoms.pickNumberInRange(anyInt(), anyInt()) 호출이 모의(Mock)된다.
    • 반환값 설정 첫 번째 호출 → MOVING_FORWARD (4), 두 번째 호출 → STOP (3)
  2. 람다식 실행 (() -> {...}):
    • run("pobi,woni", "1") 호출:
      • 입력값 "pobi,woni"은 두 명의 참가자를 나타낸다.
      • 입력값 "1"은 게임 진행 라운드 수를 나타낸다.
    • 내부적으로 게임 로직이 실행되며, MOVING_FORWARD와 STOP 값이 차례로 사용된다.
  3. 결과 검증:
    • output() 메서드를 통해 프로그램 출력 결과를 가져온다.
    • assertThat(output()).contains(...):
      • "pobi : -" → pobi가 1회 전진한 결과.
      • "woni : " → woni가 정지한 결과.
      • "최종 우승자 : pobi" → pobi가 최종 우승자로 선정된 결과

결과 :  반환값: MOVING_FORWARD(4) → pobi 전진. STOP(3) → woni 정지. -> 출력 검증 성공

 

 

입력값이 잘못되었을 때, IllegalArgumentException이 발생하는지 검증한다.

 

  1. assertSimpleTest 호출:
    • Executable로 전달된 람다식 실행을 수행하며, 실행 시간이 SIMPLE_TEST_TIMEOUT(1초)을 초과하지 않도록 검증한다.
  2. 람다식 실행 (() -> {...}):
    • runException("pobi,javaji", "1") 호출:
      • "pobi,javaji" → 참가자 이름으로 입력.
      • "1" → 게임 진행 라운드.
    • 입력 검증 로직에서 "javaji"가 유효하지 않은 이름으로 간주되어 IllegalArgumentException 발생.
  3. 예외 검증:
    • assertThatThrownBy(...):
      • 실행 중 발생한 예외를 포착한다.
      • 예외가 IllegalArgumentException인지 확인한다.

결과 : 예외가 정상적으로 발생하면 테스트 성공.