테스트 러너 인터페이스


테스트 러너는 풀이를 받아 모든 테스트를 실행하고 표준화된 출력을 반환하는 한 가지 역할만 담당해요. Exercism 웹사이트와의 모든 상호작용은 자동으로 처리되며, 이 명세의 범위에 포함되지 않아요.

실행

  • 테스트 러너는 실행 가능한 스크립트를 제공해야 해요. 자세한 내용은 docker.md 파일에서 확인할 수 있어요.
  • 스크립트는 세 개의 매개변수를 받아요.
    • 연습 문제의 슬러그 (예: two-fer).
    • 제출한 풀이 파일과 그 밖의 연습 문제 파일이 들어 있는 입력 디렉터리 경로예요 (끝에 슬래시가 붙어요). 이 디렉터리는 읽기 전용으로 생각해야 해요. 기술적으로는 여기에 쓸 수도 있지만, 임시 파일(예: 소스 컴파일)에는 /tmp를 사용하는 게 좋아요.
    • 출력 디렉터리 경로예요 (끝에 슬래시가 붙어요). 이 디렉터리는 쓰기 가능해요.
  • 스크립트는 출력 디렉터리에 results.json 파일을 써야 해요.
  • 러너는 테스트 상태와 관계없이 성공적으로 실행되었다면 종료 코드 0으로 종료해야 해요.

허용 실행 시간

테스트 러너는 풀이 하나당 20초 동안 CPU 100%와 3GB 메모리를 사용할 수 있어요. 20초가 지나면 프로세스가 중단되고 시간 초과로 보고돼요.

Note

시간 초과 가능성을 줄이려면 성능 모범 사례 문서를 따르는 것을 적극 권장해요.

출력 형식

results.json 파일에서는 다음 필드를 지원해요.

최상위

버전

키: version, 타입: number, 포함 여부: 필수

버전: 1, 2, 3

이 파일이 따르는 명세의 버전이에요.

  • 1: 테스트 러너가 개별 테스트에 대한 정보를 제공할 수 없는 트랙용이에요.
  • 2: 테스트 러너가 개별 테스트 정보를 출력할 수 있는 트랙용이에요. 개념 연습 문제가 있는 트랙의 최소 필수 버전이에요.
  • 3: 테스트 러너가 개별 테스트를 작업에 연결할 수 있는 트랙용이에요.

상태

키: status, 타입: string, 포함 여부: 필수

버전: 1, 2, 3

다음 전체 상태가 유효해요.

  • pass: 모든 테스트가 통과했어요
  • fail: 하나 이상의 테스트가 fail 또는 error 상태예요
  • error: 실행된 테스트가 없어요 (보통 컴파일 오류나 구문 오류를 뜻해요)

error 상태는 오직 모든 테스트에서 오류가 발생한 경우에만 사용해야 해요. 컴파일 언어에서는 보통 코드가 컴파일되지 않아 생기는 결과예요. 인터프리터 언어에서는 파일 파싱을 막는 구문 오류 같은 런타임 오류예요.

메시지

키: message, 타입: string, 포함 여부: status = error일 때, 또는 status = fail이고 version = 1일 때 필수

버전: 1, 2, 3

상태가 error일 때(테스트가 하나도 제대로 실행되지 않았을 때)는 최상위 message 키를 제공해야 해요. 이 키는 사용자에게 발생한 오류를 알려줘야 해요. 문제를 디버깅하는 방법에 대해 사용자가 받는 유일한 정보이므로, 최대한 명확해야 해요.

  • /full/path/to 형태는 도움이 되지 않는 ECR 관련 데이터를 포함하게 되므로, 경로를 <solution-dir>/relative/path처럼 단순화하세요.
  • 가능하거나 해당되는 경우에는 사용자 코드가 아닌 스택은 접어 주세요.
  • 맥락(즉, 오류 메시지) 없이 호출 스택을 보여주지 마세요.
  • 가능하다면 오류 메시지를 바꾸지 마세요. 그래야 오류를 더 쉽게 검색할 수 있어요.

Ruby에서는 구문 오류가 발생하면 런타임 오류와 스택 트레이스를 제공해요. 컴파일 언어에서는 컴파일 오류를 제공해야 해요.

최상위 message 값은 65535자로 제한돼요. 값에 멀티바이트 문자가 포함되면 실제 최대 길이는 그보다 짧아져요.

