Se ha denunciado esta presentación.
Utilizamos tu perfil de LinkedIn y tus datos de actividad para personalizar los anuncios y mostrarte publicidad más relevante. Puedes cambiar tus preferencias de publicidad en cualquier momento.

TDD 발담그기 @ 공감세미나

3.906 visualizaciones

Publicado el

공감세미나에서 발표할 TDD 발담그기 자료입니다. TDD를 시작하려는 분들이 TDD를 감을 잡는데 약간의 도움이 될 만한 내용을 담았습니다.

Publicado en: Tecnología
  • Hello! Get Your Professional Job-Winning Resume Here - Check our website! https://vk.cc/818RFv
       Responder 
    ¿Estás seguro?    No
    Tu mensaje aparecerá aquí

TDD 발담그기 @ 공감세미나

  1. 1. TDD 발담그기@공감세미나 최범균(madvirus@madvirus.net), 2017-05-13
  2. 2. 발표자 •최범균,madvirus@madvirus.net •주로자바로먹고살며, 잡다한분야에관심 •코딩잘하고,글잘쓰고싶은개발자 2TDD 발담그기@공감세미나 JPA 입문 곧 출간
  3. 3. 내용 •TDD발담그기 • 기능정의와TDD • 테스트코드의전형적인구성 • TDD협업대상도출 • 테스트범위 • TDD효과 • TDD시작과주의사항 •대상 • TDD를시작하려는개발자 3TDD 발담그기@공감세미나
  4. 4. 기능 •사용자가뭔가하면 •결과로뭘받음 4TDD 발담그기@공감세미나 사용자 인증 기능 사용자가 인증 시도를 하면 인증 결과로 성공/실패 여부를 받는다
  5. 5. 기능 • 어떤상황일때 • 정상인경우 • 사용자가정상요 청을하면 • 결과로정상응답 을받음 • 예외인경우 • 사용자가예외요 청을하면 • 결과로예외처리 응답을받음 TDD 발담그기@공감세미나 5 사용자인증기능 상황: 아이디가"bk"이고암호가"pw"인계정이존재할때 뭔가하면(정상인경우): 사용자가"bk","pw"로인증을요청하면 결과로뭘받음(정상인경우): 인증에성공함 뭔가하면(예외인경우): 사용자가"bk","no"로인증을요청하면 결과로뭘받음(예외인경우): 인증에실패함
  6. 6. 순서 •기능명세명세에맞게구현진행 • 구현을진행하면서명세를조정 •구현먼저기능명세 6TDD 발담그기@공감세미나
  7. 7. 무엇을(what) 만들지가 어떻게(how) 만들지보다 우선 7TDD 발담그기@공감세미나
  8. 8. 테스트 주도 개발 Test-Driven Development •테스트를먼저만들고 테스트를통과하는구현을작성 • 테스트코드부터시작 • 테스트없이제품코드작성하지않음 •테스트를통과할만큼의코드만작성 • 불필요한코드작성최소화 • 과도한설계방지 8TDD 발담그기@공감세미나
  9. 9. 야구 게임 예 •야구게임규칙 • 숫자3개를맞추면이김 • 각숫자는겹치지않음 • 예측한숫자가존재할때 • 위치가같으면스트라이크 • 위치가다르면볼 9TDD 발담그기@공감세미나
  10. 10. 야구 게임 예 •정답이371일때 • 예측한숫자가582이면0볼0스트라이크 • 예측한숫자가371이면3스트라이크 • 예측한숫자가389이면1스트라이크 • 예측한숫자가317이면2볼1스트라이크 10TDD 발담그기@공감세미나
  11. 11. 야구 게임 예: 테스트 코드로 시작 @Test publicvoidnomatch(){ //정답이479인게임에서 Gamegame=newGame(479); //123을예측하면 Scores=game.guess(123); //0스트라이크,0볼 assertThat(s.strikes()).isEqualTo(0); assertThat(s.balls()).isEqualTo(0); } 11TDD 발담그기@공감세미나
  12. 12. 테스트를 통과할 만큼만 작성 @Test publicvoidnomatch(){ //정답이479인게임에서 Gamegame=newGame(479); //123을예측하면 Scores=game.guess(123); //0스트라이크,0볼 assertThat(s.strikes()).isEqualTo(0); assertThat(s.balls()).isEqualTo(0); } publicclassGame{ publicGame(intvalue){ } publicScoreguess(intguess){ returnScore.create(0,0); } } publicclassScore{ privatescnt,bcnt; ...생성자,create() publicintstrikes(){returnscnt;} publicintballs(){returnbcnt;} } 12TDD 발담그기@공감세미나
  13. 13. 일치하지 않는 경우 추가 @Test publicvoidnomatch(){ Gamegame=newGame(479); Scores=game.guess(123); assertThat(s.strikes()).isEqualTo(0); assertThat(s.balls()).isEqualTo(0); Scores2=game.guess(568); assertThat(s2.strikes()).isEqualTo(0); assertThat(s2.balls()).isEqualTo(0); } publicclassGame{ publicGame(intvalue){ } publicScoreguess(intguess){ returnScore.create(0,0); } } publicclassScore{ … } 13TDD 발담그기@공감세미나
  14. 14. 코드 정리 @Test publicvoidnomatch(){ Gamegame=newGame(479); assertNoMatch(game.guess(123)); assertNoMatch(game.guess(568)); } publicvoidassertNoMatch(Scores){ assertThat(s.strikes()).isEqualTo(0); assertThat(s.balls()).isEqualTo(0); } 14TDD 발담그기@공감세미나 @Test publicvoidnomatch(){ Gamegame=newGame(479); Scores=game.guess(123); assertThat(s.strikes()).isEqualTo(0); assertThat(s.balls()).isEqualTo(0); Scores2=game.guess(568); assertThat(s2.strikes()).isEqualTo(0); assertThat(s2.balls()).isEqualTo(0); }
  15. 15. 일치하지 않는 경우 추가 @Test publicvoidnomatch(){ Gamegame=newGame(479); assertNoMatch(game.guess(123)); assertNoMatch(game.guess(568)); assertNoMatch(game.guess(321)); } publicvoidassertNoMatch(Scores){ assertThat(s.strikes()).isEqualTo(0); assertThat(s.balls()).isEqualTo(0); } publicclassGame{ publicGame(intvalue){ } publicScoreguess(intguess){ returnScore.create(0,0); } } publicclassScore{ … } 15TDD 발담그기@공감세미나
  16. 16. 모두 일치하는 경우 @Test publicvoidallstrikes(){ Gamegame=newGame(479); Scores=game.guess(479)); assertThat(s.strikes()).isEqualTo(3); assertThat(s.balls()).isEqualTo(0); } publicclassGame{ publicGame(intvalue){ } publicScoreguess(intguess){ if(guess==479) returnScore.create(0,3); returnScore.create(0,0); } } 16TDD 발담그기@공감세미나
  17. 17. 모두 일치하는 경우 추가 @Test publicvoidallstrikes(){ Gamegame=newGame(479); Scores=game.guess(479)); assertThat(s.strikes()).isEqualTo(3); assertThat(s.balls()).isEqualTo(0); Gamegame2=newGame(124); Scores=game.guess(124)); assertThat(s.strikes()).isEqualTo(3); assertThat(s.balls()).isEqualTo(0); } 17TDD 발담그기@공감세미나 publicclassGame{ privateintvalue; publicGame(intvalue){ this.value=value; } publicScoreguess(intguess){ if(value==guess) returnScore.create(0,3); returnScore.create(0,0); } }
  18. 18. 코드 정리 @Test publicvoidallstrikes(){ Gamegame=newGame(479); assertAllStrikes(game.guess(479)); Gamegame2=newGame(124); assertAllStrikes(game2.guess(124)); } privateassertAllStrikes(Scores){ assertThat(s.strikes()).isEqualTo(3); assertThat(s.balls()).isEqualTo(0); } 18TDD 발담그기@공감세미나 @Test publicvoidallstrikes(){ Gamegame=newGame(479); Scores=game.guess(479)); assertThat(s.strikes()).isEqualTo(3); assertThat(s.balls()).isEqualTo(0); Gamegame2=newGame(124); Scores=game.guess(124)); assertThat(s.strikes()).isEqualTo(3); assertThat(s.balls()).isEqualTo(0); }
  19. 19. 일부 일치하는 경우 @Test publicvoidsomestrikes(){ Gamegame=newGame(479); assertMatch(game.guess(359),0,1); } privateassertMatch(Scores,intb,ints){ assertThat(s.strikes()).isEqualTo(s); assertThat(s.balls()).isEqualTo(b); } publicclassGame{ … publicScoreguess(intguess){ if(value==guess) returnScore.create(0,3); intscnt=0; if(value%10==guess%10){ scnt+=1; } returnScore.create(0,scnt); } } 19TDD 발담그기@공감세미나
  20. 20. 일부 일치하는 경우 추가 @Test publicvoidsomestrikes(){ Gamegame=newGame(479); assertMatch(game.guess(359),0,1); assertMatch(game.guess(372),0,1); } … publicclassGame{ … publicScoreguess(intguess){ if(value==guess) returnScore.create(0,3); intscnt=0; if(value%10==guess%10){ scnt+=1; } if(value/10%10==guest/10%10){ scnt+=1; } returnScore.create(0,scnt); } 20TDD 발담그기@공감세미나
  21. 21. 일부 일치하는 경우 추가 @Test publicvoidsomestrikes(){ Gamegame=newGame(479); assertMatch(game.guess(359),0,1); assertMatch(game.guess(372),0,1); assertMatch(game.guess(486),0,1); } … publicclassGame{ … publicScoreguess(intguess){ if(value==guess) returnScore.create(0,3); intscnt=0; if(value%10==guess%10){ scnt+=1; } if(value/10%10==guest/10%10){ scnt+=1; } if(value/100==guest/100){ scnt+=1; } returnScore.create(0,scnt); } 21TDD 발담그기@공감세미나
  22. 22. 경우 추가 @Test publicvoidsomestrikes(){ Gamegame=newGame(479); assertMatch(game.guess(359),0,1); assertMatch(game.guess(372),0,1); assertMatch(game.guess(486),0,1); assertMatch(game.guess(478),0,2); assertMatch(game.guess(429),0,2); assertMatch(game.guess(379),0,2); assertMatch(game.guess(489),0,2); } … publicclassGame{ … publicScoreguess(intguess){ if(value==guess)returnScore.create(0,3); intscnt=0; if(value%10==guess%10){ scnt+=1; } if(value/10%10==guest/10%10){ scnt+=1; } if(value/100==guest/100){ scnt+=1; } returnScore.create(0,scnt); } 22TDD 발담그기@공감세미나
  23. 23. 코드 정리 23TDD 발담그기@공감세미나 publicclassGame{ … publicScoreguess(intguess){ if(value==guess)returnScore.create(0,3); intscnt=0; if(value%10==guess%10){ scnt+=1; } if(value/10%10==guest/10%10){ scnt+=1; } if(value/100==guest/100){ scnt+=1; } returnScore.create(0,scnt); } publicclassGame{ … publicScoreguess(intguess){ if(value==guess)returnScore.create(0,3); intscnt=0; if(value%10==guess%10){ scnt+=1; } if(value/10%10==guest/10%10){ scnt+=1; } if(value/100==guest/100){ scnt+=1; } returnScore.create(0,scnt); }
  24. 24. 코드 정리 publicclassGame{ … publicScoreguess(intguess){ intscnt=0; if(pos(value,1)==pos(guess,1))scnt+=1; if(pos(value,2)==pos(guest,2))scnt+=1; if(pos(value,3)==pos(guest,3))scnt+=1; returnScore.create(0,scnt); } privateintpos(intv,intp){ switch(p){ case1:returnv%10; case2:returnv/10%10; case3:returnv/100; } thrownewIllegalArgumentException(); } 24TDD 발담그기@공감세미나 publicclassGame{ … publicScoreguess(intguess){ intscnt=0; if(value%10==guess%10){ scnt+=1; } if(value/10%10==guest/10%10){ scnt+=1; } if(value/100==guest/100){ scnt+=1; } returnScore.create(0,scnt); }
  25. 25. 코드 정리 @Test publicvoidnomatch(){ Gamegame=newGame(479); assertNoMatch(game.guess(123)); … } @Test publicvoidallstrikes(){ Gamegame=newGame(479); assertAllStrikes(game.guess(479)); Gamegame2=newGame(124); assertAllStrikes(game2.guess(124)); } @Test publicvoidsomestrikes(){ Gamegame=newGame(479); assertMatch(game.guess(359),0,1); … assertMatch(game.guess(478),0,2); … } publicclassGame{ … publicScoreguess(intguess){ intscnt=0; for(intvi=1;vi<=3;vi++){ if(pos(value,vi)==pos(guess,vi)) scnt++; } returnScore.create(0,scnt); } … 25TDD 발담그기@공감세미나
  26. 26. TDD 과정 테스트 작성 코드 작성 코드 정리 26TDD 발담그기@공감세미나
  27. 27. 테스트 코드 = 기능 명세 •테스트코드는무엇을만들지에초점 • 구현을먼저하지않음 •다양한경우에대한테스트를점진적으 로추가 • 다양한경우곧예제기반명세 •테스트코드를통과시키는 과정에서구 현이완성 27TDD 발담그기@공감세미나
  28. 28. @Test publicvoidnomatch(){ Gamegame=newGame(479); assertNoMatch(game.guess(123)); assertNoMatch(game.guess(568)); assertNoMatch(game.guess(321)); } @Test publicvoidallstrikes(){ Gamegame=newGame(479); assertAllStrikes(game.guess(479)); Gamegame2=newGame(124); assertAllStrikes(game2.guess(124)); } @Test publicvoidsomestrikes(){ Gamegame=newGame(479); assertMatch(game.guess(359),0,1); assertMatch(game.guess(372),0,1); assertMatch(game.guess(486),0,1); assertMatch(game.guess(478),0,2); assertMatch(game.guess(429),0,2); assertMatch(game.guess(379),0,2); assertMatch(game.guess(489),0,2); } 테스트에 경우(예)를추가하면서 점차구현을완성 28TDD 발담그기@공감세미나
  29. 29. 테스트 코드의 전형적인 구성 •세개로구성 • 상황/조건(Given) • 기능실행(When) • 결과(Then) • 검증,확인 @Test publicvoidnomatch(){ //Given:정답이479일때 Gamegame=newGame(479); //When:123을예측하면 Scores=game.guess(123); //Then:0스트라이크,0볼 assertThat(s.strikes()).isEqualTo(0); assertThat(s.balls()).isEqualTo(0); } 29TDD 발담그기@공감세미나
  30. 30. 테스트 코드 작성 예 // Given: 비정상 계좌 givenAbnormalAccount("111222333444"); // When: 비정상 계좌로 자동 이체를 신청 ApplyReq applyReq = ApplyReq.builder(). .accountNum("111222333444").userId("myuser") .socialNum("060101").build(); ApplyResult rst = autoDebitSvc.applyAutoDebit(applyReq); // Then: 자동 이체 신청에 실패해야 함 assertThat(rst.isSuccess()).isFalse(); assertThat(rst.getFailCause()) .isEqualTo(FailCause.ABNORMAL_ACCOUNT); 30TDD 발담그기@공감세미나
  31. 31. // Given: 비정상 계좌 givenAbnormalAccount("111222333444"); // When: 비정상 계좌로 자동 이체를 신청 ApplyReq applyReq = ApplyReq.builder(). .accountNum("111222333444") .userId("myuser") .socialNum("060101").build(); ApplyResult rst = autoDebitSvc.applyAutoDebit(applyReq); // Then: 자동 이체 신청에 실패해야 함 assertThat(rst.isSuccess()).isFalse(); assertThat(rst.getFailCause()) .isEqualTo(FailCause.ABNORMAL_ACCOUNT); 테스트 코드 작성  설계 고민 필요 테스트 대상의 타입 이름과 메서드 이름 고민 기능에 전달할 입력 데이터 고민 결과를 검증하려면 기능의 응답 방식 고민 계좌 비정상 여부를 어떻게 지정 31TDD 발담그기@공감세미나
  32. 32. 테스트 코드 작성  설계 고민 필요 은행에 통지한다는 걸 어떻게 확인할 수 있나 @Test publicvoid정상계좌면_신청성공_은행통지(){ //Given:정상계좌 givenNormalAccount("111222333444"); // When: 정상 계좌로 자동 이체를 신청 ApplyReq applyReq = ApplyReq.builder(). .accountNum("111222333444") .userId("myuser") .socialNum("060101").build(); ApplyResult rst = autoDebitSvc.applyAutoDebit(applyReq); //Then:은행에자동이체계좌통지해야함 bankShouldBeNotified("111222333444"); } 32TDD 발담그기@공감세미나
  33. 33. 테스트 코드와 협업 객체 도출 상황/조건에서 테스트 대상의 협업 객체 도출 가능성 검토 // Given: 비정상 계좌 givenAbnormalAccount("111222333444"); // When: 비정상 계좌로 자동 이체를 신청 ApplyReqapplyReq = ApplyReq.builder(). .accountNum("111222333444").userId("myuser") .socialNum("060101").build(); ApplyResultrst = autoDebitSvc.applyAutoDebit(applyReq); // Then: 자동 이체 신청에 실패해야 함 assertThat(rst.isSuccess()).isFalse(); assertThat(rst.getFailCause()) .isEqualTo(FailCause.ABNORMAL_ACCOUNT); 33TDD 발담그기@공감세미나
  34. 34. // Given: 비정상 계좌 givenAbnormalAccount("111222333444"); // When: 비정상 계좌로 자동 이체를 신청 ApplyReqapplyReq = ApplyReq.builder(). .accountNum("111222333444").userId("myuser") .socialNum("060101").build(); ApplyResultrst = autoDebitSvc.applyAutoDebit(applyReq); // Then: 자동 이체 신청에 실패해야 함 assertThat(rst.isSuccess()).isFalse(); assertThat(rst.getFailCause()) .isEqualTo(FailCause.ABNORMAL_ACCOUNT); 협업 객체를 위한 타입 도출 AutoDebitService AccountValidator 그 시점에 적당해 보이는 용어 사용 34TDD 발담그기@공감세미나
  35. 35. 검증/확인에서 협업 객체 도출 @Test publicvoid정상계좌면_신청성공_은행통지(){ //Given:정상계좌 givenNormalAccount("111222333444"); …생략 //Then:은행에자동이체계좌통지해야함 bankShouldBeNotified("111222333444"); } privatevoidbankShouldBeNotified(StringaccNum){ ???? } publicinterfaceBankNotifier{ voidnotify(StringaccNum); } 35TDD 발담그기@공감세미나
  36. 36. 테스트에서 협업 대상 도출 •협업객체도출설계 • 역할분리 • 테스트코드가설계유도 •필요한만큼의추상화 • 과하게추상화하는것을방지 • 추상화하지않는것도방지 36TDD 발담그기@공감세미나
  37. 37. 협업 객체는 일단 대역(double)으로 대치 당장은 테스트 통과가 우선 privateAutoDebitServiceautoDebitSvc=newAutoDebitService(); privateFakeAccountValidatorfakeValidator=newFakeAccountValidator(); @Before publicvoidsetup(){ autoDebitSvc.setAccountValidator(fakeValidator); } @Test publicvoid비정상계좌는_자동이체신청_실패(){ //Given:비정상계좌 givenAbnormalAccount("111222333444"); //When:비정상계좌로자동이체를신청 ApplyReqapplyReq=ApplyReq.builder(). .accountNum("111222333444").userId("myuser") .socialNum("060101").build(); ApplyResultrst=autoDebitSvc.applyAutoDebit(applyReq); //Then:자동이체신청에실패 assertThat(rst.isSuccess()).isFalse(); assertThat(rst.getFailCause()) .isEqualTo(FailCause.ABNORMAL_ACCOUNT); } 여기서 테스트하고 싶은 것은? 자동이체신청 기능 자체임 계좌를 검증하는 기능은 아님! 테스트 대상이 아니면 협업 객체인지 고민 37TDD 발담그기@공감세미나
  38. 38. 진짜처럼 행동하는 가짜 대역 publicinterfaceAccountValidator{ AccountTypevalidate(StringaccNum); } publicclassFakeAccountValidator implementsAccountValidator{ privateMap<String,AccountType>map= newHashMap<>(); publicvoidadd(StringaccountNum,AccountTypetype){ map.put(accountNum,type); } @Override publicAccountTypevalidate(StringaccNum){ returnmap.getOrDefault(accNum,AccountType.NONE); } } @Test publicvoid비정상계좌는_자동이체신청_실패(){ //Given:비정상계좌 givenAbnormalAccount("111222333444"); … } voidgivenAbnormalAccount(StringaccNum){ fakeValidator.add(accNum, AccountType.ABNORMAL); } 38TDD 발담그기@공감세미나
  39. 39. 가짜 대역으로 일단 테스트 통과 @Test publicvoid비정상계좌는_자동이체신청_실패(){ //Given:비정상계좌 givenAbnormalAccount("111222333444"); //When:비정상계좌로자동이체를신청 ApplyReqapplyReq=ApplyReq.builder(). .accountNum("111222333444") …생략.build(); ApplyResultrst= autoDebitSvc.applyAutoDebit(applyReq); //Then:자동이체신청에실패 assertThat(rst.isSuccess()).isFalse(); assertThat(rst.getFailCause()) .isEqualTo(FailCause.ABNORMAL_ACCOUNT); } private void givenAbnormalAccount(String accNum) { fakeValidator.add(accNum, AccountType.ABNORMAL); } publicclassAutoDebitService{ privateAccountValidatoraccValidator; publicApplyResult applyAutoDebit(ApplyReqreq){ AccountTypetype= accValidator.validate(req.getAccountNum()); if(type==AccountType.ABNORMAL){ returnApplyResult.fail( FailCause.ABNORMAL_ACCOUNT); } returnnull; } …setter } 39TDD 발담그기@공감세미나
  40. 40. 모의(Mock) 대역으로 테스트 통과 privateAutoDebitServiceautoDebitSvc=newAutoDebitService(); privateBankNotifiermockBankNotifier=mock(BankNotifier.class); …@Before에서autoDebitSvc.setBankNotifier(mockBankNofitier); @Test publicvoid정상계좌면_신청성공_은행통지(){ //Given:정상계좌 givenNormalAccount("111222333444"); …생략 //Then:은행에자동이체계좌통지해야함 bankShouldBeNotified("111222333444"); } privatevoid bankShouldBeNotified(StringaccNum){ verify(mockBankNotifier).notify(accNum); } 40TDD 발담그기@공감세미나 publicclassAutoDebitService{ privateAccountValidatoraccValidator; privateBankNotifierbankNotifier; publicApplyResult applyAutoDebit(ApplyReqreq){ AccountTypetype= accValidator.validate(req.getAccountNum()); if(type==AccountType.ABNORMAL){ returnApplyResult.fail( FailCause.ABNORMAL_ACCOUNT); } bankNotifier.notify(req.getAccountNum()); returnApplyResult.success(); } …setter }
  41. 41. 대역의 이점 •협업객체의실제구현없이 테스트대상구현,검증가능 • 가짜,모의,메모리구현사용 •다양한상황을쉽게구성 •테스트속도가빨라짐 41TDD 발담그기@공감세미나
  42. 42. 테스트 범위 확대 •작은범위를테스트하는 단위테스트만 으로는부족 •각구성요소를통합한테스트필요 • DB,외부라이브러리,API연동등 •사용자입장에서기능이올바르게동작 하는지검증하는테스트필요 • 웹브라우저를포함한전체시스템 42TDD 발담그기@공감세미나
  43. 43. 테스트 범위 SVC Domain UI Infra DB 외부 브라우저 43TDD 발담그기@공감세미나
  44. 44. 테스트 코드의 효과 •자신감 • 회귀(regression) 테스트 •개발속도 • 대역으로격리개발가능 • 다양한경우를위한테스트로디버깅유리 •좋은설계가능성 • 테스트가능한구조를만드는시도 알맞은역할분리 44TDD 발담그기@공감세미나
  45. 45. TDD 시작 •쉬운경우부터작성 • 예,야구게임에서볼보다스트라이크판 단이쉬움 •예외적인경우부터작성 • 예,야구게임에서입력한숫자가3자리가 아닌경우익셉션처리하기 •어렵거나정상적인경우부터시작하면 전진을못하고중간에막히는상황발생 45TDD 발담그기@공감세미나
  46. 46. TDD 시작 •작은기능으로TDD감잡아보기 •테스트코드로협업객체도출하기 • 대역으로테스트상황(given) 구성시도 • 대역으로테스트검증/확인(then) 시도 •서비스나컨트롤러(UI)를 대상으로 통합테스트만들어보기 • 남이만든코드복붙해서빨리해보기 46TDD 발담그기@공감세미나
  47. 47. 주의 사항 • 방해꾼:"이런걸왜해?빨리개발이나해!" • 귓등으로듣고,계속시도할것 • 불안심리:"이거하고있을시간이…" • 결국개발자는테스트를해야함 • 실제로는테스트코드덕에개발시간을줄일 수있음 • 혼자서잘못하고TDD 탓하기: "TDD했더니되려개발을못하는것같아" • 잘못된자세로운동해서몸아픈것과같음 • 좋은책,좋은개발자가시키는대로하기 47TDD 발담그기@공감세미나
  48. 48. TDD 수련은 운동과 같음 •처음시작은어색하고힘듦 •올바른자세로꾸준히해야 몸에붙고코드가건강해짐 •5일하고360일쉬고그러면안됨 48TDD 발담그기@공감세미나
  49. 49. 시작을 위한 추천 도서 TDD 발담그기@공감세미나 49 테스트 주도 개발 Effective Unit Testing
  50. 50. 끝 최범균 | madvirus@madvirus.net | http://javacan.tistory.com 50TDD 발담그기@공감세미나

×