xUnit 패밀리의 세계적 확산 — JUnit에서 퍼져나간 이식의 물결

1997년 등장한 JUnit의 성공은 자바 커뮤니티에 머무르지 않았다. CppUnit, NUnit, PyUnit, Test::Unit, PHPUnit 등의 이식판이 1999년부터 2002년 무렵까지 주요 언어 전반으로 퍼져나가며 「xUnit」이라는 총칭 아래 하나의 패밀리를 이루었다. 게라드 메자로스(Gerard Meszaros)가 2007년 저서에서 체계화한 테스트 패턴과 테스트 스멀(test smell), 그리고 TestNG·xUnit.net이라는 2세대 구현까지 따라가며, 단위 테스트가 특정 언어에 얽매이지 않는 업계 표준이 된 과정을 그린다.

testinghistoryxunitjunittest-patternsunit-testing

앞 장에서 살펴본 JUnit의 성공은 자바 커뮤니티 안에만 머무르지 않았다. 1990년대 말부터 2000년대 초에 걸쳐, JUnit과 같은 골격 — TestCase, assertion, fixture, setUp/tearDown — 을 가진 프레임워크가 거의 모든 주요 언어로 잇달아 이식되어 갔다.

이 이식의 물결로 인해 「테스트 프레임워크를 사용해 테스트를 작성한다」는 스타일은 특정 언어의 관습이 아니라 업계를 가로지르는 표준적인 실천이 되었다. 이번 장에서는 이 확산의 양상과, 이들을 「xUnit」이라는 하나의 총칭으로 묶는 것의 의미를 짚어본다.

JUnit에서 시작된 이식 러시

언어 프레임워크 주요 인물 시작·초기 시기
C++ CppUnit 마이클 페더스(Michael Feathers, 윈도우용 초기 이식), 제롬 라코스트(Jerome Lacoste, 유닉스/솔라리스 이식) 2000년 무렵
.NET NUnit 필립 크레이그(Philip Craig, 2000년 XP2000 콘퍼런스에서 시연), 제임스 뉴커크(James Newkirk, 2002년 NUnit 2에서 속성 기반으로 개편) 2000년
Python unittest(PyUnit) 스티브 퍼셀(Steve Purcell) 1999년 개발, Python 2.1(2001년)에서 표준 라이브러리로 편입
Ruby Test::Unit 네이서니얼 탤벗(Nathaniel Talbott) 2000년 무렵
PHP PHPUnit 제바스티안 베르크만(Sebastian Bergmann) 2001년 착수, 2002년 첫 버전 공개
JavaScript JsUnit 외 에드워드 히엇(Edward Hieatt) 외 2001년 무렵
Perl Test::More / Test::Simple 마이클 G. 슈원(Michael G Schwern) 등 2001년 무렵

이식이 집중된 시기는 대략 1999년에서 2002년 무렵이다. JUnit이 1997년에 등장한 지 불과 몇 년 만에, 주요 언어 거의 전부에 동종의 프레임워크가 퍼진 셈이다. 이를 주도한 것은 특정 기업이나 표준화 단체가 아니라, 대부분 각 언어 커뮤니티에 속한 개인 개발자들이었다. 당시 오픈소스 문화의 확산과, xUnit 아키텍처 자체의 단순함·이식 용이성이 이 급속한 확산을 뒷받침했다.

어떤 언어에서도 같은 골격

