Test-Driven Development(TDD)로 ToDoList 만들기

bshunter·2023년 8월 9일
1

Test-Driven Development(TDD)는 소프트웨어 개발 방법론 중 하나로, 테스트 케이스를 먼저 작성한 후 코드를 구현하여 테스트를 통과하게 하는 방식입니다.

TDD의 3가지 주요 단계

TDD는 다음 3가지 주요 단계로 이루어져 있습니다.

빨간색(Red): 실패하는 테스트 케이스 작성

초록색(Green): 테스트를 통과하는 기능 구현

리팩토링(Refactor): 코드 가독성 및 구조 개선

TDD의 철학은 테스트를 통해 기대하는 동작을 먼저 정의하고, 이를 통과하는 코드를 작성함으로써 안정성 있는 코드를 구현하고 유지보수를 쉽게하기 위함입니다.
이를 통해 요구사항 변경에 대응하기 쉽고, 코드 품질 강화와 더불어 개발자의 신뢰성 또한 향상됩니다.
NestJS에서 Jest를 활용한 TDD 예제를 들어볼까요?
예를 들어, 간단한 To-Do 리스트 서비스를 구현한다고 가정해봅시다.

To-Do List

간단한 To-Do 리스트 애플리케이션에 다음과 같은 주요 기능이 필요합니다:

1.할 일 추가하기
2.할 일 완료하기
3.할 일 목록 조회하기

1. 할 일 추가하기

빨간색(Red) 단계에서는 아래와 같이 실패하는 테스트 케이스를 작성합니다.

// todo.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { TodoService } from './todo.service';

describe('TodoService', () => {
  let service: TodoService;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [TodoService],
    }).compile();

    service = module.get<TodoService>(TodoService);
  });

  it('should add a new todo item', () => {
    const newTodo = service.addTodo({ title: 'Learn TDD' });
    expect(newTodo.id).toBeDefined();
    expect(newTodo.title).toEqual('Learn TDD');
    expect(newTodo.isDone).toEqual(false);
  });
});

초록색(Green) 단계에서는 아래와 같이 테스트를 통과하는 기능을 구현합니다.

// todo.service.ts
import { Injectable } from '@nestjs/common';

export interface Todo {
  id: number;
  title: string;
  isDone: boolean;
}

@Injectable()
export class TodoService {
  private todos: Todo[] = [];
  private currentId = 1;

  addTodo({ title }): Todo {
    const newTodo: Todo = { id: this.currentId++, title, isDone: false };
    this.todos.push(newTodo);
    return newTodo;
  }
}

리팩토링(Refactor) 단계에서 코드를 개선합니다. 현재 코드가 간결하므로 리팩토링이 필요하지 않습니다.

2. 할 일 완료하기

빨간색(Red) 단계에서는 할 일을 완료하는 테스트 케이스를 작성합니다.

// todo.service.spec.ts
// ...
  it('should mark a todo item as done', () => {
    const newTodo = service.addTodo({ title: 'Learn TDD' });
    const updatedTodo = service.markAsDone(newTodo.id);
    expect(updatedTodo.isDone).toEqual(true);
  });
초록색(Green) 단계에서는 테스트를 통과하는 기능을 구현합니다.
typescript
// todo.service.ts
// ...
  markAsDone(id: number): Todo {
    const todoIndex = this.todos.findIndex((todo) => todo.id === id);
    if (todoIndex === -1) throw new Error('Todo not found');

    this.todos[todoIndex].isDone = true;
    return this.todos[todoIndex];
  }

리팩토링(Refactor) 단계에서 코드를 개선합니다. 현재 코드가 간결하므로 리팩토링이 필요하지 않습니다.

3. 할 일 목록 조회하기

빨간색(Red) 단계에서는 할 일 목록을 조회하는 테스트 케이스를 작성합니다.

// todo.service.spec.ts
// ...
  it('should return all todo items', () => {
    service.addTodo({ title: 'Learn TDD' });
    service.addTodo({ title: 'Practice TDD' });

    const todos = service.getAllTodos();
    expect(todos.length).toEqual(2);
  });
초록색(Green) 단계에서는 테스트를 통과하는 기능을 구현합니다.
typescript
// todo.service.ts
// ...
  getAllTodos(): Todo[] {
    return this.todos;
  }

리팩토링(Refactor) 단계에서 코드를 개선합니다. 현재 코드가 간결하므로 리팩토링이 필요하지 않습니다.

이처럼 TDD를 사용하여 To-Do 리스트 애플리케이션의 기본 기능을 구현해보았습니다.
실패하는 테스트 케이스를 먼저 작성한 다음, 해당 테스트 케이스에 통과하는 로직을 작성했고,
필요한 경우 코드 구조를 개선하는 리팩토링 과정을 거쳤습니다.

TDD 도입 시 발생할 수 있는 문제들과 해결 방법

문제 1: 테스트 케이스 작성의 어려움

처음 TDD를 도입하면, 테스트 케이스를 작성하는 것이 어려울 수 있습니다.
특히, 복잡한 로직을 가진 기능을 테스트할 때 어떤 케이스를 다루어야 하는지 결정하는 것이 어려운 경우가 많습니다.
이 때는 케이스를 작은 단위로 나누고 기능별로 테스트를 작성하여 점차 복잡성을 증가시키세요.

가능한 모든 입력값들을 테스트하는 것은 불가능하므로, 주요 시나리오와 경계 조건을 중점으로 테스트 케이스를 작성합니다.
또한, 기존에 축적된 경험과 무작위 테스트 방식을 도입함으로써 다양한 테스트 케이스를 생성할 수 있습니다.

문제 2: 테스트 작성에 소요되는 시간

TDD를 도입하면 초기 개발 속도가 다소 느려질 수 있습니다.

특히 프로젝트 초기에 이미 사용되고 있는 기능에 대한 테스트가 없다면, 테스트 케이스 작성에 많은 시간이 소요될 수 있습니다.
해결방법 으로는 TDD를 적용할 프로젝트를 작은 단위로 구성하고, 개발 속도를 점진적으로 높여나가야 합니다.
또한, 테스트 코드를 작성하는 동안 비효율적인 코드를 발견할 경우, 리팩토링을 통해 개선하여 개발 속도 향상에 기여할 수 있습니다.

문제 3: TDD를 적용하기 어려운 프로젝트

레거시 코드나 외부 라이브러리, 상태가 많이 변동되는 프로젝트에서는 TDD 적용에 어려움이 있을 수 있습니다.

작은 부분부터 TDD 접근법을 적용하세요.
레거시 코드에 대한 테스트를 점진적으로 추가하면서 리팩토링을 진행하세요.
통합과 모듈 단위 테스트를 혼합해서 사용하여 다양한 시나리오를 커버하도록 노력하세요.

마치며

최소한의 요구사항만 구현하는 것이 아니라, 미래의 확장성을 고려해 테스트 케이스와 코드를 작성하는 것이 좋습니다.
항상 실패하는 테스트 케이스로 시작하는 것이 중요합니다. 이를 통해 코드의 신뢰성이 확보되기 때문입니다.
코드 리뷰 과정에서 테스트 케이스를 함께 리뷰하면 프로젝트 전체의 테스트 품질 개선에 기여할 수 있습니다.
TDD는 초기 학습 곡선이 높고, 여러 문제가 발생할 수 있지만, 전반적인 코드 품질과 유지 보수 용이성이 개선되어 궁극적으로 효율적인 개발 방식이 될 수 있습니다.

0개의 댓글