잠토의 잠망경

TDD C++ 실제 예제 1 본문

공부/TDD

TDD C++ 실제 예제 1

잠수함토끼 2013. 1. 1. 15:47

간단한 예제부터 시작해보자

 

TDD는 다른데서도 잘 설명되었지만 실패하고 통과하고 refactor하는 것이다.

 

이것의 반복형태다. 상당히 추상적이고 이게 뭐냐하것다. 나도 그랬으니까 ㅋㅋ

 

하나하나 코드로 만들면서 이해해보자~~ 유후~~

 

아래와 같이 win32 콘솔 응용 프로그램을 만든다. Project 명은 마음대로 만들어보자. ㅋㅋ

 

 

난 빈 프로젝트가 좋기 때문에 아래와 같이 빈 project를 만든다. ㅋㅋ

 

 

아래와 같이 소스파일에 cpp file 하나를 생성한다. 유후~~

 

 

아참 우리가 뭐할지 얘기를 안했구만. Code를 짠다는 것은 어떤 기능을 만드는 것이고 그 기능에 해당하는 method를 만드는 것이다.

내가 아는 대로는 그렇다. ㅋ

 

 

클래스를 만들고 그 클래스에 값들을 넣고 그 값들을 xml format 문자열 형태로 작성하는 것을 만들어보자.

 

진짜 간단하다.

 

TDD 관련 Site를 뒤져보면 야구 게임/볼링 게임/은행 계좌 등등 샘플 소스들이 넘친다.

그런 분들 소스도 있으니 이런 소스도 만들어보면 어떨까? ㅋㅋ

 

그럼 우리가 만들 클래스는 학생 주소록 정도로 하자.

학생이라면 아래와 같은 정보들이 있것다.

 

  1. 이름
  2. 나이
  3. 학번
  4. 주소

 

이런것들이 있다.

이걸 간단한 xml format으로 만들어보자

 

<name>이원재</name>

<age>19</age>

<studentNumber>100979571</studentNumber>

<address>서울시 관악구 XXXX</address>

 

 

이정도면 우리가 TDD할 모든 준비가 끝났다. ㅋㅋ

 

이그림을 다시 보고 넘어가자.

 

  1. Write a test that fails
  2. Make the code work
  3. Eliminate redundancy

 

우리가 할 일은 실패할 Test Code를 먼저 짜야한다.

지금부터 나를 따르라. ㅋㅋㅋ

다음과 같이 코드를 짠다. 그런 다음 compile한다. 에러 안난다. ㅋㅋ 아무 것도 없으니 ㅋㅋ

 

 

 

다음과 같이 coding을 한다.

 

 

  1. int main(void)  
  2. {  
  3.     Student* s = new Student();  
  4.     
  5.     assert(NULL == s);  
  6.     
  7.     
  8.     return 0;  
  9. }  

 

 

그럼 아마 아래와 같이 붉은 줄들을 볼수 있다. 으미~~ ㅜㅜ

 

 

헉.. Student라는 Class를 만들지도 않았는데 객체를 생성한다.

그리고 assert라는 함수를 쓰는데 함수를 Header File을 include하지도 않았다.

이상태로 컴파일 해보자 ㅋㅋ

 

그럼 다음과 같이 에러를 뿜는다. ㅋㅋ

 

 

이게 바로 TDD의 첫번째 단계 Write a test that fails이다.

 

  1. Write a test that fails
  2. Make the code work
  3. Eliminate redundancy

 

 

그럼 이제 두번째 단계인 Make the code work 단계로 넘어가자

 

  1. Write a test that fails
  2. Make the code work
  3. Eliminate redundancy

 

우선 Class Student를 만들어보자. 다음과 같이 ㅋㅋ

  1. class Student  
  2. {  
  3.     
  4. };  
  5.     
  6.     
  7.     
  8.     
  9. int main(void)  
  10. {  
  11.     Student* s = new Student();  
  12.     
  13.     assert(NULL == s);  
  14.     
  15.     
  16.     return 0;  
  17. }  

 

