(이미지 협찬 yes24)

 

펭귄너구리 채수원님이 보내주신 TDD책을 어제 받았다. 지금까지 제법 많은 책을 우편을 통해서 받았지만 이 책만큼 기다렸던 것도 없었던 것 같다. 한동안 뜨겁던 TDD에 대한 열기가 막상 TDD를 적용하려고 했을 때 겪게 되는 막막함과 안티들의 큰 목소리 덕에 많이 식고 있는 느낌이었다. 디자인 패턴이니 SOLID는 하는 얘기만 들으면 온몸에 두드러기가 나고 환청이 들린다는 일련의 인간들은 TDD에도 역시 비슷한 이유로 비난을 퍼부어왔다. TDD만큼 닥치고-안티가 많은 것도 없다. 안티는 아닐지라도 TDD를 좀 더 나은 코드를 만들고 즐거운 개발을 하는데 사용할 수 있는 유용한 도구와 실천기술이 아니라 남들에게 우월감을 드러내고 잘난척하기 위해서 어설픈 지식으로 떠들어 대는데 사용했던 사람들도 사실은 TDD 슈도-안티군에 포함되야 할 것이다. 그러는 와중에 TDD에 대해서 오해하게 되고 거리감을 두게된 많은 개발자들이 있는 것을 보며 많이 안타까웠다.

안티에는 답도 없고 약도 없다. 그래서 신경 쓰지 않는다.

차라리 진지하게 TDD에 접근하려고 하지만 막연한 느낌과 무엇을 어떻게 해야 할지 알지 못해서 당황해 하는 사람들에게 좋은 안내를 해주고, 스스로 TDD와 테스트 코드 작성의 즐거움을 느끼게 해주는 것에 관심을 가지는 것이 나을 것이다. 그러기 위해서 몇가지 넘어야 할 장벽이 있는데, 바로 그 장벽을 낮춰주는 데 기여해줄 수 있는 것이 바로 이 책, "TDD 실천법과 도구"라고 생각한다.

펭귄너구리라는 아이디를 쓰는 채수원님은 내가 아는 가장 성실하고 진지한 엔지니어의 한 명이며 재치있고 유머감각 넘치는 이야기로 귀에 쏙쏙 들어오는 설명을 잘해주는 초특급 강사이기도 하다. 치열한 환경의 현장 개발자들에게 TDD를 가르치고 보급하려고 애쓰면서 가졌던 많은 고민과 생각을 풀어놓은 것이 바로 이 책이다. 그래서 이 책의 내용은 살아있고 싱싱하다. 그래서, 지난 몇년간 TDD를 사용하려고 애써왔던 나에게도 이 책은 참 소중하다. 매 페이지마다, 얼마나 많은 연구와 리서치를 했왔고 그것을 적용해보고 고민하는데 얼마나 많은 시간을 써왔는지를 느끼게 해주는 내용으로 가득하다.

이 책은 “TDD를 안하는 놈은 루저"라는 박탈감을 느끼게 해서 허둥지둥 TDD에 뛰어들어 고생과 삽질만 하고, 어디에 하소연도 못하면서 억지로 좋은 척, 아는 척 하게 만드는 그런 책이 아니다. 오히려 편안한 마음으로 TDD를 이해하게 해주고, 그동안 어설프게 TDD를 적용하다 받은 상처를 보듬어주고, 용기를 가지고 한발짝씩 TDD를 해보다가 어려움을 만나면 언제든 다가와 조력자가 되어 줄 수 있는 그런 책이다.

