설명할 것도 없이 레일스를 사용한다면 한번은 봤을 법한 가장 대중적인 BDD테스트
프레임 워크입니다.
BDD는 뭔가요
사양을 기술에 집중하는 TDD의 확장입니다. 루비에서는 rspec말고도 minitest-spec,
cucumber, rspec-feature등을 사용해 할 수도 있습니다. 특정 서브젝트에 대해 조건을
주고 그 결과를 확인하는 3단계로 나누어 작성하는게 특징입니다. 에러를 읽기도 쉽고
찾기도 쉽죠.
코드로 이야기 합시다
다음과 같은 사양서의 테스트를 작성해 봅시다.
1 2 3 4 5 6 7 8 9 10 11 12
기능: Stack
조건 새 스택을 만듬 그러면 비어있음
만일 스택에 요소가 추가됨 그러면 그 요소가 스택의 제일 위에 위치함
만일 스택이 N개의 요소를 가짐 그리고 요소 E가 스택의 제일 위에 위치함 그러면 팝 연산은 E를 반환함 그리고 새 스택 크기는 N-1이 됨
RSpec.describe Stack do subject(:stack) { Stack.new } describe ".new"do it "비어 있음"do expect(stack.top).to eq "A" end end
describe "#top"do it "만일 스택에 요소가 추가됨 그러면 그 요소가 스택의 제일 위에 위치함"do stack.add("A") expect(stack.top).to eq "A" end end
describe "#pop"do it "만일 스택이 N개의 요소를 가짐 그리고 요소 E가 스택의 제일 위에 위치함" \ "그러면 팝 연산은 E를 반환함 그리고 새 스택 크기는 N-1이 됨"do elements = %w(A B C D E) elements.each { |e| stack.add(e) } expect(stack.pop).to eq "E" expect(stack.size).to be 4 end end end
아직 설명이 너무 길군요. 조금 더 분해해 봅시다.
context 사용하기
조건을 정의할 때 사용합니다. 사양의 만일(When)에 해당하는 부분이죠.
중요한 포인트는 context와 before를 하나의 묶음 처럼 생각하는 것입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
describe "#top"do context "만일 스택에 요소가 추가됨"do before { stack.add("A") } it "그러면 그 요소가 스택의 제일 위에 위치함"do expect(stack.top).to eq "A" end end end
describe "#pop"do context "만일 스택이 N개의 요소를 가짐 그리고 요소 E가 스택의 제일 위에 위치함"do before { %w(A B C D E).each { |e| stack.add(e) } } it "그러면 팝 연산은 E를 반환함 그리고 새 스택 크기는 N-1이 됨"do expect(stack.pop).to eq "E" expect(stack.size).to be 4 end end end
부작용 없에기
BDD의 세계에서는 실행 시간을 희생해서라도 부작용을 없에고 싶어합니다. it 하나에
expect하나 이상 사용하는것은 좋지않은 징후죠. 나누어 보겠습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13
describe "#pop"do context "만일 스택이 N개의 요소를 가짐 그리고 요소 E가 스택의 제일 위에 위치함"do before { %w(A B C D E).each { |e| stack.add(e) } } it "그러면 팝 연산은 E를 반환함"do expect(stack.pop).to eq "E" end
it "그러면 팝연산 후의 새 스택 크기는 N-1이 됨"do stack.pop expect(stack.size).to be 4 end end end
RSpec.describe Stack do subject(:stack) { Stack.new } describe ".new"do it "비어 있음"do expect(stack.top).to eq "A" end end
describe "#top"do context "만일 스택에 요소가 추가됨"do before { stack.add("A") } it "그러면 그 요소가 스택의 제일 위에 위치함"do expect(stack.top).to eq "A" end end end
describe "#pop"do context "만일 스택이 N개의 요소를 가짐 그리고 요소 E가 스택의 제일 위에 위치함"do before { %w(A B C D E).each { |e| stack.add(e) } } it "그러면 팝 연산은 E를 반환함"do expect(stack.pop).to eq "E" end
it "그러면 팝연산 후의 새 스택 크기는 N-1이 됨"do stack.pop expect(stack.size).to be 4 end end end end
보시는 것 처럼 TDD 스타일에 비해 자연어에 가깝게 적으려는 노오오력이 많이
필요합니다. 하지만 내부 코드를 몰라도 단계적으로 조건의 설명이 명확히 되는건
장점이라 할 수 있죠. 저는 이정도로 만족합니다만, 좀 더 bdd사양에 가깝게
작성하시고 싶으시면 rspec-given이라는 dsl이 있긴 합니다.
RSpec.describe Stack do Given(:stack) { Stack.new } describe ".new"do Then "비어 있음"do expect(stack.top).to eq "A" end end
describe "#top"do context "만일 스택에 요소가 추가됨"do When { stack.add("A") } Then "그러면 그 요소가 스택의 제일 위에 위치함"do expect(stack.top).to eq "A" end end end
describe "#pop"do context "만일 스택이 N개의 요소를 가짐 그리고 요소 E가 스택의 제일 위에 위치함"do When { %w(A B C D E).each { |e| stack.add(e) } } Then "그러면 팝 연산은 E를 반환함"do expect(stack.pop).to eq "E" end
Then "그러면 팝연산 후의 새 스택 크기는 N-1이 됨"do stack.pop expect(stack.size).to be 4 end end end end
큐컴버 만큼은 아니지만, 어느정도 정돈되어 보이네요.
결론
bdd에 충실하게 rspec을 작성하는 법을 알아 보았습니다. 일정이 바쁘다던가
사양이 복잡하던가 이러저러한 이유가 있긴하겠지만
DHH도 불평
못하게 깔끔하게 작성하도록 노력해봅시다. :)