'행위 주도 개발'에 해당되는 글 2건

  1. 2009.10.31 What's in a Story? (written by Dan North)
  2. 2009.09.13 Introducing BDD (Behaviour Driven Development) 3
We can work IT out2009. 10. 31. 01:39
이 글은 행위 주도 개발(Behaviour-Driven Developement)를 주창한 댄 놀쓰(Dan North)가 그의 블로그에 쓴 글의 번역입니다. (번역: 이홍주)

행위 주도 개발은 밖에서 안으로 향한 "outside-in" 기법이다. 그것은 비지니스적 요구사항을 식별하는 것부터 시작해서 그런 요구사항 각각을  충족시키는 특성들의 묶음에 대해 파고든다. 각각의 특성들은 "스토리"로 기록되는데,  그것은 인수조건들과 함께 특성들의 범위에 대해 정의한다. 이 글은 스토리들과 그것들의 인수조건(acceptance criteria)을 정의하고 식별하는 BDD 접근법에 대해 소개한다.


서론

소프트웨어 출하는 비지니스적 요구사항을 충족시키는 소프트웨어를 만드는 행위다. 쉬워보이지만, 행적적이거나 환경적 요인들이 이런 사실을 떠올리지 못할만큼 혼란스럽게 만든다. 때때로 소프트웨어 출하는 상급자들을 만족시키는 낙관적인 보고서들을 작성하는 일이거나, 단지 월급 받는 고용인들을 바쁘게 만드는 일을 만드는 것 처럼 보여지기도 하는데, 이에 대한 이야기를 하자는 건 아니다.

보통 비지니스적 요구사항들은 너무 조잡해서 곧바로 소프트웨어를 만드는 데 사용될 수 없기 때문에 (만일 "운영 비용을 5% 절감한다" 라는 요구사항이 주어진다면 어디서부터 코딩을 시작해야 할까?) 일을 완수하기 위해서 중간 단계에서 매개하는 요구사항을 작성할 필요가 있다.

모두가 파악할 수 있을 정도로 요구사항이 명확하다면, 행위 주도 개발(Behaviour Driven Developement)은 요구사항에 대한 인식을구현, 검증되어 출하 가능한 상태의 코드가 되도록 하는 역할을 한다. 그러기 위해서 비지니스 이해관련자들, 분석가, 개발자, 그리고 테스터 모두가 그 일의 범위에 대해 공통적인 이해를 하도록 하는 어떤 수단으로 요구사항을 기술하는 일이 필요하다. 그리고 그것으로부터 모두가 공통적으로 "완료" 라는 말에 동의할 수 있게 되고, "그건 내가 바라던 바가 아니야" 라거나 "내가 이걸 말해준다는 걸 깜빡 했네" 같은 이중의 함정으로부터[각주:1] 벗어나게 된다.

동시에 이것이 스토리의 역할이다. 그것은 요구사항과 비지니스적 이득 그리고 우리 모두가 "완료" 라고 인정할 수 있게 하는 조건들에 대한 설명이어야 한다. 이것은 "요구사항 정의서" 또는 "기능 정의서"[각주:2] 등의 여러가지로 기술되는 다른 애자일 방법론들보다 더 정확한 정의다. (일의 범위가 결정되고 평가되고 합의된 상태라면, BDD 스토리로 비함수적인 요구사항을 손쉽게 기술할 수 있다.)


스토리의 형식

BDD 는 스토리에 형식을 입힌다. 이는 필수적인 건 아니어서 다른 스토리 형식을 쓰면서도 BDD 를 수행할 수는 있다. 하지만 다양한 형태와 크기의 많은 프로젝트들을 통해 효과가 있음이 증명되어왔기 때문에 여기 소개하고 있는 것이다. 스토리는 최소한 아래의 양식에 기술된 모든 요소들을 담고 있어야 한다. 

제목 (한 줄로 된 스토리 설명)
전말:
[역할]로 하여금
[이득]을 얻기 위해서
[기능]이 필요하다.

인수조건: (아래 시나리오들로 표시됨)

시나리오 1: 제목
조건 [조건 설명] 그리고 [또다른 조건 설명]...
만일 [발생한 사건 설명]
그러면 [기대 결과] 그리고 [또다른 기대 결과]...

시나리오 2: ...


스토리 전달하기

하나의 스토리는 몇몇 사람들간의 의사소통의 결과물이어야 한다. 어떤 비지니스 분석가가 비지니스 이해관계자[각주:3]와 기능 또는 요구사항에 대해 이야기하므로써 스토리의 전말(narrative)을 구조화 하는데 도움이 될 수 있다. 그런 후 테스터는 어떤 시나리오들이 중요하고 어떤 게 덜 유용한지 결정하므로써 인수조건의 형태로 스토리에 대한 범위를 정의한다. 기술분야 대표로 하여금 스토리에 포함된 일의 분량에 대한 대체적인 견적 파악을 가능하게 하고 대안을 제시하게 된다. 애초부터 요구사항을 낸 사람들만큼, 시스템에 대한 좋은 아이디어들이 요구사항을 구현하는 사람들로부터 많이 나온다.