그런다음 컴파일을 한다.

 

그럼 다음과 같이 오류가 많이 준다. ㅋㅋ

 

 

이제 assert를 없에보자 . 다음과 같이 include를 해보자.

 

  1. #include <assert.h>  
  2.     
  3. class Student  
  4. {  
  5.     
  6. };  
  7.     
  8.     
  9. int main(void)  
  10. {  
  11.     Student* s = new Student();  
  12.     
  13.     assert(NULL == s);  
  14.     
  15.     
  16.     return 0;  
  17. }  

 

 

그래도 다음과 같이 나오네.. 뭘까.. ㅋㅋ

아 assert에는 NULL을 넣으면안되나 보다.. 헐 몰랐구만…

그냥 빼주자.

 

그럼 아래와 같이 해서 돌려보자. ㅋㅋ

 

  1. #include <assert.h>  
  2.     
  3. class Student  
  4. {  
  5.     
  6. };  
  7.     
  8.     
  9. int main(void)  
  10. {  
  11.     Student* s = new Student();  
  12.     
  13.     assert(s);  
  14.     
  15.     
  16.     return 0;  
  17. }  

 

그럼 아래와 같이 굿 오류가 없다. ㅋㅋ

 

 

그럼 두번 째 단계인 Make the code work는 성공적으로 pass했다.

ㅋㅋ

 

  1. Write a test that fails
  2. Make the code work
  3. Eliminate redundancy

 

이제 세번 째 단계인 Eliminate redundancy를 해야하는데.. 근데 refacoring할게 없다. 너무 simple해서

이건 이번단계에서 skip

 

그럼 다시 Write a test that fails를 만들어보자.

 

 

  1. Write a test that fails
  2. Make the code work
  3. Eliminate redundancy

학생들에게 필요한 요소인 아래 4개에 대한 Test Code를 만들어 보자.

각각의 학생들은 4가지가 필수잖아요? ㅋㅋ

그리고 입력할 값들의 예는 아래와 같아요.

 

  1. 이름
  2. 나이
  3. 학번
  4. 주소

 

<name>이원재</name>

<age>19</age>

<studentNumber>100979571</studentNumber>

<address>서울시 관악구 XXXX</address>

 

아래와 같이 코딩을 해봐요.

아까 우리가 짠 Test Code는 지우지 않기염 ㅋㅋ 계속 Append하면서 진행됩니다. ㅋㅋ

 

  1. #include <assert.h>  
  2.     
  3. class Student  
  4. {  
  5.     
  6. };  
  7.     
  8.     
  9. int main(void)  
  10. {  
  11.     // 1. Test 1  
  12.     Student* s = new Student();  
  13.     
  14.     assert(s);  
  15.     
  16.     
  17.     // 2. Test 2  
  18.     s = new Student("이원재",19,100979571,"서울시 관악구 XXXX");  
  19.     
  20.     assert(!strcmp(s->Name, "이원재"));  
  21.     assert(s->age == 19);  
  22.     assert(s->studentID == 100979571);  
  23.     assert(!strcmp(s->address,"서울시 관악구 XXXX"));  
  24.     
  25.     
  26.     return 0;  
  27. }  

 

다 작성했다면 Compile해요.

그럼 아래와 같이 또 오류가 발생합니다.

 

 

  1. Write a test that fails
  2. Make the code work
  3. Eliminate redundancy

 

 

 

 

 

이제 Pass하기 위해서 하나씩 오류를 제거해봅니다.

저 오류들만 제거하면 우리는 Coding완성이 되는거잖아요?!! ㅋㅋ

 

우선 생성자를 만들어서 Compile Error를 하나 잡아보아요.

