본문 바로가기

.Net Technology/.NET TDD

(3) TDD의 소개 - 예제로 살펴보는 TDD 개발 프로세스

TDD 개발 프로세스
 
먼저 아래와 같은 프로그램을 작성한다고 가정하겠다. 
 
 
1부터 20까지의 수를 출력하는 프로그램을 작성하시오. 단, 3의 배수에서는 숫자대신 “JOEL”를 5의 배수에서는 “SARAH” 그리고 3과 5의 배수에서는 “JOEL SARAH” 를 출력하시오. 
 
 
위의 프로그램을 Console에 작성한다고 가정할 때 먼저 지금까지 개발해온 개발 방법론으로 접근해보도록 하겠다. 아마도 80% 이상의 개발자들은 아마 아래와 같은 코드를 작성할 것이다.
 
static void Main(string[] args)
{
    for (int i =1 ; i<=20;i++)
    {
        if (i % 3 == 0 && i % 5 == 0)
            Console.WriteLine("Joel Sarah");
        else if (i % 3 == 0)
            Console.WriteLine("Joel");
        else if (i % 5 == 0)
            Console.WriteLine("Sarah");
        else
            Console.WriteLine(i.ToString());
    }
}
 
그리고 코드를 실행해서 코드가 잘 실행되었는지 확인할 것이다. 

[화면] 결과
 
하지만 여기서 TDD 프로세스로 이 프로그램을 작성해보도록 하자.
 
 
Test-Driven Development 프로세스
 
먼저 구조를 디자인 해야 하는 것이 먼저 일 것이고, 그 다음 테스트 코드를 작성한 뒤에 마지막으로 실제 코드를 작성하게 될 것이다. 아래 샘플 코드를 읽기 전에 만약 여러분이면 어떻게 테스트 코드를 작성할지 한번 먼저 생각해 보길 권한다.
 
앞선 프로그램에서는 코드 디자인에 대해서는 별다른 생각 없이 바로 코드 작성을 시작했지만 TDD에서는 테스트 코드를 작성하기 위해서 디자인에 대해서 더 신중하게 고민하고 또 구조를 잡고 시작해야 한다. 또한, 우리는 무엇을 테스트 해야 할지를 명확하게 이해하고 있어야 한다. 즉, 테스트 요소들을 분석해내야 하는 것이다. 앞의 예제에서 우리는 프로그램을 실행함과 동시에 3과 5의 배수와 5 그리고 3과5의 배수인 15 번째 수를 찾아서 정확하게 의도한 문자가 출력되었는지 살펴 봤을 것이고 추가로 3과 5의 배수가 아닐 경우에는 숫자가 출력된 것을 살펴봤을 것이다. 즉, 이것이 우리가 테스팅 해야 하는 요소 들인 것이다. 다시 테스팅 요소들을 아래와 같이 보다 정리해 볼 수 있다. 

 
- 숫자가 3의 배수일 때 Joel을 반환

- 숫자가 5의 배수일 때 Sarah를 반환

- 숫자가 3과 5의 배수일 때 Joel Sarah를 반환

- 숫자가 3의 배수도 5의 배수도 아닐 때 숫자를 반환
 
 
테스트 요소의 정리가 끝났기 때문에 프로그램을 디자인 해보도록 하겠다. 먼저 위의 기능을 수행하는 클래스를 아래와 같이 정의 하도록 하겠다. 
 
public class MultipleCalculator
{
    public string Calculate(int i)
    {
        //To Do
        return null;
    }
}
 
먼저 클래스를 생성했고, 아직 코드는 작성하지 않고 남겨 놨다. 이 Calculate 메서드는 실제 숫자를 전달하면 그에 따른 적합한 수를 반환해줄 것이다. 그럼 이제 테스트 코드를 작성해 보도록 하자. 
 
여기서 필자는 새로운 프로젝트를 하나 추가했다. 비주얼 스튜디오 2012에서 기본적으로 지원하는 테스팅 프로젝트를 추가할 경우에 아래와 같은 기본 코드가 작성되어 있을 것이다. 
 
[TestClass]
public class UnitTest1
{
    public UnitTest1()
    {
        //
        // TODO: Add constructor logic here
        //
    }

    [TestMethod]
    public void TestMethod1()
    {
        //
        // TODO: Add test logic here
        //
    }
}
 
필자는 NUnit을 주로 선호하지만 이번 강좌에서는 MSTest를 이용할 것이다. 자세한 내용은 다음 회차에서 보다 자세히 설명할 것이다. 여기에서는 눈으로만 살펴 보도록 하자. 필자는 4개의 테스팅을 다음과 같이 작성하였다. 
 
 
[TestClass]
public class MultipleCalculatorTest
{
    [TestMethod]
    public void WhenValueDivisibleOnlyBy3()
    {
        var mCal = new MultipleCalculator();
        string valueExpected = "Joel";
        string result = mCal.Calculate(3);
        Assert.AreEqual(valueExpected, result);
    }
    [TestMethod]
    public void WhenValueDivisibleOnlyBy5()
    {
        var mCal = new MultipleCalculator();
        string valueExpected = "Sarah";
        string result = mCal.Calculate(5);
        Assert.AreEqual(valueExpected, result);
    }