이는 일련의 반복적인 과정(iterative process)일 수 있다. 이해관계자들은 그들이 원하는 것에 대해 알지언정 대게 얼마나 많은 노력이 드는지는 모르거나 혹은 어떻게 일이 배분되어야 하는지 모를 것이다. 기술전문가들과 테스트 전문가들의 도움으로 그들은 각각의 시나리오에서 비용과 이득의 타협점(trade-off)을 이해하고 그들이 그것을 원하는지에 대한 결정을 내릴 수 있다. 물론 역시 이는 다른 요구사항들과도 균형을 이뤄야 한다. 즉, 이 스토리를 더 구체화 할 것인지 혹은 다른 스토리로 넘어갈 것인지 말이다.

때때로 개발팀은 대략적인 파악조차 할 수 없을 때가 있다. 이런 경우 그들은 요구사항에 대한 이해를 위해 "spike" 라고 알려진 조사작업을 수행한다. (언젠가 이에 대해 더 자세히 다룰 계획이다.)


적절한 스토리의 특성

Introducing BDD 에서 들었던 것과 같은 예시를 통해서 ATM 에서 현금을 인출하는 경우에 대한 요구사항을 살펴보자.

스토리: 예금자 고객의 현금 인출

(역할) 예금자 고객으로 하여금
(이득) 은행이 문을 닫았을 때 돈을 얻을 수 있게 하기 위해
(기능) ATM 에서 현금을 인출이 필요하다.

시나리오 1: 계좌에 잔고가 있는 경우
(조건) 계좌 잔고가 $100 이다.
그리고 카드가 유효하다.
그리고 출금기에 현금이 충분하다.
(만일) 예금자가 $20 출금을 요청한다.
(그러면) ATM 은 $20 를 출력해야 한다.
그리고 계좌 잔고는 $80 이어야 한다.
그리고 카드가 반환되어야 한다.

시나리오 2: 계좌에 잔고가 부족한 경우
(조건) 계좌에 잔고가 $10 이다.
그리고 카드가 유효하다.
그리고 출금기에 현금이 충분하다.
(만일) 예금자가 $20 출금을 요청한다.
(그러면) ATM 은 현금을 출력하지 말아야 한다.
그리고 ATM 은 잔고가 부족함을 알려야 한다.
그리고 계좌 잔고는 $20 여야 한다.
그리고 카드가 반환되어야 한다.

시나리오 3: 사용 불능인 카드인 경우
(조건) 카드가 사용 불능이다.
(만일) 예금자가 $20 를 요청한다.
(그러면) ATM 은 카드를 반환해야 한다.
그리고 ATM 은 카드가 폐기되지 않고 존속되어왔음을 알려야 한다.

시나리오 4: ATM 에 현금이 불충분한 경우
...

이처럼 계좌 잔고나 카드 상태와 ATM 자체와 관련된 몇가지 시나리오들이 나온다. 이제 위의 스토리가 적절한지 아닌지를 판단하기 위해 파고들어가보자.



제목은 어떤 행위에 대한 것이어야 한다.


스토리의 제목, "예금자 고객의 현금 인출" 은 예금자가 수행하길 원하는 행동을 기술한다. 이 기능을 구현하기 전엔 예금자는 ATM 에서 현금을 출금할 수 없을 것이다. 반대로 그런 기능을 구현하여 적용해주면 출금이 가능해진다. 이는 우리에게 "완료"된 상태가 어떠해야 하는가를 판단하는데 명확한 출발점이 된다. 

만약 제목이 "계좌 관리자" 또는 "ATM 의 동작" 따위었다면 어느 시점이 "완료" 상태인가 불분명해지고 그것과 관련된 것들은 더욱 더 모호해질 꺼다. 일예로, "계좌 관리자"는 대출 계약에 가입시킬지도 모르고, "ATM의 동작"은 현금 카드의 PIN 번호를 바꿔버릴지도 모른다. 그래서 스토리의 제목은 항상 그 시스템을 사용하는 사용자 입장에서 바라본 실질적인 행위로 기술되어야 한다.



전말(narrative)은 역할, 기능, 이득을 포함해야 한다.


"[역할]로 하여금 [이득]을 얻기 위하여 [기능]이 필요하다" 는 템플릿에는 몇가지 장점이 있다. 전말 안에 역할을 구체화 하므로써 기능에 대해 누구와 이야기 해야할지 알게 된다. 이득을 구체화할 때는 스토리 작성자로 하여금 왜 그들이 그 기능을 필요로 하는지에 대해 생각하도록 한다.

그 기능이 실질적인 이득을 가져오지 못한다는 걸 알게 될 경우도 있다. 그런 때 대게는 어떤 스토리를 빠뜨렸음을 의미한다. 현재의 기능에 대한 하나의 스토리가 있을 때, 그 스토리가 전혀 다른 이득을 만들어 내는 상태라면 (그래서 여전히 쓸모 있긴 하지만) 본래의 이득을 위한 전혀 다른 기능에 대한 감춰진 스토리가 필요하다.

위의 예시는 적용될 기능 대해 직접적으로 관련된 예금자가 있기 때문에 그 기능이 어떤 동작을 하는지 조사하는 시작점을 보여준다.


시나리오 제목은 차이점을 나타내야 한다.

스토리를 작성하면서 시나리오들을 나란히 나열할 수 있어야 하고, 그 제목만으로만 각각이 어떻게 다른지가 나타나야 한다. 위 예시에 보면 시나리오의 설명에는 단지 각각의 시나리오가 어떻게 다른지만을 기술하고 있다. "예금자가 잔고가 불충분한 계좌에서 출금하려 할 때 트랜잭션을 처리할 수 없음을 알려야한다" 라는 식으로 장황하게 쓸 필요가 없다. 다른 것들과 비교했을 때 그것의 제목을 통해서 그것에 관심을 기울여야 할지가 명확해진다.


