JUnit로 검색한 결과 :: 시소커뮤니티[SSISO Community]
 
SSISO 카페 SSISO Source SSISO 구직 SSISO 쇼핑몰 SSISO 맛집
추천검색어 : JUnit   Log4j   ajax   spring   struts   struts-config.xml   Synchronized   책정보   Ajax 마스터하기   우측부분

회원가입 I 비밀번호 찾기


SSISO Community검색
SSISO Community메뉴
[카페목록보기]
[블로그등록하기]  
[블로그리스트]  
SSISO Community카페
블로그 카테고리
정치 경제
문화 칼럼
비디오게임 스포츠
핫이슈 TV
포토 온라인게임
PC게임 에뮬게임
라이프 사람들
유머 만화애니
방송 1
1 1
1 1
1 1
1 1
1

JUnit로 검색한 결과
등록일:2008-04-15 10:41:24
작성자:
제목:코드 품질 향상을 위해: 행위 주도 개발의 모험 (한글)


Behave를 배워야 할 때 아닌가?

developerWorks
문서 옵션

JavaScript가 필요한 문서 옵션은 디스플레이되지 않습니다.

수평출력으로 설정

이 페이지 출력

이 페이지를 이메일로 보내기

이 페이지를 이메일로 보내기

토론

영어원문

영어원문


제안 및 의견
피드백

난이도 : 초급

Andrew Glover, 대표, Stelligent Incorporated

2008 년 3 월 25 일

테스트 주도 개발(TDD)은 실제로 위대한 생각이었지만, 몇몇 개발자는 단지 '테스트(test)' 라는 단어가 주는 개념적 도약을 극복하지 못했습니다. 이 기사에서 Andrew Glover는 TDD의 위력을 프로그래밍 실천 속에 자연스럽게 녹아들게 하는 방법을 보여줍니다. JBehave를 통해 행위 주도 개발을 시작하면서, 프로그램의 결과물이 아닌 행위에 주목할 때 일어나는 일에 대해 살펴보세요.

개발자 테스팅은 분명히 좋은 것이다. 일찍 테스팅된다면, 다시 말하면 여러분이 코드를 작성할 때 더 좋을 것이다. 코드 품질에 관해서라면 특히 더 그러하다. 먼저 테스트를 작성한다면, 블루리본(역주: 블루리본에 대해 몇 가지 단서를 찾았으나 원저자가 확실히 무엇을 말하려 했는지 분명하지는 않다. 역자가 찾은 단서들의 공통점과 문맥을 고려했을 때, 일종의 '보상'의 의미로 해석하면 무리가 없을 것 같다)을 얻을 것이다. 자신이 짠 코드의 행위를 검사할 수 있고 예방적으로 디버그할 수 있다는 것이 주는 추가적인 위력은 바로 부인할 수 없을 정도의 빠른 속도다.

그 점을 알고 있다고 해도, 코드를 작성하기 전에 테스트를 작성하는 것을 표준적으로 실천하는 비판적 대중은 우리 근처에 존재하지 않는다. TDD가 단위 테스팅 프레임워크를 각광 받게 만든 익스트림 프로그래밍(Extreme Programming)으로부터 확장된 진화의 다음 단계인 것처럼, TDD라는 기초로부터 진화적 도약이 이루어질 것이다. 이번 달 기사에서는 TDD로부터 그보다 좀 더 직관적인 사촌이라고 할 수 있는 행위 중심 개발이라는 진화적 도약으로 독자들을 초대한다.

코드 품질 높이기
테스트 프레임워크, 품질에 초첨을 둔 코드 작성에 대해 알고 싶다면 Andrew Glover의 코드 품질 포럼을 보라.

행위 중심 개발

