Scala, Spring-Boot, JPA를 활용한 웹 애플리케이션 개발 과정에 대해 다룬다. Spring-Boot와 JPA 조합만으로도 생산성 있는 웹 애플리케이션 개발이 가능하다. 이 조합만으로도 충분히 의미가 있지만 여기에 Scala라는 약간은 불편한 듯 보이는 언어를 도입함으로써 얻을 수 있는 즐거움을 공유한다. Spring-Boot + JPA 조합에 Scala를 적용하면서의 좌충우돌 경험담을 전한다.
UNIST 프로그래밍 동아리 HeXA (http://www.hexa.pro) 내부 React 강의 프레젠테이션 자료의 공개버전
내용
- HTML, Javascript 속성 튜토리얼
- React 소개
- Node.js 설치
https://github.com/kimxogus/react-tutorial
Scala, Spring-Boot, JPA를 활용한 웹 애플리케이션 개발 과정에 대해 다룬다. Spring-Boot와 JPA 조합만으로도 생산성 있는 웹 애플리케이션 개발이 가능하다. 이 조합만으로도 충분히 의미가 있지만 여기에 Scala라는 약간은 불편한 듯 보이는 언어를 도입함으로써 얻을 수 있는 즐거움을 공유한다. Spring-Boot + JPA 조합에 Scala를 적용하면서의 좌충우돌 경험담을 전한다.
UNIST 프로그래밍 동아리 HeXA (http://www.hexa.pro) 내부 React 강의 프레젠테이션 자료의 공개버전
내용
- HTML, Javascript 속성 튜토리얼
- React 소개
- Node.js 설치
https://github.com/kimxogus/react-tutorial
개발은 혼자 할 수 있을까? 혹은 개발자들끼리 할 수 있을까? 저는 아니라고 생각합니다. 개발은 개발에 관여된 모든 부서와 종사자들이 함께하는 겁니다. 개발자가 어떻게 하냐에 따라 SE와 QA 그리고 심지어 Sales 까지 하나의 팀으로 공동의 목표를 쫓아 시너지를 낼 수 있습니다. 저는 그렇게 믿습니다.
TDD 테스트 주도 개발이며, 하나의 개발 방법론 입니다.
- TDD는 반복 테스트을 이용한 소프트웨어 개발법이다. 작은 단위의 테스트 케이스를 작성하고 이를 통과하는 코드를 추가하는 단계를 반복하여 소프트웨어를 구현한다.
- TDD의 목표는 작동하는 깔끔한 코드 “Clean code that works”
- TDD는 아래 단계의 반복으로 진행된다.
빨강 : 실패하는 작은 테스트 케이스를 작성한다. 처음에는 컴파일조차 안될 수 있다.
초록 : 테스트를 통과하는 코드를 작성한다.
리펙터링 : 테스트를 통과하기 위해 만든 코드의 모든 중복을 제거하고, 불명확한 것을 명확히 한다.
이러한 단계로 인해 TDD는 “업무 코드 작성 전에 테스트 코드를 먼저 만드는 것”으로 정의되기도 한다
5. 디버깅 과정?
왜 DB에 데이터가 null이지?
의
태그 나 ?
t>
npu 잘못됐
<i
이
ame
n
HTML
JS
커맨
값이 드 객체
에
안들
어가
나?
Java
Ajax
호
값을 출할 때
안주
나?
재시
작
안했
나?
XML
이
설정 가?
른
올바
SQL
WAS
이
럼
컬
서
에 나?
리
쿼 빠졌
6. 개발 시간의 증가 원인
폼에 수동 입력 (주로 ‘1’, ‘a’ 등 입력)
URL을 붙여넣기 하기도 함
웹브라우저
테스트
Java
WAS
JS
HTML
SQL
XML
한 번에 집중해서
만들어야 할 게 많음!
시간이 오래 걸리고,
흐름 깨지기 쉬움
WAS
실행
JRebel 없어
재시작해야 함
심한 경우, 재배포
버그 또는
에러
원인분석 및
코드수정
확인해 봐야 할 게 많음!
한 곳만 잘못되지 않음!
오래 걸림, 흐름 깨지기 쉬움
10. JUnit
● 테스트 프레임워크
○ 코드로 테스트 시나리오 작성
○ 코드로 결과 검증
● 쉬운 반복 실행
○ 지정한 범위의 테스트를 빠르게 실행 가능
○ IDE는 모두 JUnit 실행 기능 기본 제공
● 주로 다루는 범위
○ 한 개 클래스 (단위 테스트)
○ 서버 기능 (통합 테스트)
○ 종종, end-to-end 테스트 (통합 테스트)
11. 자바 웹 서버 개발 속도에 도움 되는 것
● 단위 테스트
○ 한 모듈(보통 클래스 한 개)의 기능을 테스트
○ 예,
■ 컨트롤러가 JSON을 제대로 만드나?
■ 엑셀 생성 모듈이 엑셀 파일을 제대로 만드나?
■ DAO가 데이터를 잘 읽어오나?
■ 스프링 설정이 올바른가?
● 서버측 통합 테스트
○ 모듈을 통합해서 수행하는 기능 테스트
○ 예,
■ 컨트롤러-서비스-DAO-DB 연동된 상태의 기능 테
스트
12. 테스트 작성 사례
● 기능
○ DB에서 대량의 데이터를 읽어와 서버의 로컬에 엑셀
파일을 생성한 후, 그 파일을 웹 브라우저에 내려주는
기능
13. 검색해서 iBATIS의 데이터를 순차적
으로 받아 엑셀로 만드는 코드 찾음
시작
● 일단 막 구현
나 초짜
복사/붙여넣기 해서 코드 작성
토드에서 쿼리 돌아감
다른 것 참조해서 스프링 설정
톰캣 실행/브라우저 실행/링크 클릭
결과는…..
21. 컨트롤러 테스트
실제 DB 연동/엑셀 파일 생성 필요 없음
컨트롤러는 다른 객체가 생성한
파일을 response로 전송해주면 됨
데이터를 어디서 읽어오는지는
중요하지 않음!
22. 진짜 대신 가짜 쓰기
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());
}
가짜 쓰도록
설정
가짜에서
컨트롤러가
사용할
파일 생성
23. 진짜 대신 가짜 쓰기
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]);
}
컨트롤러가 전송한
파일 데이터 확인
24. 진짜 대신 가짜 쓰기
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]);
}
속도감
25. 가짜를 이용한 병행 개발
개발자1: 가짜를 이용해서 개발 가능
기능 검증은 단위 테스트 코드로
개발자2: 기능 구현
기능 검증은 통합 테스트 코드로
26. 마지막: 톰캣 띄우고 기능 테스트
상당한 부분의 코드를 테스트 코드로 확인한 뒤에,
거의 마지막에 기능 테스트 수행
이 시점에서 오류는 대부분 UI 관련된 것
서버쪽은 이미 테스트가 거의 끝남
27. 사실은, 사용자 기능도 코드로 테스트
● 테스트 코드가 웹 브라우저를 실행해서 입력/
검증 수행
● 관련 프레임워크
○
○
○
○
○
○
Cucumber
FitNess
Selenium
Watir
Geb
…
28. 테스트 작성 시 주의 사항
● 동일 조건에서 테스트가 동작하도록
○ 즉, 테스트 코드를 반복해서 실행해도 문제 없도록
● 예,
○ 파일 생성 테스트면, 기능 실행 전에 기존에 생성된
파일 삭제 처리
○ DB 조회 테스트면, 동일한 데이터 집합을 갖도록 처
리
29. 정리
● 테스트 코드와 개발 시간 단축
○ 사람 대신 기계가 테스트를 수행해서, 실행 시간 단축
효과
○ 개발 범위를 좁혀주어, 구현 시간 단축 효과
○ 병행 개발 가능, 대기 시간 단축 효과
● 코딩+테스트+디버깅 시간
○ 범위 나눠 테스트 <<< 모든 코드 구현 후 테스트
■ 모든 코드 구현: 코딩+테스트+디버깅+큰뻘짓
■ 범위 나눠 구현: 코딩+테스트+디버깅+작은뻘짓
● 회귀(regression) 테스트로도 사용
● 좋은 설계도 원하면: TDD 시도 필요
30. 참고
● 테스트 관련
○ JUnit
■ 테스트 프레임워크
○ 스프링 MVC 테스트
■ 스프링 컨트롤러에 대한 테스트 진행
○ DBUnit
■ DB 데이터 초기화/확인 기능 제공
■ spring-test-dbunit (편리성 추가)
○ Mockito 등
■ 가짜 객체 생성 및 호출 여부 검증 기능 제공
31. 참고: 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)));
}