시나리오는 조건, 만일, 그러면[각주:4]의 용어로 기술되어야 한다.

여러 팀들의 경우를 봤을 때 BDD 를 적용하기에 이런 방식은(조건/만일/그러면) 가장 적합하고 유용한 수단이다. 단지 비지니스 사용자들, 분석가들, 테스터들 그리고 개발자들에게 "조건/만일/그러면" 의 어휘들을 적용하므로써 그들은 세상의 모호함에서 벗어나게 되었다.

모든 시나리오들이 단순하지만은 않다. 어떤 것들은 "조건 [기본 조건 내용] 만일 [발생한 사건 상황] 그러면 [기대 결과] 만일 [또다른 사건 상황] 그러면 [새로운 기대 결과] 등등"의 순으로 연속적인 상황들로 표현되는 게 최선이다. 예를 들면 마법사 방식의 웹사이트가 그런데, 복잡한 데이터 모델을 구성하기 위해서 연속적인 화면을 단계적으로 밟게 된다. "조건/만일/그러면" 의 단어들에 익숙해졌다면 사건과 결과를 연속적으로 혼합하는 것이 완벽하게 들어맞는다.

한가지 발생할 수 있는 행태로 의사소통이 필요한만큼 되지 못한 경우가 있다. 그럴 때는 추정된 조건("잔고 이상 출금된상태도 고려해야죠" 라는 말)을 빠뜨렸거나 기대 결과를 ("그런 경우 당연히 예금자는 카드를 돌려받게 됩니다." 라는 식으로) 검증하는 걸 잊었다는 걸 곧바로 알게 될 것이다. 나는 이런 경우를, 개발팀 리더가 분석가들과 개발자들이 서로 동문서답하고 있다고 느꼈지만 그들에게 그렇다는 걸 증명해낼 방법이 없었다고 했던 팀에서 목격한 적이 있다. "조건/만일/그러면" 어휘를 소개한지 며칠만에 그들간의 상호작용이 월등히 좋아졌음을 그들은 확인할 수 있었다.


조건들은 요구된 내용만큼만 빠짐없이 정의되어야 한다.

중도에 추가된 조건들은 혼란을 초래하는데, 그 스토리를 처음 보는 사람에게, 그가 기술진이건 비지니스적 입장에 서있건, 무얼 알고 있어야 하는지를 이해하기 어렵게 한다. 역으로 조건들 중 빠진 게 있다면 그것들은 사실이 아닌 억측이 된다. 주어진 조건으로부터 엉뚱한 결과를 만들게 됐다면 그건 무언가 빠졌기 때문일 거다.

위 예시에서 첫번째 시나리오는 계좌의 잔고, 카드 그리고 ATM 자체에 대한 이야기었다. 그 시나리오를 완성하기 위해 이 모든 것들이 필요하다. 세번째 시나리오의 경우 계좌의 잔고나 ATM 에 현금 보유 여부는 다뤄지지 않았다. 기계가 계좌 잔고나 ATM 의 상태와 상관 없이 카드를 먹은 채로 있을 꺼란 걸 암시한다.


사건(event)[각주:5]은 그것의 특징을 묘사해야 한다.

사건 자체는 매우 간결해야 하며 전형적으로 기술된 시나리오에 오직 한 번 사용 된다. 위에서 언급한 것처럼 어떤 시나리오들은 이보다 더 복잡하기도 하지만, 스토리를 이루는 대부분의 시나리오들은 하나의 사건에 초점을 맞추게 된다. 사건들은 정황(조건들)에 따라 다를 것이고 그에 상응하는 결과가 따르게 된다.


스토리는 한 번의 반복작업(iteration)이 될만큼 작아야 한다.

설명할 수 있는 단위로 쪼개지 않는 한 그렇게 할 수 있는 손쉽고 빠른 방법은 없다. 일반적으로 대여섯개 이상의 시나리오들이 있다면 그 스토리는 서로 비슷한 시나리오들끼리 묶어서 쪼개질 수도 있을 거다.

이글의 ATM 관련 예시로써는 이 스토리를 위해 얼마나 더 많은 시나리오들이 있는지 가늠할 수 없지만 몇 개는 더 나올 수 있을 걸로 짐작한다. 본래 이 스토리에는 세가지 "부품들(moving parts)"이 있는데, 이름하여 계좌 잔고, 현금 카드의 상태 그리고 ATM 의 상태 등이다. 여기서 현금카드에 대해서 좀 더 세분화 될 수 있다. 즉, 카드의 유효기간이 지난 경우 그 카드로 현금을 인출할 수는 없지만 ATM 은 그 카드를 사용자에게 돌려줄 것인가? 만약 ATM 이 트랜잭션을 처리하는 도중에 고장이 난다면? 카드에 당좌 대월 기능이 있는 경우라면?

그래서 아래와 같이 몇몇의 더 작은 단위의 스토리로 쪼개는 것이 좋을 것이다.

  • 예금주가 현금을 인출한다 (가정: ATM 이 정상이고 카드가 유효하다)
  • 예금주가 정지된 카드로 현금을 인출한다 (가정: ATM 이 정상이다)
  • 예금주가 고장난 ATM 으로 현금을 인출한다 (가정: 카드는 유효하다)