상태가 error가 아니면 값을 null로 설정하거나 키를 아예 생략하세요.

테스트

키: tests, 타입: array, 포함 여부: status = fail 또는 status = pass일 때 필수

버전: 2, 3

아래 "테스트별" 섹션에 명시된 대로, 테스트 결과의 배열이에요.

테스트는 테스트 파일에 명시된 순서대로 반드시 반환해야 해요. 테스트를 무작위 순서로 실행하는 언어라면, 결과를 테스트 파일에 명시된 순서에 맞춰 다시 정렬해야 할 수도 있어요.

그 이유는 학생에게는 첫 번째 실패만 보여주기 때문에, 올바른 실패가 표시되는 게 중요하기 때문이에요. 테스트는 보통 테스트 파일에 TDD 방식으로 정렬되어 있고, 실습 연습 문제에서는 학생이 편집기에서 테스트 파일을 보기 때문에, 결과를 테스트 파일과 일치시키는 게 아주 중요해요.

테스트별

이름

키: name, 타입: string, 포함 여부: 필수

버전: 2, 3

사람이 읽기 쉬운 형식의 테스트 이름이에요.

테스트 코드

키: test_code, 타입: string, 포함 여부: 개념 연습 문제일 때 필수

버전: 2, 3

이 키는 개념 연습 문제에서는 반드시 포함해야 하고, 실습 연습 문제에서는 가급적 포함하는 게 좋아요. 이 요구 사항이 다른 이유는 개념 연습 문제에서는 학생에게 테스트가 보이지 않기 때문이에요. 그래서 test_code가 표시되지 않으면 문제를 풀 수 없을 수도 있어요. 반면 실습 연습 문제에서는 테스트가 보여요.

테스트되는 명령의 본문이에요. 예를 들어 다음 Ruby 테스트는:

def test_duplicate_items_uniqs_list
  cart = ShoppingCart.new
  cart.add(:STARIC)
  cart.add(:MEDNEW)
  cart.add(:MEDNEW)
  assert_equal 'Newspaper, Rice', cart.items_list
end

다음과 같은 test_code 값을 반환해야 해요.

"cart = ShoppingCart.new
cart.add(:STARIC)
cart.add(:MEDNEW)
cart.add(:MEDNEW)
assert_equal 'Newspaper, Rice', cart.items_list"

(JSON이 유효하도록 줄바꿈은 \n로 바꿔요).

상태

키: status, 타입: string, 포함 여부: 필수

버전: 2, 3

다음 테스트별 상태가 유효해요.

  • pass: 테스트가 통과했어요
  • fail: 테스트가 실패했어요
  • error: 테스트에서 오류가 발생했어요. 즉, 값을 반환하지 않았어요

메시지

키: message, 타입: string, 포함 여부: status가 fail 또는 error일 때 필수

버전: 2, 3

테스트별 message 키는 status가 fail 또는 error인 테스트의 결과를 반환할 때 사용해요. 최대한 사람이 읽기 쉬워야 해요. 여기에 쓰인 내용은 학생의 테스트가 통과하지 못했을 때 학생에게 표시돼요. 테스트 실패 메시지나 오류 메시지가 없으면 값을 null로 설정하거나 키를 아예 생략하세요. 여기에 테스트 스위트의 출력을 내보내도 괜찮아요. message 값은 길이 제한이 없어요.

출력

키: output, 타입: string, 포함 여부: 선택

버전: 2, 3