테 스트 우선 프로그래밍이 몇몇 사람에게는 잘 먹히지만, 모든 사람에게 그런 것은 아니다. 열광적으로 TDD를 수용한 수많은 애플리케이션 개발자가 있는가 하면, 적극적으로 그것에 저항하는 사람도 많이 있다. TestNG, Selenium, FEST 같은 많은 테스팅 프레임워크가 있지만, 코드를 테스트하지 않는 이유는 다양하다.

TDD 를 하지 않는 이유 중 가장 흔한 것이 "테스팅할 시간이 없어요"와 "테스트하기에 너무 복잡하고 힘든 코드"라는 것이다. 테스트 우선 프로그래밍의 또 다른 장애물은 테스트 우선이라는 개념 그 자체다. 우리 대부분은 테스팅을 감촉적(tactile) 행위, 추상적이기보다는 구체적인 행위로 본다. 우리는 경험적으로 존재하지 않는 어떤 것을 테스트하는 것이 가능하지 않다고 생각한다. 이 개념적인 프레임이 머릿속에 이미 자리잡은 일부 개발자들에게는 '테스트 우선(testing first)'이라는 아이디어가 바보 같은 일로 여겨진다.

그러나 테스트를 작성하고 어떻게 테스트할 것인가 하는 관점이 아닌, 행위(behavior)에 대해 생각해 본다면 어떨까? 여기서 말하는 행위란 애플리케이션이 어떻게 동작'해야' 하는가, 즉 본질적으로 그것의 명세를 말하는 것이다.

이렇게 말하면, 여러분은 이미 이런 식으로 생각할 것이다. 우리 모두가 그렇게 하고 있다고. 다음 상황을 한 번 보라.

프랭크: 스택이 뭐죠?

린다: 객체를 선입선출 방식으로 모아두는 자료구조죠. 흔히 push()pop() 같은 메서드를 제공하는 API를 가집니다. 때로는 peek() 같은 메서드 역시 쓰이기도 하죠.

프랭크: push()는 뭘 하는 거죠?

린다: push() 는 입력 객체를 받아 (배열과 비슷한) 내부 컨테이너에 넣는 일을 합니다. push()는 일반적으로 아무것도 반환하지 않습니다.

프랭크: 만일 두 개를 push() 하면요? foobar를 넣으면 어떻게 되죠?

린다: 두 번째 객체인 bar가 (적어도 두 개의 객체를 가진) 개념적 스택의 꼭대기(top)에 있어야 하죠. 그래야 pop()을 호출했을 때, 첫 번째 객체, 이 경우엔 foo가 아닌 bar가 나올 테니까요. pop()을 다시 호출하면, foo가 나와야 하고, 스택은 비어 있어야 합니다(프랭크 씨가 두 객체를 추가하기 전에 아무것도 없었다면 말이죠).

프랭크: 그럼 pop은 스택에 들어있는 가장 최근 항목을 없애는 건가요?

린다: 네, pop()은 꼭대기의 항목을 없애야 합니다(없앨 것이 있다면요). peek() 또한 같은 규칙을 따르지만, 객체는 없애지 않아요.

프랭크: 아무것도 넣지 않은 상태에서 pop()을 호출하면 어떻게 되나요?

린다: pop()은 아직 아무것도 들어있지 않다는 것을 가리키는 예외를 던져야 합니다.

프랭크: nullpush() 하면 어떻게 되나요?

린다: nullpush() 하기에 유효한 값이 아니므로 스택은 예외를 던져야 합니다.

이 대화에서 뭔가 특별한 것을 눈치챘는가(프랭크가 컴퓨터 과학을 전공하지 않았다는 사실은 빼고)? "테스트"라는 말은 어디에도 쓰이지 않았다. 반면 "해야 한다"라는 술어는 여기저기에 상당히 자연스럽게 미끄러져 들어와 있다.

자연스럽게 떠오르는 것을 행하라

어떤 프레임워크를 써야 하나?