    [TestMethod]
    public void WhenValueDivisionBy3and5()
    {
        var mCal = new MultipleCalculator();
        string valueExpected = "Joel Sarah";
        string result = mCal.Calculate(15);
        Assert.AreEqual(valueExpected, result);
    }
    [TestMethod]
    public void WhenValueIsNotDivisibleBy3Or5()
    {
        var mCal = new MultipleCalculator();
        string valueExpected = "4";
        string result = mCal.Calculate(4);
        Assert.AreEqual(valueExpected, result);
    }
}
 
Assert.AreEqual() 이라는 메서드에서는 두 개의 값을 받아서 서로 같은지를 비교해서 테스트를 진행해준다. 지금 Calculate라는 메서드는 null 을 반환하기 때문에 당연히 테스트를 통과할리가 없다. 테스트를 실행하면 아래와 같이 빨간 신호들이 켜지게 된다. 
 
 
이제 우리가 할 일은 Calculate 메서드를 만들고 프로그램을 완성시켜서 저 빨간 불을 초록색 불로 만드는 작업이 필요하다. 앞에서 테스트 작성이 모두 끝났기 때문에 실제 구현 단계로 넘어 가서 아래와 같이 코드를 작성하도록 하겠다.
 
 
public class MultipleCalculator
{
    public string Calculate(int i)
    {
        if (i % 3 == 0 && i % 5 == 0)
            return "Joel Sarah";
        else if (i % 3 == 0)
            return "Joel";
        else if (i % 5 == 0)
            return "Sarah";
        else
            return i.ToString();
    }
}
 
이렇게 프로그램을 업데이트 하고 다시 테스트를 재실행하면 아래와 같은 결과를 확인할 수 있다.
 
 
이제 마지막으로 Main 메서드를 다음과 같이 수정하면 모든 작업을 끝이 난다. 
 
static void Main(string[] args)
{
    var mCal = new MultipleCalculator();
    for (int i = 1; i <= 20; i++)
    {
        Console.WriteLine(mCal.Calculate(i));
    }
}
 
물론, 실제 프로그램은 이렇게 간단할 리는 없을 것이다. 하지만 어떠한가? 의도가 무엇이었든 간에 코드가 조금 구조를 잡아가는 느낌이 들지 않는가? 여기서 수정 사항이 아래와 같이 있다고 가정해보자. 
4의 배수일 때는 Noah를 출력해주시오.
 
이때 우리는 당연히 테스트 코드를 먼저 작성해야 할 것이다. 즉, 4의 배수를 확인하는 메서드를 하나 작성하게 될 것이고, 빨간 에러 메세지를 본 뒤에 실제 Calculate메서드를 수정해서 테스트를 초록색 불로 만드는 작업을 하게 될 것이다. 이때 우리는 별다른 프로그램의 구동 없이 테스트 결과 만으로 테스트를 완료할 수 있음과 동시에 기존의 유닛 테스트들도 테스트가 통과됨을 알게 됨으로써 시간적으로도 충분히 절약할 수 있을 것이다. 

 
정리
 
이번 강좌에서는 통해서 먼저 TDD의 방법론에 대해서 살펴보았다. 실제 코드를 작성하기 전에 코드를 먼저 구조화 한 뒤에 테스트 코드를 작성한다. 그리고 나서 실제 동작 코드를 작성함으로써 나중에 테스트 뒤에 디자인이나 코드 구조를 바꿔야 하는 시간적인 낭비를 막을 수 있다고 설명했다.

그리고 이어서 통합 테스팅과 유닛 테스팅의 차이를 살펴 보았다. 유닛 테스팅은 테스트 요소들을 유닛테스트 프레임워크를 이용해서 실제 코드로 구현함으로써 테스트를 자동화 시킬 수 있을 뿐만 아니라 모듈 간의 의존성과 상관없이 쉽게 문제를 진단할 수 있는 이유 또한 설명했다.

마지막으로 간단한 예제를 통해서 TDD 개발 프로세스를 살펴 보았다. 이 프로젝트에서는 NUnit이 아닌 비주얼 스튜디오에서 기본적으로 제공하는 MSTest를 이용해서 간단하게 살펴봤다.

다음 장에서는 유닛 테스트 프레임워크인 NUnit을 사용하는 방법을 살펴볼 것이고 필요한 테스팅 메서드와 클래스들을 설명할 것이다.