이것이 인위적으로 보일 수 있지만, 큰 단위의 스토리의 진행을 비지니스적 용어로 설명해주고 데이터를 근거로한 추적을 가능하게 한다. 여기서 중요한 것은 항상 시나리오들을 통해서 (가정들을 명백히 하면서) 기술적인 흐름보다 (예를 들어, 반복작업에서 데이터베이스 처리나 다음 반복작업에서의 GUI 등의 처리) 비지니스적 흐름을 따라 그 스토리를 풀어내야 한다는 것이다. 이로써 비지니스는 기술적인 용어들을 사용하기보다 그 자체의 용어들을 통해서 그 과정이 설명될 수 있다.


그래서 이것이 Use Cases 와 다른 점은 뭘까?

유즈 케이스란 것에 대해 생각해보자. 나는 앨리스태어 코번(Alistair Cockburn)이 설명하는 (내가 RUP-as-waterfall 프로젝트[각주:6]들에서 맞닥드렸던 Over-engineering 이 발생하는 형태와 대립적인) Use Cases 에 광적인 팬이다. 나는 Use Case 를 바탕으로 한 프로젝트를 수행한 경험이 별로 없기 때문에 그와 비교하는 설명을 할 자신은 없다.

물론 나는 (결과나 목표의) 낮은 정밀도에서 시작해서 높은 정밀도를 향해 일을 진행하면서 그 과정에 더 많은 시나리오들을 받아들이는 그의 프로세스에 동의한다. BDD 관점에서 이것은 비지니스적 결과에서 시작하는 것을 의미하며 고차원적 기능 영역들에 대해 인수조건에 따른 구체적인 스토리들을 파고드는 것이다.

실제로는 어떤 종류의 프로세스가 요구사항을 식별하고 구체화 하는지는 문제되지 않는다. 만일 그것이 생각들을 체계화하는 데 도움이 된다면 요구사항 문서들을 작성하는 것도 괜찮다. 그러나 그 문서들이 모든 생각들을 실질적으로 요약하고 있을 것으로 착각하면서 여기저기 전달해서는 안된다. 그대신 요구사항 문서 또는 Use Case 모음을 한쪽에 놓고서, 머리 속의 모든 해답들을 쏟아낸 지식 만큼, 혹은 최소한 지금 시점에서 업무의 개요를 파악하기 충분할만큼, 비지니스적 결과로부터 스토리들을 정의하는 일에 착수해야 한다.


요약
 
행위 주도 개발은 최소 기능 단위로써, 나아가 출하의 단위로써 스토리를 사용한다. 인수조건들은 스토리의 본질적인 부분이다. 그것들은 스토리의 행위 범위를 정의하는 데 영향을 미치고 또한 스토리가 "완료" 된 상황에 대해 공통적으로 동의할 수 있도록 한다. 뿐만 아니라 계획 단계에서 견적을 추정하는 데 기본 단위로 사용되기도 한다.

가장 중요한 것은 그런 스토리들은 프로젝트의 이해관계자들, 비지니스 분석가들, 테스터들 그리고 개발자들간의 의사소통의 결과물이란 점이다. BDD 는 그것이 개발 프로세스의 산출물인만큼 프로젝트와 관계된 다양한 역할의 사람들 사이에 상호작용에 대한 것이기도 하다.



  1. 역자 주: gumption traps; 어떤 사건 또는 사고방식으로써 프로젝트를 시작하거나 계속 하도록 하는 열의를 잃게 하고 포기하도록 하는 어떤 사건 또는 사고방식을 말한다. [본문으로]
  2. 역자 주: 본래는 promise of conversation, description of a feature 지만 여러가지 개발 방법론에서 말하는 계획,분석 단계 산출물이면 무엇이든 문장의 의미를 해치지 않고 사용될 수 있을 것 같아서 다른 말로 대체함. [본문으로]
  3. 필자 주: 해당 기능에 대해서 실질적인 관심을 보이는 사람이어야 한다. 즉, 스토리에 따라서 운영자, 법무 담당 또는 보안 담당자 등이 된다. [본문으로]
  4. 역자주: 이글은 BDD 방식의 스토리 정의에 대한 것으로 특정 플랫폼의 어떤 도구에 대한 설명은 아니다. 그러나 가장 보편적으로 사용되고 있는 cucumber 에서 "given-when-then" 의 템플릿이 "조건-만일-그러면" 으로 한글화되어 사용되고 있기 때문에 이를 번역에 사용했다. "given-when-then" 은 영어 자연어에서 잘 이해되지만 이를 "조건-만일-그러면" 으로 번역한 후에는 자연어스럽지 않게 되는 문제는 있다. [본문으로]
  5. 역자주: "조건-만일-그러면" 템플릿에 "만일" 에 기술되는 사건을 의미한다. [본문으로]
  6. 역자주: 산출물 중심의 개발 방법론이 적용된 프로젝트로 이해하면 된다. [본문으로]
Posted by Lyle
We can work IT out2009. 9. 13. 18:03
이 글은 댄 놀쓰(Dan North)가 2006년 3월에 Better Software 에 기고한 Introducing BDD 의 번역입니다. (번역 : 이홍주)