ㅋㅋ

  1. #include <assert.h>  
  2.     
  3. class Student  
  4. {  
  5.     
  6. public:  
  7.     
  8.     Student()  
  9.     {  
  10.     
  11.     }  
  12.     
  13.     Student(char* _Name, int _age, int _studentID, char* _address)  
  14.     {  
  15.     
  16.     }  
  17.     
  18. };  
  19.     
  20.     
  21. int main(void)  
  22. {  
  23.     // 1. Test 1  
  24.     Student* s = new Student();  
  25.     
  26.     assert(s);  
  27.     
  28.     
  29.     // 2. Test 2  
  30.     s = new Student("이원재",19,100979571,"서울시 관악구 XXXX");  
  31.     
  32.     assert(!strcmp(s->Name, "이원재"));  
  33.     assert(s->age == 19);  
  34.     assert(s-> studentID == 100979571);  
  35.     assert(!strcmp(s->address,"서울시 관악구 XXXX"));  
  36.     
  37.     
  38.     return 0;  
  39. }  

 

그럼 아래와 같이 하나 줄였어요. ㅋㅋ

이제 또 하나씩 줄여보죠.

 

 

아래와 같이 Class에 변수들을 선언합니다. 이렇게 하면 개별 값들을 객체안에 저장할 수 있죠?!! ㅋㅋ

바로 컴파일 해보죠.

  1. #include <assert.h>  
  2.     
  3. class Student  
  4. {  
  5.     
  6. public:  
  7.     
  8.     char Name[1024];  
  9.     int age;  
  10.     int studentID;  
  11.     char address[1024];  
  12.     
  13.     Student()  
  14.     {  
  15.     
  16.     }  
  17.     
  18.     Student(char* _Name, int _age, int _studentID, char* _address)  
  19.     {  
  20.     
  21.     }  
  22.     
  23. };  
  24.     
  25.     
  26. int main(void)  
  27. {  
  28.     // 1. Test 1  
  29.     Student* s = new Student();  
  30.     
  31.     assert(s);  
  32.     
  33.     
  34.     // 2. Test 2  
  35.     s = new Student("이원재",19,100979571,"서울시 관악구 XXXX");  
  36.     
  37.     assert(!strcmp(s->Name, "이원재"));  
  38.     assert(s->age == 19);  
  39.     assert(s-> studentID == 100979571);  
  40.     assert(!strcmp(s->address,"서울시 관악구 XXXX"));  
  41.     
  42.     
  43.     return 0;  
  44. }  

 

보시면 또 오류가 줄어가고 있습니다.

ㅋㅋㅋ

 

 

이제 strcmp 함수에 대한 오류를 없에 보죠.

이건 include가 안되서 발생된걸꺼예요. ㅋㅋ

아래와 같이 코딩을 해보죠. 보이시죠.. #include<string.h>가 ㅋㅋ

  1. #include <string.h>  
  2. #include <assert.h>  
  3.     
  4. class Student  
  5. {  
  6.     
  7. public:  
  8.     
  9.     char Name[1024];  
  10.     int age;  
  11.     int studentID;  
  12.     char address[1024];  
  13.     
  14.     Student()  
  15.     {  
  16.     
  17.     }  
  18.     
  19.     Student(char* _Name, int _age, int _studentID, char* _address)  
  20.     {  
  21.     
  22.     }  
  23.     
  24. };  
  25.     
  26.     
  27. int main(void)  
  28. {  
  29.     // 1. Test 1  
  30.     Student* s = new Student();  
  31.     
  32.     assert(s);  
  33.     
  34.     
  35.     // 2. Test 2  
  36.     s = new Student("이원재",19,100979571,"서울시 관악구 XXXX");  
  37.     
  38.     assert(!strcmp(s->Name, "이원재"));  
  39.     assert(s->age == 19);  
  40.     assert(s-> studentID == 100979571);  
  41.     assert(!strcmp(s->address,"서울시 관악구 XXXX"));  
  42.     
  43.     
  44.     return 0;  
  45. }  

 

Compile 하면 아래와 같이 오류가 사라집니다. ㅎㅎ

 

 