어 노테이션을 쓰면 JUnit과 TestNG를 사용해 BDD를 실천할 수 있다. JBehave 같은 BDD 프레임워크를 사용하면 더 흥미로울 것이다. JBehave는 더 문학적인 스타일의 프로그래밍을 쉽게 할 수 있는 기대 프레임워크(expectation framework) 같은 행위 클래스 정의를 위한 기능을 제공한다.

BDD(Behavior-Driven Development)는 새롭거나 혁명적인 것이 아니다. "테스트"라는 말 대신 "해야 한다(should)" 라는 술어를 쓰는, TDD의 진화적 곁가지에 불과하다. 의미론은 논외로 하더라도, '당위(should)'의 개념이 '테스팅'의 개념보다 개발 작업을 주도하는 데 있어 더 자연스럽다고 생각하는 사람이 많다. (당위의 술어를 통해) 행위의 용어로 생각하면 명세 클래스를 먼저 작성하는 것이 더 순탄해진다. 바꿔 말하면 매우 효과적으로 구현 작업을 주도할 수 있다.

프랭크와 린다의 대화를 기초로 TDD가 의도한 개발 촉진의 길을 BDD가 어떻게 주도하는지 지켜보자.




위로


JBehave

JBehave는 xUnit 패러다임에 영감을 받아 자바 플랫폼용으로 만들어진 BDD 프레임워크다. 추측한 바와 같이, JBehave는 '테스트'보다는 '해야 한다'에 방점을 둔다. JUnit과 마찬가지로 자신이 좋아하는 IDE에서 선호하는 Ant 같은 빌드 플랫폼을 통해 JBehave 클래스들을 실행할 수 있다.

JBehave 를 쓰면 JUnit에서 테스트 클래스를 만드는 것처럼 행위 클래스를 만들어야 한다. 그러나 JBehave에서는 어떤 특별한 기반(base) 클래스를 확장할 필요가 없고, 작성한 모든 행위 메서드는 Listing 1처럼 test가 아닌 should로 시작해야 한다.


Listing 1. 스택을 위한 간단한 행위 클래스
                
public class StackBehavior {
public void shouldThrowExceptionUponNullPush() throws Exception{}
public void shouldThrowExceptionUponPopWithoutPush() throws Exception{}
public void shouldPopPushedValue() throws Exception{}
public void shouldPopSecondPushedValueFirst() throws Exception{}
public void shouldLeaveValueOnStackAfterPeep() throws Exception{}
}

Listing 1에 정의된 모든 메서드는 'should'로 시작하며, 사람이 읽을 수 있는 문장처럼 보인다. 결과적으로 StackBehavior 클래스는 프랭크와 린다의 대화에 나온 스택의 많은 기능을 기술한다.

예를 들어, 린다는 사용자가 스택에 null을 넣으려 하면 스택이 예외를 던져야 한다고 말했다. StackBehavior 클래스의 첫 번째 행위 메서드를 확인해 보면, shouldThrowExceptionUponNullPush()다. 다른 메서드들도 동일한 패턴을 따른다. 이러한 설명적인 작명 패턴은 (JBehave나 BDD에만 해당되는 것은 아니지만) 실패하는 행위를 표현할 때, 곧 보게 되겠지만, 사람이 읽을 수 있는 방식으로 표현할 수 있도록 한다.

shouldThrowExceptionUponNullPush()에서 이 행위를 어떻게 검증하게 될까? 먼저 Stack 클래스에는 push() 메서드가 필요할 것 같다. 이 메서드를 정의하기는 쉽다.


Listing 2. 행위를 더 쉽게 살펴볼 수 있도록 간단하게 정의한 스택
                
public class Stack<E> {
public void push(E value) {}
}

보는 바와 같이 필수 행위를 '먼저' 구체화하기 위해 매우 적은 양의 코드로 스택 클래스를 작성했다. 린다가 말한 것처럼 행위는 단순하다. 누군가 null 값으로 push()를 호출하면 스택은 예외를 던지는 것이다. 이제 Listing 3에서 이 행위를 어떻게 정의했는지 보라.


