TDD 프로젝트에 참여하면서 정말 많은 것들을 배우고 있습니다.
새로운 프로젝트에 익숙해 지느라 블로그 포스팅을 오랫동안 못 하고 있는데요.
배우는 것들을 다 정리를 해 두지는 못하더라도 가능한대로 그때 그때 블로그에 정리를 해서 한번 배울때 더 확실히 이해하고 다음번에 같은 일을 할 때 좀 더 편하고 깊게 작업을 해서 점점 더 훌룡한 프로의 모습을 갖춰가고 싶네요.
또 그 글을 이렇게 공개된 블로그에 정리해 두면 다른 분들에게도 도움이 될 수 있을 것 같구요.
일단 개념 정리부터 하겠습니다.
써핑하다가 좋은 글을 발견해서 그 글 부터 정리해 둘 까 합니다.
http://exceedhl.thoughtworkers.org/Programming/2007/08/02/tdd-a-top-down-style.html
영어 되시는 분은 위 링크 가서 직접 보시면 좋을 거예요.
2007년도에 작성한 글이네요.
한번에 다 정리하지는 못하고 여러번에 나눠서 정리해야 될 것 같네요.
TDD from starting from user stories - a top-down style
Programming August 02, 2007
TDD의 룰은 테스트를 먼저 하는 것이다. 대부분의 프로젝트들에는 GUI, 웹, 데이터베이스 그리고 외부 시스템과의 dependency등이 포함된다. 여기에서 unit test 를 위해 JUnit 으로 테스트 케이스를 만드는 것은 책에 나오는 것 처럼 그렇게 간단하게 만들어 지는 경우는 드물다. 또한 실제 프로젝트에서는 functional test, unit test, acceptance test 등등 다양한 테스트들이 필요하다. 각각의 프로젝트들은 그 프로젝트에 맞는 TDD 스타일을 적용해야 한다. 예를 들어 테스터들은 acceptance test를 만들고 개발자들은 unit test 들을 코딩하도록 한다던지... 나는 유저스토리부터 시작해서 production code 까지 진행되는 top-down TDD 스타일을 설명하려고 한다.
TDD from starting from user stories
What
이야기를 시작하기 전에, 우선 여러 종류의 테스트에 대한 이해부터 하고 넘어가겠습니다. 아마 여러분들은 unit test, functional test, acceptance test, module test, integration test 등등에 대해 들어보신 적이 있으실 겁니다. unit test 는 단 한개의 클래스만 테스트 하는 것으로서 그 클래스와 연결된 나머지 클래스들은 mocking을 해서 테스트를 하게 됩니다. functional test는 클래스들의 클러스트들의 그룹이 external requirements를 만나게 되고 goal들을 achieve 하는 경우를 의미합니다. Module test 와 integration test는 일종의 functional test로서 논리적으로 누눠진 모듈을 테스트 하고 모듈들 사이의 integration을 테스트 하는 것입니다. 그리고 acceptance test가 있는데요 이것도 일종의 functional test로서 좀 더 복잡한 시나리오를 cover 하기 위한 테스트 입니다.
이런 여러 종류의 테스트들에 대한 기본적인 이해에 근거해서 "top-down approach"는 개발자가 우선 첫번째로 user story 를 토대로 acceptance criteria 를 작성하고 돌려서 failure를 봐야 해야 한다고 말합니다. 그리고 나서 unit test를 작성하는 것을 실시해야 하는 것입니다. 그리고 unit test의 fail을 나게 하고 그것을 fix 하게 되는 거죠. 여러 unit test들을 끝내고 난 다음에는 이 acceptance 를 다시 돌리고 이것이 pass 하는 것을 봅니다. 그러면 이 acceptance criteria가 제대로 동작한다고 얘기할 수 있고 그 다음 acceptance criteria로 넘어가서 이 과정을 반복하게 되는 거죠.
이 process에는 몇가지 point들이 있습니다.
- 여기에는 여러분이 acceptance test와 unit test 사이에 작성해야 할 모듈 테스트나 integration 테스트같은 functional test의 여러 level이 존재합니다. 이것은 여러분이 프로젝트의 테스트를 어떻게 organize하느냐에 따라 여러 경우가 있을 수 있습니다.
- 어떤 경우든 test를 pass 하기 위한 가장 간단한 방법을 찾는 것을 항상 마음에 간직하고 계세요. unit 테스트와 functional 테스트를 pass 시키기 위한 과정에서 항상 가장 간단한 방법을 찾으라는 것을 적용하면서 작업하세요.
- 이 approach 를 진행하기 위해 사전에 준비되어 있어야 할 것은 좋은 user story 입니다. 좋은 story는 작고 테스트할 수 있는 단위로 작성 되어진 INVEST 스토리 입니다. 만약 분명한 acceptance criteria가 없거나 스토리가 testable 하지 않으면 acceptance test를 우선 write 하는데 어려움을 느끼실 겁니다. 만약 이 user story 가 너무 크면 여러분들이 진행해야 할 acceptance test들이 너무 많게 될 겁니다. 그러면 여러분은 해당 story 를 끝마치는데 아주 오랜 시간이 필요하겠죠.
Why
이 approach 만 있는 것은 아닙니다. 처음 시작할 때 언급했듯이 각각의 프로젝트와 각각의 사람들마다 그들만의 favorite들이 있습니다. 저는 이 approach를 좋아하는데요. 아래 그 이유를 적어 보겠습니다.
저 같은 TDD 빠들은 소프트웨어안에서 모든것을 테스트하기를 원하고 모든 것을 automatic하게 만들고 싶어 합니다. "no tests, no code"라는 룰을 알고 계시죠? 이 이유는 test는 requirement들을 표현하는 것이기 때문입니다. 그런 requirement들에 대해 우선 테스트부터 하게 되면 production code를 write 하기 전에 해당 requirement들에 대해 충분히 이해할 수 있게 됩니다. 또한 이런 test들은 production code의 quality 를 보장해 주고 documentation이나 refactoring 작업등과 관련해 시간을 절약해 줍니다.
story가 다른 종류나 레벨의 테스트들을 해야 하는 경우라면 좀 더 복잡하고 흥미로워 지죠. 여러분이 unit test 를 만들때 테스트 하고자 하는 클래스의 기능과 인터페이스에 대해 이미 파악하고 있을 겁니다. 하지만 이 클래스의 need는 어떻게 오게 된 걸까요? 어떻게 우리는 이 클래스가 필요한지에 대해 결정하고 우리는 이렇게 디자인할 거야... 라고 결정할까요? 정답은 story에 있는 기능에 부합되도록 이 클래스들을 만들기 위해 그런 결정을 해야 한다는 것입니다. 즉 business analysts와 대화를 통해 주로 알게 되는 스토리의 기능적인 설명에 대한 이해가 있어야 되고 그에 따라 unit test들을 만들어야 합니다.
대개 acceptance criteria로서 표현되는 스토리에 묘사된 그 기능들에 대한 requirement들을 이해하지 못한다면 우리의 코드가 어떻게 될지 그리고 unit test들은 어떻게 만들어야 할지 정확하게 감을 잡을 수 없습니다. story 에 있는 기능성의 부분 부분들 그런 unit 들에 대해 unit test들을 만들어야 합니다. (대개 public interface 인 클래스 이지요.) 유닛 테스트를 만들려면 우선 functionality 를 이해해야 합니다. 왜 requirement를 표현하기 위해 functional test부터 하지 않을까요. fail을 먼저 보고 그 다음에 solution을 찾고 그에 따라 unit test들을 만들어야 하기 때문이죠.
그러니까 acceptance test들을 먼저 만들게 되면 그 story의 requirement들을 이해할 수 있게 되는 거죠. 이 acceptance test에 의해 그 requirement들을 제대로 구현하기 위한 간단한 해결책을 찾는데 도움을 줄 수 있습니다. 그 다음에 unit test를 만드는 것이죠. 즉 각 클래스들이 solution의 기능들을 어떻게 구현하는지에 대한 분명한 이해를 기반으로 unit test를 만들 게 되고 또한 그 소스코드는 아주 간단하게 구현할 수 있게 됩니다. 이것이 아주 natural 한 TDD 접근법입니다.
How?
위에서 얘기 됐듯이 우선 acceptance criteria를 test로 만들어야 합니다. 여기서 우리에게 아주 중요한 것 중 하나는 그런 테스트들을 만들이 위해 필요한 툴들을 찾아내는 것이죠. functional testing tool들은 이 과정에서 아주 중요한 부분입니다. 각 프로그래밍 언어마다 unit test를 가능하도록 하는 툴들은 이미 많이 있습니다. 그러니 적합한 functional testing tool을 찾는게 좀 더 중요한 일이 됩니다.
unit testing tool들은 프로그래밍 언어에 따라 분류 될 수 있는데 반해 functional testing tool들은 해당 문제점들에 맞는 것들을 찾고 연구해야 합니다. 웹 어플리케이션, 윈도우 어플리케이션 그리고 Java GUI 어플리케이션 등의 technical implementation nature가 다르기 때문에 각각에 대해 다른 functional testing tool들이 있게 됩니다.
웹 어플리케이션들들을 위한 functional testing tool들에는 Selenium, Watir(Watin, Watij), Sahi 등이 있습니다. Java GUI 어플리케이션용으로는 Abbot 가 있구요. Windows Form application은 NUnitForm 이 있고 더 넓은 범위의 윈도우즈 어플리케이션 용으로는 Microsoft UIAutomation Framework등이 있습니다. QTP 같은 좀 더 heavy 한 툴들도 고려 해 볼 수 있습니다. library나 console application 같은 다른 종류의 소프테웨어에 대해서는 xUnit framework을 이용해서 functional testing을 할 수 있습니다. 그 이왜에도 다른 여러가지 접근법들을 사용할 수 있습니다.
어떤 종류의 어플리케이션에 대해서는 functional test를 위한 툴들이 좀 더 많은 것 같네요. 설사 어떤 종류의 어플리케이션에는 관련된 functional testing tool들이 상대적으로 적더라도 TDD 에 대한 개념, functional testing 에 대한 개념을 충분히 이해하신 다면 실제 상황에서는 다른 여러 해법들을 찾으실 수 있으실 겁니다.
여러번의 testing code를 계속 refactoring 함으로서 여러분의 코드를 좀 더 이해하기 쉽게 만들 수 있고 또 좀 더 business natural (DSL) 하게 만들 수 있습니다. rbehace와 jbehave같은 어떤 툴들은 business 가치를 좀 더 명확하게 반영하고 non-technical people 들이 이해할 수 있도록 acceptance test들을 write 할 수 있도록 도와 줍니다.