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

한빛출판네트워크

IT/모바일

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

한빛미디어

|

2007-10-23

|

by HANBIT

10,688

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

Test::Unit 보다 그 이상의 효과

더 나아가기 전에 생각을 해보자. 과연 이 RSpec이 루비에 포함되어 있는 Test::Unit에 비해서 어떤 장점을 가지고 있는걸까? 이걸 확인해 보기위한 방법으로 위에서 만들었던 예제와 동일한 기능을 하는 Test::Unit 코드를 작성해 보았다.
  class StringIndexingTest < Test::Unit::TestCase

    def setup
      @string = "foo" 
    end

    def test_should_return_first_char_ascii_value_for_pos_0
      assert_equal ?f, @string[0]
    end

    def test_should_return_last_chars_ascii_value_for_pos_neg_1
      assert_equal ?o, @string[-1]
    end

    def test_should_return_first_two_chars_for_range_0_to_1
      assert_equal "fo", @string[-2..-1]
    end

    def test_should_return_last_two_chars_for_range_neg_2_to_neg_1
      assert_equal "oo", @string[-2..-1]
    end

    def test_should_replace_second_char_with_new_substring
      @string[1] = "id" 
      assert_equal "fido", @string
    end

    def test_should_become_empty_when_zero_to_neg_1_set_to_empty_string
      @string[0..-1] = "" 
      assert @string.empty?
    end

  end
위의 코드를 봐서는 RSpec과 비교해 봤을 때 크게 다른 점은 없어보인다. 하지만 어떤 하나의 프레임웍이 다른 프레임웍에 비해서 표현력에 있어서는 분명히 더 나은 것을 알 수 있다. 즉 RSpec이 Test::Unit 에 비해서 더 가독성이 있다고 볼 수있다. 베테랑 TDD 개발자로서 필자의 입장에서는 두 가지 모두 읽기 쉽지만, 분명히 인정해야 할 것은 Test::Unit 쪽이 처음 접근하는데 있어서 알아야 할 것이 좀 더 있다는 것이다.

예를 들어, 자주 사용되는 assert_equal을 보자.
  assert_equal "fido", @string
"Expected first_value, but god second_value"라는 메시지를 여러 차례 보다보면, 다음과 같은 형태로 함수가 호출된다고 볼 수도 있다.
  assert_equal expected, actual
위 문장에서 expected와 actual이 뒤바뀌게 될 경우 자칫 혼란을 가져올 수 있는데, RSpec에서는 이런 점을 미리 방지하고 있다.
  actual.should == expected
우리가 사용하고 있는 자연 언어의 형태를 취하고 있는데, 마치 "온도는 75도여야 합니다(The temperature should equal 75 degrees" 라고 말하는 것과 비슷하다.

표현성 뿐만이 아니고 기술적인 면에서도 RSpec이 가지고 있는 장점이 있다. 루비에는 값을 비교하거나 동질성을 나타낼 수 있는 다른 방법이 여러가지가 있다. 나열하면 모두 총 4개의 연산자를 사용할 수가 있다.(eql?, equal?, ===)

assert_equal의 경우에는 equal? 연산자를 써야 맞을 것 같지만, IRB를 통해서 간단하게 확인해 보면 틀렸다는 것을 알 수 있다.
>> a = "foo" 
=> "foo" 
>> a.equal?("foo")
=> false
>> a.equal?(a)
=> true
>> a == "foo" 
>> => true
하지만 위에서 보듯이 equal? 연산자는 두 값을 가진 오브젝트의 동질성을 평가하는데 사용되는 연산자이다. Test::Unit에서는 위 연산자를 위해서 assert_same 이라는 명령을 제공하고 있지만, 처음 접하는 사용자에게는 접근하기가 쉽지가 않다.

그럼 assert_equal의 경우에는 어떤 걸 사용해야 할까? 간단한 테스트로 확인해 보자.
class MyClass
  def eql?(other)
    true
  end
end

if __FILE__ == $PROGRAM_NAME
  require "test/unit" 

  class MyTest < Test::Unit::TestCase

    def test_my_class_equality
      a = MyClass.new
      b = MyClass.new

      assert_equal a,b
    end

  end

end
위의 테스트는 실패한다. 그런 이제 == 를 테스트 해보자.
class MyClass
  def ==(other)
    true
  end
end
그럼 테스트는 성공적으로 종료된다. 이 결과를 통해서 assert_equal(expected, actual)은 actual.should == expected와 동일하다는 것을 알 수 있다.

그럼 동등성(Equality - 같은 타입의 오브젝트)을 확인하기 위해서는 어떻게 해야할까? 이 경우 Test::Unit에서는 assert() 함수를 이용해서 해야하지만, RSpec의 경우에는 좀 더 명시적인 방법으로 테스트 할 수 있다.
  describe "meta-test for equality expectations" do

    it "should easily allow testing different kinds of equality" do
      a = MyClass.new
      b = MyClass.new

      a.should == b
      a.should eql(b)
    end

  end
이 걸로 또 RSpec이 좀 더 투명하고 접근하기 쉽다는 것을 알 수 있다.