Listing 3. 스택은 null이 들어오면 예외를 던져야 한다.
                
public void shouldThrowExceptionUponNullPush() throws Exception{
final Stack<String> stStack = new Stack<String>();

Ensure.throwsException(RuntimeException.class, new Block(){
public void run() throws Exception {
stStack.push(null);
}
});
}




위로


위대한 기대와 재정의

Listing 3에는 JBehave에만 고유한 몇 가지 것이 있으니 설명해 보자. 먼저 Stack 클래스의 인스턴스를 생성했고, (자바 5의 제너릭스를 통해) String 타입으로만 한정했다. 그 다음에는 JBehave의 기대 프레임워크(expectation framework)를 사용해 바라는 행위를 꼭 필요한 부분만 모델링했다. Ensure 클래스는 JUnit 또는 TestNG의 Assert 타입과 비슷한 듯 보이지만, 더 가독성 있는 API(흔히 문학적 프로그래밍(literate programming)이라고도 하는)를 만들 수 있게 해주는 일련의 메서드가 추가되어 있다. Listing 3에서 push()null과 함께 호출될 때 RuntimeException을 던진다고 보증했다.

JBehave는 Block 타입을 도입하여 여러분이 기대한 행위로 run() 메서드를 재정의하여 구현될 수 있도록 한다. 내부적으로 JBehave는 기대한 예외가 던져지지 않았음을 보증하고 실패 상태를 생성한다. "Ajax 애플리케이션의 단위 테스팅" 기사에서 봤던 편이(convenience) 클래스를 재정의하는 패턴과 비슷하다고 생각할 수도 있다. 이전 기사에서는 GWT의 Timer 클래스를 재정의했다.

Listing 3의 행위를 지금 실행하면 실패를 보게 될 것이다. 현재 코드에서 push() 메서드는 아무 일도 하지 않는다. 그러므로 Listing 4의 출력에서 볼 수 있는 바와 같이 예외가 생성될 방법은 없다.


Listing 4. 바라는 행위가 거기에 없다.
                
1) StackBehavior should throw exception upon null push:
VerificationException: Expected:
object not null
but got:
null:

Listing 4의 "StackBehavior should throw exception upon null push" 문장은 클래스의 이름과 더불어 행위의 이름(shouldThrowExceptionUponNullPush())을 그대로 반복한 것이다. 본질적으로 JBehave는 기대된 행위를 실행할 경우 아무런 결과도 보고하지 않는다. 물론 다음 단계에 할 일은 저 행위를 통과시키는 것이다. Listing 5에서 null을 체크함으로써 통과시킬 수 있었다.


Listing 5. 스택 클래스에 명세 행위를 추가하기
                
public void push(E value) {
if(value == null){
throw new RuntimeException("Can't push null");
}
}

내가 만든 행위를 다시 실행했을 때 Listing 6과 같이 모든 것이 잘 되었다.


Listing 6. 성공!
                
Time: 0.021s

Total: 1. Success!




위로


행위가 개발을 주도한다

Listing 6의 출력 결과가 JUnit의 출력 결과와 비슷하지 않은가? 우연의 일치는 아닐 것이다. 이미 언급했던 것처럼 JBehave는 xUnit 패러다임을 본떠 만들어졌으며, setUp()tearDown() 같은 픽스처(fixture)까지 지원한다. 행위 클래스 전체에 걸쳐 Stack 인스턴스를 사용할 것이라고 가정하면, Listing 7처럼 저 부분을 픽스처에 넣는 것이 좋을 것이다. JBehave는 JUnit과 동일한 픽스처 약정을 따를 것이다. 즉 모든 행위 메서드에 대해 setUp()tearDown()을 실행할 것이다.


Listing 7. JBehave의 픽스처
                
