메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

IT/모바일

루비를 이용한 Behavior Driven Development(Part 1) - (1)

한빛미디어

|

2007-10-22

|

by HANBIT

10,899

제공 : 한빛 네트워크
저자 : Gregory Brown
역자 : 노재현
원문 : Behavior Driven Development Using Ruby (Part 1)

Behavior Driven Development (BDD)는 루비를 사용하는 유저들에게 요즘 들어서 많이 사용되는 용어중에 하나 이다. 그런데 아직까지 이 용어에 대해서 제대로 알고 있는 사람이 많지가 않다. 그나마 기본적인 지식을 갖추고 있는 사용자들도 TDD(Test Driven Development)와 뭐가 다른건지 헷갈려 한다. 그리고 이런 혼란이 BDD에 대해서 배우려고 하고 있는 사용자들에게 조차도 좋지 않은 영향을 준다.

물론 무언가를 배우는데 있어서 가장 좋은 방법은 당장 뛰어들어서 이것 저것 해보면서 배우는게 가장 좋을 것이고, 이 세 개의 파트로 나뉘어진 글에서도 이런 방법으로 BDD에 대해서 설명을 하고자 한다.

우선 간단한 것부터 시작해서 차츰 차츰 들어가 보도록 하겠다. 필자는 무언가를 시작하기전에 너무 이론에 몰두하는 것이 오히려 배우는데 장해가 된다고 생각하는 사람이기 때문에 이 글에서는 먼저 RSpec 이라고 불리는 BDD 툴을 사용해 봄으로써 시작해 보고자 한다. 진행하면서 BDD의 개념적인 부분에 대해서도 설명하겠지만, 이 글의 주 목적은 실제로 사용할 수 있는 도구들에 대한 설명이라고 보면 된다.

간단하게 살펴본 후에는 좀 더 깊이 있게 배워보도록 하려고 한다. 우선 mock 오브젝트로 부터 시작해서 종속적인 시스템하에서 어떻게 mock 오브젝트를 사용할 수 있는지 알아보도록 하겠다. 그리고 TDD에서 통상적으로 발생할 수 있는 안티 패턴을 피하기 위해서 어떻게 스펙(spec)을 구성해야 하는지에 대해서도 얘기를 해볼 것이고, 더 복잡한 문제에 대해서도 얘기를 할 것이다. 이 과정중에 작은 애플리케이션을 만들게 될 것인데, 이 애플리케이션을 통해서 스펙을 만들고, 반복을 통해서 BDD를 실제로 어떻게 해야하는것인지에 대해서 감을 잡을 수 있을 것이다.

RSpec과 BDD의 유용성에 대해서 어느 정도 감을 잡은 독자들을 위해서 이 글의 3번째 부분에서는 다음으로 공부해야 하는게 무엇인지에 대해서 다루려고 한다. 예를 들면, 기존에 사용하고 있던 TDD에 BDD 개념을 적용하려고 할 경우 단순히 모든 코드를 RSpec을 이용해서 다시 작성하려고 하지 않고 할 수 있는 방법에 대해서도 살펴보겠다. 그리고 여러분이 생각하고 있는 것들을 더 쉽게 할 수 있게 도와주는 툴에 대해서도 살펴보겠다.

이 글을 통해서 여러분이 얻었으면 하는 것은 TDD를 사용하면서 겪었던 어려움을 BDD를 통해서 해결했으면 하는 것이다.

RSpec의 기본

스펙이라는 것을 배워보도록 하겠다. 몇 년전에 Mike Clark 이 자신이 루비를 배우면서 여러 유닛 테스트를 어떻게 관리했었는지에 대해서 말한 적이 있다. 그의 예제를 따라서 이제 부터 루비의 String 클래스에 대한 스펙들을 만들어 보도록 하겠다.

아직 새로운 코드를 작성한 다음에 스펙이 제대로 작동하는지 알아보지 않아도 괜찮기 때문에, 이 방법을 이용하면 RSpec 프레임웍에 대해서 빨리 알 수 있게 될 것이다. 또한 Test::Unit 코드를 작성하지 않고 스펙을 작성함으로써 루비를 처음 배우는 사람에게는 실질적인 도움이 되는 경험이 될 수도 있다.

우선 배움을 위해서 오류가 있는 예제를 만들어 보았다. 아래에 있는 스펙을 보고 어디가 틀렸는지 알아보자.
string_spec.rb 
  describe "String indexing" do

    it "should return first character for position 0" do
      "foo"[0].should == "f" 
    end

  end
척 보니 어디가 틀렸는지 알 수 있겠는데, 루비 초보자라면 아마 다음 실행결과를 보고 놀랐을 것이다.
  
$ spec string_spec.rb

F

1)
"String indexing should return first character for position 0" FAILED
expected "f", got 102 (using ==)
./string_spec.rb:4:

Finished in 0.00995 seconds

1 example, 1 failure
루비 스트링에서 인덱싱을 이용해서 한 글자를 받아온 결과는 한개의 글자를 가진 스트링이 아니고, 해당 문자에 대한 ASCII 정수 값이라는 것이다. 우선 이 문제를 해결하기 위해서 스펙에 이 상황을 기록해야 겠지만, 우선 RSpec을 처음보는 독자를 위해서 조금 설명을 하고 가도록 하겠다.

RSpec 프레임웍은 우리가 작성한 코드에 대한 스펙을 작성하기 쉽도록 도와준다. 물론 유닛 테스트를 이용해서도 할 수 있지만 RSpec을 이용하게 되면 조금 다른 관점에서 할 수 있게끔 해준다.

