2. 시작하기전에..
• Effective Unit Testing <2013, 한빛미디어>
• 옮긴이의 말
Effective 로 시작하는 책 대부분은 유행이 아닌
비교적 오랫동안 가치를 인정받는 듬직함을 보여줌.
• 단순한 따라하기, 기교 중심의 어떻게(How)? 가 아닌
주제에 관한 깊은 이해와 오랜 경험으로부터 우러나온
왜(Why)? 를 보여준다!
4. Agile?
• 나는 그냥 개념이야. 컨셉같은거?
• Less Document-Oriented.
• 계획을 통해 주도해가던 과거의 방법이 아닌.
• Code-Oriented.
• 일정한 주기로 동작하는 코드를 만들어 가는 반복 과정.
5. XP?
• 1999년 Kent Beck의 저서에서 사용
(Extreme Programming Explained)
• 고객이 원하는 양질의 소프트웨어를 빠른 시간안에 전달
을 목표
• XP가 제시하는 실천방법에는 TDD, Pair Programming
등..
6. 복잡해.. 한줄로 요약을 해보면?
TDD는..
Agile 원칙을 지키는 방법론중의 하나인
XP가 제시하는 실천방법 중 하나!!
7. 음.. 그래서 TDD는 어떻게 하는건가?
코딩 -> 테스트 -> 리팩토링
을 아래의 순서로 진행
테스트 -> 코딩 -> 리팩토링
8. TDD 간단 순서
요구사항: JukeBox 에 노래제목(Title)으로 가수명(Artist)을 찾는 기능 추가
1. test 를 먼저 작성
public void testGetArtistNameByTitleName() {
JukeBox jukeBox = new JukeBox();
String titleName = “Becuase of You”;
String artistName = jukeBox.getArtistNameByTitleName( titleName );
assertTrue(“Kelly Clarkson”.equals(artistName));
}
2. JukeBox.class 를 구현
위의 Test 를 통과할 수 있는 수준으로만 코드를 구현하는것이 포인트.
앞으로 필요할것 같은? 코드를 임의로 구현하지 않음.
9. 그렇다면 TDD로 얻을 수 있는게
뭐지?
• 정확한 사용 시나리오가 포함된
자동화된 실행 가능한 코드
• 군더더기 없는 제품 코드.
11. JUnit은 Reflection을 사용
• Java Reflection API(java.lang.reflect)는
클래스, 필드, 메소드를 들여다 볼 수 있는 도구.
• JVM에서 실행되는 자바 프로그램의 내부 구성을 확인하
거나 런타임시에 프로그램의 작동방법을 변경하고자 하
는 경우에 사용.
• 예를들면.. JUnit은 @BeforeClass, @Before 등의 메소
드를 검색하고 실행하는데 활용.
12. Java Reflection 예제
public class MyReflection {
public static void main(String args[])
{
try {
Class c = Class.forName("java.lang.String");
Method m[] = c.getDeclaredMethods();
for (int i = 0; i < m.length; i++)
System.out.println(m[i].toString());
} catch (Throwable e) {
System.err.println(e);
}
}
}
13. 좋은 테스트란?
• 읽기 쉬운 테스트 코드
-소스코드와 같은 수준의 가독성 유지
• 구조를 잘 갖춘 테스트
-통짜 클래스가 아닌 적정한 수준의 구성 (화면 스크롤 제약..)
• 엉뚱한 걸 검사하지 말자
-테스트 메소드명은 중요
• 테스트가 얼마나 독립적인가?
-방금 개봉한 새PC에 버전 관리 서버에서 내려받은 테스트 코드를 바로 실행
• 믿고 쓰는 테스트 및 결과
-경계값으로 만들어진 테스트들
-하지만 assert가 없는 테스트라면?
14. 테스트 더블?
• 특별한 목적으로 테스트 전용 장치를 만들어 사용.
• 이때 여러 종류의 테스트 장치(객체)를 통칭하는 용어.
• 쉽게..
테스트 하는데 필요한 객체 대신
의도적으로 변형시킨 다른 객체로 대체하여 사용하는것.
15. 테스트 전용 장치의 필요성?
• 테스트 대상 코드를 격리한다
• 테스트 속도를 개선한다.
• 예측 불가능한 실행 요소를 제거한다.
• 특수한 상황을 시뮬레이션한다.
• 감춰진 정보를 얻어낸다.
16. 테스트 대상 코드를 격리한다.
• 테스트 기준에서 코드는 2가지로 분류 가능
테스트 대상코드
-Car
테스트 대상코드와 상호작용하는 코드
-Engine, Route
public class Car {
private Engine engine;
private Route route;
private Audio audio;
17. 테스트 속도를 개선한다.
• 실시간 교통정보를 반영하여 경로를 지도상에 출력
• 경로를 탐색하는 메소드가 아닌
계산된 경로를 화면에 출력하는 메소드의 테스트는?
• 굳이 계산할 필요 없이
미리 계산해둔 값을 반환하도록 하여 시간을 절약
18. 예측 불가능한 실행 요소를 제거한다.
• 결과에 영향을 주는 모든 요소를 결정적으로 만든다.
• 예를들면
현재의 시간을 기준으로 동작되는 로직에는
항상 일정한 시간을 리턴하는 테스트 더블을 사용
19. 특수한 상황을 시뮬레이션 한다.
• 실행도중 네트워크가 끊어지는 상황의 재현, 테스트.
• 테스트 실행중에 Lan선을 잠시 뽑을까?
• throw new java.net.ConnectException("Connection
timed out")
20. 감춰진 정보를 얻어낸다.
• 테스트 대상의 멤버변수가 private이면서
외부에서 값의 확인이 필요할때.
• 테스트 대상을 상속, 구현할때 필요한
getter메소드를 추가하여 테스트 더블을 생성
21. 테스트 더블의 종류
• 테스트 스텁
최소한의 형태만 가지는 가짜.
• 가짜 객체
최소한의 로직을 가지는 가짜.
• 테스트 스파이
추가 정보나 임의의 로직을 기존 객체를 변형하여 구현
• Mock객체
특정조건과 그에 대한 행동을 정의하여 구현
22. 테스트 스텁
public void log(LogLevel level, String message) {
//아무일도 하지 않아요~
}
public LogLevel getLogLevel() {
// 스텁은 아무일도 하지 않아요~
return LogLevel.WARN;
}
23. 가짜 객체
진짜 객체인 척? 뭔가를 하지만, 결국은 딴짓을 하는 객체
public class fake.... {
private Collection<User>users = new ArrayList<User>();
public void save(User user) {
//실제로는 DB로 저장을 해야하지만..
users.add(user);
}
public User findByName(String username) {
//실제로는 DB에서 쿼리로 읽어와야 하지만..
for (User user : users) {
if (user.getUsername().equals(username)) {
return user;
}
}
}
}
24. 테스트 스파이
public interface DLogTarget {
void write(Level level, String message);
}
public class SpyTarget implements DLogTarget {
@Override
public void write(Level level, String message);
boolean received(level level, String message) {
//필요한 정보를 물어보고 확인할 수 있다.
}
}
26. Test 코딩의 3단법? 3단구조?
준비-시작-단언 의 3단계로 구현
준비
MP3Tag mp3Tag = new MP3Tag();
ID3v2 id3v2 = null;
시작
mp3Tag.loadFile(filepath);
id3v2 = mp3Tag.getTagID3v2();
단언
assertEquals("Kelly clarkson", ID3v2.getArtist());
27. Code Smell ?
• Code Smell 이란 코드의 버그나 기술적인 문제를 뜻하지 않음.
개발 속도를 저해하거나 추후 버그나 장애를 일으킬만한
나쁜 코드 디자인을 뜻한다.
• Kent Beck이 최초로 사용했고, 이후 Martin Folwer의 책에 언
급되며 많이 쓰이는 용어.
(Refactoring: Improving the Design of Existing Code.)
• 나쁜 코드의 냄새를 3가지 유형으로 나누어 살펴봄
가독성
유지보수성
신뢰성
29. 가독성#1 기본 타입 단언
의미를 알 수 없는 단어나 숫자가 단언하려는 의도를 가리고 있음.
String out = grep.grep("match", "test.txt", content);
assertTrue(out.indexOf("test.txt:1 1st match") != -1);
-> 개선방법#1
assertTrue(out.contains('test.txt:1 1st match"));
-> 개선방법#2
assertThat(out.contains("test.txt:1 1st match"), equals(true));
-> 개선방법#3
assertThat(out, containsString("text.txt:1 1st match"));
30. 가독성#2 광역 단언
• 너무 작은 부분까지 단언하려는 집착으로
하나만 잘못되어도 바로 실패.
의도했던 핵심 단언이 나머지 단언에 묻힘.
• 로그파일에 대한 검증
광대한 로그파일. 사소한 부분의 변화에도 실패
• assertEquals(expectedOutput, output.content());
31. 가독성#3 비트 단언
• assertTrue(Platform.IS_32_BIT ^ Platform.IS_64_BIT);
• 비트 연산에 익숙한 프로그래머는 많지 않다.
32. 가독성#4 부차적 상세정보
• 읽기 쉬운 코드는 읽는 이에게 그 의도와 목적, 의미를 빠
르게 보여준다.
• 그리고 프로그래머는 코드를 훑을 때 본질이 무엇인가를
찾을 뿐, 자잘한 내용은 크게 신경 쓰지 않음.
• 테스트 코드에 부수적인 정보가 넘쳐나 본질이 숨으면?
• 테스트 의도를 파악하기 위해 코드를 빙빙~ 돌아야하면?
33. 가독성#4 부차적 상세정보
• 핵심이 아닌 설정은 private 메서드나 셋업 메서드로 추
출
• 적절한 인자와 서술형 이름을 사용하라
• 한 메서드 안에서는 모두 같은 수준으로 추상화하라
34. 가독성#5 다중인격
• 하나의 몸(메소드)에 여러개의 영혼(단언).
테스트 개선 방법중 가장 쉬운 방법.
• 하나의 테스트는 오직 한 가지만 똑바로 검색해야 한다.
(테스트 당 단언문이 하나를 뜻하는 것은 아님)
• test메소드에 객체를 생성하고 테스트 코드를 작성할때
객체를 생성한 김에 다른 단언문도 몇개 추가해볼까?
35. 가독성#6 쪼개진 논리
• 긴 코드가 나타나면 '흩어진 코드'를
찾아 스크롤을 해야한다.
• 로직을 찾아서.. 데이터 처리과정을 찾아서~
이리저리 해메지 않도록
• 외부 데이터와 코드를 모두 테스트 안으로 옮기자!
• ex) 첫번째 테스트는 변수 할당을 다루고
두번째 테스트는 메서드 호출을 다루자
• 예제는 다음장에..
36. 가독성#6 쪼개진 논리
다음과 같은 명령어가 포함된 쉘을 자바에서 실행 후
테스트를 진행한다면.
sed -i “s/이스빈다/있습니다/g” welcome.txt
테스트 소스뿐만 아니라 쉘 파일까지 모두 검토 해야함.
Eclipse IDE툴이 편리하다고는 하지만...
37. 잠깐. 데이터와 로직의 분리기준
• 짧다면 통합하라
• 통합하기에 너무 길다면
팩토리 메서드나 테스트 데이터 생성기로 분리
• 이것도 쉽지 않다면 독립 파일로 남겨둬라
44. 유지보수성#1 중복
상수 중복 그리고 메소드 중복
@Test
public void emptyTemplate() throws Exception {
assertEquals("", new Template("").evaluate());
}
@Test
public void plainTextTemplate() throws Exception {
assertEquals("plaintext", new Template("plaintext").evaluate());
}
45. 유지보수성#1 중복
1차 상수 중복 개선 (2차는 메소드 중복 개선)
@Test
public void emptyTemplate() throws Exception {
String template = "";
assertEquals(template, new Template(template).evaluate());
}
@Test
public void plainTextTemplate() throws Exception {
String template = "plaintext";
assertEquals(template, new Template(template).evaluate());
}
46. 유지보수성#2 조건부 로직
• 테스트중에 실패가 확인되었다.
• 어디서 실패가 나온건가?
• 조건부 로직이 이를 방애한다.
47. 유지보수성#2 조건부 로직
유지 보수를 어렵게 하는 테스트 코드 속의 조건부 로직
public void returnAnIteratorForContents() throws Exception {
Dictionary dict = new Dictionary();
dict.add("A", new Long(3));
dict.add("B", "21");
for (Iterator e = dict.iterator(); e.hasNext();) {
Map.Entry entry = (Map.Entry) e.next();
if ("A".equals(entry.getKey())) {
assertEquals(3L, entry.getValue());
}
if ("B".equals(entry.getKey())) {
assertEquals("21", entry.getValue());
}
}
}
49. 유지보수성#3 양치기 테스트
• 간헐적으로 실패하는 테스트.
• 날짜, 시간에 따라 동작이 다르거나
입출력, CPU 부하, 네트워크에 따라 다른 결과
50. 유지보수성#4 파손된 파일 경로
테스트를 순식간에 망가뜨릴 수 있는 절대 경로
new File(“C:workspacecatalog.xml”);
-> 개선방법#1
new File("/workspace/catalog.xml");
-> 개선방법#2
new File(“./src/test/data/catalog.xml");
51. 유지보수성#5 끈질긴 임시 파일
• 가장 먼저.. 파일이 꼭 있어야 하는가?
• 테스트에 사용한 임시파일이
다음 테스트까지 남아 있다면?
• @Before 메서드에서 파일을 삭제하라
가능하면 임시 파일명도 유일하게 지어라
• File.createTempFile("catalog", ".xml", DirectoryPath);
JVM프로세스가 종료될 때에나 지워지기 때문에 비추천
52. 유지보수성#6 잠자는 달팽이
• 메모리는 빠르지만
디스크, Network은 상대적으로 느리다
• 또한 Thread.sleep() 역시 느.리.다.
• 예제코드는 다음장에..
• Thread가 동작하는것에 대해
충분한 시간(sleep())을 준다는것은 비효율.
• java.util.concurrent패키지의
CountDownLatch() 활용
53. 유지보수성#6 잠자는 달팽이
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
values.add(counter.getAndIncrement());
}
}
for (int i=0; i < 10; i++ ) {
new Thread(runnable).start();
}
Thread.sleep(500);
assertEquals(.., ..);
54. 유지보수성#8 파라미터화된 혼란
• 파라미터화된 테스트 패턴 이란?
데이터를 아주 조금씩만 바꿔가며 수차례 반복적인 검사.
• 나쁜냄새가 나는 코드
• JUnit이 제공하는 방법
• 여러 단언문을 하나의 메서드로 합치는 방법
55. 유지보수성#8 파라미터화된 혼란
@Test
public void testOne() {
assertEquals("I", format(1));
}
public void testTwo() {
assertEquals("II", format(2));
}
public void testThree() {
assertEquals("III", format(3));
}
public void testFour() {
assertEquals("IV", format(4));
}
56. 유지보수성#8 파라미터화된 혼란
@RunWith(Parameterized.class)
public class RomanNumeralsTest {
private int number;
private String numeral;
public RomanNumeralsTest(int number, String numeral) {
this.number = number;
this.numeral = numeral;
}
@Parameters
public static Collection<Object[]> data() {
return asList(new Object[][] {
{ 1, "I" }, { 2, "II" }, { 3, "III" }, { 4, "IV" } }
}
@Test
public void formatsPositiveIntegers() {
assertEquals(numeral, format(number));
}
}
하지만 테스트 결과가 익명으로 보여짐.
57. 유지보수성#8 파라미터화된 혼란
여러 단언문을 하나의 메서드로 합치기
public void formatsPositiveIntegers() {
assertEquals("I", format(1));
assertEquals("II", format(2));
assertEquals("IV", format(4));
assertEquals("V", format(5));
}
58. 유지보수성#9 메서드간 응집력 결핍
• OOP 의 개념중
낮은 결합도, 높은 응집력
• 각각의 테스트 메소드가 일부 픽스처만 사용
• 픽스처(Fixture)란?
테스트를 수행하는데 필요한 정보나 객체들
• 예제코드는 다음장에..
59. 유지보수성#9 메서드간 응집력 결핍
public class AccountTest {
Account account;
Rule rule1, rule2, rule3, rule4;
..
@Test
public void testCalcTax() {
List list = new Array....
...
create(rule1, 1500);
create(rule2, 80);
}
@Test
public void testCalcFee() {
List list = new Array....
...
create(rule3, 1500);
create(rule4, 80);
}
61. 신뢰성#1 주석으로 변한 테스트
• 특정 테스트 메소드 전체가 주석이 되어 있다면?
• 주석처리한 이유를 찾기 위해 아까운
프로그래머의 시간을 좀먹게 한다.
• 안쓰는건지?
특수한 상황에서만 써야하는건지?
혼란스러워진다.
• 최고의 방법은 지워버리자!
62. 신뢰성#2 오해를 낳는 주석
• 실제 동작과 다른 주석이 있다면?
이런 불일치를 그대로 믿고 진행한다면?
• 주석 대신 더 적절한 변수명/메서드명 사용
• 주석이 사용된 부분을 의미있는 메소드명으로 분리
63. 유지보수성#3 절대 실패하지 않는 테스트
코드가 정상동작한다면 catch 블록이 예외를 잡아서 결과는 성공
코드가 비정상동작하여 예외가 발생하지 않으면 테스트 메서드는 정상
종료
@Test
public void includeForMssingResourceFails() {
try {
new Environment().include("없는 자원");
} catch (IOException e) {
assertThat(.e.getMessage(), contains("없는 자원"));
}
}
64. 유지보수성#3 절대 실패하지 않는 테스트
개선 코드
public void includeForMssingResourceFails() {
try {
new Environment().include("없는 자원");
fail();
} catch (IOException e) {
assertThat(.e.getMessage(), contains("없는 자원"));
}
}
65. 유지보수성#3 절대 실패하지 않는 테스트
더욱 좋은방법은
@Test 애너테이션에 expected 속성 사용
@Test(expected = IOException.class)
public void includeingMissingResourceFails() {
new Environment().include("실존하지 않는 자원");
}
66. 신뢰성#4 지키지 못할 약속
• 아무 '일'도 안하는 테스트
• '무언가 일은 하지만, 정작 '검증'은 안하는 테스트
• 이름값 못하는 테스트
67. 신뢰성#4 지키지 못할 약속
아무 '일'도 안하는 테스트
@Test
public void testFilteringObject() throws Exception {
//List list........
//String result = create(........
//...
//...
//assertThat(........, .......);
}
68. 신뢰성#4 지키지 못할 약속
'무언가 일은 하지만, 정작 '검증'은 안하는 테스트
테스트 메소드에 단언문이 없다면?
Exception이 발생하지 않는 한 정상
69. 신뢰성#4 지키지 못할 약속
이름값 못하는 테스트
@Test
public void zipBetweenTwoArraysProducesAHash() throws Exception
{
...
...
...
assertNotNull("We have a hash back", zipped.flatten();
}
70. 신뢰성#5 낮아진 기대치
종종 게으르고 가장 쉬운 길로 가려는 경향.
쉬운 방법이 옳을 수도 있지만.. 정도가 심하다면?
double sample1 = calc1.cal();
double sample2 = calc2.cal();
assertThat(sample1, is(greaterThan(0.0)));
assertThat(sample2, is(greaterThan(0.0)));
assertTrue(sample1 != sample2);
71. 신뢰성#6 플랫폼 편견
• JAVA가 제창하던
Write Once, Run Anywhere
하지만 실패..
• 플랫폼 편견이란 필요한 플랫폼을 모두
다루지 못하는 것.