public class StackBehavior {
private Stack<String> stStack;

public void setUp() {
this.stStack = new Stack<String>();
}
//...
}

다음 행위 메서드로 가자. shouldThrowExceptionUponPopWithoutPush()Listing 3shouldThrowExceptionUponNullPush()와 비슷한 행위를 보증해야 할 것임을 암시한다. Listing 8에서 볼 수 있는 것처럼 특별한 마술은 없다.


Listing 8. pop의 행위를 보증하기
                
public void shouldThrowExceptionUponPopWithoutPush() throws Exception{

Ensure.throwsException(RuntimeException.class, new Block() {
public void run() throws Exception {
stStack.pop();
}
});
}

알고 있겠지만, Listing 8은 pop()이 아직 작성되지 않았기 때문에 지금 시점에선 실제로 컴파일되지 않을 것이다. 그러나 pop() 작성을 시작하기 전에 그에 대해 몇 가지 것을 생각해 보자.

행위를 보증하기

기술적으로 pop()을 구현함에 있어, 지금 시점에서 호출 순서와는 상관없이 예외만을 던지도록 할 수 있을 뿐이다. 그러나 이 행위의 경로(route)를 따라 내려가면 내가 기대한 명세를 지원하는 구현에 관해 생각해 보게 된다. 이 경우에는 push()가 호출되지 않았다면(또는 논리적으로 말해 스택이 비어 있다면), pop()이 예외를 던지도록 보증하는 것은 스택이 상태를 가져야 한다는 것을 의미한다. 그리고 일찍이 린다가 심사 숙고해 말한 바와 같이, 스택은 일반적으로 항목들을 실제로 담는 내부 컨테이너를 가지고 있다. 그에 따라 Stack 클래스를 위해 push() 메서드 속으로 통과한 값들을 붙잡기 위해 Listing 9에서 보는 바와 같이 ArrayList 클래스를 생성할 수 있다.


Listing 9. 스택은 객체를 붙잡기 위한 내적 방법이 필요하다.
                
public class Stack<E> {
private ArrayList<E> list;

public Stack() {
this.list = new ArrayList<E>();
}
//...
}

이제 pop() 메서드를 위한 행위를 코딩할 수 있다. pop() 메서드는 스택이 논리적으로 비어 있다면 예외를 던질 것을 보증한다.


Listing 10. pop 구현이 더 쉬워졌다.
                
public E pop() {
if(this.list.size() > 0){
return null;
}else{
throw new RuntimeException("nothing to pop");
}
}

Listing 8에서 행위를 실행할 때는 모든 것이 기대했던 대로 동작했다. 스택이 아무 값도 잡고 있지 않으므로(그래서 스택 크기가 0보다 크지 않다) 예외를 던진다.

다음 행위 메서드는 shouldPopPushedValue()라고 하며, 명세화하기 쉬운 것으로 밝혀진다. 단순히 값("test")을 push() 했고, pop()을 부를 때 같은 값이 리턴될 것임이 보증된다.


Listing 11. 값을 집어넣으면(push) 꺼내야(pop) 한다.
                
public void shouldPopPushedValue() throws Exception{
stStack.push("test");
Ensure.that(stStack.pop(), m.is("test"));
}




위로


Matcher를 위해 다이얼 'M'을 돌려라

UsingMatchers 타입에 대해

Listing 12의 코드가 그다지 우아하지 않음을 알아챘을지도 모른다. Listing 11의 m은 가독성에 영향을 약간 미친다("ensure that pop's value m (what the?) is test"). JBehave에서 제공하는 특별한 기본 클래스(UsingMiniMock)를 확장해 UsingMatchers 타입을 사용하는 걸 피할 수 있다. 이런 식으로 하면 Listing 11의 마지막 줄은 Ensure.that(stStack.pop(), is("test"))가 된다. 좀 더 읽기 좋다.

