2018.10.18
OKKYCON: 2018 《The Real TDD - TDD 제대로 알기》
한성곤님의 <코드 품질을 위한 테스트 주도 개발> 발표자료입니다.
[연사 소개]
한성곤 - 삼성SDS Principal Engineer
삼성SDS SW엔지니어링팀에서 코드에 대한 품질 지표 수립 및 개선을 위한 다양한 활동(리팩토링/클린코드 교육, 코드리뷰 및 점검 수행 등)을 하고 있다. 상대적으로 코드 품질 지표가 좋은 프로젝트에서 기능적인 유효성(Validation, Are we building the right product?)이 좋지 못한 경우에 충격을 받고, TDD에 관심을 갖기 시작했다. TDD를 현장에 적용하기에는 여러 현실적 문제들이 있기에 그에 대한 대안을 찾아가는 중이다. 그중 특히 관심 있는 것은 DSL(Domain-Specific Language)를 활용한 BDD(Behaviour-Driven Development)의 적용이다.
[발표 소개]
코드 품질 측면에서 TDD를 간단하게 살펴보고, TDD가 소프트웨어의 품질을 어떻게 높일 수 있는지 알아봅니다. 또한 TDD 적용에 따른 트레이드 오프(trade-off)와 이에 대한 새로운 대안으로 BDD(Behavior-Driven Development) 적용을 고민해보는 시간을 갖습니다.
http://okkycon.com
4. TDD Cycle (Red-Green-Refactor)
• Red - write test that fails
• Green - write code to pass the test
• Refactor - remove duplications, etc.
• 짧은 개발 주기(보통 몇 분에서 몇 시간)의 반복에 의존하는 개발 프로세스
• XP(Extreme Programming)의 Test-First 개념을 기반으로, 단순한 설계(디자인)를
중요시 하는 Practice (Test-Driven Design, Design by Example)
<이미지 출처: Lukasz Nowacki, Quora’s Answer>
5. The three laws of TDD
• 실패하는 테스트 코드를 작성할 때까지 프로덕션 코드를 작성하지 않는다.
• 컴파일은 실패하지 않으면서 실행이 실패하는 정도로만 테스트 코드를
작성한다.
• 현재 실패하는 테스트를 통과할 정도로만 프로덕션 코드를 작성한다.
- Rebert C. Martin, “The Three Rules of TDD”,
<http://butunclebob.com/ArticleS.UncleBob.TheThreeRulesOfTdd>
6. What are the benefits of TDD
• 동작하는 코드에 대한 자신감
• 회귀테스트를 통한 자유로운 리팩토링
• 코드에 대한 지식이 증가
• 개발 생산성 향상
• 과도한 설계를 피하고, 간결한 interface를 가짐
• 불필요한 기능(Gold-Plating)을 줄임
• 실행 가능한 문서(Executable documents)를 가짐
• 코드 품질을 높임
7. What are the benefits of TDD – cont’d
출처 : Lasse Koskela (2013). Effective Unit Testing: A guide for Java developers
8. What are the benefits of TDD – cont’d
• 동작하는 코드에 대한 자신감
• 회귀테스트를 통한 자유로운 리팩토링
• 코드에 대한 지식이 증가
• 개발 생산성 향상
• 과도한 설계를 피하고, 간결한 interface를 가짐
• 불필요한 기능(Gold-Plating)을 줄임
• 실행 가능한 문서(Executable documents)를 가짐
• 코드 품질을 높임
9. What are the benefits of TDD – cont’d
• Test coverage vs Quality (SonarCloud 측정 사례)
Test Coverage Projects
Duplication
(10% ↓)
Reliability
(A rating)
Security
(A)
Maintain-
ability (A)
No Test 10,960 (40.4%) 11.6% 15.1% 16.4% 17.0%
X < 30% 11,489 (42.3%) 82.6% 48.1% 59.4% 96.1%
X < 50% 12,462 (45.9%) 83.3% 48.4% 59.7% 96.3%
X < 80% 14,115 (52.0%) 84.8% 50.0% 60.8% 96.6%
X >= 80% 2,054 (7.6%) 97.8% 88.7% 92.8% 99.3%
Sum 27,129
참조 : https://sonarcloud.io
10. What are the benefits of TDD – cont’d
• Test coverage vs Quality (SonarCloud 측정 사례)
11. What are the benefits of TDD – cont’d
• Test coverage vs Quality (S社 SonarQube 측정 사례)
Test Coverage Projects
Duplication
(10% ↓)
Reliability
(A rating)
Security
(A)
Maintain-
ability (A)
No Test 159 (23%) 66.0% 74.8% 90.6% 90.6%
X < 30% 439 (63.4%) 64.9% 69.0% 82.2% 98.9%
X < 50% 466 (67.3%) 65.7% 70.6% 82.8% 98.9%
X < 80% 514 (74.3%) 67.1% 73.0% 84.4% 99.0%
X >= 80% 19 (2.7%) 68.4% 84.2% 94.7% 100.0%
Sum 692
12. What are the benefits of TDD – cont’d
• Test coverage vs Quality (S社 SonarQube 측정 사례)
14. Software Quality
• Software quality : External quality + Internal quality
(functional ↔ structural, software/production ↔ code)
(ISO 9126 software product quality)
<이미지 출처: Jonathan Bloom, Titanic Dilemma:
The Seen Versus the Unseen>
External quality Internal quality<
Good internal quality Bad internal quality
TimeExternalquality
15. External Quality
• External quality : 발견된 detects (per code size)
<IBM 사례>
• POS 관련 Device Drivers 개발 팀 (9명 -> 5명)
• 비교대상 : 1998 ~ 2002까지 7개 제품 릴리즈 VS. 2002 1개 릴리즈
• Metric
Williams, L., Maximilien, E. M., & Vouk, M. (2003). Test-driven development as a defect-reduction
practice
항목 데이터
Source KLOC 41.0
Test KLOC 28.5 (70%)
Block coverage 95%
개발 기간(MM) 119
* KLOC: Kilo LOC(Lines of code)
• LOC당 defeats 발생비율 40% 정도 ↓
• Test Case 당 1.8배 많은 defeats 발견 (Test
코드 품질 ↑)
16. External Quality – cont’d
<Microsoft 사례 1>
• Windows Network 팀 (6명)
• 개발언어 : C/C++
• Metric 항목 Non TDD TDD
Source KLOC 6.0 4.5
Test KLOC 4.0 (66%)
Block coverage 79%
개발 기간(MM) 24 12
• LOC당 defeats 발생비율 62% 정도 ↓
(Non TDD가 2.6배 많음)
• TDD가 25~35% 더 많은 시간 소요
Microsoft research : Bhat, T., & Nagappan, N. (2006). Evaluating the efficacy of test-driven
development: industrial case studies
Level 1
Manager
Level 2
Manager
(TDD)
Level 2
Manager
(Non TDD)
17. External Quality – cont’d
<Microsoft 사례 2>
• MSN 웹 서비스 개발팀 (5~8명)
• 개발언어 : C++, C#
• Metric 항목 Non TDD TDD
Source KLOC 26.0 149
Test KLOC 23.2 (89%)
Block coverage 88%
개발 기간(MM) 46 144
• LOC당 defeats 발생비율 76% 정도 ↓
(Non TDD가 4.2배 많음)
• TDD가 15% 더 많은 시간 소요
Microsoft research : Bhat, T., & Nagappan, N. (2006). Evaluating the efficacy of test-driven
development: industrial case studies
18. • The Seven Axes of Code Quality (from SonarQube)
Internal Quality
Coding Rules
Potential
Bugs
Comments
Duplications
Complexity
Unit Test
Design and
Architecture
• Coding Rules : 개발 표준 및 기본적인 관례 준수
• Potential bugs : Inspection을 활용한 점검
• Comments : 적정한 주석 (“Not Enough or Too Many
Comments”)
• Duplication : DRY (Don’t Repeat Yourself)
• Complexity : 적절한 분포 (Distribution)
• Unit Tests : Lack of unit tests
• Design & architecture : Spaghetti Design (minimize
dependencies, 결합도/응집도 등)
19. Code Quality Metrics
구분 항목 설명
복잡도
(Complexity)
McCabe Cyclomatic
Complexity (MCC)
실행경로 가지수
Cognitive Complexity 블록 깊이(depth)에 대한 가중치 추가
결합도
(Coupling)
Martin Metrics 패키지의 안정성 및 추상화 상태 측정
Depth Of Inheritance Tree (DIT) 객체 상속 계층 구조상, 상위 객체 개수
응집도
(Cohesion)
Lack of Cohesion Methods
(LCOM)
멤버필드와 메소드 사이의 상관관계 측정
Specialization Index (SI) Overload되는 정도 측정
20. Code Quality Case Study
• 오픈소스SW Projects에 대한 코드품질 평가
Rod Hilton, (2009). Quantitatively Evaluating Test-Driven Development by Applying Object-Oriented
Quality Metrics to Open Source Projects
• 50개 이상 OSS(Open Source Software), 57명 Committer/Contributor Survey 조사
4개 TDD & 4개 Non TDD projects 선정 (Small, Medium, Large, Very Large)
Eclipse Metrics Plugin을 통해 측정
21. Code Quality Case Study – cont’d
• 오픈소스SW Projects에 대한 코드품질 평가 – cont’d
TDD Non TDD
JUnit Math Fitnesse Hudson Jericho JAMWiki JSPWiki XWiki
MCC 1.399 2.174 1.525 1.679 2.258 2.334 2.815 2.210
D(Martin) 0.276 0.302 0.373 0.157 0.007 0.412 0.401 0.301
DIT 1.875 2.013 1.771 2.014 2.054 1.194 1.968 2.302
LCOM 0.129 0.240 0.241 0.150 0.239 0.332 0.220 0.178
SI 0.132 0.098 0.239 0.249 0.196 - 0.258 0.506
22. Code Quality Case Study – cont’d
• 오픈소스SW Projects에 대한 코드품질 평가 – cont’d
• McCabe Cyclomatic Complexity
• TDD group : 1.7430383 (21.18% 우수)
• Non TDD group : 2.361
23. Code Quality Case Study – cont’d
• 오픈소스SW Projects에 대한 코드품질 평가 – cont’d
• Normalized Distance from the main sequence
• TDD group : 0.2701667 (18.68% 우수)
• Non TDD group : 0.332
24. Code Quality Case Study – cont’d
• 오픈소스SW Projects에 대한 코드품질 평가 – cont’d
• Lack of Cohesion Methods
• TDD group : 0.1934524 (6.97% 우수)
• Non TDD group : 0.208
25. Code Quality Case Study – cont’d
• 오픈소스SW Projects에 대한 코드품질 평가 – cont’d
• Specialization Index
• TDD group : 0.2128426 (40.67% 우수)
• Non TDD group : 0.359
26. [Metrics] Complexity
• McCabe Cyclomatic Complexity (MCC)
- 분기문 등(if, for, while 등)에 의해 구분되는 실행 경로의 가지 수
- 복잡도 = E – N + 2P
where E = # of edges of graph (edge : between two block of code if control may pass from 1st to 2nd)
N = # of nodes of graph (node : block of code)
P = # of connected components (단일 프로그램(method, subroutine 등)의 경우 항상 1..)
또는 복잡도 = 분기문 + 1
※ SonarQube 산정 방식 : For each of the following java statements the number increments by one:
if, for, while, case, catch, throw, return, &&, ||, and ?.
- QI(Quality Index) Complexity : [(complexity > 30) * 10 + (complexity > 20) * 5 + (complexity >
10) * 3 + (complexity > 1) * 1] / effective lines of code
28. [Metrics] Complexity – cont’d
• MCC 측정 평가
• McCabe는 10 이하로 유지하라고 권고 (1976년)
• Microsoft: 임계치 15, 10이상 refactoring 고려 대상
• JSF Air Vehicle C++ Coding Standard: 20이하를 유지
※ 일반적으로 프레임워크를 사용하면 복잡도가 낮음 (복잡도 전가)
시스템의 성격에 따라 전반적으로 복잡도가 높을 수 있음
분포로 평가 (eg: 20 이상 메소드 비율 유지, 표준표차 이상 조치 대상 등)
30. [Metrics] Coupling
• Rebert C. Martin Metrics : 추상화 정도를 측정
• Afferent and Efferent Coupling
• Instability (I) = Ce / (Ca + Ce)
Code Element
(package)
Afferent
Coupling
(Ca)
Efferent
Coupling
(Ce)
• Fan-in 참조 > Fan-out 참조 Instability ↓
• Fan-in 참조 < Fan-out 참조 Instability ↑
외부의 영향을 많이 받는 정도
31. [Metrics] Coupling – cont’d
• Rebert C. Martin Metrics – cont’d
• Abstraction (A) = Na / (Nc + Na)
• Distance (D) = | A + I -1 |
• Nc : Number of Concrete classes
• Na : number of Abstract classes (include interface)
Main Sequence : 가장 이상적은 값
(A + I = 1)
• D는 Main Sequence와의 거리로 보통
0.8 이상이 문제가 있다고 판단
<이미지 출처: https://www.future-processing.pl/blog/object-oriented-metrics-by-robert-martin/>
32. [Metrics] Coupling – cont’d
• Martin Metrics 측정 예 1
AW
X
Y
ZI = Ce / (Ca + Ce) = 3 / 4 = 0.75
A
Interface
Impl ImplFactory
A = Na / (Nc + Na) = 1 / 4 = 0.25
D = | A + I -1 | = | 0.25 + 0.75 – 1 | = 0
33. [Metrics] Coupling – cont’d
• Martin Metrics 측정 예 2
AY
I = Ce / (Ca + Ce) = 0 / 3 = 0
A
Util
A = Na / (Nc + Na) = 0 / 3 = 0
D = | A + I -1 | = | 0 + 0 – 1 | = 1
X
Z Util
Util
40. [Metrics] Cohesion – cont’d
• SI (Specialization Index)
• ex)
• SI = (DIT * NORM) / NOM
• DIT : Depth of Inheritance Tree
• NORM : Number of Overridden Methods
• NOM : Number of Methods
SI ↑ : abstraction is inappropriate
Bird
+ fly()
+ layEggs()
Woodpecker
+ peckWood()
Penguin
+ fly()
+ swim()
Woodpecker : (2 * 0) / 1 = 0
Penguin : (2 * 1) / 2 = 1
41. [Metrics] Tool
• STAN (Structure Analysis for Java)
• 500 classes까지는
community license 사용 가능
• Understand (https://scitools.com/)
• Visualization, 상용
• Code Analyst
(https://github.com/RedCA-
Family/code-analyst)
• 삼성SDS 개발 Tool (Apache 2.0 공개)
42. 쉬어 가기
다음과 같이 출력하는 프로그램을 작성하세요.
*
* *
* * *
* * * *
46. To succeed with TDD
Test technique
& Refactoring
Requirements
Elicitation
Communication
&
Documentation
47. Test technique
• 다양한 테스트 기법 및 적절한 Mocking 활용 (Unit Test & Mock Framework)
• 빠른 피드백
• 의존성 분리
• 지속 통합(Continuous Integration) 등 Tool 활용
• 테스트 커버리지 측정 등
48. Test technique – cont’d (Mock)
•Mock Object
• 실제 객체가 아닌 가상의 “흉내”를 내는 객체로 테스트의 의존성
분리 등을 위해 사용하는 Test doubles 중에 하나
LoginService
MockAccountDAO
AccountDAO
login(user)
DB
49. Test technique – cont’d (Mock)
•Mock
• 객체의 내부 상태뿐 아니라 동작까지도 감시할 수 있게 만든 객체로
테스트 도중에 호출되어야 하는 메소드들을 명세로 지정
LinkedList<String> mockedList = mock(LinkedList.class);
// stubbing
when(mockedList.get(0)).thenReturn("first");
when(mockedList.get(1)).thenThrow(new RuntimeException());
// act
String result = mockedList.get(0);
// ...
verify(mockedList).get(0);
verify(mockedList, never()).get(1);
50. Refactoring
•다양한 Code Smell & Refactoring 기법
참조 : https://refactoring.com/catalog/ 등 참조
메소드 정리
• Extract Method
• Inline Method
• Inline Temp
• …
객체간 기능 이동
• Move Method
• Move Field
• Extract Class
• …
데이터 체계화
• Replace Data Value with
Object
• Replace Array with Object
• …
조건문 간결화
• Decompose Conditional
• Consolidate Conditional
Expression
• …
메소드 호출 단순화
• Rename Method
• Add/Remove Parameter
• …
일반화 처리
• Pull Up Field/Method
• Pull Down Field/Method
• …
R
GRefactor
51. Requirements Elicitation
•BDD (Behavior-Driven Development) / ATDD (Acceptance Test-
Driven Development) 활용
•요구사항 또는 기능(feature) 정의 단계부터 Test를 활용
•다양한 이해관계자(고객, 테스터, 도메인 전문가, 분석가,
개발자 등) 참여 유도
•BDD Practices 활용: Feature Injection, Impact Mapping,
Purpose-Based Alignment Model, …
52. Requirements Elicitation – cont’d (BDD)
• Documentation for non-coders
• 요구사항으로써 활용되고
• 실행될 수 있으며
• 작업의 결과를 확인할 수 있고
• 모두에 의해 작성되고 이해될 수 있다.
<이미지 출처: Lukasz Nowacki, Quora’s Answer>
• 구조화되고 특화된, 공통적인
언어를 제공함으로써 프로젝트
팀원 및 업무 이해관계자와의
효율적인 소통을 제공
53. Requirements Elicitation – cont’d (BDD)
• 시나리오 (≒ Acceptance Criteria)
Scenario : 관리자 REST 서비스에 대한 접근 권한 기능
Given 일반 사용자 로그인 상태에서
When 관리자용 서비스가 호출되면
Then 403 Forbidden 오류 코드와 함께 오류에 대한 내용을 body로 제공한다
55. Communication & Documentation
•많은 문제들이 잘못된 커뮤니케이션으로부터 발생
•효율적인 커뮤니케이션 기법 활용 (다양한 Agile 기법 등)
•사용하는 언어의 통일 (모델링)
•기술적 Specification이 중요한 경우 DSL(Domain-Specific
Languages) 활용 고려
•표준 절차(Process)나 측정 지표(Metrics)는 참고로 활용
56. Communication & Documentation – cont’d (DSL)
Product Management
Customer Service
Physicists
System Engineering
HW / FW
SW Experts Software
Package
Installed Software
57. Communication & Documentation – cont’d (DSL)
Product Management
Customer Service
Physicists
System Engineering
HW / FW
SW Experts Software
Package
Installed Software
DSL
DSL
DSL
DSL
Code generation space
DSL
58. Communication & Documentation – cont’d (DSL)
• Language Workbench Example
• MPS(Meta Programming System) by JetBrains
• Apache 2.0 license
• 주요 특징
• DSL 설계 및 실행환경 지원
• Projectional Editor 지원 (eg. Math notations, diagram, forms, ..)
• IDE Editor 지원 (quick fixes, word completion, and intentions)
• Code 생성
61. References
• Alex Garcia; Viktor Farcic, (Packt Publishing, 2018). Test-Driven Java Development –
Second Edition
• Lasse Koskela (Manning, 2013). Effective Unit Testing: A guide for Java developers
• Microsoft research : Bhat, T., & Nagappan, N. (2006). Evaluating the efficacy of test-driven
development: industrial case studies
• Williams, L., Maximilien, E. M., & Vouk, M. (2003). Test-driven development as a defect-
reduction practice
• Rod Hilton, (2009). Quantitatively Evaluating Test-Driven Development by Applying
Object-Oriented Quality Metrics to Open Source Projects
• John Ferguson Smart (Manning Publications, 2014). BDD in Action: Behavior-Driven
Development for the whole software lifecycle
62. [추가] Connect 4 Example
• Connect 4 게임
• 2 Player 게임 (2 색상의 디스크)
• 7 column * 6 row
• 4개의 같은 디스크가 가로, 세로, 또는
대각선으로 연결되면 승리
• 디스크 공간이 없으면 무승부
<이미지 출처: Wiki, Connect Four>
63. [추가] Connect 4 Example – cont’d
• Test-last implementation
Alex Garcia; Viktor Farcic, (2018). Test-Driven Java Development – Second Edition
• 테스트 코드 작성이 어려움 (불필요한
메소드 추가)
• Coverage 자체에 집중하게됨
• 테스트 코드가 프로덕션 코드의
의존도가 높음 (변경용이성 저하)
• Coverage 100% 달성 어려움 (main
메소드 제외하면, 98%)
64. [추가] Connect 4 Example – cont’d
• Test-first implementation
Alex Garcia; Viktor Farcic, (2018). Test-Driven Java Development – Second Edition
Editor's Notes
TDD is a process that relies on the repetition of a very short development cycle.
It is based on the test-first concept of extreme programming (XP) that encourages simple design with a high-level of confidence.
- You are not allowed to write any production code unless it is to make a failing unit test pass.
- You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures.
- You are not allowed to write any more production code than is sufficient to pass the one failing unit test.
No code goes into production without tests associated with, so … that all of the code they release behaves as expected (Astels, D.)
Having a suite of regression tests covering all aspects of the system (Martin, R. C.)
Gives engineers a change to learn more about code (Beck, K.)
The biggest value of writing a test lies in what we learn from writing it (Koskela, Lasse)
Helps develop code quickly, since developers spend very little time debugging and find mistakes sooner. (Shore, J. and Warden, S.)
빠른 피드백 : 대부분의 실수는 단위 테스트만으로 쉽게 잡을 수 있다.
TDD is not about writing tests, it's about driving the creation of the code to improve its quality.
1 ~ 10 : 5% 또다른 오류 발생 확률
20 ~ 30 : 20%
50 이상 : 40%
A maximally cohesive class should have every single instance variable used by every single method defined in the class. A class where one of the variables isn't used by one of the methods is slightly less cohesive than a class where all of the variables are used by all of the methods.
A maximally cohesive class should have every single instance variable used by every single method defined in the class. A class where one of the variables isn't used by one of the methods is slightly less cohesive than a class where all of the variables are used by all of the methods.
Good programs follow what Martin and Martin (2006) call the “Liskov Substitution Principle," which means that subtypes are easily substitutable for their base types.
When this principle is violated, it means that a relationship between classes has been created that isn't a natural one, often simply to reuse small pieces of code such as methods or variables within subtypes. This is an indicator of low cohesion.
How effectively subclasses add new behavior while utilizing existing behavior.
Using TDD, we got a class with a constructor, five public methods, and six private methods. In general, all methods look pretty simple and easy to understand. In this approach, we also got a big method to check winner conditions: checkWinner. The advantage is that with this approach we got a bunch of useful tests to guarantee that future modifications do not alter the behavior of the method accidentally, allowing for the introduction of new changes painlessly. Code coverage wasn't the goal, but we got a really high percentage.
Additionally, for testing purposes, we refactored the constructor of the class to accept the output channel as a parameter (dependency injection). If we need to modify the way the game status is printed, it will be easier that way than replacing all the uses in the traditional approach. Hence, it is more extensible. In the test-last approach, we have been abusing the System.println method and it will be really tedious task if we decide to change all the occurrences for any other thing.
In large projects, when you detect that a great number of tests must be created for a single class, this enables you to split the class following the Single Responsibility Principle. As the output printing was delegated to an external class passed in a parameter in initialization, a more elegant solution would be to create a class with high-level printing methods. That would keep the printing logic separated from the game logic.