여러분이 Test::Unit을 기존에 미리 접해보았다고 해도, 어떤 방법이 새롭게 배우려고 하는 개발자들에게 있어서 좀 더 쉽고 유익할지를 알 수 있을 것이다.

좀 더 명확한 문장으로

여러분이 스펙을 실행파일로써 또는 문서화를 위해서 사용하게 된다면 RSpec이 이런 부분에 있어서 도움을 줄 수가 있다.

이런 기능을 지원하기 위해서 프레임웍에 내장되어 있는 기능이 있는데, 예를 들어서 설명하자면 테스트를 수행중에 모둔 예제에 대한 설명을 추출하고 싶을 때는 다음과 같이 하면 된다.

String 클래스에 대한 스펙을 실행할 경우에 다음과 같은 결과를 볼 수 있다.
  $ spec -f s string_spec.rb 

  String indexing
  - should return first character"s ASCII value for position 0
  - should return the last character"s ASCII value for position -1
  - should return the first two characters for the range 0..1
  - should return the last two characters for the range -2..-1
  - should replace the second character with a new substring
  - should become empty when 0..-1 is set to empty string

  Finished in 0.009989 seconds

  6 examples, 0 failures
실제로 스펙 코드를 보지 않고서도 이전에 작성했던 테스트에 대한 설명을 얻을 수가 있다. 이렇게 확인하는 과정을 통해서 실제로 원하는 모든 테스트를 다 수행하고 있는지에 대해서도 확인할 수가 있다. 또 테스트에 대한 문구가 이상하게 되어 있을때는 보통 테스트 자체가 잘못되어 있다는 것을 확인할 수가 있다. 이런 확인과정이 모든 문제를 해결하기 위한 방법이 될 수는 없지만, 최소한 전체 코드를 한 번에 살펴볼 수 있는 방법으로 활용될 수도 있다.

이런 방법으로 rdoc 형태로 결과를 만들어 낸 후에 온라인 문서에 사용할 수도 있다.
$ spec -f r string_spec.rb 
# String indexing
# * should return first character"s ASCII value for position 0
# * should return the last character"s ASCII value for position -1
# * should return the first two characters for the range 0..1
# * should return the last two characters for the range -2..-1
# * should replace the second character with a new substring
# * should become empty when 0..-1 is set to empty string
또 HTML 형태로도 만들어낼 수 있는데, HTML형태가 가장 보기에 좋아보인다. 여기서 실패가 발생하도록 한 스펙파일의 실행결과를 보자.
$ spec -f h string_spec.rb


물론 좀 보기좋게 꾸며놓은 결과일 수도 있지만, 이런 예쁜 결과가 보면서 RSpec을 사용하는데 조금 더 즐거움을 더해줄 수 있을 것이다.

초보자에서 벗어나면서

이제 겨우 String 인덱싱 관련한 하나의 구문 만을 만들어 보았을 뿐이지만, 여기서 더 나아가서 String Case 관련 스펙을 추가해보도록 하자.
  describe "String case manipulations" do

    before :each do
      @string = "fOo" 
    end

    it "should be converted to all caps when #upcase is sent" do
      @string.upcase.should == "FOO" 
    end

    it "should start with a capital letter followed by lowercase letters when #capitalize 
    is sent" do
      @string.capitalize.should == "Foo" 
    end

    it "should be converted to all lowercase when #downcase is sent" do
      @string.downcase.should == "foo" 
    end

  end
역시 마찬가지로 각 구문에서는 "before :each"라는 훅을 설정해서 각 예제를 실행할때마다 필요한 초기화를 수행하도록 하고 있다. 이제 스펙을 실행해 보면 우리가 배우기 위해서 그 동안 작성했던 스펙들이 String 클래스의 동작을 얼마만큼이나 잘 묘사하고 있는지를 볼 수 있다.
  $ spec -f s string_spec.rb

  String indexing
  - should return first character"s ASCII value for position 0
  - should return the last character"s ASCII value for position -1
  - should return the first two characters for the range 0..1
  - should return the last two characters for the range -2..-1
  - should replace the second character with a new substring
  - should become empty when 0..-1 is set to empty string

  String case manipulations
  - should be converted to all caps when #upcase is sent
  - should start with a capital letter followed by lowercase letters when #capitalize is sent
  - should be converted to all lowercase when #downcase is sent

  Finished in 0.012205 seconds

  9 examples, 0 failures
여기서부터 여러분 스스로 더 예제를 추가해 봐도 좋고, 아니면 다음 글에서 조금 더 고급 주제를 다룰 때까지 다른 걸 하면서 기다려도 괜찮다.

이번 글에서는 어떻게 스펙을 사용해야 하는지에 대해서 다루었지만 아직 BDD의 철학적인 부분이나 BDD를 어떻게 디자인 과정에서 유용하게 사용할 수 있는지에 대해서는 다루지 않았다. 곧 이런 주제에 대해서 다루겠지만, 오늘 배웠던 RSpec이 그 때 큰 도움이 될 것이다.

RSpec을 바로 사용해 보고 싶은 독자는 Test::Unit에 관련된 문서를 보면 RSpec의 함수를 배우는데 많은 도움이 된다.
TAG :
댓글 입력
자료실

최근 본 상품0