Listing 11에서 pop()"test"라는 값을 리턴할 것임을 보증한다. JBehave의 Ensure 클래스를 사용하는 과정에서 여러분은 때로 기대를 명세화하기 위해서는 더 멋진 방법이 필요하다는 점을 알았을 것이다. JBehave는 멋진 기대 사항들을 구현하는 Matcher 타입을 제공함으로써 이러한 필요를 만족시킨다. 이 경우 is(), and(), or() 같은 메서드들, 그리고 좀 더 문학적인 스타일의 기대를 만들기 위한 다른 깔끔한 메커니즘들을 사용할 수 있도록, JBehave의 UsingMatchers 타입(Listing 11의 UsingMatchers m 변수)을 재사용하기로 결정했다.

Listing 11의 m 변수는 Listing 12에서와 마찬가지로 StackBehavior 클래스의 정적 멤버다.


Listing 12. 행위 클래스에서 Matcher 사용하기
                
private static final UsingMatchers m = new UsingMatchers(){};

Listing 11에서 작성한 새로운 행위 메서드와 함께 이제 실행할 시간이다. 그러나 Listing 13처럼 실패할 것이다.


Listing 13. 새로 작성한 행위가 동작하지 않는다.
                
Failures: 1.

1) StackBehavior should pop pushed value:
java.lang.RuntimeException: nothing to pop

무슨 일이 일어났을까. push() 메서드가 아직 완성되지 않은 것으로 밝혀졌다. Listing 5로 돌아가 행위들이 동작하도록 하기 위해 거의 최소한의 구현을 코딩했다. 이제 작업을 끝낼 시간이다. 푸시된 값을 내부 컨테이너에 실제로 추가했다(값이 null이 아니라면). Listing 14를 보라.


Listing 14. push 메서드를 감싼다.
                
public void push(E value) {
if(value == null){
throw new RuntimeException("Can't push null");
}else{
this.list.add(value);
}
}

행위를 재실행할 때까지 기다리자. 여전히 실패하고 있다.


Listing 15. JBehave는 예외 대신에 널 값을 보고한다.
                
1) StackBehavior should pop pushed value:
VerificationException: Expected:
same instance as <test>
but got:
null:

적어도 Listing 15의 실패는 Listing 13의 실패와 다르다. 이번에는 예외를 던지기보다는 "test" 값이 발견되지 않았다. null이 빠져 나온(pop) 것이다. Listing 10을 자세히 들여다보면 문제가 드러난다. 처음에 내부 컨테이너가 아무것도 가지고 있지 않으면 null을 리턴하도록 pop() 메서드를 코딩했다. 그걸 고치기는 쉽다.


Listing 16. pop 메서드 코딩을 마무리할 때
                
public E pop() {
if(this.list.size() > 0){
return this.list.remove(this.list.size());
}else{
throw new RuntimeException("nothing to pop");
}
}

그러나 지금 행위를 다시 실행하면 새로운 실패를 맛보게 된다.


Listing 17. 또 다른 실패
                
1) StackBehavior should pop pushed value:
java.lang.IndexOutOfBoundsException: Index: 1, Size: 1

Listing 17의 정보를 자세히 읽으면 이 문제를 밝혀낼 수 있다. ArrayList를 다룰 때 0에 대해 좀 더 고려해야 한다.


Listing 18. 0을 특별히 고려함으로써 문제를 해결한다.
                
public E pop() {
if(this.list.size() > 0){
return this.list.remove(this.list.size()-1);
}else{
throw new RuntimeException("Nothing to pop");
}
}




위로


스택의 논리

지금까지 통과시킬 수많은 메서드를 승인하는 식의 방법으로 push()pop()을 그럭저럭 구현했다. 그러나 아직 스택의 알맹이, 즉 이따금 peek()를 던지는 것과 더불어 다양한 push(), pop()과 관련된 논리 속으로 직접 뛰어들지는 않았다.

