[[TableOfContents]] = 소개 = * 안혁준 선배님이 진행하는 TDD 강의 = 스터디 시간 = * 7월 29일 ~ 7월 31일 * 1시/2시부터 약 2시간 동안 진행. = 스터디 = == 1일차 == * 참석자 : [유재범], [강민승], [신형철] == 2일차 == * 참석자 : [유재범], [강민승], [신형철]. [이승현], [홍성현] == 3일차 == * 참석자 : [유재범], [강민승], [신형철], [오영은] == 참고자료 == == 내용 요약 == === 개요 === * TDD란 TestDrivenDevelopment를 가리키는 말로, 번역하자면 테스트가 프로그래밍을 주도해가는 과정을 말합니다. * Test란 무엇일까요? 말 그대로 프로그램이 의도한 대로 작동하는지 확인하는 과정을 말합니다. * 일반적으로 컴퓨터 공학에서 Test라고 하면 사람에 의한 수동적 방식이 아닌 자동화된 테스트를 말합니다. === 테스트 === * 어떻게 하면 수동적 테스트 과정을 자동화 할 수 있을까요? * 다음과 같은 코드가 있다고 합시다. (사용 언어는 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..."); } } } }}} * 좋습니다! 코드에 변화를 주지 않으면서, 테스트/실행 코드를 자연스럽게 분리시킬 수 있습니다. * --다만 코드가 길어졌습니다.-- * 이처럼, 테스트를 제작할 때는 코드가 달라지지 않도록 유의하면서, 사람이 해야하는 것과 그렇지 않은 것을 어떻게 분리할지 고려해야합니다. === 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라고 합니다. * 추가로, 이런 테스트 코드는 다음과 같은 장점을 줍니다. * 자신이 코드를 수정했을 때, 그것이 버그를 발생시키는 지에 대한 여부를 바로 파악할 수 있습니다. * 테스트용 코드 각각이 별도의 사용 설명서를 겸하는 예시 코드가 됩니다. = 댓글 = * 나름대로 정리했습니다. 잘못되거나 부족한 내용이 있을 때 수정해주시면 감사하겠습니다. :) - [신형철] * 계획대로다... - [유재범] * 올ㅋ - [김한성] --------------------------------------------------------------------- [활동지도/2015]