1. 꾸준히 자라나는 소프트웨어 (Software that grows!)
만들기 - 테스트 자동화, 리팩토링
박종빈 (jongbhin@nhn.com)
ⓒ 2010 NHN CORPORATION
2. 목차
0. 들어가기
1. NHN의 한 개발자 이야기
2. 자라나는 소프트웨어 (Software that Grows!)
3. 리팩토링
4. 테스트 자동화
5. NHN의 리팩토링, 테스트 자동화 체계
6. Quality Practice를 활용한 리팩토링, 테스트 자동화
7. 맺음말
24. 3.3 리팩토링 예제2 17
읽기 어려운 코드 - 네이밍
코딩은 글쓰기(Writing)의 일종임
클래스, 메소드, 변수의 이름은 의도하는 바를 명확하게 드러내야 이해하기 쉬움
네이밍 예제
if (argv[0] == TYPE1) { if (numberOfAttendees == CROWDED) {
return 0; return INCREASE_LUNCH_BOX;
} else { } else {
return 1; return DECREASE_LUNCH_BOX;
} }
컴퓨터가 이해할 수 있는 코드는 어느 바보나 다 짤 수 있다.
좋은 프로그래머는 사람이 이해할 수 있는 코드를 짠다.
- 마틴 파울러
26. 4 테스트 자동화 16
사진출처: http://www.automatedtestinginstitute.com
27. 4.1 테스트 자동화의 필요성 15
기능이 추가되면서 전체 테스트 항목도 증가하나 충분한 테스트가 수행되지 않음
Regression 결함이 발생할 수 있어 테스트 자동화가 필요함
142 147
137
130
비용 기능수
120
100
전체기능수
QA 테스트 비용
100
+20 개발 비용
+10 +7 +5 +5
Ver.1.0 Ver.1.1 Ver.1.2 Ver.1.3 Ver.1.4 Ver.1.5
28. 4.2 개발자 테스트의 필요성 14
대부분의 결함은 코딩단계에서 발생함
수정비용은 단계가 지날 수록 기하급수적으로 증가함
개발자 테스트가 비용대비 효과가 좋음
100% $16,000
50% $8,000
0% $0
코딩 통합 테스트 운영 결함발생 비율
결함발견 비율
결함수정비용
29. 4.3 단계별 테스트 자동화 13
단위, 통합, QA 전체 테스트 단계에서 테스트 자동화가 필요함
빨리, 자주
실행
• 최소 단위의 White-box 테스트
단위 테스트
• 클래스 별로 메소드에 대해 정상 동작 단위테스트 자동화
개발자가
여부를 확인 (JUnit)
수시로 수행
• 개별 클래스 단독으로 신속하게 수행 가능
단위테스트자동화
• 두 개 이상의 구성 요소 간의
(JUnit)
통합 테스트 인터페이스를 테스트
통합테스트 자동화
하루에 한번 • Black-box 테스트
(FitNesse)
수행 • 시스템 간의 연동 테스트
UI 테스트 자동화
• 기능이 제대로 수행되는지 검증
(Selenium)
QA 테스트 UI 테스트 자동화
QA • 시나리오 기반의 테스트 (Selenium)
단계에서 • 개발과 독립적인 조직이 수행 성능테스트
수행 (PerformaSure)
상위 레벨
32. 5.1 NHN의 테스트 자동화와 리팩토링 11
Quality Practice를 활용
QP 활동의 정착이 테스트 자동화와 리팩토링의 문화적 밑거름으로 작용
- 각 지표를 통해 코드의 성장 모습을 확인할 수 있음
Quality Practice (QP) 지표
Code Coverage Code Coverage
오류 없는 코드
테스트
자동화
잔존 정적분석
Static Analysis
결함 밀도
Coding Convention 코딩 표준 준수율 성장하는
모습확인
이해하기 쉬운 코드
개선 포인트
Code Review 코드 리뷰 수행율
리팩토링
CC≥30
Code Complexity
모듈 비율
Code Duplicate Analysis -
33. 5.2 Quality Practice 지원 도구 와 CI 서버 10
활동 별로 지원도구가 있음
CI 서버와 연동하여 빌드 수행 시 마다, 꾸준하게 현재수준과 개선 포인트를 제공
QP 도구
Code
Clover
오류 없는 코드
Coverage Checkstyle
Static
Klocwork
Analysis
Coding CPD Klocwork
Checkstyle
Convention CI 서버
(Hudson)
이해하기 쉬운 코드
Code Review Crucible
Code NSIQ NSIQ
Complexity Collector Clover
Collector
Code
Duplicate CPD
Analysis Java환경에서 가장 많이 활용하는 도구들임
36. 6.1 복잡하고 위험한 코드 식별 8
코드 복잡도가 높으나 테스트가 이루어지지 않은 항목을 쉽게 찾을 수 있음
리팩토링을 수행하여 복잡도를 낮추거나, 추가 테스트로 커버리지를 높임
QP
커버리지-복잡도 산점도
Code
오류 없는 코드
Coverage 이렇게 복잡도는 높으나
테스트가 이루어지지 않은
Static 메소드가 있어서는 안됨
Analysis
Coding
Convention 테스트
이해하기 쉬운 코드
Code Review
리팩토링
Code
Complexity
Code
Duplicate
NHN 개발 Hudson Plug-in
Analysis http://wiki.hudson-ci.org/display/HUDSON/Coverage+Complexity+Scatter+Plot+PlugIn
37. 6.2 커버리지 확인 및 테스트 케이스 보완 7
소스코드의 테스트가 수행되지 않은 부분(분기, 구문)을 파악하여 테스트를 보완
QP
Code
오류 없는 코드
Coverage
Static
Analysis
Coding
Convention
이해하기 쉬운 코드
Code Review
Code
Complexity
Code
Duplicate
Analysis 테스트가 수행되지 않은 구문
38. 6.3 읽기 쉬운 코드 만들기 6
코딩 컨벤션 중 구문 형식을 따르지 않은 코드는 도구로 검출할 수 있음
의도가 명확한 이름처럼, 의미론적인(Semantic) 것은 사람이 리뷰를 통해 확인
QP
Code NHN 코딩 컨벤션
오류 없는 코드
Coverage - 네이밍 규칙
- 코딩작성 규칙
Static - 주석작성 규칙
Analysis - JSP작성 규칙
- 보안코드 작성규칙
Coding
Convention
이해하기 쉬운 코드
Code Review
Code
Complexity
Code
Duplicate
Analysis
39. 6.4 코드와 함께 자라나는 테스트 자동화 5
코드 작성과 함께 (또는 이전에) 테스트 코드를 작성하는 문화가 정착되어 가고 있음
LOC
QP
Code
오류 없는 코드
Coverage
Static
Analysis
Coding
Convention Test Result Trend
이해하기 쉬운 코드
Code Review
Code
Complexity
Code
Duplicate Code Coverage
Analysis
40. 6.5 테스트로 찾기 어려운 결함 검출 4
정적 분석 도구를 활용하여 잠재된 결함을 조기에 발견하고 제거함
동적 테스트로는 검출하기 어려운 결함을 발견해 줌
QP
Code 자주 발견되는 정적 분석 결함
오류 없는 코드
Coverage
중요도 카테고리 설명
Static
Analysis
Coding Null Pointer Exception
Possible Runtime
Convention Critical Null 값을 역참조할 경우
Failures
발생
이해하기 쉬운 코드
Code Review
Code Memory Leak
Complexity 리소스가 할당되었으나 사용
Error Resource Leaks
후 적절히 해제되지 않을 때
Code
Duplicate
발생
Analysis
41. 6.6 리팩토링, 테스트 자동화 사례 3
대상 서비스: 핵심 서비스
- 1일 방문자수 : 630만
- 1일 페이지뷰수 : 1억7천만
- 8년 된 코드
리팩토링
- 주요 기능의 코드 70% 리팩토링
- 미사용 테이블 삭제
테스트 자동화
- 단위 테스트 자동화
- UI 테스트 자동화
42. 6.7 리팩토링, 테스트 자동화 결과 2
Java 코드 감소 : 20~30% 감소
DB 테이블 감소 : 50% 감소 (464개)
테스트 커버리지 월간 장애 발생건수
60
50
40
개선된 코드 배포
30
서비스
20
어드민
10
0
09/01
10/01
03
06
02
04
05
07
08
09
10
11
12
02
03
개선전 단위 통합(단위+UI)
45. 한편.... 0
도입부의 NHN의 한 개발자는
리팩토링과 테스트 자동화에 관하여 연구에 연구를 거듭,
NHN의 Key Man으로 성장해 가고 있음
46. 별첨: 리팩토링 사례
리팩토링 전
http://dev.naver.com/scm/viewvc.php/trunk/src/main/java/com/naver/dev/refactoring/servicestop/ServiceStopper.java?view=mar
kup&root=refactoring&pathrev=2
리팩토링 후
http://dev.naver.com/scm/viewvc.php/trunk/src/main/java/com/naver/dev/refactoring/servicestop/ServiceStopper.java?revision=1
2&root=refactoring&view=markup&pathrev=12
http://dev.naver.com/scm/viewvc.php/trunk/src/test/java/com/naver/dev/refactoring/servicestop/ServiceStopperTest.java?view=
markup&root=refactoring&pathrev=13
리팩토링 과정
#1. 리팩토링 전 ServiceStopper에 대한 단위 테스트를 만듬.
http://dev.naver.com/scm/viewvc.php/trunk/src/test/java/com/naver/dev/refactoring/servicestop/ServiceStopperTest.java?view=
markup&root=refactoring&pathrev=3
#2. 파라메터 변수 type의 의미가 명확하지 않아 stopType으로 변경함.
http://dev.naver.com/scm/viewvc.php/trunk/src/main/java/com/naver/dev/refactoring/servicestop/ServiceStopper.java?view=diff
&root=refactoring&pathrev=6&r1=2&r2=4&diff_format=h
#3. Integer로 구분하던 StopType을 Enum StopType으로 바꿈. 그 결과로 잘못된 타입정보에 대한 검사구문이 사라졌고, 1,2로 구분되던 코
드가 COMPLETE_STOP과 같은 문자열로 바뀌어 의도가 더 명확히 드러나게 됨.
http://dev.naver.com/scm/viewvc.php/trunk/src/main/java/com/naver/dev/refactoring/servicestop/ServiceStopper.java?view=diff
&root=refactoring&pathrev=6&r1=4&r2=5&diff_format=h
#4. StopType과 Reason의 Type검사를 하기 위해 존재하던 중첩 IF 문을 깊이 1단계의 IF문으로 변경함. 가독성에 긍정적 영향을 미치기를 기
대함.
http://dev.naver.com/scm/viewvc.php/trunk/src/main/java/com/naver/dev/refactoring/servicestop/ServiceStopper.java?view=diff
&root=refactoring&pathrev=6&r1=5&r2=6&diff_format=h
47. 별첨: 리팩토링 사례
#5. Reason 입력검사하는 부분의 조건절의 코드를 Commons StringUtils.isEmpty로 교체함. 그 결과로 가독성 향상을 기대함.
http://dev.naver.com/scm/viewvc.php/trunk/src/main/java/com/naver/dev/refactoring/servicestop/ServiceStopper.java?view=diff
&root=refactoring&pathrev=6&r1=6&r2=7&diff_format=h
#6. 유형별로 stopNow를 호출하는 부분에 중복이 있어 해당 부분을 stopNowCleanly라는 메서드로 분리시킴. 중복을 제거하고 가독성을 향
상시키고자 함.
http://dev.naver.com/scm/viewvc.php/trunk/src/main/java/com/naver/dev/refactoring/servicestop/ServiceStopper.java?view=diff
&root=refactoring&pathrev=6&r1=7&r2=8&diff_format=h
#7. 위 3번에서 실수로 생긴 버그를 수정함. 이 실수는 만들어 놓은 테스트를 실행해보지 않아 생겼음. 따라서 테스트를 작성하는 것도 중요하지
만 코드를 수정할 때마다 테스트를 실행해보고 문제가 발생하지 않았나를 검사하는 것도 매우 중요한 요소임.
http://dev.naver.com/scm/viewvc.php/trunk/src/main/java/com/naver/dev/refactoring/servicestop/ServiceStopper.java?root=refa
ctoring&r1=9&r2=8&pathrev=9
#8. ReadOnlyStop시 관련 서비스가 있는지 검사하고, 관련 서비스가 있을 때 알림을 전송하던 부분을 ReadOnlyNotifier로 이동시킴. 이 부분
에 대한 책임은 ServiceStopper보다는 ReadOnlyNotifier에 있는 것이 더 적합하다는 판단임.
http://dev.naver.com/scm/viewvc.php/trunk/src/main/java/com/naver/dev/refactoring/servicestop/ServiceStopper.java?root=refa
ctoring&view=diff&r1=9&r2=10&diff_format=h
#9. StopMessage 생성하는 부분을 별도의 메서드로 분리시킴. 핵심로직이 아닌데 많은 줄을 차지하는 부분을 의미를 드러내는 한줄의 메서드
로 바꿈으로써 가독성을 향상시키기 위함임.
http://dev.naver.com/scm/viewvc.php/trunk/src/main/java/com/naver/dev/refactoring/servicestop/ServiceStopper.java?root=refa
ctoring&view=diff&r1=10&r2=11&diff_format=h
#10. StopMessage는 한 곳에서만 사용되며 굳이 임시변수로 둘 필요가 없기 때문에 메서드 호출로 변경함.
http://dev.naver.com/scm/viewvc.php/trunk/src/main/java/com/naver/dev/refactoring/servicestop/ServiceStopper.java?root=refa
ctoring&view=diff&r1=11&r2=12&diff_format=h