행위 주도 개발 (BDD)
[번역] Behavior-Driven Development
행위 주도 개발(BDD)은 피드백 루프 feedback loop를 최소화 하는 것입니다. 이는 소프트웨어 개발 프랙티스(실천, 관례)의 발전에 있어 논리적 진전입니다. 이 글에서는 개념과 그 기원에 대해 설명합니다.
폭포수 Waterfall
여러분이 소프트웨어 개발자 또는 엔지니어링 관리자라면 다음 다이어그램에 표시된 폭포수 모델에 대해 잘 알고 있을 것입니다:
나중에 “폭포수”라고 이름 붙여진 것은 윈스턴 로이스 Winston Royce가 1970년에 발표한 논문 “대형 소프트웨어 시스템 개발 관리 Managing the development of large software systems“에서 공식적으로 처음 언급되었습니다. 이 아이디어를 이해하기 위해 전체 논문을 읽는 것을 추천합니다. 대부분 사람은 폭포수에 대해 간접적으로 배우고 이 프로세스가 그 당시 최고의 해결책으로 제시되었다고 생각합니다. 그러나, 로이스는 개발 프로세스 마지막에 테스트 단계를 가지는 것이 주요 문제라는 것을 인식했습니다.
“저는 이 개념을 믿지만, 위에서 설명한 실행법은 위험하고 실패를 초래합니다. […] 개발 사이클 마지막에 존재하는 테스트 단계는 분석과는 구별되는 타이밍, 저장 storage, 입출력 전송 등을 경험하는 첫 번째 이벤트입니다. […] 설계 변경이 필요한 경우 개발에 매우 지장을 줄 수 있어서 설계의 기반이 되고 모든 것에 대한 근거를 제공하는 소프트웨어 요구사항이 위반될 수 있습니다. 요구 사항을 수정하거나 설계를 크게 변경해야 합니다. 사실상, 개발 프로세스는 원점으로 돌아가고 일정 및/또는 비용이 최대 100% 초과할 것으로 예상할 수 있습니다.
이 모델은 다양한 이유로 전 세계 많은 회사에서 소프트웨어를 개발하는 데 여전히 사용됩니다. 폭포수는 흐름을 의미하지만, 실제로는 단계 사이에 항상 피드백 루프가 존재합니다. 이 모델의 모든 주요 개선 사항은 시간이 지남에 따라 피드백 루프를 최소화하고 가능한 한 예측 할 수 있게 만드는 식으로 이루어졌습니다.
예를 들어, 프로그램을 만드는 경우, 우리는 그것이 동작하는지 확인하는 데 얼마나 시간이 걸리는지 알고 싶습니다. 반면에, 시스템 일부를 설계한다면, 실제로 프로그래밍을 할 수 있고 검증할 수 있는 것인지, 그리고 비용은 얼마인지 알고 싶습니다.
그래서 피드백 루프를 볼 때, 그것을 최소화하기 위해 사용할 수 있는 방법을 찾습니다. 우선, 우리의 목표는 명백하게 낭비되는 작업을 제거하는 것입니다. 나중엔, 예전 방식으로 일했을 때 상상했던 것보다 더 나은 최적화와 더 빠르고 잘할 수 있다는 것을 깨닫게 됩니다.
첫 번째 최적화: 테스트 우선 프로그래밍 Test-First Programming
첫 번째 최적화는 코딩과 테스트 단계에서 나타났습니다. 전통적인 품질보증 기반(QA 기반) 개발 모델에서는 프로그래머가 코드를 작성하여 QA 팀에 제출했습니다. 코드가 동작하는지, 그리고 나머지 프로그램도 제대로 동작하는지에 대한 보고서를 받는 데 하루, 몇 주 또는 수 주가 걸렸습니다. 버그가 자주 발생했기 때문에, 작업이 끝났다고 생각했음에도 프로그래밍으로 돌아가 문제를 고쳐야 했습니다.
피드백 루프를 줄이기 위해 개발자들은 코딩과 검증을 동시에 시작했습니다. 즉, 코드를 작성하고 그 뒤 테스트를 작성하는 것입니다. 테스트는 언제든지 테스트를 작성한 시스템의 모든 부분을 검증하기 위해 실행할 수 있는 훌륭한 부수 효과를 만들어냈습니다 - 자동화된 테스트 스위트 the automated test suite - 여기에서부터 가능한 한 안전하게 작업할 수 있도록 전체 시스템을 포괄하는 테스트 스위트를 갖추려는 열망이 생겨났습니다.
코딩 뒤 테스트가 뒤따르는 피드백 루프는 여전히 좀 시간이 걸리므로, 그래서 다음 단계는 그것을 뒤집는 것이었습니다: 한 줄의 코드를 작성하기 전에 테스트를 작성하는 것입니다. 이렇게 하면 피드백 루프가 줄어들고 개발자들이 기존 테스트를 통과하기 위해 필요한 코드만 작성한다는 사실을 깨닫는 데 오래 걸리지 않습니다.
이것을 테스트 우선 프로그래밍 이라고 합니다. 테스트 우선 작업을 할 때, 테스트는 구현을 올바르게 “작성”하는 데 도움 되도록 사용됩니다. 이는 버그 수를 줄이고, 프로그래머 생산성을 높이며 전체 팀의 템포에 긍정적인 영향을 미칩니다.
나중에 테스트를 작성하는 문제
폭포수 모델은 개발 주기의 마지막에 테스트를 합니다. 우리가 보았듯이, 이것은 매우 비효율적입니다. 그러나 이게 유일한 문제는 아닙니다. 개발자들이 테스트 가능한 코드를 작성하기 보다는 문제를 해결하는 데 집중하기 때문에 따로 작성된 코드는 테스트하기 어렵습니다.
나중에, 테스트 작성을 시작할 때 사고방식은 다시 잘못된 곳에 있습니다: 코드의 동작을 테스트하는 대신에 작성한 코드가 실제로 우리가 작성한 코드인지 테스트하는 것에 집중하고 있습니다.
그 결과 코드와 테스트가 높은 결합이 됩니다. 이것은 우리가 리팩토링할 때 훨씬 더 많은 문제를 일으킵니다. 밀접하게 결합한 코드를 변경하면 관련 테스트가 더 이상 쓸모가 없게 됩니다. — 코드에서 무언가를 변경하려 할때마다 많은 테스트를 다시 작성해야 합니다. 피드백 루프는 우리가 과거 결정의 결과를 계속해서 처리함에 따라 고통을 겪고 길어집니다.
해결책은 테스트 우선 사고방식을 가지는 것입니다. 테스트가 처음에 올 때, 기존의 어떤 코드라도 그것들을 작성하는 방법에 영향을 미치지 않으며 코드가 실제로 무엇을 해야 하는지 확인하는 테스트를 작성할 수 있습니다. 또 다른 이점도 있습니다: 결과 코드는 모드 모듈식이고 테스트 가능하므로 테스트를 더 작고 읽기 쉽게 만듭니다.
테스트 주도 개발 Test-Driven Development
테스트와 코딩의 반복이 계속되면 우린 여전히 모든 프로그램 설계를 미리 수행하고 있습니다. 코드가 제대로 작동하는지 확인하기 위해 테스트 우선 프로그래밍을 사용하고 있지만, 테스트하기 어려운 설계거나, 코딩할 수 없거나, 성능이 떨어지거나 혹은 구현하려고 할 때 나머지 시스템과 핏이 맞지 않거나 하는 것을 알게 되는 피드백 루프가 있습니다.
이 루프를 최소화하기 위해 동일한 테크닉을 적용합니다. 설계를 시작하기 전에 테스트 우선 프로그래밍을 통해 다시 반전시킵니다. 또는 테스트, 코딩 그리고 프로그램 설계 단계를 모두 동시에 수행합니다. 테스트는 코드에 영향을 주고, 이는 다시 설계에 영향을 주며, 이는 또 다음 테스트에 영향을 줍니다.
이 주기가 설계 아이디어를 유기적으로 추진하는 것이 금방 분명해지며, 쉽게 발전할 수 있는 방식으로 필요한 설계 부분만 구현하기 시작합니다. 설계는 이제 상당한 리팩토링 단계를 포함하므로 오버 엔지니어링 대신 언더 디자인(과소 설계)을 확신할 수 있습니다. 즉, 현재 요구 사항을 충족하는 충분한 설계와 적절한 코드를 얻을 수 있습니다.
이것이 테스트 주도 개발 (TDD) 입니다. 리팩토링 원칙과 패턴을 지속적으로 적용하여 테스트 우선 프로그래밍과 디자인 씽킹 design thinking을 결합합니다. 이제 긍정적인 부수 효과가 증폭되었습니다: 버그 수를 줄일 뿐만 아니라 기능 구현에 도움이 되지 않는 어떠한 코드도 작성하지 않고 있습니다. 따라서 나중에 수정하는 데 비용이 더 많이 드는 설계 실수를 방지할 수 있어서 팀의 생산성이 더욱 향상됩니다.
TDD는 설계와 테스트가 연속적으로 반복하는 루프에서 교차되어야 한다는 오래된 아이디어의 결정체입니다:
“소프트웨어 시스템은 설계 후에 사용되는 대신, 설계와 테스트가 교차된다면 가장 잘 설계될 수 있습니다. [..] 요구 사항과 일치하는 시뮬레이션에는 시스템 설계를 구성하는 컨트롤이 포함되어 있습니다. [..] 교차된 테스트와 설계 프로세스를 연속적으로 반복함으로써 모델은 궁극적으로 소프트웨어 시스템 그 자체가 됩니다.”
행위 주도 개발 Behavior-Driven Development 로 다음 단계 만들기
이제 하나의 루프에서 설계, 코딩 그리고 테스트를 하고 있으므로 분석 단계를 다시 살펴볼 차례입니다. 분석에 의해, “만들어야할 것을 이해”하고 있다고 가정합니다. 다시 말하자면, 효율성이라는 말로 루프를 최적화하는 데 관심이 있습니다. 실제로, 여기에 12개 이상의 기능 목록을 준비하여 개발자에게 전달해야 하며 개발자는 앞으로 진행하기 전에 모든 기능을 완료해야 합니다. 이런 식으로, 종종 필요하지 않는 기능을 구현하게 됩니다. 때로는 예상하지 못한 새로운 기능을 발견하거나 필요한 기능에 대한 새로운 기능을 발견하기도 합니다.
동일한 테크닉을 적용하여 분석을 루프로 가져올 수 있습니다. 이제 다른 기능을 구현하기 전에 기능을 테스트합니다. 개발자의 이러한 주기 기간은 며칠 또는 몇 주가 아니라 몇 시간 또는 때로는 몇 분으로 측정된다는 점을 설명할 가치가 있습니다.
이 테크닉을 한동안 지속적으로 적용한 후에는, 가장 작은 단위로 모든 기능을 세분화하여 일관되게 하나씩 제공하는 경향이 있음을 알 수 있습니다. 기능이 서로 어떻게 영향을 미치는지에 대한 이해가 향상되며 변화에 빠르게 대응할 수 있게 됩니다. 이를 통해 원하지 않는 기능을 신속하게 식별하고 폐기하며 중요한 기능을 우선적으로 지정할 수 있습니다.
분석을 테스트함으로써, 시스템의 동작과 적절하게 설계하고 구현 방법을 더 잘 이해할 수 있습니다. 동시에, 첫날부터 하는 모든 일은 테스트 스위트를 만드는 것이며 테스트 스위트는 전체 시스템을 지속해서 검증할 수 있게 해줍니다.
이를 행위 주도 개발 Behavior-Driven Development (BDD)이라고 합니다. BDD는 이해 관계자(비지니스 오너)와 개발 팀 모두의 시간을 절약해 줍니다. 조기에 질문 함으로써, 개발자는 자신과 이해 관계자 모두가 자신이 만들고 있는 것을 깊이 이해할 수 있도록 돕습니다. 이해 관계자는 예측 가능한 페이스로 결과를 얻으며, 기능이 작은 덩어리로 처리되기 때문에 더 정확하게 추정할 수 있고 그에 따라 새로운 기능을 계획할 수 있으며 우선 순위를 정할 수 있습니다.
BDD: 어떻게가 아니라 무엇을
BDD 실천을 위해, 시스템의 동작을 설명하는 명세서를 작성해야 합니다. 다음과 같은 질문을 통해 이를 수행합니다: “사용자가 X를 했을 때 시스템이 어떻게 반응해야 합니까?” 혹은 “X가 사용자의 기대에 따라야하나요?” 이는 구현 세부 사항에 빠지지 않고 현재 진행 중인 문제에 집중하게 해줍니다. 우리는 시스템이 어떻게 하는지보다는 시스템이 무엇을 하는지를 생각합니다.
이 단계에서 시스템이 어떻게 보이는지, 동그랗거나 네모난 버튼들이 있는지, 심지어 어떤 장치에서 실행되는지도 전혀 상관하지 않습니다. 따라서, 해결하고자 하는 문제를 기술적 세부 사항에서 분리합니다. 명세서는 아래 예와 같이 그 사례를 명확하고 간결한 형식으로 제시합니다:
Scenario: 블로그 검색
Given 나는 블로그 페이지에 방문합니다
When "BDD"를 검색합니다
Then "BDD"와 관련 있는 게시물들을 확인합니다
이 예제는 BDD 스타일로 테스트 케이스를 정의하기 위해 Cucumber와 같은 프레임워크에서 사용하는 언어인 Gherkin으로 작성되었습니다. 내부적으로, 시나리오는 설계 명세에 준수하는지 확인하는 일련의 자동화 테스트를 진행합니다.
BDD를 사용하면 테스트, 분석과 설계를 서로 연결하여 개발을 가이드하고 진행 상황을 추적할 수 있는 피드백 루프를 만들어 서로에게 영향을 줍니다.
BDD는 UI 테스트가 아닙니다
필연적으로, 아이디어가 대중화되면서 중요한 뉘앙스 일부를 잃게 됩니다. BDD에 대한 가장 큰 오해 중 하나는 BDD가 UI 테스트의 동의어라는 것입니다.
예를 들어봅시다:
Scenario: 사용자가 애플리케이션에 로그인
Given 권한이 있는 사용자 "John"가 있습니다
When 사용자 이름 필드에 "John"을 입력합니다
And 암호 필드에 "sekret1"을 입력합니다
And 로그인 버튼에 클릭합니다
Then 홈페이지에 접근합니다
이 Gherkin 시나리오에는 몇 가지 문제가 있습니다. 우선, UI에 너무 집중되어 있어서 망가지기 쉽고 구현에 너무 얽매여 있습니다 - 필드 이름 중 하나라도 변경되면 테스트가 중단됩니다.
또 다른 문제는 개발자들이 레시피를 따르도록 강제하기 때문에 테스트의 로직이 제한적이라는 것입니다. 심지어 지문이나 얼굴 인식과 같은 더 나은 인증 메커니즘을 사용할 수 있을 때도 기존 사용자 username/password 인증을 구현해야 합니다. 너무 자세한 시나리오는 따라야 하는 많은 단계의 목록 아래로 해결해야 할 문제를 숨기기 때문에 개발자의 삶을 더 힘들게 합니다.
더 나은 버전의 시나리오가 아래에 나와 있습니다. 이 예에서, 세부 사항(어떻게 해야 하는지)을 설명하지 않고 설계 명세(어떤 것을 시스템이 수행해야 하는지)를 설정합니다. 개발자는 미리 결정된 솔루션을 따라야 하는 족쇄에서 벗어나 자유롭게 혁신할 수 있습니다.
Scenario: 사용자가 애플리케이션에 로그인
Given 권한이 있는 사용자 "John"가 있습니다
When “John”이 올바르게 로그인합니다
Then “John”은 아이템에 접근할 수 있습니다
BDD는 모든 레벨에서 동작합니다
BDD에 대한 또 다른 큰 오해는 통합 및 유닛 테스트에 작동하지 않는다는 것입니다. 그 이유는 Gherkin이 추가적인 추상화 계층을 도입하여 사람들이 BDD가 E2E 테스트와 동의어라고 믿게 했기 때문입니다. BDD는 일반적으로 인수 테스트 acceptance tests와 함께 사용되지만, 테스트 피라미드 testing pyramid의 어떤 레벨에서도 BDD를 사용하는 데 방해가 되는 것은 없습니다.
BDD가 모든 레벨에서 어떻게 사용될 수 있는지 알아보기 위해 여기 Gherkin으로 작성된 유닛 테스트가 있습니다:
Feature: 한 쌍을 합함
한 쌍의 숫자를 합산합니다
Scenario: 숫자를 더함
Given 1이 있습니다
When 2를 더합니다
Then 합은 3입니다
예제는 매우 간단해서 Gherkin을 버리고 다음과 같이 Jest 같은 동작 주도 DSL behavior-driven DSLs을 지원하는 모든 프레임워크에서 테스트를 직접 작성할 수도 있습니다:
import { sum } from './maths';
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});
BDD가 높은 레벨의 테스트에서만 작동한다고 생각하면 거꾸로 된 테스트 피라미드(아이스크림 콘이라고도 함)로 이어집니다. 때에 따라서는, 역피라미드가 허용할 수 있는 솔루션일 때도 있지만, 더 자주는 매우 느리게 실행되며 유지 보수가 어려운 복잡한 테스트 스위트만 생기게 됩니다.
더 빠르게 진행
폭포수 다이어그램에 나열된 나머지 단계를 살펴보면 방법론과 관계없이 모두 이루어져야 합니다. 이 단계에서도 같은 피드백 루프 최소화를 적용할 수 있는지 궁금할 수 있습니다. 대답은, 물론, 그렇습니다. 그러나, 이러한 루프는 단순한 설계와 개발보다 더 넓은 범위에 속하며, 매우 다른 분야에서 일하는 사람들을 포함하므로 이 글의 범위를 벗어납니다. 하지만, 간단히 언급하겠습니다.
린 스타트업 Lean Startup은 스타트업이 구축해야 할 것을 학습하는 데 루프를 가깝게 하는 방법으로 요구 사항 수집, 기능 개발 그리고 마케팅을 통합하는 가장 가까운 개념입니다. 물론, 기업에서는 프로세스가 다소 다르게 진행되지만, 그런데도 많은 프로젝트에서 린 스타트업 원칙을 적용하는 방법을 배우고 있습니다.
BDD를 배포 및 운영과 병합하면 지속적 배포 continuous delivery라는 광범위한 개념을 얻을 수 있습니다. 가장 중요한 프로세스는 지속적 통합 continuous integration (CI)과 지속적 배포로, Semaphore에서 모든 프로젝트에 대해 쉽게 설정할 수 있습니다.
결론
행위 주도 개발은 소프트웨어 개발 프로세스에서 다양한 단계의 최적화로부터 발전했습니다. 하나의 짧은 피드백 루프에서 시스템을 분석, 테스트, 코딩 그리고 설계함으로써 더 나은 소프트웨어를 생산할 수 있으며, 이는 실수와 낭비적인 작업을 피할 수 있도록 도와줍니다.
BDD가 TDD에 기원을 두고 있어서 TDD는 테스트에 관한 것이고 BDD가 소프트웨어 테스트에 접근하는 또 다른 방법일 뿐이라는 것은 일반적인 오해입니다. 이것은 사실이 아닙니다. (비록 테스트가 좋은 부산물이지만) BDD는 다음과 같은 하나의 간단한 아이디어에서 파생된 소프트웨어 개발에 대한 전체론적 접근 방식입니다: 피드백 루프를 최적화하려는 열망
BDD는 효과적인 소프트웨어 개발을 위한 강력한 도구이고, 제대로 배우고 적용하는 데 시간이 걸립니다. BDD는 가치를 더하고 의미를 부여하며 설계에 보탬이 되는 테스트를 작성하는 데 도움이 됩니다. ‘무엇’에 집중하고 ‘어떻게’를 피하는 한, 여러분은 괜찮을 것입니다.