TDD, 테스트 주도 개발에 대한 이야기

원태연·2022년 1월 3일
0

TDD

(Test Driven Development)

TDD란

"테스트 주도 개발" 이라는 의미로, 작은 단위의 테스트 케이스를 작성하고 이를 통과하는 코드를 구현하는 단계를 반복적으로 수행하며 소프트웨어(프로그래밍)을 구현한다는 방법론.

보통 프로그램을 개발하겠다고 생각한다면, 설계 -> 개발 -> 테스트과 같은 단계를 거칠 것이다. 사실 개발 뿐만아니라 문제를 해결하기 위해선 이런 사고과정이 지배적인것 같다. 제품도 만들어져야 테스트를 할 수 있듯이, 코드도 구현이 되어있어야 테스트를 하지 않겠는가.

하지만 TDD는 테스트를 먼저 만들어 내고, 이를 구현하고, 리팩토링하는 짧은 개발주기를 반복하며 Extream Programming을 실현할 수 있다.

Extream Programming : 미래에 대한 예측을 줄이고, 지속적으로 프로토타입을 완성하는 애자일 방법론 중 하나.

  • 예시

우선, 예시로 살펴보자

class Person {
	String name;
	int age;

	public Person(String name, int age) {
		this.name = name;
		this.age = age;
	}
}

Person이라는 객체가 존재한다고 했을때, 두 가지 기능사항을 요구받았다고 해보자.

  • 이름을 통해 사람을 찾는 기능
  • 나이가 20살 이상이면 성인

일반적인 방식이라면, 메서드를 생성 -> 기능 구현 -> 테스트 라는 순서로 개발할 것이다.

TDD에서는 테스트 부터 설계한다.

이름을 통해 나이를 찾는 기능에 대한 테스트

  1. 존재하는 이름인지에 대한 테스트가 필요함
  2. 없으면 False, 있으면 True를 반환하여야 한다
public class funcTest() {
  @Test
  void isValidNameTest() {
    //given
    PersonRepo ps = new PersonRepo();
    
    //when
    Person person = new Person("Royce", 15);
    ps.addPerson(person);
      
    //then
    Assertions.assertThat(ps.isValidName("Hello"))
      .isFalse();
  }
}

이렇게 테스트만 먼저 설계해둔다.

PersonRepo 클래스나 findByName 메서드도 존재하지 않은채 이렇게 테스트를 하겠다라고 테스트를 만들어 두고,

해당 로직을 만든다.

public class PersonRepo {
  List<Person> personRepository = new ArrayList<>();
  
  public void addPerson(Person person) {
    personRepository.add(person);
  }
  
  public boolean isValidName(String name) {
    return personRepository.stream()
      .filter(p -> p.getName().equals(name))
			.findAny()
			.isPresent();
  }
}

만든 뒤 설계해 두었던 Test를 실행하여 통과하면 새로운 테스트를 추가하고
(존재하는 이름을 통해 Person조회하기)

통과하지 못하면 로직으로 돌아가 수정하여 완성한다.

이러한 단위테스트들의 반복을 수행하며 프로그래밍을 해 나아가는 방식이다.

Why TDD?