먼저 스택(선입선출)의 기본 알고리듬이 shouldPopSecondPushedValueFirst() 행위를 통해 건강하다는 것을 보증해야 한다.


Listing 19. 전형적인 스택의 논리를 보증하기
                
public void shouldPopSecondPushedValueFirst() throws Exception{
stStack.push("test 1");
stStack.push("test 2");
Ensure.that(stStack.pop(), m.is("test 2"));
}

Listing 19의 코드는 계획한 대로 동작한다. 따라서 pop()을 두 번 사용해도 적절한 결과가 나온다는 것을 보증하기 위해 또 다른 행위 메서드를 구현할 것이다(Listing 20).


Listing 20. 스택의 행위 속으로 더 깊이 들어가기
                
public void shouldPopValuesInReverseOrder() throws Exception{
stStack.push("test 1");
stStack.push("test 2");
Ensure.that(stStack.pop(), m.is("test 2"));
Ensure.that(stStack.pop(), m.is("test 1"));
}

더 나아가 peek()이 의도한 대로 동작한다는 점을 보증하고 싶다. 린다가 말했듯이 peek()pop()과 같은 규칙을 따른다. 그러나 "스택에 꼭대기 항목을 남겨두어야 한다(should leave the top item on the stack)". 그에 따라 Listing 21에서 shouldLeaveValueOnStackAfterPeep() 메서드를 위한 행위를 구현했다.


Listing 21. peek가 스택의 꼭대기 항목을 남겨두는 것을 보증하기
                
public void shouldLeaveValueOnStackAfterPeep() throws Exception{
stStack.push("test 1");
stStack.push("test 2");
Ensure.that(stStack.peek(), m.is("test 2"));
Ensure.that(stStack.pop(), m.is("test 2"));
}

peek()이 아직 정의되지 않았기 때문에, Listing 21의 코드는 컴파일되지 않을 것이다. Listing 22에서 peek()의 뼈대만 있는 구현을 정의했다.


Listing 22. peek이 물론 있어야 한다.
                
public E peek() {
return null;
}

이제 StackBehavior 클래스가 컴파일될 것이다. 그러나 아직 실행되지는 않을 것이다.


Listing 23. null이 리턴되었지만 놀랍지 않다.
                
1) StackBehavior should leave value on stack after peep:
VerificationException: Expected:
same instance as <test 2>
but got:
null:

논리적으로 peek()은 내부 컬렉션에서 항목을 제거하지 않는다. 기본적으로 참조만 통과시킨다. 결과적으로 Listing 24에서 보는 바와 같이, remove()가 아닌 ArrayListget() 메서드를 사용한다.


Listing 24. 지우지 말라.
                
public E peek() {
return this.list.get(this.list.size()-1);
}

여기서는 더 이상 볼 것이 없다

Listing 21의 행위를 재실행하자 통과 점수가 나왔다. 그런데 문제가 다시 드러났다. 가진 게 아무것도 없다면 peek()의 행위는 어떻게 될까? 만일 가진 게 없는데 pop()을 하면 예외를 던져야 할까? peek()도 같은 일을 해야 할까?

린다는 이 점에 대해 명백하게 아무것도 말하지 않았지만, 몇 가지 새로운 행위에 스스로 살을 붙여야 한다. Listing 25에서 "push() 없이 peek()을 호출하면 어떻게 될까" 같은 경우에 대한 시나리오를 코딩했다.


Listing 25. 집어넣지(push) 않고 들여다보면(peek) 어떻게 되나?
                
public void shouldReturnNullOnPeekWithoutPush() throws Exception{
Ensure.that(stStack.peek(), m.is(null));
}

이번에도 놀랍지 않다. Listing 26에서 보듯 작업이 날아갔다.


Listing 26. 들여다 볼(peek) 것이 없다
                
1) StackBehavior should return null on peek without push:
java.lang.ArrayIndexOutOfBoundsException: -1

