Okjsp 13주년 발표자료: 생존 프로그래밍 Test
Upcoming SlideShare
Loading in...5
×
 

Okjsp 13주년 발표자료: 생존 프로그래밍 Test

on

  • 3,902 views

okjsp 13주년 행사에서 발표한 자료입니다.

okjsp 13주년 행사에서 발표한 자료입니다.

Statistics

Views

Total Views
3,902
Views on SlideShare
2,137
Embed Views
1,765

Actions

Likes
10
Downloads
34
Comments
0

4 Embeds 1,765

http://javacan.tistory.com 1710
http://xorb.tistory.com 46
https://twitter.com 7
http://cafe.naver.com 2

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    Okjsp 13주년 발표자료: 생존 프로그래밍 Test Okjsp 13주년 발표자료: 생존 프로그래밍 Test Presentation Transcript

    • 생존 프로그래밍:TEST OKJSP 13주년 컨퍼런스, 2013-12-07 최범균(madvirus@madvirus.net)
    • 목표 ● 목표임 ○ 테스트 코드를 만들지 않는 프로그래머들에게, 어쩐 지 테스트 코드가 개발 시간을 줄여줄 것 만 같은 “느 낌”을 주는 것 ● 목표 아님 ○ JUnit 설명하기 ○ 기타 프레임워크 설명하기 ○ TDD 설명하기
    • 내용 ● 사람이 하는 TEST ● 테스트 시간을 단축하려면 ● 기계가 하는 테스트 ○ 스프링 통합 테스트 ○ 한 클래스 / Mock ● 주의 사항 ○ 테스트 환경 맞추기 ● 정리
    • 구현 과정? Java 웹브라우저 테스트 XML SQL HTML JS WAS 기능과 관련된 모든 코드 작성 WAS 실행 버그 또는 에러 원인분석 및 코드수정
    • 디버깅 과정? 왜 DB에 데이터가 null이지? 의 태그 나 ? t> npu 잘못됐 <i 이 ame n HTML JS 커맨 값이 드 객체 에 안들 어가 나? Java Ajax 호 값을 출할 때 안주 나? 재시 작 안했 나? XML 이 설정 가? 른 올바 SQL WAS 이 럼 컬 서 에 나? 리 쿼 빠졌
    • 개발 시간의 증가 원인 폼에 수동 입력 (주로 ‘1’, ‘a’ 등 입력) URL을 붙여넣기 하기도 함 웹브라우저 테스트 Java WAS JS HTML SQL XML 한 번에 집중해서 만들어야 할 게 많음! 시간이 오래 걸리고, 흐름 깨지기 쉬움 WAS 실행 JRebel 없어 재시작해야 함 심한 경우, 재배포 버그 또는 에러 원인분석 및 코드수정 확인해 봐야 할 게 많음! 한 곳만 잘못되지 않음! 오래 걸림, 흐름 깨지기 쉬움
    • 개발/디버깅/테스트 시간을 줄이려면 범위 좁히기 (즉, 나누기)
    • 한 놈씩 골라서! 관련된 것만 묶어서! 서버 기능은 정 상동작하나? HTML JS 스프링 설정 문제없나? Java Controller SVC 컨트롤러가 응답을 정 상으로 만드나? DAO SQL +DB DAO 잘 되나? 다 잘 되나? XML WAS
    • 테스트 종류 통합 테스트 HTML JS Java Controller 단위테스트 SVC DAO SQL +DB 단위테스트 end-to-end (통합) 테스트 Acceptance 테스트 XML WAS
    • JUnit ● 테스트 프레임워크 ○ 코드로 테스트 시나리오 작성 ○ 코드로 결과 검증 ● 쉬운 반복 실행 ○ 지정한 범위의 테스트를 빠르게 실행 가능 ○ IDE는 모두 JUnit 실행 기능 기본 제공 ● 주로 다루는 범위 ○ 한 개 클래스 (단위 테스트) ○ 서버 기능 (통합 테스트) ○ 종종, end-to-end 테스트 (통합 테스트)
    • 자바 웹 서버 개발 속도에 도움 되는 것 ● 단위 테스트 ○ 한 모듈(보통 클래스 한 개)의 기능을 테스트 ○ 예, ■ 컨트롤러가 JSON을 제대로 만드나? ■ 엑셀 생성 모듈이 엑셀 파일을 제대로 만드나? ■ DAO가 데이터를 잘 읽어오나? ■ 스프링 설정이 올바른가? ● 서버측 통합 테스트 ○ 모듈을 통합해서 수행하는 기능 테스트 ○ 예, ■ 컨트롤러-서비스-DAO-DB 연동된 상태의 기능 테 스트
    • 테스트 작성 사례 ● 기능 ○ DB에서 대량의 데이터를 읽어와 서버의 로컬에 엑셀 파일을 생성한 후, 그 파일을 웹 브라우저에 내려주는 기능
    • 검색해서 iBATIS의 데이터를 순차적 으로 받아 엑셀로 만드는 코드 찾음 시작 ● 일단 막 구현 나 초짜 복사/붙여넣기 해서 코드 작성 토드에서 쿼리 돌아감 다른 것 참조해서 스프링 설정 톰캣 실행/브라우저 실행/링크 클릭 결과는…..
    • 잔뜩 코딩한 뒤 실행한 결과는
    • 테스트 방법 복사&붙여넣기 버튼 클릭넣기 반복 뭔가 코드 수정 (서버 재시작..) 여러 코드 탐색 에러 발생
    • 범위 좁히기: 에러 나는 영역 에러 유발자!
    • 에러 잡기 위한 테스트 코드 작성 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration( locations = { "classpath:spring-*.xml" }) public class BaseBigExcelDownDAOIntTest { @Autowired private BaseBigExcelDownDAO excelDownDao; private String tempFilePath = "...."; @Test public void shouldCreateExcelFile() { String queryId = "..."; excelDownDao.createSheetFile( querId, createCommand(), createSheetMap(), tempFilePath); assertTempFileCreated(); } @Before public void setUp() { deleteIfTempFileExists(); } private void assertTempFileCreated() { File file = new File(tempFilePath); assertThat(file.exists(), equalTo(true)); } private CustomerServiceRequestCommand createCommand() { CustomerServiceRequestCommand command = new CustomerServiceRequestCommand(); command.setMenuId("C000732"); command.setCust_flag("0"); command.setReceive_num_flag("2"); ... return command; } …. …. } 수동 입력과 눈 확인 대체
    • 테스트 과정 원인 확인 테스트 및 결과 확인 에러 확인
    • 테스트 과정 집중 속도감 수동 입력/서버/브라우저 필요 없음
    • 범위 좁히기: 파일 전송 파일 전송 기능 구현 전
    • 컨트롤러 테스트 실제 DB 연동/엑셀 파일 생성 필요 없음 컨트롤러는 다른 객체가 생성한 파일을 response로 전송해주면 됨 데이터를 어디서 읽어오는지는 중요하지 않음!
    • 진짜 대신 가짜 쓰기 public class CustomerServiceRequestControllerTest { private CustomerServiceRequestController controller = new CustomerServiceRequestController(); @Mock private BaseBigExcelDownDAO mockBaseBigExcelDownDAO; private MockHttpServletResponse mockResponse = new MockHttpServletResponse(); private CustomerServiceRequestCommand command = new Customer…(); private byte[] realBytes = new byte[125]; @Before public void setUp() { controller.setBaseBigExcelDownDAO(mockBaseBigExcelDownDAO); for (byte i = 0; i < realBytes.length; i++) realBytes[i] = i; doAnswer(new Answer<Void>() { public Void answer(InvocationOnMock invocation) throws Throwable { String genFilePath = (String) invocation.getArguments()[3]; FileCopyUtils.copy(realBytes, new File(genFilePath)); return null; } }).when(mockBaseBigExcelDownDAO).createSheetFile(anyString(), any(), any(LinkedHashMap.class), anyString()); } 가짜 쓰도록 설정 가짜에서 컨트롤러가 사용할 파일 생성
    • 진짜 대신 가짜 쓰기 public class CustomerServiceRequestControllerTest { private CustomerServiceRequestController controller = new CustomerServiceRequestController(); @Mock private BaseBigExcelDownDAO mockBaseBigExcelDownDAO; private MockHttpServletResponse mockResponse = new MockHttpServletResponse(); private CustomerServiceRequestCommand command = new Customer…(); private byte[] realBytes = new byte[125]; @Before public void setUp() { controller.setBaseBigExcelDownDAO(mockBaseBigExcelDownDAO); … } 가짜 사용 @Test public void should_create_filepath_in_random_and_send_filedata_to_client() { controller.customerServiceRequestExcel(mockResponse, command); assertSendDataBytes(); } private void assertSendDataBytes() { byte[] sendedFileBytes = mockResponse.getContentAsByteArray(); assertEquals(realBytes.length, sendedFileBytes.length); for (int i = 0; i < realBytes.length; i++) assertEquals(realBytes[i], sendedFileBytes[i]); } 컨트롤러가 전송한 파일 데이터 확인
    • 진짜 대신 가짜 쓰기 public class CustomerServiceRequestControllerTest { private CustomerServiceRequestController controller = new CustomerServiceRequestController(); @Mock private BaseBigExcelDownDAO mockBaseBigExcelDownDAO; private MockHttpServletResponse mockResponse = new MockHttpServletResponse(); private CustomerServiceRequestCommand command = new Customer…(); private byte[] realBytes = new byte[125]; 스프링 초기화 없음 @Before public void setUp() { controller.setBaseBigExcelDownDAO(mockBaseBigExcelDownDAO); … } @Test public void should_create_filepath_in_random_and_send_filedata_to_client() { controller.customerServiceRequestExcel(mockResponse, command); assertSendDataBytes(); } DB 연동 없음 WAS/브라우저 없음 private void assertSendDataBytes() { byte[] sendedFileBytes = mockResponse.getContentAsByteArray(); assertEquals(realBytes.length, sendedFileBytes.length); for (int i = 0; i < realBytes.length; i++) assertEquals(realBytes[i], sendedFileBytes[i]); } 속도감
    • 가짜를 이용한 병행 개발 개발자1: 가짜를 이용해서 개발 가능 기능 검증은 단위 테스트 코드로 개발자2: 기능 구현 기능 검증은 통합 테스트 코드로
    • 마지막: 톰캣 띄우고 기능 테스트 상당한 부분의 코드를 테스트 코드로 확인한 뒤에, 거의 마지막에 기능 테스트 수행 이 시점에서 오류는 대부분 UI 관련된 것 서버쪽은 이미 테스트가 거의 끝남
    • 사실은, 사용자 기능도 코드로 테스트 ● 테스트 코드가 웹 브라우저를 실행해서 입력/ 검증 수행 ● 관련 프레임워크 ○ ○ ○ ○ ○ ○ Cucumber FitNess Selenium Watir Geb …
    • 테스트 작성 시 주의 사항 ● 동일 조건에서 테스트가 동작하도록 ○ 즉, 테스트 코드를 반복해서 실행해도 문제 없도록 ● 예, ○ 파일 생성 테스트면, 기능 실행 전에 기존에 생성된 파일 삭제 처리 ○ DB 조회 테스트면, 동일한 데이터 집합을 갖도록 처 리
    • 정리 ● 테스트 코드와 개발 시간 단축 ○ 사람 대신 기계가 테스트를 수행해서, 실행 시간 단축 효과 ○ 개발 범위를 좁혀주어, 구현 시간 단축 효과 ○ 병행 개발 가능, 대기 시간 단축 효과 ● 코딩+테스트+디버깅 시간 ○ 범위 나눠 테스트 <<< 모든 코드 구현 후 테스트 ■ 모든 코드 구현: 코딩+테스트+디버깅+큰뻘짓 ■ 범위 나눠 구현: 코딩+테스트+디버깅+작은뻘짓 ● 회귀(regression) 테스트로도 사용 ● 좋은 설계도 원하면: TDD 시도 필요
    • 참고 ● 테스트 관련 ○ JUnit ■ 테스트 프레임워크 ○ 스프링 MVC 테스트 ■ 스프링 컨트롤러에 대한 테스트 진행 ○ DBUnit ■ DB 데이터 초기화/확인 기능 제공 ■ spring-test-dbunit (편리성 추가) ○ Mockito 등 ■ 가짜 객체 생성 및 호출 여부 검증 기능 제공
    • 참고: Spring MVC 응답 테스트 예시 public class PaymentSyncSourceListControllerTest { private MockMvc mockMvc; private PaymentSyncSourceListController controller = new PaymentSyncSourceListController(); @Before public void setUp() { mockMvc = MockMvcBuilders.standaloneSetup(controller).build(); } @Test public void jsonList() throws Exception { mockMvc.perform(get("/sync/syncsources.json").param("offset", "0").param("size", "10")) .andExpect(status().isOk()) .andExpect(content().contentType(createJsonMediaType())) .andExpect(jsonPath("$", hasSize(2))) .andExpect(jsonPath("$[0].id", is(2))) .andExpect(jsonPath("$[0].totalAmount", is(50000))) ... .andExpect(jsonPath("$[0].orderLines", hasSize(1))) .andExpect(jsonPath("$[0].orderLines[0].id", is(2))) ... .andExpect(jsonPath("$[1].deliveryCharge", is(2500))); }
    • 맺음말 일단 한 번 테스트 코드 만들기!
    • Q&A, 의견 트위터: @madvirus 이메일: madvirus@madvirus.net