TDD는 간단하게 생각하면 일반적인 개발 순서만 뒤집힌 것 뿐이라고도 생각할 수 있다. 그 이상의 기대값에 대해 무게를 좀 더 실어보자.

  1. 빠른 피드백

    • 기존 방식의 문제점

    방법론의 존재 이유와 핵심적인 기대사항에 걸맞게 유지보수의 용이함과 재사용성을 높인다.

    기존 개발방식은 다음과 같은 문제에 치명적이다.

    의뢰자의 요구사항이 초기단계와 항상 동일 할 수 없기 때문에 설계는 항상 변한다.

    부분적인 변경사항에 대해서도 테스트를 위해 모든 프로그램 과정(로직)을 테스트해야하며 이로 인한 버그 발생시, 어느 부분이 버그를 유발하는지 명확하게 찾아내기 어렵다.

  • TDD의 단위 테스트

    기존 방식과 달리 단위마다 테스트를 진행한다면, 버그를 유발하는 부분에 대해 순간순간 찾아낼 수 있다. 코드가 개발자의 손들 벗어난 뒤, 가장 빠르게 피드백을 받을 수 있다.

  1. 결정과 피드백 사이의 갭

    TDD를 공부하며 처음 알게된 개념이다.

    결정(decision) : 개발 과정에서 '이 방법으로 해야지', '이걸 사용해서 해결해야지'와 같은 결정

    피드백(feedback) : 프로그램의 성공/실패라는 결과

    이 둘(결정 - 피드백) 사이의 갭을 인지할 수 있다는 것이 장점이다.

    "이 방법으로 해야지"결정 의 "실패" 피드백 의 간격을 인식하는 것이 프로그램을 의도한 대로 작동하는데 중요하다고 한다.

    내가 이해한 뉘앙스는 문제 해결의 정답(?)과 내가 선택한 정답과의 거리라고 이해하고 있다.

  2. 코드 트래킹의 용이성

    테스트 코드 하나하나가 만들어진 프로그램의 로직을 설명하는 근거가 되기도 한다. 그 당시의 결정에 대한 이유와 그 과정을 살필 수 있기 때문에 돌아보는데 있어 유리하다고 한다.

TDD Always?

위와 같은 장점들은 TDD가 프로그래밍에서 필수적이라 얘기하는 것처럼 들린다. 하지만 모든 개발과정에서 TDD가 사용되진 않는다고 한다.

  • 개발 시간에 대한 의문
    TDD를 적용하면 기존 방식보다 코드량이 2배정도는 많이 작성되어야 한다. 프로그래밍의 규모가 크다면, 시간 대비 비용이 크기 때문에 TDD를 통해 얻는 장점보단 비용이 크다고 한다. TDD환경 세팅을 위한 초기 비용을 잘 비교해가며 사용하는것이 좋다

    TDD가 코드의 길이가 늘어나는 것은 맞지만, 어느 수준의 프로그램에서는 전체적인 시간이 줄어든다. 기존방식에서 버그를 잡기위해 소모되는 시간이 줄어든다는 것 만으로도 2배 정도의 코드를 작성하는 것이 시간을 더 줄인다는 것이다.

  • 버그가 없는지에 대한 의문
    버그를 보다 빠르고 효과적으로 개선할 수 있을 뿐, 완전히 없다고 보장할 순 없다. 사실 완전히 없는 프로그래밍 기법이 존재할 수 도 없다.

  • TDD가 익숙하지 않다
    기존 방식으로 개발을 해오고, 학습해왔다면 TDD를 활용하기 어색할 것이다. 기존 방식을 벗어나 새로운 기법을 활용하는 것 자체로도 모험일 수 있다. 체득되어있던 기법을 변경하기 때문에 TDD를 인식해도 쉽사리 적용하기 어렵다고 한다. 그래서 TDD를 진행하는 과정을 정리해 두고 있다.

TDD Process

프로세스 3단계

  • RED : 테스트 실패
  • GREEN : 테스트 성공
  • REFACTOR : 리팩토링
  1. RED : 테스트 실패

    • 구현해야할 기능에 대한 테스트 작성
    • 테스트 수행

    처음 작성한 테스트는 무조건 실패되어야 한다. 해당 테스트를 구현한 코드가 아직 존재하거나 변경되지 않았기 때문이다.

  2. GREEN : 테스트 성공

    • 추가된 테스트를 반영하여 구현 코드를 작성한다
    • 테스트 성공을 위해선 최소한의 코드 변경만 존재해야 한다

    TDD에서는 테스트 성공을 위해선 최소한의 코드만 추가하거나 변경해야 한다. 추가된 테스트에 대해서만 메인 코드가 작성되어야 부작용이 적기 때문이다.

  3. REFACTOR : 리팩토링

    • 설계 개선

위 단계들을 고려하며 TDD프로그래밍을 연습하자.

profile
앞으로 넘어지기

0개의 댓글