dev-sohee 님의 블로그
효율적인 개발 방법론의 진화: TDD, BDD, DDD 본문
소프트웨어 개발의 세계는 점점 복잡해지고 있습니다. 이런 환경에서 개발자들은 품질 높은 코드를 작성하기 위해 다양한 방법론을 도입하고 있습니다. 오늘 다룰 내용인 TDD, BDD, DDD가 그 예시입니다. 이번 글에서는 스프링 프레임워크를 활용하여 이 세 가지 방법론을 효과적으로 적용하는 방법을 살펴보겠습니다.
*TDD(Test Driven Development)
*BDD(Behavior Driven Development)
*DDD(Domain Driven Development)
#TDD
TDD(Test Driven Development)는 테스트를 먼저 작성하고, 그 테스트를 통과할 수 있도록 최소한의 코드를 개발하는 방식입니다. 이로 인해 코드의 품질과 완성도에 대한 신뢰도가 높아집니다. 1990년대 말, 소프트웨어의 복잡성이 증가하면서, 코드의 품질과 유지보수성을 높이기 위해 TDD의 필요성이 대두되었습니다.
//두 숫자를 더하는 기능을 구현하기 위한 테스트 코드
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class CalculatorTest {
@Test
public void testAdd() {
Calculator calculator = new Calculator();
assertEquals(5, calculator.add(2, 3));
}
}
//실제 코드
public class Calculator {
public int add(int a, int b) {
return a + b;
}
}
TDD 개발과정
- 테스트 코드 작성
먼저, 구현할 기능에 대한 테스트 코드를 작성합니다. 이 단계에서는 아직 해당 기능이 구현되어 있지 않기 때문에, 작성한 테스트는 반드시 실패해야 합니다. 이 과정을 통해 테스트의 목적을 명확히 하고, 무엇을 구현해야 할지 이해하게 됩니다. (제가 실제로 회사에서 진행했던 프로젝트 중, 무전기 단말 UI의 좌우 스크롤 키 동작을 구현해야 했었는데 테스트 코드를 작성하지 않고 기능을 먼저 구현한 후 테스트를 수행했더니 초기값, 한계값 설정 누락으로 인해 side effect가 발생해서 아찔했던 경험이 있었습니다.) - 리팩토링
테스트가 통과하면, 코드를 리팩토링합니다. 이 과정에서는 코드의 가독성, 유지보수성, 성능 등을 향상시키는 데 집중합니다. 리팩토링 후에도 기존의 테스트가 모두 통과하는지 확인하여, 변경이 기존 기능에 영향을 미치지 않도록 합니다.
장점
- 버그 감소: 코드 작성 시 항상 테스트를 고려하므로, 버그를 조기에 발견할 수 있습니다.
- 명확한 요구 사항: 테스트를 통해 개발할 기능에 대한 명확한 요구 사항을 정의할 수 있습니다. 즉, 각 모듈의 역할이 단순해지고 명확해집니다.
- 코드 품질 향상: 지속적인 리팩토링을 통해 코드의 품질을 유지하고 개선하기 수월합니다.
- 테스트 안정성: 코드 변경 후 테스트가 통과하면 기존 기능이 안전하다는 확신을 가질 수 있고 놓칠 수 있는 것들을 반복 테스트하기 좋습니다.
단점
- 초기 시간 투자: 테스트 코드를 먼저 작성해야 하므로 초기 개발 시간이 길어질 수 있습니다.
- 복잡한 테스트 코드: 코드가 복잡해지면 테스트 코드도 복잡해질 수 있어 유지보수가 어려워질 수 있습니다.
- 불완전한 테스트: 테스트가 모든 케이스를 포괄하지 못할 수 있으며, 이로 인해 잠재적인 버그가 남아 있을 수 있습니다.
- 기능 변경의 어려움: 요구사항이 변경되면 기존 테스트를 수정해야 하므로, 이를 관리하는 데 추가적인 노력이 필요합니다.
#BDD
BDD(Behavior Driven Development)는 TDD에서 파생된 개발 방법론으로, 2000년대 초에 등장했습니다. BDD는 비즈니스 요구 사항과 사용자 행동에 중점을 두어, 사용자의 행위까지 테스트하며 개발하기 위해 고안되었습니다. TDD와 BDD는 상호 보완적인 관계로, BDD의 테스트 케이스로 시나리오 검증을 하고, 해당 시나리오에서 사용하는 각 모듈들은 TDD의 테스트 케이스로 검증을 하는 것이 좋습니다.
//사용자 로그인 기능을 구현하기 위한 테스트 코드(Cucumber)
Feature: User Login
Scenario: Successful login with valid credentials
Given the user is on the login page
When the user enters "username" and "password"
Then the user should be redirected to the homepage
//실제 코드
import io.cucumber.java.en.*;
public class LoginSteps {
@Given("the user is on the login page")
public void userOnLoginPage() {
// 코드로 로그인 페이지로 이동
}
@When("the user enters {string} and {string}")
public void userEntersCredentials(String username, String password) {
// 로그인 정보 입력 코드
}
@Then("the user should be redirected to the homepage")
public void userRedirectedToHomepage() {
// 홈페이지로 리디렉션 확인 코드
}
}
BDD 개발 과정
- 요구 사항 수집
개발팀과 비즈니스 이해관계자가 함께 모여, 시스템의 요구 사항을 논의합니다. 이 과정에서 사용자 스토리와 기능 요구 사항을 정의합니다. - 시나리오 작성
정의된 요구 사항을 바탕으로 Gherkin 형식으로 시나리오를 작성합니다. 이 시나리오는 "Given", "When", "Then" 구조를 사용하여 비즈니스 로직을 설명합니다.
예시) Feature: User Login - 피드백 및 리팩토링
TDD와 마찬가지로 테스트가 통과하면, 코드를 리팩토링합니다.
장점
- 협업 강화: 개발자와 비즈니스 이해관계자 간의 소통을 통해 요구 사항을 명확히 이해할 수 있습니다.
- 가독성 높은 문서: Gherkin 형식의 시나리오는 비즈니스 요구 사항을 쉽게 이해할 수 있도록 도와줍니다.
- 자동화된 테스트: 시나리오가 자동화된 테스트로 변환되어 지속적인 검증이 가능합니다.
단점
- 명세 관리의 복잡성: 다양한 이해관계자와의 커뮤니케이션이 필요해, 명세가 일관되지 않거나 관리하기 어려울 수 있습니다.
- 툴 의존성: 특정 BDD 프레임워크나 도구(ex) Cucumber, JUnit + JBehave)에 의존하게 되어, 도구 변경 시 어려움이 발생할 수 있습니다.
#DDD
DDD(Domain Driven Development)는 2003년 소프트웨어 개발에서 도메인의 중요성이 강조되면서, 복잡한 비즈니스 문제를 효과적으로 해결하기 위한 방법론으로 자리 잡았습니다. DDD는 도메인 전문가와의 협업을 통해 도메인을 깊이 이해하고, 이를 바탕으로 소프트웨어 설계를 진행함으로써, 비즈니스 요구 사항에 충실한 시스템을 만드는 것을 목표로 합니다.
//쇼핑 카트 시스템에서 상품 추가 기능을 구현하기 위한 도메인 모델
public class Product {
private String id;
private String name;
private double price;
}
public class ShoppingCart {
private List<Product> products = new ArrayList<>();
public void addProduct(Product product) {
products.add(product);
}
public List<Product> getProducts() {
return products;
}
}
//실제 코드
public class ShoppingCartService {
private ShoppingCart shoppingCart;
public ShoppingCartService(ShoppingCart shoppingCart) {
this.shoppingCart = shoppingCart;
}
public void addProductToCart(Product product) {
shoppingCart.addProduct(product);
}
}
DDD 개발 과정
- 도메인 이해 및 모델링
도메인 전문가와 개발자가 함께 모여 비즈니스 도메인에 대한 깊이 있는 이해를 구축하고 도메인을 표현하는 모델을 설계합니다. 도메인 용어, 규칙, 프로세스 등을 논의하며, 비즈니스 목표를 명확히 합니다. 이 과정에서 엔티티, 값 객체, 집합체, 도메인 서비스 등을 정의합니다. 도메인 모델은 실제 비즈니스 로직을 담고 있어야 합니다. - 유비쿼터스 언어 개발
개발팀과 비즈니스 이해관계자가 공통으로 사용할 수 있는 유비쿼터스 언어를 정의합니다. 이는 도메인 모델을 설명하는 데 사용되는 용어로, 모든 팀원이 이해할 수 있도록 합니다. - 경계 Context 식별
도메인을 여러 개의 경계 Context(Bounded Context)로 나누어 각 Context 내에서 일관된 모델을 유지합니다. 서로 다른 Context 간의 관계를 정의하고, 필요한 경우 통합을 고려합니다. - 리팩토링
코드와 도메인 모델을 지속적으로 개선하여 품질을 높입니다. 변경 사항이나 새로운 요구 사항이 생기면 도메인 모델을 업데이트하고, 이를 반영하여 코드를 리팩토링합니다.
장점
- 비즈니스 중심: 실제 비즈니스 로직과 사용자의 필요를 명확히 반영하여 소프트웨어를 설계할 수 있습니다.
- 복잡성 관리: 경계 Context(Bounded Context)를 활용하여 복잡한 도메인을 여러 부분으로 나누어 관리함으로써, 시스템의 복잡성을 효과적으로 줄이고 각 부분의 일관성을 유지할 수 있습니다.
- 유연성과 확장성: DDD는 도메인 모델의 변화를 쉽게 수용할 수 있도록 설계되었기 때문에 새로운 비즈니스 요구 사항이나 변화에 대해 신속하게 대응할 수 있습니다.
단점
- 도메인 경계의 모호성: 도메인을 여러 개의 경계로 나누어야하는데, 경계가 애매하면 팀 간에 도메인에 대한 이해가 일치하지 않아, 요구사항이나 설계에 대한 불일치가 발생할 수 있고 여러 팀이나 모듈에서 유사한 기능이 중복 개발될 위험이 있습니다. (실제로 제가 개발했던 무전기 프로젝트는 하드웨어, 시스템, 앱팀이 협업했던 프로젝트였는데 시스템 팀이 하드웨어와 앱팀을 이어주는 역할이라서 양쪽 팀들과 의견 충돌이 잦았습니다. 예를 들어 메모리 과부하에 대하여 하드웨어에서 새로운 RAM으로 교체하거나 시스템 팀에서 코드를 수정하여 해결할 수 있었는데 단가 문제와 소프트웨어 속도 문제가 얽혀있어서 회의를 통해 타협을 해야 했습니다.
- 전문가 필요: 도메인 전문가와의 협업이 필수적이며, 이들이 없으면 제대로 된 모델을 만들기 어려울 수 있습니다.
TDD와 BDD, DDD 중 어떤 개발 방법론이 더 효율적이고 옳다고 얘기할 수는 없습니다. 다만 개발하고자 하는 도메인의 기술 성숙도와 사용자 및 기획자의 개발 방향성에 따라 어떤 방법론을 선택하는 것이 옳은지는 판단할 수 있을 것 같습니다. 또한 이 접근법들은 서로 보완적이며, 상황에 맞게 적절히 조합하여 사용할 때 더 큰 시너지를 발휘할 수 있습니다. 따라서, 이들을 잘 이해하고 활용하는 것이 개발자의 필수 요소인 것 같습니다.
'spring' 카테고리의 다른 글
프록시의 세계: 다이나믹 프록시와 CGLIB의 차이 (0) | 2024.09.21 |
---|---|
코드의 신뢰도를 높이는 JUnit 단위 테스트 (0) | 2024.09.07 |
Spring의 3대 프로그래밍 모델 : 스프링 삼각형(IoC/DI, AOP, PSA) 2탄 (0) | 2024.08.31 |
Spring의 3대 프로그래밍 모델 : 스프링 삼각형(IoC/DI, AOP, PSA) 1탄 (1) | 2024.08.31 |