물론 이 책을 본다고 갑자기 TDD의 고수가 되는 것은 아니다. 김창준님이 추천사에도 썼듯이 TDD는 한번에 비급을 배워서 쉽게 마스터할 수 있는 것이 아니다. 오히려 충분한 시간을 들여서 고민하고 생각하고 많은 시도를 해보면서 차근차근 몸에 익히는 내공을 쌓는 훈련이 반드시 필요하다. 프로그래밍이라는 것이 원래 그렇듯이 TDD에도 정답이란 없고, 항상 더 나은 방법이 있을 것이라는 기대를 가지고 새로운 시각을 가지고 창의적인 접근을 하려는 노력이 동반되야 한다. 이 책에 소개한 내용이나 인용한 글들이 모두 정답이라는 생각을 가지기 보다는, 저자와 한판 붙어보자는 심정으로 책을 읽는 내내 가상의 토론을 즐기는 것도 좋을 것이다. 무엇보다도 꾸준히 작은 분량이라도 TDD를 해보는 습관을 들이는 것이 중요하다. 내가 작년초에 결심한 것 한가지는 TDD를 거창한 개발에만 사용하는 것이 아니라, 평소에 간단한 코드 하나를 만들 때도 사용하도록 습관을 들이자는 것이었다. 그 뒤로 HelloWorld 수준의 간단한 코드를 만들때도 항상 테스트를 먼저 만들어보려고 노력했다. 그리고 시간이 얼마 흐른뒤에 느낀 것은, 이전에는 TDD를 한다면 뭔가 힘을 잔뜩 주고 TDD를 해보고 말테다라는 긴장감 있었는데,  TDD를 코딩 습관으로 만들어버린 후로는 그냥 평범하고 자연스러운 것이 되어버렸다. 테스트를 따로 만든다가 아니라 테스트가 그냥 내가 당연히 만들어야 할 자연스러운 코드가 되어버린 것이다. JUnitMax가 테스트를 빌드과정의 하나로 만들어버린 것과 비슷한 느낌이다. TDD를 특별히 한다는 생각도 들지 않는다. 그냥 원래 코드는 그렇게 만드는 것이라는 습관이 들었고 그것이 편하고 자연스럽게 느껴지기 때문이다. 김창준님이 예전에 TDD를 제대로 하려면 최소한 6개월은 수련을 해야 한 기억이 (가물가물) 난다. TDD책을 보고 가끔 개발에 적용하는 시간이 아니라, 매일 빠짐없이 TDD를 좀 더 잘하기 위한 훈련의 시간을 30분에서 한시간이라도 잡고 그것을 6개월간 꾸준히 해야 한다는 얘기라고 생각한다.

이 책에 대해서 한가지 불만이 있다면 그것은 책이 너무 얇다는 것이다. 아이폰과 안드로이드, 트위터 따위가 장악해버린 출판시장에 비인기 종목인 TDD의 책이 나온 것만으로도 다행이라고 생각하지만 그래도 조금만 더 많은 얘기를, 좀 더 구체적으로 할 수 있는 지면이 허용되었더라면 좋았을 것을 이라는 아쉬움이 남는다.

하나 더 바라기는 이 책의 예제를 시연하는 동영상, 스크린캐스트가 공개되었으면 좋겠다. 나는 김창준님과 강규영님이, 그 무뚝뚝하고 어색한 목소리로 TDD를 진행하는 동영상을 우연히 본 것이 TDD에 관심을 가지게된 계기가 되었다. 이 책에 많은 그림과 코드가 잘 나와있긴 하지만 역시 TDD는 그 역동적인 과정을 눈으로 보는게 제맛이다. 수원님이 아니더라도 성실한 독자들 중에서 누군가 만들어 공개하지 않을까 기대도 해본다.

다음 주까지는 스프링 책을 마무리하는 최종 작업 때문에 바빠서 여유가 없겠지만, 그 후에 이 책의 내용을 차근 차근 정리해서 블로그에 올려볼 생각이다.

 

책을 주문하고 싶으면 여기. http://www.yes24.com/24/goods/3908398?scode=032&srank=1

저자의 책 소개를 보고 싶으면 저기. http://blog.doortts.com/128

웹에만 매쉬업이 있는게 아니다. JUnit에도 있다. 아참, 앞으로 있을 것이다.

