E D R , A S I H C RSS

TDD_ver.2015



1. 소개

  • 안혁준 선배님이 진행하는 TDD 강의

2. 스터디 시간

  • 7월 29일 ~ 7월 31일
  • 1시/2시부터 약 2시간 동안 진행.

3. 스터디

3.1. 1일차

3.3. 3일차

3.4. 참고자료

3.5. 내용 요약

3.5.1. 개요

  • TDD란 TestDrivenDevelopment를 가리키는 말로, 번역하자면 테스트가 프로그래밍을 주도해가는 과정을 말합니다.
    • Test란 무엇일까요? 말 그대로 프로그램이 의도한 대로 작동하는지 확인하는 과정을 말합니다.
    • 일반적으로 컴퓨터 공학에서 Test라고 하면 사람에 의한 수동적 방식이 아닌 자동화된 테스트를 말합니다.

3.5.2. 테스트

  • 어떻게 하면 수동적 테스트 과정을 자동화 할 수 있을까요?
  • 다음과 같은 코드가 있다고 합시다. (사용 언어는 C#입니다.)

public class Practice
{
    public static void Main(string[] args)
    {
        int number = 0;

        number = int.Parse(Console.ReadLine());

        if (number > 3)
        {
            Console.WriteLine("Pass!");
        }
        else
        {
            Console.WriteLine("Fail...");
        }
    }
}
  • 이제, 이 코드의 테스트를 자동화 해봅시다.
    • 반복문으로 해볼까요?

public class Practice
{
    public static void Main(string[] args)
    {
        int number = 0;

        //number = int.Parse(Console.ReadLine());

        for (int input = 0; input < 10; input++)
        {
            number = input;

            if (number > 3)
            {
                Console.WriteLine("Pass!");
            }
            else
            {
                Console.WriteLine("Fail...");
            }
        }
    }
}
  • 이 테스트 방식에는 몇 가지 문제가 있습니다.
    • Input 범위가 한정됩니다.
    • 가장 큰 문제는, 실제 코드와 테스트용 코드가 달리집니다.
      • 예를 들면, 실제 코드에서는 콘솔에서 입력을 받지만, 테스트 코드에서는 그렇지 않고 for문으로 감싸버림으로써 다른 코드가 되어버렸습니다.
    • 그래서....

  • 코드를 다르게 하지 않으면서, 따로 테스트를 할 수 있어야 합니다.
    • 코드를 함수(메서드)로 분리시키고, 전처리기를 이용하여 구현하면 어떨까요?

public class Practice
{
    public static void Main(string[] args)
    {
#if DEBUG
        Test();
#else
        Run();
#endif
    }

    internal static void Test()
    {
        // ......
    }

    internal static void Run()
    {
        int number = 0;

        number = int.Parse(Console.ReadLine());

        if (number > 3)
        {
            Console.WriteLine("Pass!");
        }
        else
        {
            Console.WriteLine("Fail...");
        }
    }
}
  • 좋습니다! 코드에 변화를 주지 않으면서, 테스트/실행 코드를 자연스럽게 분리시킬 수 있습니다.
    • 다만 코드가 길어졌습니다.

  • 이처럼, 테스트를 제작할 때는 코드가 달라지지 않도록 유의하면서, 사람이 해야하는 것과 그렇지 않은 것을 어떻게 분리할지 고려해야합니다.

3.5.3. TDD

  • 테스트를 자동화 해주는 툴에는 Java에 junit, C/C++에는 gtest, Javascript에는 mocha 등 각종 언어에 여러가지가 있습니다.
    • 각 언어에 있는 Unit 계열의 테스트 자동화 툴을 XUnit이라고 합니다.
    • 유의할 것은, 테스트 툴이 없다고 해서 테스트를 제작하지 못하는 것은 아니라는 점입니다.
      • 예를 들어... (사실 위 코드에서도 보실 수 있습니다.)

// 사용 언어는 Java입니다.
import java.util.*;

public class Gugudan
{
	public Gugudan()
	{
		// TODO Auto-generated constructor stub
	}
	
	public static void main(String[] args) 
	{
		System.out.println("Hello World");
		
		Gugudan module = new Gugudan();
		
		module.test();
		//module.run();
	}

	private void test()
	{
		String ret = dan(1);
		
		if (ret.equals("1 * 1 = 1\n2 * 1 = 2\n3 * 1 = 3\n4 * 1 = 4\n5 * 1 = 5\n6 * 1 = 6\n7 * 1 = 7\n8 * 1 = 8\n9 * 1 = 9\n"))
		{
			System.out.println("S");
		}
		else
		{
			System.out.println("F");
		}
	}

	private void run()
	{
		System.out.println("Hello World!");
		System.out.println(dan(1));
	}

	String dan(int level)
	{
		String ret = "";
		
		for (int i = 1; i < 10; i++)
		{
			ret += (String.format("%d * %d = %d", i, level, i * level)) + "\n";
		}
		
		return ret;
	}
}
  • 다음과 같은 경우에는 테스트 툴을 사용하진 않았지만, 테스트를 제작했습니다.

  • TDD를 쓰면 빠른 피드백을 받을 수 있는 장점이 있습니다. 또한, 테스트를 위한 코드를 계속해서 만들어가면 설계 구조가 자연스럽게 되는 점도 있습니다.
    • 보너스로 Failure를 Success로 바꿀 때의 성취감을 얻을 수 있습니다.
    • 다만, 사람이 좀 피곤해진다는 단점도 있을 수 있습니다.....

  • TDD는 다음의 과정을 거칩니다.
    • 초기에 Failure가 되는 테스트를 제작한다.
    • 최대한 빠르게 이 테스트를 Success로 만드는 코드를 만든다.
    • 코드 리팩토링 과정을 거친다.
      • 참고로, 리팩토링 과정은 본 코드와 테스트 코드 전체에 걸쳐 이루어져야합니다.
    • 반복한다.

  • 직접 만들어봅시다! (사용 언어는 Java이고, JUnit을 사용했습니다.)
    • 돈이라는 클래스를 만들어볼까요?
      • 먼저, 테스트 코드를 만들어봅시다.

import org.junit.Test;
import static org.junit.Assert.*;

public class MoneyTest 
{
	@Test
	public void testMoney()
	{
		//Given
		//When
		Money money = new Money();
		
		//Then
		assertNotNull(money);
	}
}
  • 당연히 Money라는 클래스가 없으므로 컴파일 오류가 나면서, 테스트를 실행해보면 Failure가 뜹니다. 이제 이것을 Success로 고치기 위해 Money라는 클래스를 만들어봅시다.

public class Money
{
	public Money()
	{
	}
}
  • 이제 테스트를 실행해보면 Success가 됩니다.
  • 다음으로 이제 리팩토링을 해야하지만, 이 코드의 경우 너무나도 간단하므로 생략합니다.
  • 이렇게 하면 한 과정을 끝냈습니다! 이제 이 과정을 반복하면 됩니다.

  • 자, 다음으로 이제 Money 인스턴스를 만드는 것을 끝내고 나니, Money 인스턴스를 만들 때 초기 금액이 필요할 것 같습니다. 이 기능을 추가하기 위해 테스트를 하나 더 만들어봅시다.

..........
        @Test
	public void testMoneyWithDraw()
	{
		//Given
		Money money = new Money(10000);
		//When
		double amount = money.getAmount();
		//Then
		assertNotNull(money);
		assertEquals(10000, amount, 0.000001);
	}
...........
  • 일단 당연히 Money 클래스에는 getAmount라는 메서드가 없으므로 Failure가 납니다. 이제 이것을 Success로 고쳐봅시다. Money 클래스에 다음을 추가합니다.

.....
private double amount;
        public Money(double draw, Currency currency)
	{
		this.amount = draw;
	}
........
       public double getAmount()
	{
		return 10000;
	}
......
  • 뭔가 이상하지만, 일단 다시 테스트를 실행해보면 Success가 됩니다. 이제 리팩토링 과정을 거치게 되는데, 코드를 다음과 같이 고쳐봅니다.

......
       public double getAmount()
	{
		return this.amount;
	}
......
  • 다시 테스트를 돌려보면 Success가 됩니다. 또 다시 한 과정을 모두 끝냈습니다!
  • 이렇게, 테스트가 주도가 되어 개발하는 것을 TDD라고 합니다.

  • 추가로, 이런 테스트 코드는 다음과 같은 장점을 줍니다.
    • 자신이 코드를 수정했을 때, 그것이 버그를 발생시키는 지에 대한 여부를 바로 파악할 수 있습니다.
    • 테스트용 코드 각각이 별도의 사용 설명서를 겸하는 예시 코드가 됩니다.

4. 댓글

  • 나름대로 정리했습니다. 잘못되거나 부족한 내용이 있을 때 수정해주시면 감사하겠습니다. :) - 신형철

Valid XHTML 1.0! Valid CSS! powered by MoniWiki
last modified 2015-08-03 10:15:19
Processing time 0.0951 sec