Upload
beom-kyun-choi
View
5.039
Download
6
Embed Size (px)
DESCRIPTION
okjsp 13주년 행사에서 발표한 자료입니다.
Citation preview
생존 프로그래밍:TEST OKJSP 13주년 컨퍼런스, 2013-12-07
최범균([email protected])
목표
● 목표임○ 테스트 코드를 만들지 않는 프로그래머들에게, 어쩐지 테스트 코드가 개발 시간을 줄여줄 것 만 같은 “느낌”을 주는 것
● 목표 아님○ JUnit 설명하기○ 기타 프레임워크 설명하기○ TDD 설명하기
● 사람이 하는 TEST● 테스트 시간을 단축하려면● 기계가 하는 테스트
○ 스프링 통합 테스트○ 한 클래스 / Mock
● 주의 사항○ 테스트 환경 맞추기
● 정리
내용
구현 과정?
Java
XML
SQL
HTML
JS
WAS
기능과 관련된 모든 코드 작성
WAS실행
웹브라우저 테스트
버그 또는에러
원인분석 및코드수정
WAS
디버깅 과정?
Java XML SQLHTML JS
<input> 태그의
name이 잘못됐나
?커맨드 객체에
값이 안 들어가나?
쿼리에서
컬럼이
빠졌나?
Ajax 호출할 때값을 안 주나?
설정이
올바른가
?
재시작안했나?
왜 DB에 데이터가 null이지?
개발 시간의 증가 원인
WAS실행
웹브라우저 테스트
버그 또는에러
원인분석 및코드수정
XMLSQLHTMLJSWASJava
한 번에 집중해서만들어야 할 게 많음!시간이 오래 걸리고,흐름 깨지기 쉬움
확인해 봐야 할 게 많음!한 곳만 잘못되지 않음!오래 걸림, 흐름 깨지기 쉬움
폼에 수동 입력 (주로 ‘1’, ‘a’ 등 입력)URL을 붙여넣기 하기도 함
JRebel 없어재시작해야 함심한 경우, 재배포
개발/디버깅/테스트 시간을 줄이려면
범위 좁히기(즉, 나누기)
한 놈씩 골라서! 관련된 것만 묶어서!
Java XMLSQL+DB
HTML JS WAS
Controller SVC DAO
DAO 잘 되나?
스프링 설정문제없나?
컨트롤러가 응답을 정상으로 만드나?
서버 기능은 정상동작하나?
다 잘 되나?
테스트 종류
Java XMLSQL+DB
HTML JS WAS
Controller SVC DAO
단위테스트단위테스트
end-to-end (통합) 테스트
Acceptance 테스트
통합 테스트
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 = "....";
@Before public void setUp() { deleteIfTempFileExists(); }
private CustomerServiceRequestCommand createCommand() { CustomerServiceRequestCommand command = new CustomerServiceRequestCommand(); command.setMenuId("C000732"); command.setCust_flag("0"); command.setReceive_num_flag("2"); ... return command; }
@Test public void shouldCreateExcelFile() { String queryId = "..."; excelDownDao.createSheetFile( querId, createCommand(), createSheetMap(), tempFilePath); assertTempFileCreated(); }
private void assertTempFileCreated() { File file = new File(tempFilePath); assertThat(file.exists(), equalTo(true)); }
…. ….}
수동 입력과눈 확인 대체
테스트 과정
테스트 및 결과 확인
에러 확인
원인 확인
테스트 과정
속도감수동 입력/서버/브라우저
필요 없음
집중
범위 좁히기: 파일 전송파일 전송 기능 구현 전
컨트롤러 테스트
실제 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(); }
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]); }
진짜 대신 가짜 쓰기
스프링 초기화 없음
DB 연동 없음
WAS/브라우저 없음
속도감
가짜를 이용한 병행 개발개발자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
이메일: [email protected]