그럼 이렇게 모든 오류를 잡았습니다. 그럼 debug mode로 돌려보죠. ㅋㅋ

F5를 누르자마자 요래 뜨네요. 허허

 

다시 시도를 눌러서 뭐가 문제인지 찾아보죠.

확인해보니 여기서 문제군요.

우리가 만든 객체에 우리가 넣어준 "이원재", 19, 100979571, "서울시 관악구 XXXX"가 안들어 있었어요.

그럼 들어가게 수정해보자구요.

 

 

오래 생성자에 함수를 추가합니다.

그리고 컴파일해봐요. ㅋㅋ

  1. #include <string.h>  
  2. #include <assert.h>  
  3.     
  4. class Student  
  5. {  
  6.     
  7. public:  
  8.     
  9.     char Name[1024];  
  10.     int age;  
  11.     int studentID;  
  12.     char address[1024];  
  13.     
  14.     Student()  
  15.     {  
  16.     
  17.     }  
  18.     
  19.     Student(char* _Name, int _age, int _studentID, char* _address)  
  20.     {  
  21.         sprintf(Name,"%s",_Name);  
  22.         age = _age;  
  23.         studentID = _studentID;  
  24.         sprintf(address,"%s",_address);  
  25.     }  
  26.     
  27. };  
  28.     
  29.     
  30. int main(void)  
  31. {  
  32.     // 1. Test 1  
  33.     Student* s = new Student();  
  34.     
  35.     assert(s);  
  36.     
  37.     
  38.     // 2. Test 2  
  39.     s = new Student("이원재",19,100979571,"서울시 관악구 XXXX");  
  40.     
  41.     assert(!strcmp(s->Name, "이원재"));  
  42.     assert(s->age == 19);  
  43.     assert(s->studentID == 100979571);  
  44.     assert(!strcmp(s->address,"서울시 관악구 XXXX"));  
  45.     
  46.     
  47.     return 0;  
  48. }  

 

sprintf 관련 오류가 발생하네요.

그럼 넣어줘야죠.

 

 

이렇게 stdio.h를 추가해봐요. 컴파일 오류도 안나고 assert 오류도 발생안되네요. ^^ 요후

 

  1. #include <stdio.h>  
  2. #include <string.h>  
  3. #include <assert.h>  
  4.     
  5. class Student  
  6. {  
  7.     
  8. public:  
  9.     
  10.     char Name[1024];  
  11.     int age;  
  12.     int studentID;  
  13.     char address[1024];  
  14.     
  15.     Student()  
  16.     {  
  17.     
  18.     }  
  19.     
  20.     Student(char* _Name, int _age, int _studentID, char* _address)  
  21.     {  
  22.         sprintf(Name,"%s",_Name);  
  23.         age = _age;  
  24.         studentID = _studentID;  
  25.         sprintf(address,"%s",_address);  
  26.     }  
  27.     
  28. };  
  29.     
  30.     
  31. int main(void)  
  32. {  
  33.     // 1. Test 1  
  34.     Student* s = new Student();  
  35.     
  36.     assert(s);  
  37.     
  38.     
  39.     // 2. Test 2  
  40.     s = new Student("이원재",19,100979571,"서울시 관악구 XXXX");  
  41.     
  42.     assert(!strcmp(s->Name, "이원재"));  
  43.     assert(s->age == 19);  
  44.     assert(s->studentID == 100979571);  
  45.     assert(!strcmp(s->address,"서울시 관악구 XXXX"));  
  46.     
  47.     
  48.     return 0;  
  49. }  

 

이제 대망의 Refactoring 시간이 돌아왔습니다.

이제 Code를 이쁘게 해보죠. ㅋㅋ

 

 

  1. Write a test that fails
  2. Make the code work
  3. Eliminate redundancy

 

우선 Debug로 잡아보니 Class 객체의 Name[] 배열에 쓰레기 값들로 채워져 있네요.