흥미로운 점은 정적 타입 언어(자바, C#, C++)든 동적 타입 언어(파이썬, 루비, PHP, 펄)든, 객체지향을 전제로 하든 아니든, 거의 같은 골격이 아무런 위화감 없이 이식된다는 것이다.

다음은 파이썬의 unittest와 루비의 Test::Unit으로 「계산기에 2와 3을 더하면 5가 된다」는 완전히 같은 테스트를 작성한 예다.

# Python: unittest(PyUnit 계열)
import unittest

class CalculatorTest(unittest.TestCase):
    def setUp(self):
        self.calculator = Calculator()

    def test_adds_two_positive_numbers(self):
        self.assertEqual(5, self.calculator.add(2, 3))
# Ruby: Test::Unit 계열
require 'test/unit'

class CalculatorTest < Test::Unit::TestCase
  def setup
    @calculator = Calculator.new
  end

  def test_adds_two_positive_numbers
    assert_equal(5, @calculator.add(2, 3))
  end
end

TestCase 상속, setUp, assert*라는 세 가지 요소는 언어가 달라져도 거의 동일한 형태로 나타난다. 단위 테스트라는 행위의 본질이 「전제 조건을 갖춘다 → 대상을 실행한다 → 결과를 검증한다」는 단순한 세 단계로 환원된다는 사실을, 이 보편성이 말해준다.

「xUnit」이라는 총칭의 정착

이들 JUnit 계열 프레임워크 군을 가리켜 「xUnit」이라 부르는 관습이 자리 잡았다. 「x」 자리에 각 언어나 도구의 머리글자(J, N, Cpp, Py…)를 넣으면 그대로 프레임워크 이름이 된다는 말장난인 동시에, 「공통의 아키텍처를 가진 하나의 패밀리」라는 인식을 간결하게 나타내는 이름이기도 하다.

이 총칭이 널리 쓰이게 된 결정적 계기가 다음에 살펴볼 게라드 메자로스의 저서다. 언어와 도구의 차이를 넘어 공유되는 설계 패턴과 안티패턴을 정리하면서, 메자로스는 이들 구현체 전체를 「xUnit 패밀리」로 묶어 다루었다. 이로써 「JUnit 방식」도 「NUnit 방식」도 아닌 「xUnit 방식」이라는, 특정 구현에 얽매이지 않는 논의가 가능해졌다.

게라드 메자로스, 『xUnit Test Patterns』(2007)

2007년, 게라드 메자로스는 Addison-Wesley에서 『xUnit Test Patterns: Refactoring Test Code』를 출간했다. 그때까지 각 커뮤니티에 암묵지로 쌓여 있던 xUnit 계열 프레임워크 활용 노하우를, 패턴(pattern)과 테스트 스멀(test smell)이라는 형식으로 처음으로 체계적으로 정리한 책이다.

제목이 보여주듯 이 책은 「xUnit」이라는 말을 표제에 내건 가장 유명한 문헌이며, 이 총칭이 업계에 자리 잡는 데도 큰 역할을 했다. 두꺼운 책이지만 패턴집이라는 성격상, 필요한 부분만 사전처럼 참조하는 용도를 전제로 쓰였다.

대표적인 테스트 패턴은 다음과 같다.

  • Test Data Builder — 테스트에 필요한 객체를 유연하게 조립하는 빌더
  • Object Mother — 자주 쓰는 테스트용 객체 생성을 한데 모아 제공하는 팩토리 성격의 장치
  • Test Stub / Mock Object — 의존 대상을 대체하는 테스트 더블(test double)의 여러 패턴

이들 패턴은 모두 「테스트 준비 코드를 읽기 쉽게, 그러면서도 변경에 강하게 유지하려면 어떻게 해야 하는가」라는 일관된 문제의식에서 나왔다.

반면 피해야 할 「테스트 스멀」로는 다음과 같은 것들이 꼽힌다. 특정 언어나 프레임워크에 고유한 문제가 아니라, xUnit 아키텍처를 쓰는 한 어떤 구현에서도 일어날 수 있는 공통의 함정으로 정리되어 있다는 점이 중요하다.

테스트 스멀 내용
Fragile Test(깨지기 쉬운 테스트) 프로덕션 코드의 사소한 변경에도 테스트가 자주 깨진다
Obscure Test(불명료한 테스트) 무엇을 검증하는지 읽어내기 어렵다
Mystery Guest 테스트 외부(파일이나 DB 등)에 의존해, 테스트만 봐서는 전제를 알 수 없다
Test Code Duplication 테스트 코드 간 중복이 많아 유지보수 비용이 높다
Slow Tests 실행이 느려 개발의 피드백 루프를 저해한다
Assertion Roulette 하나의 테스트에 다수의 assertion이 나열되어, 어느 것이 실패했는지 알기 어렵다

이러한 분류는 오늘날에도 실무 코드 리뷰에서 참조되는, 테스트 유지보수성 논의의 직접적인 원류다.

2세대 xUnit 구현 — TestNG와 xUnit.net

2000년대 전반의 이식 러시가 일단락된 뒤에도, xUnit 아키텍처 위에 서면서 초기 설계상의 제약을 해소하려는 「2세대」 구현이 등장했다.

  • TestNG — 2004년, 세드릭 뵈스트(Cédric Beust)가 개발해 자바 커뮤니티에 공개했다. 당시의 JUnit(JUnit 3 계열)이 테스트 스위트 구성, 병렬 실행, 파라미터화된 테스트 기술에서 유연성이 부족하다고 느낀 것이 개발 동기로 알려져 있다. JSR 175로 도입된 자바의 애노테이션 기능을 일찍이 활용해, 테스트 그룹화·병렬 실행·타임아웃 지정 등을 선구적으로 구현했다.
  • xUnit.net — 2006년 무렵, NUnit의 원작자인 제임스 뉴커크가 마이크로소프트 동료 브래드 윌슨(Brad Wilson)과 함께 개발을 시작한 .NET용 신규 테스트 프레임워크로, 2007년 9월에 공개되었다. 오랜 확장으로 NUnit 코드베이스가 복잡해진 데 대한 반성을 바탕으로, [SetUp]/[TearDown] 같은 전용 애트리뷰트를 없애고 생성자나 IDisposable로 fixture를 표현하는 등, 더 단순한 API를 목표로 처음부터 다시 작성되었다.

이들 2세대 구현은 xUnit 아키텍처의 기본 발상(TestCase, assertion, fixture)을 계승하면서도, 초기 이식판이 안고 있던 제약을 하나씩 해소해 나갔다. xUnit 패밀리가 단순한 이식의 역사가 아니라, 지금도 개선이 계속되는 살아 있는 아키텍처임을 보여준다.

정리 — 이 이식의 물결이 단위 테스트를 세계 표준으로 만든 이유

JUnit에서 xUnit 패밀리로의 확산이 지닌 의미는 단순히 「편리한 도구가 늘었다」는 것이 아니다. 다음 세 가지가 더 중요하다.

  1. 어휘의 공통화 — 어떤 언어든 「TestCase」「assertion」「fixture」「setUp/tearDown」라는 같은 말로 단위 테스트를 논할 수 있게 되었다
  2. 지식의 이전 가능성 — 한 언어에서 xUnit 계열 프레임워크 사용법을 익힌 개발자는, 다른 언어로 옮겨가서도 같은 발상으로 테스트를 쓰기 시작할 수 있다
  3. 패턴과 안티패턴의 언어 횡단적 축적 — 메자로스의 정리 덕분에 「좋은 테스트」「나쁜 테스트」를 판단하는 기준이 특정 언어나 도구에 얽매이지 않는 형태로 공유되었다

이 보편적인 토대가 있었기에, 이어지는 테스트 주도 개발(TDD), 행위 주도 개발(BDD), 목(mock)의 체계화 같은 논의가 언어를 가리지 않고 횡단적으로 발전할 수 있었다.

이 글에서 다음 글로

앞 장에서 다룬 JUnit의 탄생 이후 불과 몇 년 만에 xUnit 패밀리가 전 세계로 퍼져나간 과정을 따라갔다. 그러나 이 공통 기반 위에서 「테스트를 어떻게 쓸 것인가」라는 기법론이 독자적으로 발전하기 시작한다. 다음 장에서는 익스트림 프로그래밍(XP)의 한 실천에서 출발해 마침내 독립된 기법으로 자리 잡은 테스트 주도 개발(TDD) — 그리고 2014년의 대논쟁 「TDD is dead」에 이르는 흥륭과 대립의 역사를 따라간다.

← Back to 소프트웨어 테스트의 계보