내게 객체 기반 프로그래밍 (Object-Oriented Programming)를 한마디로 축약하라면, 행동(Behavior)을 기준으로 도메인을 나누는(decomposition)하는 프로그래밍 방식이라고 정의할 것이다. 다시 말해, 객체(Object)는 잘 정의된 행동(Behavior)을 대표해야한다는 것이다. 행동(Behavior)이 아니라 자료(Data)를 기준으로 디자인하면, 구조화 프로그래밍(Structured programming)의 수동적인 자료구조와 그것을 조작하는 함수들의 집합이 탄생할 뿐이다. 설령 그것이 클래스라는 프로그래밍 언어 상의 장치를 사용한다고 하더라도 객체 기반 프로그래밍이라고 부를 수는 없다. (이러한 밈(meme)에 대해서는 Object Thinking이라는 책을 참고하라.)
Behavior-Driven Development라는 말을 강문식 군에게 처음 들었을 때는 이러한 객체 기반 프로그래밍의 생각을 대변하는 일반적인 개념을 가진 말이라고 추측했는데, 알고보니 정체는 그 개념을 TDD에 적용한 좁은 의미의 것이었다. Dave Astels 자신이 정의한대로 Behavior-Driven Specification 같은 용어를 썼으면 좋았을텐데 하는 생각이 든다.
설령 그렇다고 하더라도, 행동을 기준으로 하는 디자인이 맨 먼저 테스트에 적용되는 것은 상당히 훌륭한 개념이라고 생각한다. TDD를 통해서 얻을 수 있는 이점인 재사용과 테스트가 쉬운 (Reusable and Testable) 프로그램을 만드는데 있어서 객체 기반 프로그래밍의 기본 개념인 행동(Behavior)을 기준으로 한 디자인까지 할 수 있다면, 더할 나위 없이 좋을 것이다. 다만, 그것이 테스트 또는 스펙에만 머무르지 않고 실제 행동(Behavior)을 나타내는 디자인에까지 적용되어야 하겠지만 말이다.
아래는 Dave Astels의 BDD에 대한 Google TechTalks 강연 내용 요약.
Unit vs. Behavior
unit: isolated focus like class, method
behavior: little fine-grained focused picses of behavior
unit should replaced by behavior
test should replaced by specification
assertion should replaced by expectation
rSpec
xunit should replaced by rSpec
assert_equal(expected, actual)->actual.should.equal expected
The Expectation API
- equality
- e.g. should.equal/sould.not.equal
- counts
- should.have(5).items
- should.have.at.least(5).items
- should.have.at.most(5).items
- arbitrary block
- should.satisfy { |obj| … }
- should.not.satisfy { |obj| … }
- pattern matching
- should.match
- arbitrary predicate
- should.predicate (predicate? is defined on the target)
- forced failure
- violated(message)
- exception
- should.raise <exception>
- direct class
- should.be.an.instance.of C
- ancestor class
- should.be.a.kind.of C
- interface
- should.response.to :message
The Mocking API
just like jMock
- creating a mock
- m = mock(“mock name”)
- expecting a method
- should_receive(:name)
- counts
- never/once/twice/at_least_once/any_number_of_times
- Arguments
- with_no_args
- with_any_args
- with(arg1, arg2, …)
- Return values
- and_returns(value)
- and_returns_consecutively([…])
- Provide a Block
- should_receive(:name) {
- |arg1, args2, …|
Why Ruby?
- Dynamic
- Productive
- Fun