문제를 해결하기 위한 논리가 pop()에 있던 것과 상당히 비슷하다. Listing 27에서 볼 수 있다.


Listing 27. peek()에 몇 가지 수정이 필요하다.
                
public E peek() {
if(this.list.size() > 0){
return this.list.get(this.list.size()-1);
}else{
return null;
}
}

Stack 클래스에 행한 모든 수정과 해결이 Listing 28에 나와 있다.


Listing 28. 스택이 드디어 동작한다.
                
import java.util.ArrayList;

public class Stack<E> {

private ArrayList<E> list;

public Stack() {
this.list = new ArrayList<E>();
}

public void push(E value) {
if(value == null){
throw new RuntimeException("Can't push null");
}else{
this.list.add(value);
}
}

public E pop() {
if(this.list.size() > 0){
return this.list.remove(this.list.size()-1);
}else{
throw new RuntimeException("Nothing to pop");
}
}

public E peek() {
if(this.list.size() > 0){
return this.list.get(this.list.size()-1);
}else{
return null;
}
}
}

이 시점에서 StackBehavior 클래스는 Stack 클래스가 린다의 (그리고 나 자신의 것도 몇 개 있는) 명세에 따라 동작한다는 것을 보증하는 행위 일곱 개를 실행한다. Stack 클래스는 약간의 리팩토링을 사용할 수 있을 것이다. 아마도 pop() 메서드는 size() 확인 목적이라기보다는 테스트 목적으로 peek()을 호출해야 할 수도 있다. 그러나 지금까지의 행위 주도 프로세스 덕분에 거의 완전 확신에 가까운 변화를 만들어내는 하부구조를 손에 넣을 수 있었다. 뭔가를 깨뜨린다면, 곧바로 알 수 있을 것이다.




위로


결론

행 위 주도 개발(BDD)에 대한 이달의 탐험에서, 린다는 본래 고객이라는 것을 알아챈 사람도 있을 것으로 생각한다. 이 시나리오에서 프랭크를 개발자로 생각할 수도 있다. 자료구조 영역을 벗어나 콜 센터 애플리케이션 같은 다른 무언가로 대체해 봐도 비슷하다. 고객이며 자료구조 영역의 전문가인 린다는 시스템, 기능, 애플리케이션이 무엇을 해야 하는가를 말하고, 프랭크 같은 사람은 BDD를 이용해 린다의 얘기를 듣고 있고, 요구사항을 구현할 것이라는 확신을 주어야 한다.

많은 개발자에게 테스트 주도 개발에서 BDD로 이동은 영리한 움직임이다. BDD와 함께라면 테스트에 대해 생각할 필요가 없고, 애플리케이션의 요구사항에 주목하고 애플리케이션의 행위가 그러한 요구사항을 만족하도록 보증하기만 하면 된다.

이 글에서 BDD와 JBehave를 이용해 린다의 명세를 기초로 동작하는 스택을 쉽게 구현했다. 린다가 말하는 것을 듣기만 하고, 그에 따라 행위 '우선'의 용어로 생각하며 스택을 만들었다. 또한, 개발을 진행하면서 린다가 스택에 관해 잊고 있었던 몇 가지 것을 그럭저럭 발견할 수 있었다.



참고자료

교육

제품 및 기술 얻기

토론
  • 포럼에 참여하기.

  • Discussion forum: Improve your code quality: 코드 품질 완벽주의자로부터 배우자. Andrew Glover가 코드 품질 개선에 초점을 두고 그의 전문 지식을 공유한다.


필자소개

Andrew Glover 사진

Andrew Glover는 Stelligent Incorporated의 사장이다. 회사들이 코드 품질을 일찍 그리고 자주 모니터할 수 있게 하는 효과적인 개발자 테스팅 전략과 지속적 통합 기법으로 소프트웨어 품질 문제를 해결하는 것을 돕고 있다. Andy의 저서 목록은 그의 블로그를 보라.