깔끔하게 0로 초기화해보죠. ㅎㅎ

 

 

 

요래 coding해봤습니다.

memset으로 0초기화 ㅋㅋㅋ

그리고 student() 생성자도 동일하게 초기화 code를 넣었습니다.

이제좀 깔끔하네요.

 

  1. #include <stdio.h>  
  2. #include <string.h>  
  3. #include <assert.h>  
  4.     
  5. class Student  
  6. {  
  7.     
  8. public:  
  9.     
  10.     char Name[1024];  
  11.     int age;  
  12.     int studentID;  
  13.     char address[1024];  
  14.     
  15.     Student()  
  16.     {  
  17.         ::memset(Name,      0,sizeof(char)*1024);  
  18.         ::memset(address,   0,sizeof(char)*1024);  
  19.     
  20.         age         = 0;  
  21.         studentID   = 0;  
  22.     }  
  23.     
  24.     Student(char* _Name, int _age, int _studentID, char* _address)  
  25.     {  
  26.         ::memset(Name,      0,sizeof(char)*1024);  
  27.         ::memset(address,   0,sizeof(char)*1024);  
  28.     
  29.     
  30.         sprintf(Name,"%s",_Name);  
  31.         age = _age;  
  32.         studentID = _studentID;  
  33.         sprintf(address,"%s",_address);  
  34.     }  
  35.     
  36. };  
  37.     
  38.     
  39. int main(void)  
  40. {  
  41.     // 1. Test 1  
  42.     Student* s = new Student();  
  43.     
  44.     assert(s);  
  45.     
  46.     
  47.     // 2. Test 2  
  48.     s = new Student("이원재",19,100979571,"서울시 관악구 XXXX");  
  49.     
  50.     assert(!strcmp(s->Name, "이원재"));  
  51.     assert(s->age == 19);  
  52.     assert(s->studentID == 100979571);  
  53.     assert(!strcmp(s->address,"서울시 관악구 XXXX"));  
  54.     
  55.     
  56.     return 0;  
  57. }  

 

이 코드의 문제점은 class를 동적할당한 이후에 해제하는 code가 없었습니다.

이와 같이 delete code를 넣어줘서 메모리 누스를 또 막아보죠. ㅋㅋㅋ

 

  1. #undef UNICODE  
  2.     
  3. #include <stdio.h>  
  4. #include <string.h>  
  5. #include <assert.h>  
  6.     
  7. class Student  
  8. {  
  9.     
  10. public:  
  11.     
  12.     char Name[1024];  
  13.     int age;  
  14.     int studentID;  
  15.     char address[1024];  
  16.     
  17.     Student()  
  18.     {  
  19.         ::memset(Name,      0,sizeof(char)*1024);  
  20.         ::memset(address,   0,sizeof(char)*1024);  
  21.     
  22.         age         = 0;  
  23.         studentID   = 0;  
  24.     }  
  25.     
  26.     Student(char* _Name, int _age, int _studentID, char* _address)  
  27.     {  
  28.         ::memset(Name,      0,sizeof(char)*1024);  
  29.         ::memset(address,   0,sizeof(char)*1024);  
  30.     
  31.     
  32.         sprintf(Name,"%s",_Name);  
  33.         age = _age;  
  34.         studentID = _studentID;  
  35.         sprintf(address,"%s",_address);  
  36.     }  
  37.     
  38. };  
  39.     
  40.     
  41. int main(void)  
  42. {  
  43.     // 1. Test 1  
  44.     Student* s = new Student();  
  45.     
  46.     assert(s);  
  47.     
  48.     delete s;  
  49.     
  50.     
  51.     
  52.     // 2. Test 2  
  53.     s = new Student("이원재",19,100979571,"서울시 관악구 XXXX");  
  54.     
  55.     assert(!strcmp(s->Name, "이원재"));  
  56.     assert(s->age == 19);  
  57.     assert(s->studentID == 100979571);  
  58.     assert(!strcmp(s->address,"서울시 관악구 XXXX"));  
  59.     
  60.     delete s;  
  61.     
  62.     
  63.     return 0;  
  64. }  

 

 