켄트 벡과 함께 JUnit4 개발을 책임지고 있는 David Saff라는 사람이 있다. David Saff는 MIT에서 JUnit Max의 기본 아이디어인 Continuous Testing을 연구했던 사람이다. 아마 성훈(http://sestory.tistory.com/)이가 있던 그 연구소 같은데.. 아님 말고. 작년엔가 구글에 입사해서 테스팅 엔지니어로 활동하고 있다.

아무튼 David Saff는 JUnit 메일링 리스트에서 여러번 테스트에서 사용하는 assert 방법에 대한 자신의 아이디어와 연구중인 내용을 소개한 적이 있다. JUnit은 Hamcrest의 assertThat과 Matcher 등을 차용해서 기존의 assertEquals()와 같은 assert문을 대체한 바 있다. 하지만 assertThat과 Matcher방식이 처음에는 읽기가 편한 것 같고 확장성도 좋아보이지만, 나름 단점도 많이 있다.

David Saff는 그래서 Mock프레임워크 등에서 애용되는 스타일인 dot(.)으로 연결되는 문장으로 assert을 기술하는 방법을 연구해왔다. 얼핏 그 내용을 JUnit 메일링 리스트에 공개한게 작년 봄인가 여름인가 그랬던 것 같은데, 오늘 보니 dotmesh(http://github.com/dsaff/dotmesh)라는 이름으로 공개를 했다. 자세한 내용은 그의 블로그(http://saffgreenbar.blogspot.com/2010/03/dotmesh-free-assertions-from-domain.html)의 글을 읽어보면 알 수 있을 것이다.

예를 들면 다음과 같은 문장을

assertThat(responseMessage, containsString(”OK”));

닷메쉬에서는 아래와 같이 만들 수 있다.

assertThat(responseMessage).contains(”OK”);

그 외에도 예를 보면

assertThat(Arrays.asList("a", "b", "c")).contains("a"); // passes

assertThat("abc").matches(".*a.*"); // passes

assertThat(not(Arrays.asList("a", "b", "c"))).isEmpty(); // passes

assertThat("abc").contains("c"); // passes

assertThat(Arrays.asList("a")).isEmpty(); // error: “Failed: <[a]>.isEmpty()”

assertThat("abcde").startsWith("ghi", 1); // error: "Failed: <abcde>.startsWith(<ghi>,<1>)"

assertThat(1).notEquals(2);

assertThat(not(1)).equals(2);

 

이런 식이다.

장단점과 특징에 대해서는 블로그 글에 잘 설명되어있다. 아직 더 발전시킬 내용이 많겠지만, 아무튼 기대된다. 언젠가는 JUnit에 들어가겠지.

당장은 짬을 내기 힘들어서 써보지는 못하겠지만, 다음 개발작업에 들어가면 꼭 쓰고 말테다. 누가 좀 적용해보고 감상 좀…

그런데 왜 이름이 닷메쉬(dotmesh)일까? 메쉬업이 인터넷의 다양한 서비스를 엮어서 새로운 서비스를 만들어내는 작업이듯(맞나?), 닷메쉬는 메소드 호출에 사용하는 dot(.)기호로 메소드를 엮어주기 때문이다. 지금은 assertThat() 뒤에 하나 씩이지만, 아마 굴비엮듯이 계속 이어져 만들어질 수도 있겠지. 그런데 이름을 hamcrest처럼 assertThat으로 써서 좀 맘에 안든다. 그냥 assert(…).equals(..) 이런 식이면 안되나?

사용후기는 다음 기회에.

UPDATE: 아차! 매쉬업은 MashUp이었지! 발음이 비슷하다고 이런 착각을… :(

역시 블로그는 졸릴 때 쓰면 안돼. 그냥 낚시용 제목으로 남겨놔야지.

JUnit 4.7이 공개됐다.

초기에 @Interceptor라는 이름으로 소개됐던 기능이 막판에 @Rule이라고 이름을 바꾸고 실험기능이 아닌 코어기능으로 바로 들어가면서 등장했다. 애노테이션을 확인해서 테스트를 실행하는 방식으로 완전히 바뀌었던 JUnit4.0이후 가장 큰 변화이다.

JUnit의 핵심클래스인 BlockJUnit4ClassRunner에 있는 테스트 메소드를 실행하는 과정을 담은 methodBlock()에 큰 변화가 있었다.

기존에는 After->Before->Timeout->Expected->테스트메소드 실행이라는 테스트메소드 실행규칙을 하드코딩해서 내부에 가지고 있었다. Nested Object Method니 Conditional Factory니 하는 이름이 무색하게도 테스트를 실행하는 규칙의 조합과 순서는 고정되어있었고 확장이 불가능했다.

이를 굳이 확장하려면 Theories처럼 BlockJUnit4ClassRunner를 상속한 후에 super의 methodBlock()을 먼저 호출하고, 자신의 기능을 추가하는 방법을 사용하든가(Theories가 테스트 실행순서의 가장 바깥에 위치하기 때문에 가능한 것이다), 아니면 스프링의 SpringJUnit4ClassRunner처럼 BlockJUnit4ClassRunner 상속한 후에 주요한 메소드의 코드를 그대로 배낀 후에 일부만 고쳐서 사용해야 했다(스프링은 테스트 실행 순서가 바꾸기 때문에 Theories 같은 방식을 쓸 수 없다)

필터,인터셉터,COR,데코레이터,Nested Method Object 등등 뭐라고 불러도 좋지만 아무튼 다음 단계에 대한 포인트를 가지는 구현을 사용하면서 Statement라는 단일 인터페이스를 구현해서 이를 중첩시키는 방법을 썼다면 충분히 그런 설계의 장점인 런타임시 다이나믹한 재구성 가능성을 염두에 두었어야 했다.

하지만 기존 Junit4.6까지의 코드는 기껏 그런 멋진패턴과 인터셉터 구조를 만들어 놓고도 그것을 핵심코드에서 실행순서를 고정해버리는 한심한 선택을 했다. 때문에 단지 그런 실행과정을 확장하기 위해서 테스트 러너의 코드 전체를 상속해서 오버라이드를 통해서 뜯어고쳐야 하는 방법 외에는 유연한 확장의 길을 막아버린 것이다. 게다가 핵심기능을 그렇게 마구 고치다보면 RSP를 위반할 가능성이 매우 높아진다.

아래 메소드가 4.6까지의 핵심 테스트 실행규칙을 고정적으로 담고 있는 BlockJUnit4ClassRunner의 메소드이다. 각 단계를 거치면서 애노테이션이나 엘러먼트를 확인해서 Statement오브젝트를 중첩하는 구조로 만들어진다. 최종 작업인 테스트 메소드 실행이 리플렉션으로 일어나기 때문에 타겟의 인터페이스를 구현하는 방식으로 만들어지지는 않지만 내용을 보자면 전형적인 책임부가를 위한 데코레이터 패턴 또는 인터셉터의 구조이다.

protected Statement methodBlock(FrameworkMethod method) {
   …

    Statement statement= methodInvoker(method, test);
    statement= possiblyExpectingExceptions(method, test, statement);
    statement= withPotentialTimeout(method, test, statement);
    statement= withBefores(method, test, statement);
    statement= withAfters(method, test, statement);
    return statement;
}

문제는 이렇게 핵심클래스를 코드로 고정시켜버리면 유연한 확장의 여지가 없다는 점이다. 켄트 벡의 말대로 JUnit4가 혁명적인 변화를 통해서 맨땅에서부터 새로 만들어졌기 때문에 코드는 인라인화되었고 중복도 용인할 수 있는 혼란의 상태에 여전히 있다는 것일까?

Sometimes an effective revolutionary strategy is just to inline absolutely everything in a class and helpers into a single method and begin re-extracting from there.

 

이제 4.7에 이르러서 겨우 테스트를 실행하는 단계와 그 내용에 대해서 @Rule이라는 이름으로 모듈화를 하고, 재구성 할 수 있고, 분리하는 것이 가능해졌다. 위의 메소는 JUnit4.7에서 다음과 같이 바뀌었다.

protected Statement methodBlock(FrameworkMethod method) {
    ….

    Statement statement= methodInvoker(method, test);
    statement= possiblyExpectingExceptions(method, test, statement);
    statement= withPotentialTimeout(method, test, statement);
    statement= withRules(method, test, statement);
    statement= withBefores(method, test, statement);
    statement= withAfters(method, test, statement);
    return statement;
}

기존에 코드에 박혀있었던 테스트 실행 단계 4가지는 이제 @deprecated되었다. 최종 테스트를 실행하는 Statement인 MethodInvoker를 부여하는 것과 테스트 실행전의 인터셉터에 해당하는 Rule을 다이나믹하게 적용할 수 있도록 바뀌었다. @deprecated된 메소드들을 보면 조만간 이 메소드들은 전혀 확장의 여지가 없게 private으로 바뀔 것이고(호환성을 고려해 차마 없애지는 않나보다) 대신 Rule을 사용하라고 되어있다. 적어도 테스트를 어떻게 실행하는지에 대해서는 이제 확장의 여지가 들어간 것이다.

그럼에도 아직 JUnit4.7의 코드도 여전히 핵심적인 기능들은 BlockJUnit4ClassRunner에 박혀있다. Rule을 적용하는 방법은 @Rule이 붙은 필드로 제한되어있다. 그 Rule을 읽어와 중첩 Statement를 구성해주는 코드가 BlockJUnit4ClassRunner에 박혀있기 때문이다. 왜 클래스 단위로 테스트를 실행해주는 책임을 가진 클래스에 테스트 실행 규칙을 어떻게 읽어오는지에 대한 책임도 들어있고, 이를 어떻게 구성하는지에 대한 책임도 들어있는지 모르겠다.

 

켄트 벡은 OCP는 혁명적인 변화가 있을 때는 필연적으로 위반할 수 밖에 없다고 항변한다. JUnit3에서 JUnit4로 바뀐 것에 대해서라면 동의한다. 이건 OCP위반정도가 아니라 목적만 비슷할뿐 아답터를 쓰지 않고는 서로 호환도 안되는 완전히 새로운 프레임워크를 만든 것이니깐.

하지만 JUnit4에 들어와서 4.7에 이르기까지의 변화에 대해서도 같은 이유로 OCP를 위반하는 것이 어쩔 수 없다고 주장한다면 나는 동의할 수 없다. 적어도 @Rule 방법을 도입하면서 핵심코드를 수정할 수 밖에 없었던 이유는 혁명적인 변화 때문이라기 보다는 충분히 고려할만한 수준의 유연한 확장을 위한 설계가 JUnit4.6까지 존재하지 않았기 때문이라고 생각한다. 만약 테스트 실행방법에 대해서 러너 외부에서 이를 규정할 수 있도록 분리하고 위임하는 설계를 만들었다면 @Rule의 도입정도는 얼마든지 기존 코드를 건드리지 않고 간단한 추가를 통해서 가능했던 일이다.

스프링은 애노테이션을 통한 DI나 @Configuration가 달린 자바코드를 통한 DI 라는 혁명적인 변화조차도 1.x부터 설계해놨던 컨테이너의 PostProcessor 확장포인트만들 이용해서 기존 컨테이너 코드에 영향을 주지않으면서 새로운 기능의 확장을 가져올 수 있었다. ApplicationContext의 핵심클래스들은 스프링이 나오기 한참 전인 2001년부터 만들어서 쓰던 것들이다. AspectJ의 포인트컷 표현식이나 @Transactional 같은 애노테이션으로 통합된 트랜잭션 속성+포인트컷의 결합기능이 추가될 때도 스프링의 1.x부터 존재했던  Pointcut, Advice, Advisor의 확장 구조를 이용하는 것만으로 충분했다. 스프링이라고 완벽한 것은 아니겠지만, 적어도 프레임워크라는 입장에서 극대화된 확장성을 충분히 고려해서 모든 것을 설계해 두었기에 개발된지 6년째 3.0이 될때까지 코드를 갈아 엎는 일 한번 없이 거의 완벽에 가까운 구버전호환성을 지키면서도 최신 기능과 새로운 동작방식을 끝도없이 추가할 수 있었던 것 같다.

 

아무튼, 나는 기존 JUnit4.6까지의 코드가 사실은 OCP를 따르지 않았기 때문에 기능을 확장하기 위해서 러너의 코드를 변경할 수 밖에 없었던 것이라고 본다. 자유로운 테스트 실행방법의 기능을 확장하는 것에는 굳게 닫혀있고, 그 때문에 핵심코드를 변경하는 것에는 혼란스럽게도 열려있었을 뿐이다.

JUnit4가 처음부터 조금만 더 유연한 사고를 가지고 설계되었더라면 좋았을 것이라는 아쉬움을 항상 가지고 있다. 이제 JUnit4.7의 변화에 대해서 SpringJUnit4ClassRunner는 과연 어떻게 바뀔지 궁금하다. 혹시 4.6이전버전과 4.7이후 용으로 따로 만들어지는 것일까? JUnit이 OCP를 따르지 않은 탓에 스프링과 같이 무식한 방법을 동원해서 겨우 JUnit4 확장 러너를 만들 수 밖에 없었던 경우에는 앞으로 상당한 혼란을 겪을지도 모르겠다. 4.x대는 4.7을 실험적인 버전으로 놔두고, 더욱 유연한 Rule기반의 5.x를 설계하는 것이 어떨까 싶은데, Junit메일링 리스트를 보면 4.8에 대한 추가기능에 대한 이야기를 하고 있으니 앞으로 얼마나 더 나갈지 모르겠다.

 

참, @Interceptor라는 이름을 막판에 @Rule로 바꾼 이유에 대해서 켄트 벡은 "메카니즘 보다는 의도를 가지고 이름을 지어야 한다는 원칙을 깜빡했었다"라고 설명했다. Rule이라는 게 그다지 의도가 잘 느껴지지는 않지만 어쨌든 인터셉터보다는 낫다. Struts2를 처음 봤을 때 인터셉터에 핵심기능들을 담는 다는 것이 잘 이해가 되지 않았던 것도 그런 이유일 것이다.

메커니즘보다는 의도를 가지고 이름을 정하라는 원칙는 DI의 번역에도 적용할 수 있다고 생각된다. DI를 의존(종속)객체주입이라고 번역하는 사람들의 주장은 그 것이 메카니즘을 잘 반영한다는 것이다. 하지만 그래서는 충분한 DI의 의도를 담지 못한다는 게 내 생각이다. Manning의 Dependency Injection책을 쓰고 있는 구글웨이브의 개발자인 D어쩌고(이름이 당최 외우기가 힘들어서) DI의 Dependency를 의존서비스라고 설명한다. 원래 용어가 만들어진 의도를 제대로 드러내면서 번역하자면 의존성주입은 아주 적절한 용어이다이다.  의존성이라는 말이 모호해서 의도를 잘 드러내주지 못한다면 의존관계주입(설정) 또는 의존서비스주입이라고 해도 될 것 같고.

그나저나 스프링은 무슨 의도를 가지고 지은 이름인지 그게 갑자기 궁금하다.

가끔 하는 뒷북치기. :)

이게 언제부터 되는 것인지는 모르겠다. 아무튼 우연히 JUnit으로 만든 테스트코드를 실행하다가 특정 테스트메소드만 실행하는 것이 가능하다는 것을 알게되었다.

JUnit4의 기본 TestRunner는 클래스단위로 테스트를 수행한다. 해당 클래스의 모든 @Test메소드를 실행한다. 경우에 따라 특정 테스트메소드를 제외하고 싶다면 @Ignore를 부여해서 임시로 해당 테스트메소드가 실행되지 않게 할 수는 있다.

이클립스는 테스트 실행코드를 만들지 않아도, 테스트스윗을 만들지 않아도 JUnit창이나 소스에서 바로 Run as JUnit Test를 통해서 하나의 테스트클래스나 한 패키지 아래의 모든 테스트를 실행할 수 있다.

그런데 만약 테스트메소드가 많은 테스트에서 단 한개의 테스트만 달랑 실행하고 싶다면 다음과 같이 하면 된다.

  1. 실행하고 싶은 테스트메소드의 이름으로 커서를 가져간다.
  2. Run as JUnit Test로 테스트를 실행한다. (단축키 Shift-Alt-X + T 또는 컨텍스트 메뉴나 메인 메뉴에서 Run > JUnit Test로 실행)

이런 편한 기능이 있었을 수가.

이래서 이클립스의 Help Contents를 가끔 읽어줘야 한다. Contents의 각 대표항목의 Tips and tricks항목만 읽어도 충분하다. 그것만 해도 워낙 양이 많아서 그렇지..  getter/setter를 자동으로 만들어주는 기능이 Shift-Alt-S + R을 쓰지 않아도, 필드 이름에 가서 ctrl-1만 해도 된다는 것도 어제 알았다.  ctrl-1(quick-fix)는 에러가 났을 때 수정하는 용도로 쓰는 것 외에도 상당히 많은 영리한 기능을 적용하는데 사용할 수 있다. 이것만 잘써도 코드 작성 속도가 두배쯤 빨라질 수도 있을 듯.

참, 내가 쓰는 이클립스 버전은 3.5이고 JUnit은 4.6이다.

켄트 벡의 JUnitMax 개발과 비즈니스가 공식적으로 중단되었다는 안타까운 소식이다. 현재 비즈니스 모델과 시장으로는 충분한 수익을 창출할 수 없기 때문이라고 한다. 충분히 이해는 하지만 아무튼 슬픈 소식이다. JUnitMax를 더 이상 사용하지 않는다고 하면 subscription의 남은 기간의 돈은 환불해준다고 한다. 물론 계속 사용해도 상관은 없지만, 더 이상의 지원이나 라이선스 갱신은 없다.

특별히 구입사실을 체크하는 과정이 없다고, updatesite  URL 알아내서 무단으로 사용한 사람들은 반성을 해야한다.

용역제공이나 기술컨설팅, 공짜 서비스 후 광고판매 말고 순수하게 소프트웨어 개발로 먹고 사는 것은 점점 힘들어지는 것일까. 아니면 TDD기반의 제품이 성공하기엔 아직 TDD의 보급이 너무 더딘 탓일까.

© 2017 Toby's Epril Suffusion theme by Sayontan Sinha