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

536 visualizaciones

Publicado el

Test Driven Development

Publicado en: Software
  • Yes you are right. There are many research paper writing services available now. But almost services are fake and illegal. Only a genuine service will treat their customer with quality research papers. ⇒ www.HelpWriting.net ⇐
       Responder 
    ¿Estás seguro?    No
    Tu mensaje aparecerá aquí
  • Hi there! I just wanted to share a list of sites that helped me a lot during my studies: .................................................................................................................................... www.EssayWrite.best - Write an essay .................................................................................................................................... www.LitReview.xyz - Summary of books .................................................................................................................................... www.Coursework.best - Online coursework .................................................................................................................................... www.Dissertations.me - proquest dissertations .................................................................................................................................... www.ReMovie.club - Movies reviews .................................................................................................................................... www.WebSlides.vip - Best powerpoint presentations .................................................................................................................................... www.WritePaper.info - Write a research paper .................................................................................................................................... www.EddyHelp.com - Homework help online .................................................................................................................................... www.MyResumeHelp.net - Professional resume writing service .................................................................................................................................. www.HelpWriting.net - Help with writing any papers ......................................................................................................................................... Save so as not to lose
       Responder 
    ¿Estás seguro?    No
    Tu mensaje aparecerá aquí

TDD

  1. 1. TDD Kihoon Kim
  2. 2. 김기훈 Samsung SDS. ACT group # 역할 developer # 관심사 spring, node.js, react, android, msa, agile # email koreakihoon@gmail.com # blog https://kihoonkim.github.io
  3. 3. 소프트웨어의 모든 것은 변한다. 요구사항은 변한다. 설계도 변한다. 비즈니스도 변한다. 기술도 변한다. 팀도 변한다. 팀 구성원도 변한다. 변화는 반드시 일어나기 때문에, 문제가 되는 것은 변화가 아니다. 그보다는 변화를 극복하지 못하는 우리의 무능력이 문제다. - 익스트림 프로그래밍 -
  4. 4. 패키지 여행 vs 자유 여행
  5. 5. 어떻게 개발 하고 있나요?
  6. 6. 왜 TDD를 하려고 하나요?
  7. 7. 지금 그대로에 TDD 만 추가하시나요?
  8. 8. 일단, Test가 뭐하는 거죠?
  9. 9. 테스트 목적 원하는 기능이 동작하는 가? 결함은 없는가? 사용성은 좋은가? ...
  10. 10. Test = checking + exploring 자동화 할 수 있어요 https://www.thoughtworks.com/insights/blog/qa-dead
  11. 11. 테스트 종류와 범위 DB manual test exploratory test …. Test Pyramid (https://martinfowler.com/bliki/TestPyramid.html) UI unit service UI, e2e
  12. 12. 테스트를 자동화 하려면 Test Code 가 필요해요
  13. 13. 그럼 TDD 하면 되겠네!?
  14. 14. TDD? Test Driven Development? 그게 뭐에요?
  15. 15. test the program before you write it
  16. 16. The three rules of TDD : uncle bob 1. You are not allowed to write any production code unless it is to make a failing unit test pass. 실패하는 테스트를 작성하기 전에는 제품 코드를 작성하지 않는다. 2. You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures. 실패하는 테스트 코드를 한번에 하나 이상 작성하지 않는다. (컴파일 에러도 실패다) 3. You are not allowed to write any more production code than is sufficient to pass the one failing unit test. 현재 실패한 테스트 코드를 성공시키는데 충분한 정도로만 제품 코드를 작성한다.
  17. 17. Design Coding Test Design Test Coding 이렇게 하던 걸 이렇게 하라는 말이죠 Refactoring Refactoring
  18. 18. TDD의 시작은 화이트보드에서..
  19. 19. 테스트 코드를 먼저 작성 한다는 것은 무엇을 테스트 할지 결정해야 한다는 것 프로그래밍 목적을 명확히 해야 한다는 것
  20. 20. 하지만, TDD하면 생산성이 떨어지지 않나요?.
  21. 21. 사실 테스트 코드 만드는 시간보다 불필요한 기능 만드는데 쓰는 시간이 훨씬 더 많아요.. 그걸 먼저 줄이고.. 말씀...읍..읍..
  22. 22. 결함은 일찍 찾을 수록 고치는 비용이 적게 든다 Time Cost
  23. 23. 생산성은 언제 떨어지나? https://builttoadapt.io/why-tdd-489fdcdda05e Time Velocity 기능이 많아 졌을 때 변경이 발생 할 때 결함이 발생 했을 때 새로운 사람이 들어왔을때 개발자가 휴가 갔을 때... Bad Code가 생기기 시작할때!!
  24. 24. 일정한 속도를 유지하려면? Time Velocity Clean Code!!
  25. 25. Code를 Clean하게 유지하는 방법?
  26. 26. Refactoring!! '결과의 변경 없이 코드의 구조를 재조정함'을 뜻한다. 주로 가독성을 높이고 유지보수를 편하게 한다. 버그를 없애거나 새로운 기능을 추가하는 행위는 아니다. 사용자가 보는 외부 화면은 그대로 두면서 내부 논리나 구조를 바꾸고 개선하는 유지보수 행위이다. - 위키피디아
  27. 27. 리팩토링 - CHAPTER 03 코드의 구린내 중복 코드 (Duplicated Code) 장황한 메서드 (Long Method) 방대한 클래스 (Large Class) 과다한 매개변수 (Long Parameter List) 수정의 산발 (Divergent Change) : 한 클래스가 여러 원인으로 수정 기능의 산재 (Shotgun Surgery) : 한 기능 수정시 여러 클래스 변경 잘못된 소속 (Feature Envy) 데이터 뭉치 (Data Clumps) 강박적 기본 타입 사용 (Primitive Obsession) switch 문 (Switch Statements) 평행 상속 계층 (Parallel Inheritance Hierarchies) 직무유기 클래스 (Lazy Class) 막연한 범용 코드 (Speculative Generality) 임시 필드 (Temporary Field) 메시지 체인 (Message Chains) 과잉 중개 메서드 (Middle Man) 지나친 관여 (Inappropriate Intimacy) 인터페이스가 다른 대용 클래스 (Alternative Classes with Different Interfaces) 미흡한 라이브러리 클래스 (Incomplete Library Class) 데이터 클래스 (Data Class) 방치된 상속물 (Refused Bequest) 불필요한 주석 (Comments)
  28. 28. (테스트 짤 시간이 없어요 라고 말하는 사람들께..) 테스트가 중요하다고 해도 클린코드를 유지하는 방법은 결국 리팩토링을 하는 것 입니다. (주기적으로 리팩토링이라도 하세요.)
  29. 29. 근데 테스트 코드없이 리팩토링 할 수 있나요?
  30. 30. Test code 가 있다면? 자신감 있게 리팩토링! 빠르게 회귀 테스트! 내가 짠 코드를 확신할 수 있다. $ ./gradlew test $ git push 다른 팀원이 짠 코드도 믿을 수있다. $ git pull $ ./gradlew test
  31. 31. Test Driven == Test First ??
  32. 32. 코드와 테스트들은 어떤 순서로 작성해도 상관없다. XP에서는 가능하다면 테스트를 구현보다 먼저 작성한다. 테스트를 먼저 작성하는 것에는 여러가지 좋은 점이 있다.
  33. 33. eXtreme Programming
  34. 34. “운전은 차를 똑바른 방향으로 가도록 맞추어 놓고 그대로 두는 게 아니야. 운전은 계속 신경을 쓰면서 이번에는 이쪽으로 조금, 다음에는 저쪽으로 조금씩 방향을 고치면서 가는 거지.” XP 패러다임: 깨어 있고 적응하며 변하는 것
  35. 35. XP 비즈니스 상의 요구가 시시각각 변동이 심한 경우에 적합한 개발 방법 - 가치 의사소통, 단순성, 피드백, 용기, 존중
  36. 36. TDD를 한다는 것은 단순히 테스트 코드를 먼저 작성하는 것이 아니라 Refactoring을 통해 Clean Code를 유지하려고 노력하는 것 Simple design으로 시작해서 점진적으로 설계 개발하겠다는 것 작은 기능이라도 사용자에게 가치있는 재품을 자주 전달하겠다는 것
  37. 37. 결국 얻고자 하는건 좋은 설계, 좋은 제품
  38. 38. 가장 나쁜 코드 1 대부분 비즈니스 로직이 데이터 모델에 존재하고 SQL로 구현된 코드 컬럼 변경시 영향받는 것 내가 짠 sql, mapper, vo, service….. 다른팀이 참조하는 sql, mapper, vo, service 변경 할 수가 없다
  39. 39. 가장 나쁜 코드 2 if else if if else if if else if else if else if if else if else else if else else if else if else if if ... Super Giant Huge Class 기능을 추가하거나 변경하기 어려움.. 기존 로직에 영향이 없는지 테스트 하기도 어려움..
  40. 40. 객체 지향 프로그래밍
  41. 41. 적절한 책임과 역할을 객체에게 부여하고 객체들 간의 협력 관계를 만들어 나가는 것이 중요 객체는 다른 객체에게 메시지를 전달 함으로써 다른 객체들과 협력을 함 (추상화, 캡슐화, 정보은닉) 역할 책임 협력 message message
  42. 42. 자율적인 객체 객체는 메시지를 수신했을 때만 자신의 책임을 수행하게 된다. 다른 객체는 메시지를 전달하며 협력 할 뿐, 그 객체가 내부적으로 어떻게 동작하는지 관심없다. 즉, 객체 내부의 구현이 변경되더라도 메시지가 변하지 않는다면 다른 객체를 변경하지 않아도 된다. 비즈니스 요구사항의 세부 구현을 객체 내부로 숨김으로써 변경에 대응할 수 있게 된다.
  43. 43. 예를 들면.. 집을 지으려면 나무가 필요한데.. 나무를 잘라서 갖다 줬으면 좋겠어. 나무를 자르는 책임을 목수라는 역할에게 부여하고 목수에서 요청하자. 톱으로 자르든지 도끼로 자르든지 그건 목수가 알아서..
  44. 44. class 목수 { // 연장 private 톱 saw; private 망치 hammer; private 못 nail; private 드라이버 driver; … } 연장 생성에 대한 책임이 목수가 가짐 연장이 추가/제거 될 때마다 목수 객체가 변경됨 원하는 연장이 없을때(null)일때 어떻게 할지도 목수가 모두 판단해야 됨 목수 톱 망치 못
  45. 45. class 목수 { private 공구상자 toolbox; } class 공구상자 { private 톱 saw; private 망치 hammer; private 못 nail; private 드라이버 driver; … } 공구상자 톱 망치 못 목수
  46. 46. 객체는 어디에서 오는가? 분해: 큰 객체를 협력 객체들로 나누기 파생: 신규 기능을 담당하는 새로운 객체 생성 포장: 관련된 여러 객체를 하나의 객체로 만들기
  47. 47. TDD를 하면 설계에 어떻게 영향을 줄까요?
  48. 48. 무엇을 개발할지 고민 - 추상화 단위 테스트 작게 유지 - 객체 추출 객체 의존성 전달 - 컨텍스트 독립성 ...
  49. 49. Interface로 관계 식별 역할에 이름을 붙이고 메시지를 기술 자연스럽게 인터페이스 분리의 원칙 객체지향의 목표 중 하나는 객체의 경계를 명확하게 보이게 하는 것
  50. 50. 무엇을 테스트 할까요? Interface 에 대해서 테스트 코드 작성 - User Interface - Input : input text, click button… - output : show toast/dialog, launch Activity.. - API (Application Programming Interface) - return_value method_name (input_value) - void method_name() ← ex) Input: message queue, Output: Rest Call
  51. 51. Mock 객체
  52. 52. Mocking 해야 하는 것 협력하는 객체들 @Service public class NotificationService { private final AdminApi adminApi; @Autowired public NotificationService(AdminApi adminApi) { this.adminApi = adminApi; } } Notification Service Admin Api test target mocking
  53. 53. Mocking 하지 말아야 하는 것 변경할 수 없는 객체. 프레임워크, 외부라이브러리 객체들: → 실제 어떻게 동작 하는지 알 수 없다. 테스트 코드에 복잡성이 더해진다. Value Object 불변 객체: → 그냥 인스턴스를 생성해서 쓰면된다.
  54. 54. 클린코드와 테스트하기 좋은 코드는 다를 수 있다
  55. 55. 외부의 도움 없이 객체를 생성, 테스트 할 수 있어야 한다. field injection → constructor injection 컨텍스트 독립성 @Service class MyService { @Autowired private OtherService other; } @Service class MyService { private final OtherService other; @Autowired public MyService(OtherService other) { this.other = other; } } @RunWith(SpringRunner.class) @SpringBootTest class MyServiceTest { @MockBean private OtherService other; @Autowired private MyService subject; } @RunWith(MockitoJunitRunner.class) class MyServiceTest { @Mock OtherServie other; private MyService subject; @Before public setup() { subject = new MyService(other); } }
  56. 56. VisibleForTesting private field, method 를 테스트 하고 싶다. 1. public method에서 커버되는 경우 테스트 하지 않는다. 2. Refactoring 이 필요한 시점이 아닌지 고민해 본다. 해당 객체가 불필요한 책임을 가지고 있는 경우일 수 있다. 3. private 접근 제한을 public으로 바꾼다. @VisibleForTesting mark annotation 을 달아 준다.
  57. 57. 숨겨져 있는 의존성
  58. 58. Date Utils obj.setTommorow( LocalDate.now().plusDays(1) ); obj.setDate( new Date() ); public Date() { this(System.currentTimeMillis()); } public static LocalDate now() { return now(Clock.systemDefaultZone()); } 시간이 테스트 실행 할 때 마다 바뀌는데 어떻게 테스트 하지?
  59. 59. 외부 라이브러리 public void handleInitializedTasks() { final List<ReleaseTask> tasks = findInitializedTasks(); TransactionSynchronizationManager.registerSynchronization( new TransactionSynchronizationAdapter() { @Override public void afterCommit() { tasks.forEach(releaseExecutor::handleInitializedTask); } } ); } 충분히 clean code 하지만, test code 에서 afterCommit 을 어떻게 호출하게 하지? spring framework 영역인데.. 업무 로직
  60. 60. 기능과 정책이 섞여 있음
  61. 61. Async, Thread @Service public class AsyncService { @Autowired private OtherService otherService; public void callAsync() { CompletableFuture.runAsync(() -> { // 정책 Thread.sleep(20); otherService.call("hello"); // 비즈니스 로직 }); otherService.call2("hello2"); // 비즈니스 로직 } } 만약 thread 생성에 시간이 걸려 테스트 실행 시 call2() 만 호출되고 끝나버린 경우, 실패 원인이 기능 문제 인지, 쓰레드 처리 문제인지 확신하기 어렵다. 기능과 동기화 정책의 관심사를 분리하는 것이 좋다. @RunWith(MockitoJUnitRunner.class) public class AsyncServiceTest { @Mock private OtherService mockOtherService; private AsyncService subject; @Before public void setUp() throws Exception { subject = new AsyncService(mockOtherService); } @Test public void testCallAsync() throws Exception { subject.callAsync(); verify(mockOtherService).call(eq("hello")); verify(mockOtherService).call2(eq("hello2")); } }
  62. 62. 애플리케이션과 해당 애플리케이션 관점 밖에 존재하는 세계와의 관계를 규정하는 인터페이스가 만들어 지고, 저수준의 기술적인 개념이 애플리케이션 도메인 모델로 스며들지 않게 할 수 있다. Adapter Layer 애플리케이션 객체 어댑터 계층 서드 파티 API callback
  63. 63. new Date( ) @Component public class DateManager { public Date now() { return new Date(); } } ... private DateManager dateManager; @Autowired public OtherService(DateManager dateManager) { this.dateManager = dateManager; } public void method() { obj.setDate(dateManager.now()); } ... @RunWith(MockitoJUnitRunner.class) public class OtherServiceTest { private final Date FROZEN_DATE = parseStringDateTime("2018-08-29T01:01:01"); @Mock private DateManager mockDateManager; private OtherService subject; @Before public void setUp() throws Exception { subject = new OtherService(mockDateManager); when(mockDateManager.now()) .thenReturn(FROZEN_DATE); } } OtherService DateManager new Date
  64. 64. Async @Service public class AsyncService { private final OtherService otherService; private final MyAsyncRunner myAsyncRunner; @Autowired public AsyncService(OtherService otherService, MyAsyncRunner myAsyncRunner) { this.otherService = otherService; this.myAsyncRunner = myAsyncRunner; } public void callAsync() { myAsyncRunner.run(() -> { otherService.call("hello"); }); otherService.call2("hello2"); } } @Component public class MyAsyncRunner { public void run(Runnable runnable) { CompletableFuture.runAsync(runnable); } } @RunWith(MockitoJUnitRunner.class) public class AsyncServiceTest { ... @Before public void setUp() throws Exception { subject = new AsyncService(mockOtherService, mockAsyncRunner); doAnswer(invocation -> { ((Runnable)invocation.getArgument(0)).run(); return null; }).when(mockAsyncRunner).run(any()); } @Test public void testCallAsync() throws Exception { subject.callAsync(); verify(mockOtherService).call(eq("hello")); verify(mockOtherService).call2(eq("hello2")); } }@Async, CompletableFuture, Thread 든 비동기 정책을 분리한다
  65. 65. 테스트하기 어려운 코드가 있다면 어떻게하면 테스트 할 수 있을까 보다는 왜 테스트 하기 어려울까를 고민
  66. 66. 코드 스멜 테스트 스멜 비대한 생성자 여러기능이 같이 너무 많은 Mock Stub 들.. 너무 많은 assert 구문들.. → 무엇을 테스트 하려는지 파악하기 어렵다
  67. 67. 테스트 가독성
  68. 68. 테스트 코드는.. 대상 코드의 의도를 표현해야 한다. 대상 코드가 무엇을 하는지 드러내야 한다.
  69. 69. Don't Repeat Yourself method() → testMethod() (X) 테스트 이름은 예상하는 결과 동작 등에 대해 말해줘야 한다. # template: given_when_then() 데이터가 아닌 행위를 테스트 해야한다.
  70. 70. 복잡한 테스트 데이터는 Builder를 사용하자 Schedule storedSchedule = new ScheduleBuilder() .events(new EventBuilder("weekly event") .eventId(123L) .storeId(3L) .startDateTime(dummyDateTime()) .endDateTime(dummyDateTime()) .description("event description") .recurrence(new RecurrenceBuilder(RecurrenceType.Weekly) .startDate(2017, 8, 13) .endDate(2017, 9, 16) .daysOfWeek(Monday, Wednesday, Friday) .build()) .content(new ContentBuilder() .id("123") .type(playlist) .fitMode(contain) .durationSeconds(20000) .build()) .build()) .tags(firstTags) .build();
  71. 71. 리터럴 값에 이름을 부여하자 설명이 없는 리터럴 값은 이해하기 어렵다. 그 기능을 기술하는 이름을 부여하자. public static final Chat UNUSED_CHAT = null; public static final int INVALID_ID = 123;
  72. 72. Assertion message # junit.Assert assertEquals(4, 1 + 2); // expected, actual java.lang.AssertionError: Expected :4 Actual :3 String obj = null; assertNotNull(obj); java.lang.AssertionError: # assertj; assertThat(1+2).isEqualTo(4); org.junit.ComparisonFailure: Expected :4 Actual :3 String obj = null; assertThat(obj).isEmpty(); java.lang.AssertionError: Expecting actual not to be null
  73. 73. Test code든 Production code든 읽기 좋은 코드가 좋은 코드다.
  74. 74. Backend Test <spring boot> https://www.baeldung.com/spring-boot-testing
  75. 75. junit4 https://junit.org/junit4/ @RunWith( {{ test runner }} ) @BeforeClass @Before @Test @After @AfterClass
  76. 76. mockito http://static.javadoc.io/org.mockito/mockito-core/2.21.0/org/mockito/Mockito.html mock() spy() when().thenReturn() verify() assertXxxxx() > BDD style given(dog.bark()).willReturn(2); // when ... then(person).should(times(2)).ride(bike);
  77. 77. AssertJ http://joel-costigliola.github.io/assertj/ assertThat().isEqualTo() assertThat().isNotEqualTo() assertThat().isEmpty() assertThat().isNotEmpty() assertThat().contains() assertThat().doesNotContain() assertThat().hasSize() assertThat().isGreaterThan() assertThat().isLessThan() ...
  78. 78. Controller test - TestRestTemplate @RestController @RequestMapping(value = "/users", produces = MediaType.APPLICATION_JSON_VALUE) public class UserController { private final UserService userService; @Autowired public UserController(UserService userService) { this.userService = userService; } @GetMapping public List<User> findAllUsers() { return userService.findAllUsers(); } } @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) public class UserControllerTest { @Autowired private TestRestTemplate client; @MockBean(name = "userService") private UserService mockUserService; @Test public void findAllUsers() { given(mockUserService.findAllUsers()) .willReturn(asList(new User("kihoon"))); ParameterizedTypeReference<List<User>> type = new ParameterizedTypeReference<List<User>>() {}; ResponseEntity<List<User>> response = client.exchange("/users", HttpMethod.GET, new HttpEntity<>(null, null), type); List<User> users = response.getBody(); assertThat(users).hasSize(1); } } mocking stub request rest api assert target
  79. 79. Controller test - WebMvc @RestController @RequestMapping(value = "/users", produces = MediaType.APPLICATION_JSON_VALUE) public class UserController { private final UserService userService; @Autowired public UserController(UserService userService) { this.userService = userService; } @GetMapping public List<User> findAllUsers() { return userService.findAllUsers(); } } @RunWith(SpringRunner.class) @WebMvcTest(UserController.class) public class UserControllerTest { @Autowired private MockMvc mvc; @MockBean(name = "userService") private UserService mockUserService; @Test public void givenUsers_whenFindAllUsers_thenReturnJsonArray() throws Exception { given(mockUserService.findAllUsers()) .willReturn(asList(new User("kihoon"))); mvc.perform( get("/users") .contentType(MediaType.APPLICATION_JSON_VALUE) ) .andExpect(status().isOk()) .andExpect(jsonPath("$", hasSize(1))) .andExpect(jsonPath("$[0].name", is("kihoon"))); } } target
  80. 80. Service test - verify @Service @Transactional public class UserService { private final UserRepository userRepository; @Autowired public UserService(UserRepository userRepository) { this.userRepository = userRepository; } public List<User> findAllUsers() { return userRepository.findAll(); } } @RunWith(MockitoJUnitRunner.class) public class UserServiceTest { @Mock private UserRepository mockUserRepository; private UserService subject; @Before public void setUp() throws Exception { subject = new UserService(mockUserRepository); } @Test public void testFindAllUser() { subject.findAllUsers(); verify(mockUserRepository).findAll(); } } target spring을 띄우지 않아 테스트 실행이 빠르다 mocking assert
  81. 81. Service test - call with @Service @Transactional public class UserService { private final UserRepository userRepository; @Autowired public UserService(UserRepository userRepository) { this.userRepository = userRepository; } public User findUserBy(Long id) { Optional<User> userOptional = userRepository.findById(id); return userOptional .orElseThrow(() -> new RuntimeException("Data Not Found")); } } @RunWith(MockitoJUnitRunner.class) public class UserServiceTest { @Mock private UserRepository mockUserRepository; private UserService subject; @Before public void setUp() throws Exception { subject = new UserService(mockUserRepository); } @Test public void givenExistUser_whenFindUserBy1_thenReturnTheUser() { when(mockUserRepository.findById(eq(1L))) .thenReturn(Optional.of(new User("kihoon"))); User user = subject.findUserBy(1L); verify(mockUserRepository).findById(eq(1L)); assertThat(user.getName()).isEqualTo("kihoon"); } } argument 를 제대로 전달 하고 있나? mocking target stub assert
  82. 82. Service test - ArgumentCaptor @Service @Transactional public class UserService { private final UserRepository userRepository; @Autowired public UserService(UserRepository userRepository) { this.userRepository = userRepository; } public void createUser(String name) { User user = new User(name); userRepository.save(user); } } @RunWith(MockitoJUnitRunner.class) public class UserServiceTest { @Mock private UserRepository mockUserRepository; @Captor private ArgumentCaptor<User> captor; private UserService subject; @Before public void setUp() throws Exception { subject = new UserService(mockUserRepository); } @Test public void createUser() { subject.createUser("kihoon"); verify(mockUserRepository).save(captor.capture()); User newUser = captor.getValue(); assertThat(newUser.getName()).isEqualTo("kihoon"); } } target captor assert
  83. 83. Service test - exception : expected @Service @Transactional public class UserService { private final UserRepository userRepository; @Autowired public UserService(UserRepository userRepository) { this.userRepository = userRepository; } public User findUserBy(Long id) { Optional<User> userOptional = userRepository.findById(id); return userOptional .orElseThrow(() -> new RuntimeException("Data Not Found")); } } @RunWith(MockitoJUnitRunner.class) public class UserServiceTest { @Mock private UserRepository mockUserRepository; private UserService subject; @Before public void setUp() throws Exception { subject = new UserService(mockUserRepository); } @Test(expected = RuntimeException.class) public void givenInExistUser_whenFindUserBy1_thenThrowException() { when(mockUserRepository.findById(eq(1L))) .thenReturn(Optional.empty()); subject.findUserBy(1L); } } mocking target stub exception test Exception class type 만 테스트 가능
  84. 84. Service test - exception : try-catch @Service @Transactional public class UserService { private final UserRepository userRepository; @Autowired public UserService(UserRepository userRepository) { this.userRepository = userRepository; } public User findUserBy(Long id) { Optional<User> userOptional = userRepository.findById(id); return userOptional .orElseThrow(() -> new RuntimeException("Data Not Found")); } } @RunWith(MockitoJUnitRunner.class) public class UserServiceTest { @Mock private UserRepository mockUserRepository; private UserService subject; @Before public void setUp() throws Exception { subject = new UserService(mockUserRepository); } @Test public void givenInExistUser_whenFindUserBy1_thenThrowException() { when(mockUserRepository.findById(eq(1L))) .thenReturn(Optional.empty()); try { subject.findUserBy(1L); } catch (RuntimeException ex) { assertThat(ex.getMessage()).isEqualTo("Data Not Found"); } } } target exception이 발생하지 않거나 catch에 걸리지 않는 다면 테스트 정상 통과 assert
  85. 85. Service test - exception : @Rule @Service @Transactional public class UserService { private final UserRepository userRepository; @Autowired public UserService(UserRepository userRepository) { this.userRepository = userRepository; } public User findUserBy(Long id) { Optional<User> userOptional = userRepository.findById(id); return userOptional .orElseThrow(() -> new RuntimeException("Data Not Found")); } } @RunWith(MockitoJUnitRunner.class) public class UserServiceTest { @Mock private UserRepository mockUserRepository; @Rule public ExpectedException thrown = ExpectedException.none(); private UserService subject; @Before public void setUp() throws Exception { subject = new UserService(mockUserRepository); } @Test public void givenInExistUser_whenFindUserBy1_thenThrowException() { when(mockUserRepository.findById(eq(1L))) .thenReturn(Optional.empty()); thrown.expect(RuntimeException.class); thrown.expectMessage("Data Not Found"); subject.findUserBy(1L); } } target assert
  86. 86. Repository test public interface UserRepository extends JpaRepository<User, Long> { } JpaRepository를 상속받는 interface를 만들어 두면, spring-data-jpa가 자동으로 CRUD를 처리해주는 구현체를 만들어 준다. 자동으로 만들어지는 소스를 굳이 개발자가 테스트를 할 필요가 있을까요?
  87. 87. Repository test - 그래도 하고 싶다면.. @RunWith(SpringRunner.class) @DataJpaTest public class UserRepositoryTest { @Autowired private UserRepository userRepository; @Before public void setUp() throws Exception { userRepository.save(new User("kihoon")); } @Test public void findAll() { List<User> users = userRepository.findAll(); assertThat(users).hasSize(1); } }
  88. 88. Frontend test <react.js>
  89. 89. test tools - jest (https://jestjs.io/) vs. - mocha (https://mochajs.org/) + chai (http://www.chaijs.com/api/) : BDD / TDD assertion library + sinon (https://sinonjs.org/releases/v6.1.5/) : test spies, stubs and mocks + istanbul (https://istanbul.js.org/) : javascript code coverage - enzyme (http://airbnb.io/enzyme/) : assert, manipulate, and traverse your React Components
  90. 90. mocha, chai, sinon describe('hooks', function() { before(function() { // runs before all tests }); after(function() { // runs after all tests }); beforeEach(function() { // runs before each test }); afterEach(function() { // runs after each test }); it('should be ..', function() { // test cases }); }); --- expect var expect = require('chai').expect , foo = 'bar'; expect(foo).to.be.a('string'); expect(foo).to.equal('bar'); --- sholud var should = require('chai').should() , foo = 'bar'; foo.should.be.a('string'); foo.should.equal('bar'); --- spy : records arguments var callback = sinon.spy(); PubSub.subscribe("msg", callback); assertTrue(callback.called); --- stub : pre-programmed behavior var callback = sinon.stub(); callback.withArgs(42).returns(1); --- mock : pre-programmed expectations var mock = sinon.mock(myAPI); mock.expects("method").once().throws(); mock.verify();
  91. 91. enzyme import { shallow } from 'enzyme'; import sinon from 'sinon'; import Foo from './Foo'; describe('<MyComponent />', () => { // tests }); it('renders three <Foo /> components', () => { const wrapper = shallow(<MyComponent />); expect(wrapper.find(Foo)).to.have.lengthOf(3); // find by component }); it('renders an `.icon-star`', () => { const wrapper = shallow(<MyComponent />); expect(wrapper.find('.icon-star')).to.have.lengthOf(1); // find by class }); it('renders children when passed in', () => { const wrapper = shallow(( <MyComponent> <div className="unique" /> </MyComponent> )); expect(wrapper.contains(<div className="unique" />)).to.equal(true); }); it('simulates click events', () => { const onButtonClick = sinon.spy(); const wrapper = shallow(<Foo onButtonClick={onButtonClick} />); wrapper.find('button').simulate('click'); expect(onButtonClick).to.have.property('callCount', 1); });
  92. 92. Q&A
  93. 93. 대부분 급하게 시도하다 쉽게 포기 합니다.
  94. 94. 테스트 라이브러리에 익숙해지고 테스트 스멜을 많이 겪어 보세요. 테스트 코드에 익숙해진 다음에 TDD도 XP도 시도해 보세요.
  95. 95. 같이 보면 좋은 책들 객체지향의 사실과 오해 : http://www.yes24.com/24/goods/18249021 테스트 주도 개발로 배우는 객체 지향 설계와 실천 : http://www.yes24.com/24/goods/9008455 리팩토링 : http://www.yes24.com/24/Goods/7951038 익스트림 프로그래밍 2판: http://www.yes24.com/24/goods/2126201
  96. 96. 감사합니다.

×