본문으로 건너뛰기
입문20

xUnit 계열 프레임워크: JUnit·pytest·Jest의 공통 구조

언어마다 도구 이름은 다르지만(JUnit, pytest, Jest), xUnit 계열 프레임워크는 테스트 러너, 어서션, setup/teardown이라는 같은 뼈대를 공유한다.

  • #xUnit
  • #JUnit
  • #pytest
  • #Jest
  • #테스트프레임워크

개념

이름은 달라도 뼈대는 같다

Java는 JUnit, Python은 pytest, JavaScript는 Jest(또는 Vitest)를 주로 쓴다. 언어마다 도구 이름이 다르니 완전히 별개로 보이지만, 이들은 모두 1998년에 만들어진 원조 프레임워크 SUnit/JUnit의 구조를 계승한다 — 그래서 통칭 **"xUnit 계열"**이라 부른다. 하나의 구조를 이해하면 나머지도 낯설지 않다.

공통 구조 1 — 테스트 러너(Test Runner)

테스트 파일들을 찾아서 실행하고, 결과(통과/실패/에러)를 모아 보고서로 보여주는 도구다. 보통 명령어 한 줄로 프로젝트 전체의 테스트를 돈다.

# 언어마다 명령어는 다르지만 하는 일은 같다
pytest              # Python
npx jest            # JavaScript
./gradlew test       # Java (JUnit)

공통 구조 2 — 어서션(Assertion)

레슨 1의 AAA 패턴에서 "Assert" 단계에 쓰이는 문법이다. "이 값이 저 값과 같아야 한다", "이 값이 참이어야 한다" 같은 검증 문장을 표현한다.

// Jest
expect(result).toBe(9000)
expect(isValid).toBeTruthy()
# pytest
assert result == 9000
assert is_valid
// JUnit
assertEquals(9000, result);
assertTrue(isValid);

표현 방식은 다르지만 전부 "기대값과 실제값을 비교해서, 다르면 테스트를 실패시킨다"는 같은 일을 한다.

공통 구조 3 — Setup과 Teardown

여러 테스트가 공통으로 필요한 준비 작업(레슨 1의 Arrange에 해당)을 매번 반복해서 쓰지 않도록, 테스트 실행 전후에 자동으로 실행되는 훅이 있다.

beforeEach(() => {
  // 각 테스트 실행 전에 매번 실행 — 예: 테스트용 장바구니 초기화
})
 
afterEach(() => {
  // 각 테스트 실행 후에 매번 실행 — 예: 테스트 데이터 정리(모듈 7 레슨 2의 리셋과 연결)
})

이 개념은 모듈 7(레슨 2)에서 배운 시딩과 리셋이 테스트 코드 수준에서 구현된 형태다 — beforeEach가 시딩, afterEach가 리셋 역할을 한다.

테스트 결과 읽는 법

테스트를 실행하면 보통 이런 형태의 결과가 나온다.

✓ 10% 할인율이면 가격이 10% 줄어든다 (2ms)
✗ 재고가 0이면 구매 버튼이 비활성화된다 (1ms)
  Expected: true
  Received: false
 
Tests: 1 failed, 1 passed, 2 total

실패한 테스트는 기대값(Expected)과 실제값(Received)을 나란히 보여준다 — 이 두 값의 차이가 바로 "무엇이 예상과 다르게 동작했는가"에 대한 직접적인 단서다.

실무에서 왜 필요한가

CI 파이프라인(스테이지 4에서 다룬다)이 실패했을 때, 그 로그에 찍힌 Expected/Received 값을 읽을 줄 알면, 개발자를 부르기 전에 QA 스스로 "이게 실제 버그인지, 테스트 코드 자체의 문제인지" 1차 판단을 할 수 있다. 또한 여러 언어를 넘나드는 프로젝트(백엔드는 Python, 프론트엔드는 JavaScript)에서도, 프레임워크 이름에 겁먹지 않고 "이것도 결국 AAA 구조겠지"라고 접근할 수 있다.

실습 과제

과제 1 — 프레임워크 간 대응 관계 찾기 (10분)

다음 Jest 코드와 같은 의미의 pytest 코드를 이 레슨에서 배운 어서션 문법을 참고해 작성해본다.

test("빈 장바구니는 총액이 0이다", () => {
  expect(calculateTotal([])).toBe(0)
})

과제 2 — 실패 결과 해석하기 (10분)

다음 테스트 결과를 보고, 어떤 값이 예상과 다르게 나왔는지, 그리고 이게 코드 버그인지 테스트 코드 문제인지 확인하려면 무엇을 먼저 봐야 할지 적는다.

✗ VIP 회원은 배송비가 무료다 (3ms)
  Expected: 0
  Received: 3000

자가 체크리스트

  • xUnit 계열 프레임워크들이 공유하는 공통 구조 세 가지(러너·어서션·setup/teardown)를 설명할 수 있다
  • 서로 다른 언어의 어서션 문법이 결국 같은 개념이라는 것을 이해한다
  • beforeEach/afterEach가 모듈 7(레슨 2)의 시딩·리셋과 어떻게 연결되는지 설명할 수 있다
  • 테스트 실패 결과에서 Expected/Received 값을 읽고 원인을 추정할 수 있다

흔한 실수

  • 프레임워크 이름이 낯설면 아예 읽기를 포기한다. 이름은 달라도 구조는 거의 항상 같다.
  • Expected/Received 값을 확인하지 않고 "테스트가 실패했다"는 사실만 보고 원인을 개발자에게 전부 떠넘긴다. 두 값을 비교하는 것만으로도 1차 원인 추정이 가능한 경우가 많다.
  • setup/teardown 없이 매 테스트가 서로 영향을 주는 코드를 정상으로 여긴다. 이는 모듈 7(레슨 3)에서 배운 플래키 테스트의 흔한 원인이다.

참고 자료