문제가 있었다면, 서로 다른 환경의 프로젝트에 대해 애자일 방법론인 테스트 주도 개발(test-driven development; 이하 TDD)을 수행하도록 하거나 가르칠 때마다 동일한 혼란 또는 오해들과 마주치곤 했다는 거다. 개발자들은 어디서부터 시작할지, 어떤 것을 테스트하고 어떤 것은 하지 않을지, 한번에 얼마만큼을 테스트할지, 테스트에 어떤 이름을 붙일지 그리고 테스트가 왜 실패했는지 등을 고민했다.

TDD 라는 계곡에 깊히 들어갈 수록 내 스스로의 여정이 점점 더 어두운 계곡의 연속이었던 것에 비해, 반복 수련을[각주:1]를 사용할 때 유용하다.


의미심장한 테스트 이름은 테스트가 실패했을 때 도움이 된다.

얼마 후, 코드를 변경한 것이 테스트 실패로 이어졌을 때 테스트 메서드 이름을 보는 걸로 그 코드에 의도된(부여된) 행위를 확인할 수 있다는 걸 알았다. 대체로 아래 셋 중 하나의 경우였다.

  • 내가 버그를 만들었다. 내가 나빠. 해결책: 버그 고치기.
  • 의도된 그 행위는 여전히 적절하지만 다른 곳으로 옮겨졌다. 해결책: 테스트를 옮기고 필요하면 내용도 고친다.
  • 시스템의 조건이 바뀌거나 해서 그 행위는 더이상 맞지 않다. 해결책: 테스트를 삭제한다.

애자일 프로젝트에선 그것에 대한 이해가 진전된 만큼 후자쪽의 경우가 더 많이 발생한다. 불행히도, TDD 초보자들은 테스트 코드들을 삭제하는 일이 마치 그들의 코드 품질을 조금이라도 낮추는 일이 될 것처럼 선천적인 두려움을 갖는다.

형태 변화인 will 또는 shall 과 비교하면 should 란 단어에 대한 애매함이 명확해진다. should 는 해당 테스트의 조건에 대해 "진짜 그래야 하나?" ("Should it? Really?") 라는 암시적인 질문을 하도록 한다. 이는 테스트 실패가 우리가 만든 버그에서 비롯된 것인지 아니면 단지 시스템의 행위에 대한 과거의 가정들이 현재에는 맞지 않기 때문인지를 쉽게 판별하도록 해준다.


"Behaviour" 는 "test" 보다 더 유용한 단어다.

이제 "test" 단어를 지울 수 있는 도구(agiledox)와 테스트 메서드마다 적용할 탬플릿이 생겼다. 문득 TDD 에 대한 사람들의 오해들이 거의 대부분 "test" 란 단어에서 시작됐다는 게 떠올랐다.

테스팅을 TDD 의 본질이 아니라고는 할 수 없지만, 작성한 테스트 메서드 집합은 우리가 코딩 작업을 확실히 하도록 보장하는 효과적인 방법이다. 그럼에도 메서드들이 시스템의 행위를 완전하게 기술하고있지 못하면 결국 그것들은 우리들을 방심하게 만든다.


나는 TDD 를 하면서 "test" 대신 "behaviour" 라는 말을 쓰기 시작했고 그것은 단지 적합할뿐만아니라 훈련과정에서 발생하는 모든 영역의 의문들을 풀어주기도 했다. 이제 나는 그런 TDD 관련 질문들에 대한 답을 얻었다. 당신의 테스트를 뭐라고 부를지는 쉽다. 당신이 관계된 다음 행위를 기술하는 문장이면 된다. 얼마나 테스트를 해야 하는지는 고민할 필요도 없다. 그냥 그만큼의 행위들을 하나의 문장으로 기술하면 된다. 테스트가 실패했을 때 어떻게 할지는 단순히 위에서 언급한 단계를 따라하면 된다. 버그를 만났거나 행위가 바뀌거나 혹은 테스트가 더이상 적절하지 않은 경우 등에도 말이다.

테스트에 관한 사유를 행위에 대한 사유로 바꾸는 것에 심각해지면서 나는 테스트 주도 개발(TDD)를 행위 주도 개발(BDD)로 부르기 시작했다.


JBehave 는 테스트에 있어 행위를 강조한다.

2003년 말에 내가 설파해온 것에 대해 돈 또는 시간을 투자하기로 했다. JUnit 을 대체할 JBehave 를 만들기 시작했는데, 테스트에 관한 것들을 행위를 검증하는 내용을 중심으로 한 용어들로 대체했다. 나의 새로운 행위 주도적 주문(呪文)들을 엄격하게 고수했을 때 그런 프레임워크가 어떻게 진화하는지 확인하기 위함이었다. 또한 그것이 태스트 주도적인 용어들로 인한 소동들을 배제한 채 TDD 와 BDD 를 소개하는 데 유용한 교육용 도구가 될 걸로 기대했다.

위에서 예시한 가상의 CustomerLookup 클래스의 행위를 정의한다면 CustomerLookupBehaviour 라는 클래스를 작성할 수 있겠고, 이는 "should" 로 시작되는 메서드들을 포함하게 된다. JUnit 이 테스트를 수행할 때 처럼, 특정 행위가 수행될 때 행위 클래스를 초기화하고 각각의 행위에 대한 메서드들이 차례로 호출되는 식이다. 그 수행되는 진행상황이 리포트될 거고 종국에 요약된 내용이 출력되게 된다.