스펙은 어떤 상황에 대한 예제를 구성하고 그 예제의 예상 값을 포함하고 있는 일련의 구문라고 보면 된다. 여기서 해당 구문는 다음과 같이
describe "String indexing" do
  # ...
end
구문은 단순히 여러 예제들을 포함하기 쉽도록 도와주는 개념이라고 볼 수 있다. 여기서는 구문이 하나의 예제를 포함하고 있는 걸 알 수 있다.
it "should return first character for position 0" do
  "foo"[0].should  "f" 
end
이제 위에서 만들었던 스펙을 수정해서 정상적으로 작동하도록 수정하고, 공통 설정을 추가해서 다른 인덱싱 관련 예제들을 작성하는데 사용할 수 있도록 하겠다.
  describe "String indexing" do

    before :each do
      @string = "foo" 
    end

    it "should return first character"s ASCII value for position 0" do
      @string[0].should == ?f
    end

  end
위에서 보듯이 문제에 대한 ASCII 정수 값과 기대하는 값이 제대로 일치하는지를 확인하도록 수정했다.

그리고 "before :each" 라는 훅(hook)을 사용한 것을 알 수 있는데, 이 훅은 각 예제가 실행되기 전에 한 번씩 자동으로 실행이 되어서 테스트할때 필요한 기본 값을 설정하도록 하는 명령이다.

그럼 "before :each" 훅이 어떻게 도움이 되는지 예제를 통해서 알아보자.
  describe "String indexing" do

    before :each do
      @string = "foo" 
    end

    it "should return first character"s ASCII value for position 0" do
      @string[0].should == ?f
    end

    it "should return the last character"s ASCII value for position -1" do
      @string[-1].should == ?o
    end

    it "should return the first two characters for the range 0..1" do
      @string[0..1].should == "fo" 
    end

    it "should return the last two characters for the range -2..-1" do
      @string[-2..-1].should == "oo" 
    end

    it "should replace the second character with a new substring" do
      @string[1] = "id" 
      @string.should == "fido" 
    end

    it "should become empty when 0..-1 is set to empty string" do
      @string[0..-1] = "" 
      @string.should be_empty
    end

  end
보면 알겠지만, 일부 예제에서는 @string의 값을 수정하고 있다. 하지만 사전에 설정했던 훅에서 매번 @string의 값을 초기화 해주고 있기 때문에, 다음 예제에서 사용할 때 문제가 발생하지 않는다는 것을 알 수 있다.

또 다음과 같은 새로운 타입의 헬퍼를 넣었는데
  @string.should be_empty
정확하게 어떻게 동작하는 것인지 알기 위해서 한 번 헬퍼를 조금 수정 한후에 에러 메시지를 보도록 하겠다.
  @string.should be_incredible
다음과 같이 에러가 발생한다.
  NoMethodError in "String indexing should become empty when 0..-1 is set to empty
  string"
  undefined method "incredible?" for "":String
에러를 보면 be_something이라는 헬퍼를 사용했을 경우 어떤 식으로 동작이 일어나는지를 추측해 볼 수 있다. RSpec은 입력으로 받은 오브젝트에서 something이라는 함수를 찾은 후에 그 함수를 실행해서 true를 리턴하는지 확인하는 과정을 수행한다.

더 정확하게 우리가 생각하는데로 작동하는지 확인해 보기 위해서 부정문을 사용해 보겠다. 여기서는 should_not() 함수를 호출해 보겠다. 예상대로, 다음과 같은 에러가 발생한다.
  it "should become empty when 0..-1 is set to empty string" do
    @string[0..-1] = "" 
    @string.should_not be_empty
  end
"String indexing should become empty when 0..-1 is set to empty string" FAILED
expected empty? to return false, got true
./string_spec.rb:30:

Finished in 0.01074 seconds

6 examples, 1 failure
모든 걸 확인했으니 다시 원상태로 되돌려 놓겠다. 이렇게 부정문의 형태를 취하는 방법은 여러분이 작성한 스펙이 정상적으로 작동하는지 확인해 보기 위한 방법으로 사용될 수도 있다. 필자의 경우 보통 스펙을 작성하기 시작할때 부터 고의적으로 오류가 발생하도록 해서 정확하게 작동하는지를 확인해 보기도 한다. 사실 이런 테스트를 위한 방법이 RSpec에 존재한다. violated 라는 함수를 이용할 수 있다.
  it "should fail no matter what" do
    violated "The Interstellar Rules Of The Galaxy" 
  end
물론 모든 예제를 작성할때 마다 이런 실패 테스트를 할 필요는 없겠지만, 이런 과정을 거쳐가게 되면 나중에 개발하면서 큰 도움이 될 수가 있다.


역자 노재현님은 어렸을 때부터 컴퓨터를 접하게 된 덕에 프로그래밍을 오랫동안 정겹게 하고 있는 프로그래머 입니다. 특히나 게임 및 OS 개발에 관심이 많으며, 심심할 때면 뭔가 새로운 프로그램을 만들어내는 것을 좋아합니다. 다음에서 웹 관련 개발을 한 후에 현재는 www.osguru.net이라는 OS관련 웹사이트를 운영하며 넥슨에서 게임 개발을 하고 있습니다.
* e-mail: wonbear@gmail.com
* homepage: http://www.oguru.net
TAG :
댓글 입력
자료실

최근 본 상품0