테스트별 output 키는 사용자가 테스트를 위해 의도적으로 출력한 내용을 저장하고 내보낼 때 사용해야 해요.

  • 사용자 출력을 만들어 내는 모든 테스트 결과에 이 키를 붙여야 해요.
  • 사용자가 직접 출력한 내용만 표시해야 하고, 테스트 러너가 자동으로 출력한 내용은 표시하면 안 돼요.
  • 일반적인 방법으로 출력되는 내용을 잡아낼 수도 있고(예: Ruby의 puts, Python의 print, C#의 Debug.WriteLine), 사용자가 쓸 수 있는 메서드를 제공할 수도 있어요(예를 들어 Ruby 테스트 러너는 표준 puts 메서드와 같은 특성을 지닌, 전역에서 사용할 수 있는 debug 메서드를 사용자에게 제공해요).
  • 출력은 반드시 500자로 제한해야 해요. 이 경우 잘라내면서 "출력이 잘렸어요. 500자로 제한해 주세요"라는 메시지를 붙이거나, 오류를 반환하는 것 모두 괜찮아요.

작업 ID

키: task_id, 타입: number, 포함 여부: 선택

버전: 3

테스트를 작업 ID를 통해 특정 작업에 연결해요. 작업 ID는 작업 제목 앞에 쓰인 숫자예요. 정확히 _하나_의 작업에만 연결할 수 있을 때만 테스트를 작업에 연결하세요.

현재로선 개념 연습 문제에만 테스트를 연결할 수 있는 잘 정의된 작업이 있지만, 나중에는 달라질 수도 있어요.

예를 들어 다음 instructions.md 파일을 살펴봐요.

# Instructions

You're going to write some code to help Lucian cook an exquisite lasagna from his favorite cook book.

## 1. Define the expected oven time in minutes

...

## 2. Calculate the remaining oven time in minutes

...

이 지시문은 두 개의 작업을 정의해요.

  1. 예상 오븐 시간을 분 단위로 정의하기
  2. 남은 오븐 시간을 분 단위로 계산하기

그러면 results.json 파일에 다음과 같은 항목을 넣을 수 있어요.

{
  "name": "Expected oven time in minutes",
  "status": "pass",
  "task_id": 1,
  "test_code": "Assert.Equal(40, Lasagna.ExpectedMinutesInOven());"
}

이 테스트는 이제 첫 번째 작업인 "예상 오븐 시간을 분 단위로 정의하기"에 연결돼요. 이름이 작업 설명과 꼭 일치해야 하는 건 아니라는 점에 유의하세요.

트랙에서 이를 구현하는 방법은 여러 가지예요.

  • 테스트 파일 안의 테스트에 메타데이터를 추가하고(예: 어트리뷰트/애너테이션/주석 사용), 테스트를 실행할 때 테스트 러너가 이 메타데이터를 읽게 해요.
  • 테스트 이름과 작업 ID의 매핑을 별도 파일(예: 연습 문제의 .meta/config.json 파일)에 저장하고, 이 정보를 생성된 results.json 파일에 병합해요.
    • 테스트 스위트를 자동으로 정적 분석하고, 테스트 실행 시점에 테스트 결과와 병합해요.
      • AST 분석이나 텍스트 파싱으로 처리할 수 있어요.

예시

다음은 버전별로 유효한 results.json 파일이 어떤 모습인지 보여주는 예시예요.

v1 예시

{
  "version": 1,
  "status": "fail",
  "message": "Failed: test_answer\nExpected: 42, actual: 3"
}

v2 예시

{
  "version": 2,
  "status": "fail",
  "message": null,
  "tests": [
    {
      "name": "Test that the thing works",
      "status": "fail",
      "message": "Expected 42 but got 123123",
      "output": "Debugging information output by the user",
      "test_code": "assert_equal 42, answerToTheUltimateQuestion()"
    }
  ]
}

v3 예시

{
  "version": 3,
  "status": "fail",
  "message": null,
  "tests": [
    {
      "name": "Test that the thing works",
      "status": "fail",
      "message": "Expected 42 but got 123123",
      "output": "Debugging information output by the user",
      "test_code": "assert_equal 42, answerToTheUltimateQuestion()",
      "task_id": 1
    }
  ]
}

UI/UX 관련 고려 사항

테스트 실패 시

학생의 풀이가 테스트에 실패하면 다음과 같은 내용을 표시해야 해요.

Test Code:
  <test_code>

Test Result:
  <message>

테스트 성공 시

풀이가 테스트를 통과하면 다음과 같은 내용을 표시해야 해요.

Test Code:
  <test_code>

언어의 테스트 스위트에 메타데이터를 추가하는 방법

모든 길은 로마로 통한다고, 여기에 정해진 방법은 없어요. 지금까지는 여러 가지 접근 방식이 쓰였어요.

  • 직접 작성한 보조 JSON 파일을 테스트 실행 시점에 테스트 결과와 병합해요.
  • 테스트 스위트를 자동으로 정적 분석하고, 테스트 실행 시점에 테스트 결과와 병합해요.
    • AST 분석이나 텍스트 파싱으로 처리할 수 있어요.