나의 첫번째 이정표는 JBehave 에 자체 검증 기능을 만드는 것이었다. 그러기 위해 단지 스스로를 실행할 수 있도록 하는 행위를 추가했다. JUnit 의 모든 테스트들을 JBehave 의 행위들로 옮기고 JUnit 과 동일하게 즉각적인 응답을 얻을 수 있었다.


그 다음으로 가장 중요한 행위를 판단한다.

그런 후에 비지니스적 가치에 대한 개념을 확정했다. 물론 내가 소프트웨어를 만드는 목적에 대해 언제나 알고 있었지만 지금 당장 코딩하고 있는 것에 대한 가치에 대해 심각하게 생각해본 적은 없었다. 또한명의 동료인 비지니스 아날리스트 크리스 매츠(Chris Matts)는 행위 주도 개발의 맥락에서의 비지니스적 가치에 대해 생각해보도록 유도했다.

JBehave 가 그것에 대해서도 재귀적(self-hosting)으로 사용될 수 있도록 만들려고 작정한 상태에서, 그것에 계속 집중할 수 있도록 하는데 "이 시스템이 하지 않는 가장 중요한 것은 무엇인가?" 라고 질문하는 것이 유용하단 걸 알게 됐다.

이 질문은 우리가 아직 구현하지 않은 기능의 가치를 확인하고 우선순위를 메기도록 한다. 또한 행위에 관한 메서드 이름을 체계적으로 나타내는 데도 유용하다. 해당 시스템이 어떤 의미 있는 행위인 X 를 하지 않는 상태에서, 그 X 가 중요하여 행위하도록 해야 한다면, 그 다음의 행위 메서드는 단순히 아래와 같이 된다.
public void shouldDoX() {
    // ...
}

이로써 TDD 에 대한 또다른 질문, 어디서 시작할 것인가에 대한 답을 얻었다.


요구사항 또한 행위이다.

이제 나는 TDD가 어떻게 수행되는지 내가 맞닥드린 함정들을 피할 수 있는 접근법을 이해함은 물론 그것을 설명할 수 있는 프레임워크를 갖게 됐다.


2004년이 끝나갈 무렵 나의 이런 새로운 발견인 행위 주도적 용어들을 맷츠에게 설명하는 중에 그는 "그건 그냥 분석 같은데?" 라는 말을 했다. 긴 침묵이 흐른 뒤에 우리는 모든 행위 주도적 생각을 요구사항 정의에 적용해보기로 했다. 만약 우리가 분석가들, 테스터들, 개발자들과 비지니스를 위한 일관적인 용어를 만들 수 있다면, 기술진이 비지니스 관련자들과 이야기할 때 발생하는 모호함과 소통 오류를 상당수 없앨 수 있는 방법이 될 것 같았다.



BDD는 분석가들에게 "공통언어(ubiquitous language)" 가 된다.

근래에 에릭 에반스(Eric Evans)는 그의 베스트셀러가 된 Domain-Driven Design 이란 책을 출간했다. 그 책에서 그는 모델링의 개념을 비지니스 도메인을 기반으로한 공통언어를 사용하는 시스템으로 기술하므로써 프로그램 코드의 근저에 비지니스 용어가 배어들도록 했다.

크리스와 나는 우리가 분석 과정 그 자체에 대한 공통언어를 정의하려고 시도하고 있다는 걸 실감했다. 우리는 훌륭한 출발점을 얻었다. 조직 안에서 공통적으로 사용되는 아래와 같은 스토리 템플릿이 이미 있었다.


[X] 로 하여금 (As a)
[Z] 를 얻게 하기 위하여 (so that)
[Y] 가 필요하다 (I want)


여기서 Y 는 어떤 기능이고, Z 는 그 기능에서 얻어지는 이득이거나 가치고, X 는 득을 보게 되는 사람이거나 역할이다. 이것들의 장점은 그것을 처음 정의할 때에 스토리를 배포하는 것의 가치를 명확히 하도록 강제한다는 데 있다. 어떤 스토리에 실질적인 비지니스적 가치가 없을 때 그것은 "... [단지 내가 하기] 때문에 나는 [그 기능]이 필요해"[각주:2] 템플릿을 적용하면 위 예시에서 두 가지 시나리오는 아래와 같이 표현될 수 있다.

시나리오 1: 계좌에 잔고가 있음
조건 계좌에 잔고가 있다
그리고 카드가 유효하다
그리고 출금기에 현금이 들어있다
만일 고객이 현금을 요청한다
그러면 계좌에 출금액을 기입한다
그리고 현금을 출금한다
그리고 카드가 반환한다

"그리고"는 다수의 기본 조건이나 다수의 결과를 상호 연결하기 위해 씌었다.

시나리오 2: 계좌가 당좌 대월 한도를 넘어 당좌 차월 상태

조건 계좌가 당좌 차월 상태다
그리고 카드가 유효하다
만일 고객이 현금 요청한다
그러면 출금거절 메시지가 출력된다
그리고 현금이 출금되지 않는다
그리고 카드가 반환된다

두 시나리도 모두 똑같은 이벤트 발생을 바탕으로 하고 일부 기본 조건들과 결과들 조차도 보편적인 것들이다. 기본 조건들과, 이벤트 발생, 그리고 그결과들이란 템플릿을 재사용하여 활용하길 바란다.


인수조건은 실행 가능해야 한다

이 시나리오의 요소들(조건, 사건, 결과)은 잘 다듬어져있어서 코드에 직접 재연될 수 있다. JBehave 는 시나리오의 요소들을 자바 클래스들로 곧바로 사상할 수 있는 객체 모델을 정의한다.

