[DDD ⑤] BDD(Behavior Driven Development)

일반입력 :2008/09/10 10:45

강규영(오픈마루 스튜디오 웹서비스 개발팀)

행동 주도 개발(Behavior Driven Development, 이하 BDD)이란 TDD(Test Driven Development)에 DDD(Domain Driven Design)의 스타일(유비쿼터스 언어, 임베디드 도메인 전용 언어 등)을 적용하여 탄생한 소프트웨어 개발 방법의 하나이다. 이 글에서는 필자가 직접 개발하여 스프링노트 서비스 개발에 적용한 자바스크립트용 BDD 프레임워크인 JSSpec을 통해 BDD를 소개하고, BDD를 둘러싼 몇 가지 논의들을 살펴보고자 한다.

TDD라는 이름은 테스터와 개발자 모두에게 다양한 오해를 받아왔다. TDD를 처음 접할 때, 테스터는 '왜 개발자들이 우리 일을 하려고 하지?'와 같은 오해를, 개발자는 '개발하기에도 바쁜데 테스트 코드까지 만들어야 한다고?'와 같은 오해를 하기 십상이었다.

또는 ‘정말 TDD가 프로그램을 완벽하게 테스트해서 시스템의 무결성을 보장해 주는가?’에 대한 와 같은 종류의 의구심도 가졌다.

BDD는 처음에 TDD가 가지는 테스트 이외의 측면, 특히 설계(design)적인 측면을 강조한다. 이를 통해 TDD를 처음 접하는 사람의 오해를 덜어주고 초심자가 TDD를 더 빨리 익힐 수 있도록 하자는 취지에서 ThoughtWorks(리팩토링 책의 저자인 Martin Fowler의 회사)의 Dan North에 의해 탄생되었다.

어떻게 오해를 덜겠다는 것일까? 가장 중요한 것은 어휘의 변화다. ‘테스트’라는 단어 자체를 비롯하여 TDD에서 쓰는 다양한 어휘들이 TDD에 대한 각종 오해의 근원이라고 보고 이러한 어휘들을 바로잡은 것이 BDD의 시초라고 할 수 있다.

BDD 소개

‘코드를 작성하기 전에 테스트를 먼저 작성해야 한다’는 문장은 TDD에 대한 설명이다. 그런데 잠깐, 코드가 없는데 뭘 테스트하지?

‘코드가 있다고 가정하고 이 코드가 제대로 수행될 때 얻게 될 예상되는 결과를….’ 설명이 길어진다. 하지만 용어를 다음과 같이 바꿔보면 어떻게 될까?

‘코드를 작성하기 전에 코드가 수행할 행위에 대한 명세를 먼저 작성해야 한다’이렇게 하면 쉽게 좋은 습관이라고 수긍하게 되지 않을까?

그렇다. 아직 존재하지 않는 코드에 대해여 테스트를 작성한다기 보다는, 행위에 대한 명세를 작성하는 것이라고 생각하면 직관적으로 쉽게 이해가 된다. 이것이 BDD의 핵심이다. 물론 바꿔야 할 어휘는 ‘테스트’ 뿐이 아니다.

assert (expected_value, actual_value)

TDD에서는 xUnit 스타일의 프레임워크는 위와 같은 방식을 사용한다. 한글로 옮겨보자면 ‘단언(예상되는 값, 실제 값)’ 정도가 된다. BDD 프레임워크는 보통 다음과 같은 표현을 선호한다.

actual_value.should_be (expected_value)

역시 한글로 옮겨보자면 ‘실제 값.같아야 한다(예상되는 값과)’과 같이 자연스럽다(영어 문장으로는 어순이 더욱 자연스럽다는 것을 알 수 있다).

이와 같이 용어를 바꿔서 결과적으로 ‘테스트(test)’는 ‘행동 명세(behavioral specification)’가 되고, 개개의 ‘테스트 케이스(test case)’들의 행동에 대한 ‘예시(example)’가 되며 ‘단언’은 ‘~이어야 한다’가 된다.