이제 파일을 좀 쪼개보자. 하나의 파일에 다 넣기에도 점점 코드량이 늘어나고 있다.

Class student는 cpp, header로 쪼개고

Test code는 main.cpp에 그냥 넣어놓자.

 

[student.h]

  1. #undef UNICODE  
  2.     
  3. #include <stdio.h>  
  4. #include <string.h>  
  5. #include <assert.h>  
  6.     
  7.     
  8. class Student  
  9. {  
  10.     
  11. public:  
  12.     
  13.     char Name[1024];  
  14.     int age;  
  15.     int studentID;  
  16.     char address[1024];  
  17.     
  18. public:  
  19.     
  20.     Student();  
  21.     Student(char* _Name, int _age, int _studentID, char* _address);  
  22. };  

 

[student.cpp]

  1. #include "student.h"  
  2.     
  3. Student::Student()  
  4. {  
  5.     ::memset(Name,      0,sizeof(char)*1024);  
  6.     ::memset(address,   0,sizeof(char)*1024);  
  7.     
  8.     age         = 0;  
  9.     studentID   = 0;  
  10. }  
  11.     
  12.     
  13. Student::Student(char* _Name, int _age, int _studentID, char* _address)  
  14. {  
  15.     ::memset(Name,      0,sizeof(char)*1024);  
  16.     ::memset(address,   0,sizeof(char)*1024);  
  17.     
  18.     
  19.     sprintf(Name,"%s",_Name);  
  20.     age = _age;  
  21.     studentID = _studentID;  
  22.     sprintf(address,"%s",_address);  
  23. }  

 

[main.cpp]

  1. #include "student.h"  
  2.     
  3.     
  4. int main(void)  
  5. {  
  6.     // 1. Test 1  
  7.     Student* s = new Student();  
  8.     
  9.     assert(s);  
  10.     
  11.     delete s;  
  12.     
  13.     
  14.     
  15.     // 2. Test 2  
  16.     s = new Student("이원재",19,100979571,"서울시 관악구 XXXX");  
  17.     
  18.     assert(!strcmp(s->Name, "이원재"));  
  19.     assert(s->age == 19);  
  20.     assert(s->studentID == 100979571);  
  21.     assert(!strcmp(s->address,"서울시 관악구 XXXX"));  
  22.     
  23.     delete s;  
  24.     
  25.     
  26.     return 0;  
  27. }  

 

요래 Test Code와 Class를 분리하니 태가 좀 난다. ㅋㅋ

 

 

난 sprintf를 좀 싫어해서 memcpy로 고쳐볼까 한다.

 

[student.cpp]

  1. #include "student.h"  
  2.     
  3. Student::Student()  
  4. {  
  5.     ::memset(Name,      0,sizeof(char)*1024);  
  6.     ::memset(address,   0,sizeof(char)*1024);  
  7.     
  8.     age         = 0;  
  9.     studentID   = 0;  
  10. }  
  11.     
  12.     
  13. Student::Student(char* _Name, int _age, int _studentID, char* _address)  
  14. {  
  15.     ::memset(Name,      0,sizeof(char)*1024);  
  16.     ::memset(address,   0,sizeof(char)*1024);  
  17.     
  18.     memcpy(Name,_Name,strlen(_Name)*sizeof(char));  
  19.     age = _age;  
  20.     studentID = _studentID;  
  21.     memcpy(address,_address,strlen(_address)*sizeof(char));  
  22.     
  23. }  

 

변경점이 있을때마다 F5를 눌러주라. 그럼 알아서 Test를 해준다. ㅋㅋ

바로 이게 좋은 것중 하나다. 내가 변경한것들을 바로바로 Test할 수 있다는 것 TDD의 최대 장점이다.

 

 

 

Comments