각각의 조건들을 재연하는 클래스를 작성해보자.

public class AccountIsInCredit implements Given {
    public void setup(World world) {
        ...
    }
}
public class CardIsValid implements Given {
    public void setup(World world) {
        ...
    }
}

그리고 사건 발생을 위한 클래스를 작성한다.
public class CustomerRequestCash implements Event {
    public void occurIn(World world) {
        ...
    }
}

그리고 결과들에 대해서도 마찬가지로 적용한다.  JBehave 는 이 모든 것들을 연결하시켜 실행한다. 이는 말하자면 하나의 "세상"을 창조하는 식으로, 객체들을 저장하는 어딘가가 되기도 하면서 그것들을 각각의 조건에 순차적으로 전달하므로써 투명한 상태로 세상을 번창시킬 수 있도록 한다. 그런다음 JBehave는 "사건 발생"을 세상에 알리고, 그로써 그 시나리오의 실질적인 행위를 수행한다. 결국 그것은 우리가 정의한 스토리의 결과들로 제어된다.

각각의 요소들을 대변하는 클래스가 있다면 다른 시나리오들 또는 스토리들에 재사용할 수도 있다. 첫번째로 그런 요소들은 계좌에 잔고가 있거나 혹은 카드가 유효한 것으로 설정되어 가상으로 구현된다. 이것들은 행위를 구현하는 출발점을 형성한다. 응용프로그램을 구현하면서 특정 조건들과 결과들은 구현된 실상의 클래스들이 사용되도록 대체되는데, 그리하여 시간이 지남에 따라 해당 시나리오가 완성되고, 구석구석의 기능들을 적절하게 테스트하기에 적합해지는 것이다.


BDD의 현재와 미래

잠깐동안의 중단이 있었지만, JBehave 다시 활발한 개발이 이뤄졌다.  그것의 핵심부는 꽤 견고하게 완성된 상태다. 다음 단계는 IntelliJ IDEA 와 Eclipse 와 같은 보편적인 Java IDE 와 통합하는 작업이다.

데이브 아스텔스(Dave Astels)는 BDD 에 활발하게 진척시켜왔다. 그의 블로그와 다양한 기고들은 질풍같은 활동을 자극해왔는데, 특히 rspec 프로젝트가 루비 언어로 BDD 프레임워크를 만들도록 하는 데서 그랬다. 나는 JBehave 를 루비로 구현하는 rbehave 작업에 착수했다.

내 몇몇 동료들은 그들이 수행하고 있는 다양한 프로젝트들에 BDD 를 적용하고 있고 그것들은 매우 성공적이었다. JBehave 의 (인수조건을 검증하는) 스토리 실행기능은 개발이 활발히 진행중이다.

목표는 분석가들과 테스터들이 일반적인 문서편집기로 스토리들을 포착할 수 있도록 하기 위해, 비지니스 영역의 모든 언어들로 행위 클래스들에 대한 토막들을 생성할 수 있는 통합 에디터를 만드는 것이다. BDD 는 많은 사람들의 도움으로 발전했고 나는 그들 모두에게 크게 감사한다.




  1. [/footnote] 통한 점진적 숙달은 별로 없었다는 느낌이 들었다. 돌아보건데 내가 "와, 문이 열렸구다." 라고 앞으로를 생각했던 것에 비해 "누군가 그걸 말해줬더라면 지금 같진 않았을텐데!" 하며 뒤만 돌아보는 경우가 더 많았다. 그래서 나는 TDD 가 좋은 일들로 연결되면서 함정들을 피해가는 방법으로써 보여질 수 있도록 해야겠다고 생각했다.

내가 찾은 해답은 행위 주도 개발(behaviour-driven development; 이하 BDD)이었다. BDD는 기정 애자일 방법론들로부터 진화한 것이면서 그런 방법론들이 애자일적인 소프트웨어의 출하를 처음 접하는 팀들에게 접근하기 쉽고 효율적이도록 디자인 되었다. 시간이 지남에 따라 BDD 는 테스팅에 대한 애자일적 분석과 자동화된 합격판정 등의 더 넓은 영역을 포함하도록 확장되어왔다.


테스트 메서드 이름은 문장이 되어야 한다.

나의 첫번째 깨달음의 순간은 내 동료인 크리스 스티븐슨(Chris Stevenson)이 개발한 agiledox 라는 믿을 수 없이 단순한 도구를 보았을 때였다. 그것은 JUnit 의 테스트 클래스를 사용하여 메서드 이름들을 평범한 문장들로 출력하는데, 예를 들어 하나의 테스트 케이스는 아래와 같은 모양을 하고 있다. 
public class CustomerLookupTest extends TestCase {
    testFindsCustomerById() { ... }
    testFailsForDuplicateCustomers() { ... }
    ...
}
그리고 이를 아래와 같이 풀어서 표현한다.

CustomerLookup
- finds customer by id
- fails for duplicate customers
- ...

클래스 이름과 메서드 이름들에 쓰인 "test" 란 단어는 벗겨 없어졌고, 캐멀 케이스(camel-case) 메서드 이름은 일반적인 구문으로 바뀌었다. 이것이 전부지만 그 영향은 놀라울 정도다.