‘단지 말만 바꾸었을 뿐이라고! 말장난 아니야?’ 그렇게 생각할 수도 있다. 하지만 ‘말을 바꾼 것’은 ‘단지’라기 보다는 ‘무려’라고 표현하는 것이 더 적절하다(‘언어가 사고에 미치는 영향’ 참조). 새로운 어휘의 도입을 시작으로 사고의 전환과 다양한 통찰을 얻게 되고, 이를 통해 BDD는 계속 발전하고 있다(BDD 공식 위키인 http://behaviour-driven.org/ 에서 현재 진행형인 BDD의 확장/발전을 지켜볼 수 있다).

TDD에 xUnit 시리즈의 프레임워크가 있다면 BDD에도 그런 무언가가 있지 않을까? 물론 있다. 아직 xUnit 시리즈만큼 다양한 언어를 지원하고 있지는 않지만 몇 가지 쓸 만한 프레임워크들이 공개되어 있는 상태다.

최초의 BDD 프레임워크인 JBehave( http://jbehave.org/ )는 이름에서 유추할 수 있듯이 자바를 위한 프레임워크이다. 가장 활발한 개발이 이루어지고 있는 프레임워크는 아마도 루비의 RSpec( http://rspec.rubyforge.org/ )이 아닐까 싶다. 그리고 자바스크립트에는 (필자가 만든 ) JSSpec( http://jania.pe.kr/aw/moin.cgi/JSSpec )이 있다. 이 글에서는JSSpec을 조금 자세히 살펴볼 것이다. 그 밖의 구현들에 대해서는 다음 두 URL을 참고하기 바란다.

http://behaviour-driven.org/Implementations

http://en.wikipedia.org/wiki/Behavior_driven_development

자바스크립트 BDD 프레임워크

그 많은 언어들을 놔두고 왜 하필 자바스크립트 BDD 프레임워크일까? 짧고 직관적인 대답은 이렇다. 필자가 지금 하는 일이 자바스크립트로 웹 기반 편집기를 개발하는 것이다 보니 자연스럽게 그렇게 되었다. 조금 더 설득력 있는 대답을 다음과 같다.

우선 동적인 언어들(자바스크립트를 비롯하여 루비, 파이선, 그루비 등)은 정적인 언어들(자바나 C# 등 정적 타입 검사를 하는 언어들)에 비해 BDD나 TDD의 결과로 얻어지는 일종의 안전망(테스트 혹은 스펙 모음)으로부터 더 많은 혜택을 누릴 수 있다.

둘째, 자바스크립트는 그 특성상 언어 명세서에 의해 결정되지 않는 부분들이 많다. 자바스크립트는 애초에 독립적으로 실행되기 보다는 웹 브라우저나 웹 서버 등과 같은 호스트 환경(host environment) 안에서 호스트 환경이 제공하는 객체들을 조작하기 위해 쓰일 작은 언어로 설계되었다.

또 실제로도 그렇게 많이 쓰이기 때문에 다양한 호스트 환경에서 동일하게 작동할 것임을 보장하기 위한 가장 좋은 방법은 최대한 다양한 환경에서 최대한 자주 실행을 해보는 것이다. 자동화된 테스트(혹은 스펙)는 이러한 상황에서 엄청난 이점을 제공한다.

마지막으로, 스프링노트 서비스의 편집기 모듈을 오픈 소스 프로젝트(오픈 소스 프로젝트 Xquared 참고)로 분리하는 과정에서 BDD 기반의 스펙들을 만들고 싶었는데 아직 쓸 만한 자바스크립트 BDD 프레임워크가 없었기 때문이기도 했다.

Script.aculo.us 라는 유명한 자바스크립트 라이브러리 내에 기본적인 BDD 어휘를 사용하는 단위 테스트 프레임워크가 내장되어 있긴 한데 매우 불안정한 상태이다.

JSSpec 소개

개념적인 부분은 어느 정도 설명을 한 것 같으니 이제 자바스크립트를 위한 BDD 프레임워크인 JSSpec을 기반으로 한 실제 예제를 통해 BDD의 구체적인 모습을 살펴보자.

JSSpec의 프로젝트 홈페이지(http://code.google.com/p/jsspec)에 접속하여 최신 릴리즈를 다운받아서 압축을 풀면 자바스크립트 파일과 HTML 파일 그리고 CSS 파일이 나온다. 이 중 demo.html을 열어보면 <리스트 1>과 같은 JSSpec 예제 코드를 볼 수 있다.

<리스트 1>의 강조 표시된 부분의 코드는 JSSpec의 실행에 필요한 파일들을 링크하고 있다. 그 아래에 나오는 자바스크립트 코드 블록은 JSSpec의 실행 가능한 행동 명세들(behavioral specification)이다. 하나를 살펴보면 <리스트 2>와 같다.

<리스트 2>는 이퀄리티 연산자(==)의 행동(behavior)에 대한 명세(specification)를 설명(describe)하고 있다. 즉, 이퀄리티 연산자는(Equality operator) 동일한 값을 갖는 서로 다른 두 데이터 객체에 대해서 참(true)을 반환해야 한다(should be)고 말하고 있다.

불행히도 자바스크립트의 이퀄리티 연산자에서는 이 스펙이 실패한다. 따라서 이 HTML 파일을 브라우저에서 열어보면 <화면 1>과 같은 메시지가 나올 것이다.

이 예제에서는 단일한 예제(example)를 포함하고 있는 단일한 describe()만 살펴보았지만, 보통은 하나의 HTML 파일 안에 두 개 이상의 describe()를 놓기도 하고, 각각의 describe() 안에는 여러 개의 예제들이 포함된다.

<화면 2>는 스프링노트 편집기인 Xquared의 스펙이 실행되고 있는 화면이다(Xquared는 현재 300여 개의 예제로 이루어진 스펙 모음을 포함하고 있으며 현재도 계속 늘고 있다).

JSSpec의 어휘들

JSSpec은 다른 BDD 프레임워크들과 비슷하게, 작성된 예제들이 자연스러운 영어 문장처럼 읽힐 수 있도록 하기 위해 다양한 어휘들을 제공한다. 앞서 살펴본 ‘should_be_….’ 이외에도 다음과 같은 표현들이 가능하다(이 밖의 나머지 어휘들에 대해서는 http://jania.pe.kr/aw/moin.cgi/JSSpec/Manual을 참조하길 바란다).

expect(Hello).should_have(4, characters);

// “Hello”는 4 글자이어야 한다.

expect([1,2,3]).should_have(4, items);

// 배열 [1,2,3]의 원소는 4 개이어야 한다.

expect({name:'Alan Kang', email:'jania902@gmail.com', accounts:['A', 'B']})

.should_have(3, accounts);

// 주어진 객체( {……} )는 3 개의 account를 가지고 있어야 한다.

expect([1,2,3]).should_have_at_least(4, items)

// 배열 [1,2,3]은 최소한 4 개의 원소를 가져야 한다.

expect(Hello world).should_be(Good-bye world)

// 문자열 “Hello world”는 “Good-bye world”와 같아야 한다.

코드 상의 표현뿐만 아니라 실행 결과도 최대한 자연스럽게 읽힐 수 있고, 무언가 잘못 되었을 경우 잘못된 부분이 무엇인지 쉽게 알아차릴 수 있도록 <화면 3>과 같은 형식으로 출력된다(위 예시들은 모두 실패하도록 만들어져 있는데 실패한 경우의 화면을 보여주기 위해 고의적으로 그렇게 한 것이다).

지금까지 JSSpec으로 작성된 BDD 예제를 몇 가지 살펴보았다. 여기까지만 보면 외관상 TDD와 별로 달라 보이지도 않는다. 하지만 지금까지 살펴본 모습은 BDD의 초창기 형태라고 할 수 있다. 앞서 잠깐 언급했지만, BDD는 TDD의 어휘들을 바로잡으면서 시작되었지만 현재는 그 이상의 개념으로 발전되고 있다.

아직 JSSpec에서는 지원하지 않고 있지만 완전한(full-stack) BDD가 어떤 모습인지 잠깐 살펴보도록 하자.

완전한 BDD는 요구사항 분석 단계부터 시작된다. 요구사항 분석이라고 하면 조금 거창하고, 사용자 스토리(XP 등에서 이야기하는 그것)에서부터 시작된다고 하는 것이 더 적절하겠다.

(도메인 주도 설계의 영향으로) BDD에서는 어휘의 통일(혹은 제한)을 매우 중요하게 생각하는데 이는 사용자 스토리를 적는 데에 있어서도 마찬가지이다. BDD는 사용자 스토리를 다음과 같이 형식화하여 적을 것을 장려한다.

<Story Title>

<Narrative>

As a <Role>

I want <Feature>

So that <Benefit>

예를 들면 다음과 같다.

Title: Customer withdraws cash

(고객이 현금을 인출한다)

Narrative

As a customer

(고객으로써)

I want to withdraw cash from ATM

(나는 현금인출기에서 현금을 인출하기를 원한다)

So that I don’t have to wait in line at the bank

(그래서, 은행에서 줄을 서지 않아도 될 수 있도록)

여기까지가 형식화된 사용자 스토리의 예시이다. 각 예시에 대해서는 여러 개의 시나리오로 구성된 ‘acceptance criteria’가 존재하는데, 행동 중심의 사례를 통해 위 스토리의 구체적인 예를 명시하는 것으로 형식은 다음과 같다.

Given , When, Then

이번에도 구체적인 예를 들어보자(위 스토리와 관련지어서).

Given the account is in credit, and the card is valid, and the dispenser contains cash

(계좌 및 카드가 정상이고 인출기 내부에 현금이 있는 상황에서)

When the customer requests cash

(고객이 현금 인출을 요청하면)

Then ensure the account is debited, and ensure cash is dispensed, and ensure the card is returned

(계좌에서 돈이 차감되어야 하고, 현금과 카드가 배출되어야 한다)

하나의 스토리에 대해 위와 같은 시나리오가 여러 개 존재할 수 있고, 시스템이 이 시나리오에 명시된 대로 행동하면 스토리가 올바르게 구현된 것으로 판단할 수 있다. 몇몇 독자 분들은 이미 눈치를 채셨겠지만 JSSpec은(전체 스토리의 맥락 하에서가 아닌) 개별 시나리오들을 표현하기에 적합하게 만들어져 있다.

시나리오 부분을 JSSpec으로 표현하면 다음과 같다. <리스트 3>은 Xquared 스펙의 일부인데, 현재 캐럿이 있는 곳의 블록 단위 엘리먼트를 삭제하는 명령(RemoveBlock)에 대한 스펙이다.

‘before’ 함수는 기존 xUnit의 setUp과 같이 각각의 예제(should ……)가 실행되기 먼저 실행된다. 위 예제에서는 ‘before’ 부분에서 Givens를 명시하고 있는데, 편집기에 A, B, C 세 개의 블록 엘리먼트가 주어진 상황이다.

‘should ……’ 부분은 Event와 Outcome을 함께 포함하고 있다. 가운데 블록(b)에 대해 RemoveBlock 명령을 수행하면 A, C 두 개의 블록만 남고, 캐럿은 C 블록으로 이동해야 한다(Outcome)고 명시하고 있다.

JSSpec은 앞으로 완전한 형태의 BDD를 지원하는 방향으로 가려고 하는데, 아마도 앞서 언급한 ATM 예제를 다음과 비슷한 형태로 표현할 수 있게 되지 않을까 싶다:

그럴듯하지 않은가? JSSpec 이야기는 이 정도에서 접기로 하고, 이제 BDD 자체에 다시 집중하도록 하자.

BDD는 애초에 TDD의 어휘들을 변형하면서 시작되었다. 따라서 비유적으로 말하자면 같은 혈통이라고 할 수 있다. 그리고 비슷한 점이 많으면 더 자주 싸우게 마련이다.

서로 다른 종교 간의 갈등 보다는 같은 종교 내의 종파 간 갈등이 더 잦은 경우가 많고, 완전히 다른 생물들 보다는 서로 비슷한 생태를 공유하는 생물들이 더 심하게 경쟁하며, 기술 또한 대체적으로 마찬가지이다.

BDD와 TDD도 예외가 아니어서 인터넷 상에 이런 저런 논쟁이 오가곤 하는데 필자가 생각하기에 유익해 보이는 이야기들을 몇 가지 소개해볼까 한다. BDD가 TDD보다 좋은 점에 대해서는 앞에서도 설명했고, 인터넷에 널린 자료들이 대부분 그러한 점에 대해 설명하고 있으므로 필자는 약간 반대로 해보고자 한다.

TDD는 너무 경직되어 있다?

인터넷에서 BDD에 관련된 글을 읽다 보면 간혹 ‘TDD는 너무 경직되어 있다’거나 ‘너무 교조적이다’와 같은 지적을 듣게 된다.

TDD는 너무 촘촘한 단위로 개발할 것을 강요한다거나, TDD의 사이클(테스트, 실패, 최단 시간 내 성공, 중복제거 및 의도 드러내기, 성공의 사이클)을 너무 교조적으로 따를 것을 권한다는 등의 내용이다. 하지만 필자는 이 같은 주장은 오해에서 비롯된 것이라고 생각한다.

TDD의 창시자라고 할 수 있는 켄트 백이 쓴 최초의 TDD 책(테스트 주도 개발, 인사이트)은 책의 절반가량을 아주 단순한 예제(Money 클래스)를 TDD로 개발하는 과정을 보여주는 것에 할애하고 있고, 그 개발 단계는 그야말로 굉장히 촘촘한 편이며, 반복적으로 ‘작은 스텝’의 중요성을 강조한다.

이렇다 보니 TDD에 대한 오해가 생길 법도 하다. 하지만 책을 상세히 읽은 독자라면 알겠지만, 작은 스텝을 강조하는 진짜 이유는 ‘항상 그렇게 작은 스텝으로 개발을 하게 하려고’하는 것이 아니라, ‘개발의 흐름상에서 원하는 순간에 작은 스텝을 밟고자 할 때 원하는 만큼 작은 스텝을 밟을 수 있기 위한 훈련’에 있다.

켄트 백은 ‘짧은 스텝을 밟을 수 있어야 한다’고 말하는 것이지 ‘짧은 스텝만을 밟아야 한다’고 말하는 것이 아니다.

사실 켄트 백은 TDD를 굉장히 넓은 의미로 사용하기도 하는데, 앞서 말한 책의 서문에 나오는 TDD의 정의를 살펴보면 그가 TDD를 어떻게 생각하고 있는지 감을 잡을 수 있다:

“TDD는 프로그래밍 도중에 내린 결정과 그 결정에 대한 피드백 사이의 간격에 대한 인식이자 그 간격을 제어하기 위한 기술이다.”

프로그래밍을 하다 보면 여러 가지 결정을 내릴 필요가 있다. ‘클래스를 새로 만들까? 메서드를 어느 클래스에 넣어야 하지? 변수 이름은 뭐로 지을까?’ 그리고 일단 결정을 내리면 얼마 후에 그에 대한 피드백이 돌아온다.

때론 버그로, 때론 지저분한 코드로, 때론 새로운 설계 메타포의 발견으로. 어떠한 피드백은 빠르게 돌아오고(몇 초 후에 발생하는 컴파일 에러), 어떠한 피드백은 매우 느리게 돌아온다(경쟁 서비스나 경쟁 제품으로 이탈하는 고객 수의 증가). TDD는 이러한 결정과 피드백 사이의 간격을 ‘인식’하고, 간격의 길이를 적극적으로 ‘제어’하기 위한 기술이라는 의미이다.

BDD는 TDD보다 더 좋은 대체물이다?

BDD는 TDD의 진보된 형태이니 더 좋은 대체물이라는 주장 또한 종종 볼 수 있지만 이것 역시 타당하지 않다고 생각한다. TDD에서 BDD로의 전이는 '진보'라기 보다는 '패러다임의 전환'과 유사한 성격을 가진다고 보는 편이 더 적절하다.

특히, 새 패러다임이 기존 패러다임은 갖지 못하는 장점을 제공하는 것과 동시에, 기존 패러다임이 갖고 있던 장점의 일부를 잃어버리기도 한다는 점에서 그렇다. BDD는 더 적절해 보이는 어휘를 제공하고, 이러한 어휘의 사용을 통해 BDD를 실천하는 개발자는 다양한 장점을 얻을 수 있다.

하지만 필자의 경험에 비추어 보면 BDD의 어휘로 인해 잃게 되는 것도 있는 것 같다.

한 가지 예를 들어보자. BDD의 B는 행동(behavior)을 뜻한다. 좀 더 정확히 말하자면 외적인 행동을 의미한다. 즉, BDD 스타일의 스펙을 작성할 때 모듈의 내부 구현이 아닌 외적인 행위에 초점을 맞출 것을 권장한다는 의미이다.

인터페이스와 구현을 분리하고 인터페이스에 집중하자는 얘기로 볼 수도 있는데 이는 일반적으로 좋은 습관이고, TDD를 실천하는 사람들도 종종 이렇게 하기 위해 노력한다. 하지만 TDD에는 분명 그 이상의 것이 있다.

예전에 TDD의 테스트가 블랙박스 테스트인지, 화이트박스 테스트인지에 대한 질문에 대해 켄트 백이 기똥찬 답을 한 적이 있는데, 바로 그레이박스 테스트라는 것이다. 때론 모듈이 블랙박스인 것처럼, 때론 모듈이 화이트박스인 것처럼 그때그때 필요에 따라 개발을 적절히 주도할 수 있는 방향으로 사용하면 되는 것이다.

예를 들어, TDD로 피보나치수열을 구하는 과정(http://jania.pe.kr/JavascriptTddFibo.html 참고)에서 개발자는 피보나치 함수를 블랙박스로 취급하지 않는다. TDD를 할 때엔 가끔 (테스트 케이스로) 객체를 이리 찔러보고, 저리 찔러보면서 앞으로 설계가 어떻게 진행될지 흥미진진해 하던 그런 느낌이 있었는데, BDD를 할 때엔 그런 느낌을 느끼기가 쉽지 않다.

그래서 BDD와 TDD 중 무엇을 선택하라는 말인가? 앞서 밝혔지만 BDD는 TDD의 모든 장점을 포함하고 덤으로 조금 더 얹어주는 TDD에 대한 대체물이 아니다. 이는 TDD 또한 마찬가지이다.

필자는 TDD를 몇 년 간 실천해 왔는데, 최근 BDD를 하면서 TDD를 할 때와는 상당히 다른 느낌을 받을 때가 많고, 이러한 느낌은 종종 새로운 지식의 학습이나 통찰로 이어진다. 결과적으로 평화를 사랑하는 필자의 제안은 이렇다: 둘 다 경험해 보는 것. :-)

필자는 독자 여러분들이 이 글을 통해 BDD에 대한 호기심을 갖게 되었기를 희망한다. 그리고 읽고만 끝낼 것이 아니라 직접 한 번 시도해보기를 강력히 권한다. 앞서 소개한 바와 같이 언어별로 다양한 BDD 프레임워크들이 이미 만들어져 있으니 자신이 주로 사용하는 언어의 프레임워크를 직접 한 번 사용해보시라.

혹시 자신이 주로 쓰는 언어에 BDD 프레임워크가 없다면? 그 또한 기쁜 일이 아닐 수 없다. 이참에 한 번 만들어 보면 좋지 않겠는가? 켄트 백은 새로운 언어를 배우기 위한 수단으로 해당 언어를 위한 xUnit 프레임워크를 만들곤 한다고 하였다.

또 위키의 아버지인 와드 커닝헴은 자신에게 유용한 작은 프로그램을 매일 하나씩 만들어보는 것이야 말로 고수가 되는 지름길이라 하였다(참고로, 그는 이 지침과 함께 다른 사람이 만든 길고 복잡한 코드를 읽는 연습도 병행하라고 말한다). 그럼, 즐거운 (B

참고자료

1. BDD 공식 홈페이지 : http://behavior-driven.org

2. JSSpec 홈페이지 : http://jania.pe.kr/aw/moin.cgi/JSSpec

3. 2007년 9월 한국 루비 세미나에서 BDD를 주제로 발표한 자료와 동영상 : http://jania.pe.kr/aw/moin.cgi/200709RubyKrSeminar

4. 모의객체주의와 고전주의에 대한 마틴 파울러의 글 : http://martinfowler.com/articles/mocksArentStubs.html

5. 켄트 백이 쓴 최초의 TDD 서적 : 테스트 주도 개발, 인사이트 출판사

* 이 기사는 ZDNet Korea의 제휴매체인 마이크로소프트웨어에 게재된 내용입니다.