개발자들은 이것이 적어도 그들의 문서화 작업에 유용하겠다는 생각으로 테스트 메서드의 이름들을 실상의 문장들처럼 작성하게 됐다. 더욱이 메서드 이름에 비지니스 용어를 사용한다면 생성된 문서가 비지니스 사용자들이나 분석가들 그리고 테스터들까지 이해할 수 있게 된다는 것도 알게 됐다.


간단한 문장 템플릿이 테스트 메서드에 집중하게 한다.

그래서 나는 테스트 메서드 이름이 "should" 로 시작되도록 하는 관습을 만들고 따르기 시작했다. "이 클래스가 어떤 행위를 해야 한다(should do something)" 라는 식의 문장 템플릿은 현재 다루는 클래스에 대해서만 정의될 수 있음을 의미한다. 이때문에 집중하게 되고, 만약 이런 템플릿에 어울리지 않는 테스트 코드를 작성하고 있다면 그것이 엉뚱한 행위란 걸 알게 한다.

실례로, 내가 화면에서 입력받은 내용에 대한 유효성 검사(validation)를 수행하는 클래스를 작성할 때, 모든 필드들은 이름과 성 따위의 일반적인 고객에 대한 상세정보였는데, 거기엔 생일 필드와 나이 필드도 함께 있었다. 나는 ClientDetailsValidatorTest 라는 클래스를 작성하면서 testShouldFailForMissingSurname 과 testShouldFailForMissingTitle 등의 메서드들을 명명했다.

그런 후에 나이를 계산하는 코드를 짜면서 판단하기 묘한 문제에 직면했는데 이런 것들이다. 나이와 생일이 빠짐 없이 있다 해도 서로 맞지 않으면? 생일 값이 오늘 날짜를 나타내고 있다면? 연도 없이 월일만 있어서 나이를 계산하지 못하는 경우라면? 이런 문제들이 생각날 때마다 그런 행위들에 대해 성가신 테스트 메서드 이름들 추가해야 했기 때문에 손 떼고 다르게 해보기로 마음먹었다. 결국 AgeCalculatorTest 라는 테스트 클래스를 동반한 새로운 클래스 AgeCalculator 를 작성하게 됐다. 나이를 계산하기 위한 모든 행위들은 나이 계산기 역할을 하는 새로운 클래스로 옮겨갔고, 결국 유효성 검사에서 클래스의 나이 계산이란 행위에 대해서는 새로운 클래스와 함께 올바로 작용하는지만을 테스하면 되게 되었다.

만약 어떤 클래스가 하나 이상의 행위를 하고 있다면, 보통 내가 다른 클래스들을 만들어서 그것들을 처리하도록 해야 한다는 징조라고 본다. 그럴 땐 무엇을 한다 를 기술하는 인터페이스로써 새로운 서비스를 정의한 다음 그 서비스를 클래스의 생성자에 전달한다.
public class ClientDetailsValidator {
    private final AgeCalculator ageCalc;
    public ClientDetailsValidator(AgeCalculator ageCalc) {
        this.ageCalc = ageCalc;
    }
}
객체들을 서로 연결하는, dependency injection 으로 알려진 이러한 방법은 특히 시뮬레이션을 위한 모의 객체[footnote] [본문으로]
  • [/footnote] 라는 식으로 귀착되곤 한다. 이런 접근은 일부 더 난해한 요구사항들에 대한 유효범위를 판단하기 쉽게 해주기도 한다.

    그런 출발점에서부터 매츠와 나는 모든 애자일 테스터들이 이미 알고 있는 것을 발견하기 시작했다. 어떤 스토리의 행위는 단순히 그것의 인수조건(acceptance criteria)이며, 그 시스템이 모든 인수조건을 만족할 때 그것은 정확히 동작하고 있으며 그렇지 않을 경우는 그 반대라는 것이다. 그래서 우리는 스토리의 인수조건을 포착한 하나의 템플릿을 만들어냈다.

    이 템플릿은 어느정도 자유로워서 분석가들에게 인위적이거나 강제적이지 않으면서도 충분히 구조화 되어서 그 스토리의 구성 요소들에 접근하여 자동화할 수 있어야 했다. 우리는 시나리오라는 명목으로 그런 인수조건의 작성에 착수했는데 아래와 같은 형식을 채택했다.


    조건이 주어짐, (Given)
    만일 사건이 발생했을 때, (When)
    그러면 어떤 일이 수행된다. (Then)


    이를 설명하기 위해서 고전적인 ATM 기계의 예시를 들어보자. 스토리 카드들 중 하나는 아래와 같을 것이다.

    제목: 고객이 현금을 출금한다
    고객으로 하여금,
    은행에서 줄을 서지 않아도 되기 위해,
    ATM을 통한 현금을 출금이 필요하다.

    이제 우리는 이 스토리가 어느 시점에 배포되어야 할지를 어떻게 알 수 있을까? 몇가지 시나리오들을 고려해볼 수 있다. 계좌에 잔고가 있을 수도 있고, 잔고 이상의 당좌 차월 상태지만 당좌 대월 한도를 넘지 않은 경우, 그리고 당좌 대월 한도보다 더 당좌 차월이 발생한 경우 등이다. 물론 그밖의 경우, 말하자면 계좌에 잔고는 있으나 출금 금액으로인해 당좌 차월이 된다거나, 출금기에 충분한 현금이 들어있지 않은 등의 상황도 있을 수 있다.

    위의 조건-만일-그러면(given-when-then)[footnote] [본문으로]
  • Posted by Lyle