Ce diaporama a bien été signalé.
Nous utilisons votre profil LinkedIn et vos données d’activité pour vous proposer des publicités personnalisées et pertinentes. Vous pouvez changer vos préférences de publicités à tout moment.

TDD.JUnit.조금더.알기

3 597 vues

Publié le

Effective Unit Testing

Publié dans : Formation
  • Identifiez-vous pour voir les commentaires

TDD.JUnit.조금더.알기

  1. 1. TDD/JUnit 조금더 알기 TDD/JUnit 파헤치기 myrisinsun@gmail.com
  2. 2. 시작하기전에.. • Effective Unit Testing <2013, 한빛미디어> • 옮긴이의 말
 Effective 로 시작하는 책 대부분은 유행이 아닌
 비교적 오랫동안 가치를 인정받는 듬직함을 보여줌. • 단순한 따라하기, 기교 중심의 어떻게(How)? 가 아닌
 주제에 관한 깊은 이해와 오랜 경험으로부터 우러나온
 왜(Why)? 를 보여준다!
  3. 3. Agile, XP 그리고 TDD 얘들은 왜 뭉쳐 다닐까?
  4. 4. Agile? • 나는 그냥 개념이야. 컨셉같은거? • Less Document-Oriented. • 계획을 통해 주도해가던 과거의 방법이 아닌. • Code-Oriented. • 일정한 주기로 동작하는 코드를 만들어 가는 반복 과정.
  5. 5. XP? • 1999년 Kent Beck의 저서에서 사용
 (Extreme Programming Explained) • 고객이 원하는 양질의 소프트웨어를 빠른 시간안에 전달 을 목표 • XP가 제시하는 실천방법에는 TDD, Pair Programming 등..
  6. 6. 복잡해.. 한줄로 요약을 해보면? TDD는.. Agile 원칙을 지키는 방법론중의 하나인
 XP가 제시하는 실천방법 중 하나!!
  7. 7. 음.. 그래서 TDD는 어떻게 하는건가? 
 코딩 -> 테스트 -> 리팩토링 을 아래의 순서로 진행 테스트 -> 코딩 -> 리팩토링
  8. 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. 9. 그렇다면 TDD로 얻을 수 있는게 뭐지? • 정확한 사용 시나리오가 포함된
 자동화된 실행 가능한 코드 • 군더더기 없는 제품 코드.
  10. 10. 그렇다면 JUnit은 ? ‘테스트’ 자체가 중요시 되면서
 편의성과 자동화된 테스트 환경을 제공해주기 위한
 프레임워크
  11. 11. JUnit은 Reflection을 사용 • Java Reflection API(java.lang.reflect)는
 클래스, 필드, 메소드를 들여다 볼 수 있는 도구. • JVM에서 실행되는 자바 프로그램의 내부 구성을 확인하 거나 런타임시에 프로그램의 작동방법을 변경하고자 하 는 경우에 사용. • 예를들면.. JUnit은 @BeforeClass, @Before 등의 메소 드를 검색하고 실행하는데 활용.
  12. 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. 13. 좋은 테스트란? • 읽기 쉬운 테스트 코드
 -소스코드와 같은 수준의 가독성 유지 • 구조를 잘 갖춘 테스트
 -통짜 클래스가 아닌 적정한 수준의 구성 (화면 스크롤 제약..) • 엉뚱한 걸 검사하지 말자
 -테스트 메소드명은 중요 • 테스트가 얼마나 독립적인가?
 -방금 개봉한 새PC에 버전 관리 서버에서 내려받은 테스트 코드를 바로 실행 • 믿고 쓰는 테스트 및 결과
 -경계값으로 만들어진 테스트들
 -하지만 assert가 없는 테스트라면?
  14. 14. 테스트 더블? • 특별한 목적으로 테스트 전용 장치를 만들어 사용. • 이때 여러 종류의 테스트 장치(객체)를 통칭하는 용어. • 쉽게..
 테스트 하는데 필요한 객체 대신
 의도적으로 변형시킨 다른 객체로 대체하여 사용하는것.
  15. 15. 테스트 전용 장치의 필요성? • 테스트 대상 코드를 격리한다 • 테스트 속도를 개선한다. • 예측 불가능한 실행 요소를 제거한다. • 특수한 상황을 시뮬레이션한다. • 감춰진 정보를 얻어낸다.
  16. 16. 테스트 대상 코드를 격리한다. • 테스트 기준에서 코드는 2가지로 분류 가능
 
 테스트 대상코드
 -Car
 
 테스트 대상코드와 상호작용하는 코드
 -Engine, Route
 
 public class Car {
 private Engine engine;
 private Route route;
 private Audio audio;
  17. 17. 테스트 속도를 개선한다. • 실시간 교통정보를 반영하여 경로를 지도상에 출력 • 경로를 탐색하는 메소드가 아닌
 계산된 경로를 화면에 출력하는 메소드의 테스트는? • 굳이 계산할 필요 없이
 미리 계산해둔 값을 반환하도록 하여 시간을 절약
  18. 18. 예측 불가능한 실행 요소를 제거한다. • 결과에 영향을 주는 모든 요소를 결정적으로 만든다. • 예를들면
 현재의 시간을 기준으로 동작되는 로직에는
 항상 일정한 시간을 리턴하는 테스트 더블을 사용
  19. 19. 특수한 상황을 시뮬레이션 한다. • 실행도중 네트워크가 끊어지는 상황의 재현, 테스트. • 테스트 실행중에 Lan선을 잠시 뽑을까? • throw new java.net.ConnectException("Connection timed out")
  20. 20. 감춰진 정보를 얻어낸다. • 테스트 대상의 멤버변수가 private이면서
 외부에서 값의 확인이 필요할때. • 테스트 대상을 상속, 구현할때 필요한
 getter메소드를 추가하여 테스트 더블을 생성
  21. 21. 테스트 더블의 종류 • 테스트 스텁
 최소한의 형태만 가지는 가짜. • 가짜 객체
 최소한의 로직을 가지는 가짜. • 테스트 스파이
 추가 정보나 임의의 로직을 기존 객체를 변형하여 구현 • Mock객체
 특정조건과 그에 대한 행동을 정의하여 구현
  22. 22. 테스트 스텁 public void log(LogLevel level, String message) { //아무일도 하지 않아요~ } public LogLevel getLogLevel() { // 스텁은 아무일도 하지 않아요~ return LogLevel.WARN; }
  23. 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. 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) { //필요한 정보를 물어보고 확인할 수 있다. } }
  25. 25. Mock 객체 Map<String, String> testMock = mock(Map.class); when( testMock.get("best1") ).thenReturn("Taylor Swift"); when( testMock.get("best2") ).thenReturn("Kelly Clarkson"); when( testMock.get("best3") ).thenReturn("Marie Digby"); System.out.println( testMock.get("best1") ); System.out.println( testMock.get("best2") ); System.out.println( testMock.get("best3") );
  26. 26. Test 코딩의 3단법? 3단구조? 준비-시작-단언 의 3단계로 구현 준비 MP3Tag mp3Tag = new MP3Tag(); ID3v2 id3v2 = null; 시작 mp3Tag.loadFile(filepath); id3v2 = mp3Tag.getTagID3v2(); 단언 assertEquals("Kelly clarkson", ID3v2.getArtist());
  27. 27. Code Smell ? • Code Smell 이란 코드의 버그나 기술적인 문제를 뜻하지 않음.
 개발 속도를 저해하거나 추후 버그나 장애를 일으킬만한
 나쁜 코드 디자인을 뜻한다. • Kent Beck이 최초로 사용했고, 이후 Martin Folwer의 책에 언 급되며 많이 쓰이는 용어.
 (Refactoring: Improving the Design of Existing Code.) • 나쁜 코드의 냄새를 3가지 유형으로 나누어 살펴봄
 가독성
 유지보수성
 신뢰성
  28. 28. 가독성
 Readability
  29. 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. 30. 가독성#2 광역 단언 • 너무 작은 부분까지 단언하려는 집착으로
 하나만 잘못되어도 바로 실패.
 의도했던 핵심 단언이 나머지 단언에 묻힘. • 로그파일에 대한 검증
 광대한 로그파일. 사소한 부분의 변화에도 실패 • assertEquals(expectedOutput, output.content());
  31. 31. 가독성#3 비트 단언 • assertTrue(Platform.IS_32_BIT ^ Platform.IS_64_BIT); • 비트 연산에 익숙한 프로그래머는 많지 않다.
  32. 32. 가독성#4 부차적 상세정보 • 읽기 쉬운 코드는 읽는 이에게 그 의도와 목적, 의미를 빠 르게 보여준다. • 그리고 프로그래머는 코드를 훑을 때 본질이 무엇인가를 찾을 뿐, 자잘한 내용은 크게 신경 쓰지 않음. • 테스트 코드에 부수적인 정보가 넘쳐나 본질이 숨으면? • 테스트 의도를 파악하기 위해 코드를 빙빙~ 돌아야하면?
  33. 33. 가독성#4 부차적 상세정보 • 핵심이 아닌 설정은 private 메서드나 셋업 메서드로 추 출 • 적절한 인자와 서술형 이름을 사용하라 • 한 메서드 안에서는 모두 같은 수준으로 추상화하라
  34. 34. 가독성#5 다중인격 • 하나의 몸(메소드)에 여러개의 영혼(단언).
 테스트 개선 방법중 가장 쉬운 방법. • 하나의 테스트는 오직 한 가지만 똑바로 검색해야 한다.
 (테스트 당 단언문이 하나를 뜻하는 것은 아님) • test메소드에 객체를 생성하고 테스트 코드를 작성할때
 객체를 생성한 김에 다른 단언문도 몇개 추가해볼까?
  35. 35. 가독성#6 쪼개진 논리 • 긴 코드가 나타나면 '흩어진 코드'를
 찾아 스크롤을 해야한다. • 로직을 찾아서.. 데이터 처리과정을 찾아서~
 이리저리 해메지 않도록 • 외부 데이터와 코드를 모두 테스트 안으로 옮기자! • ex) 첫번째 테스트는 변수 할당을 다루고
 두번째 테스트는 메서드 호출을 다루자 • 예제는 다음장에..
  36. 36. 가독성#6 쪼개진 논리 다음과 같은 명령어가 포함된 쉘을 자바에서 실행 후 테스트를 진행한다면. sed -i “s/이스빈다/있습니다/g” welcome.txt 테스트 소스뿐만 아니라 쉘 파일까지 모두 검토 해야함. Eclipse IDE툴이 편리하다고는 하지만...
  37. 37. 잠깐. 데이터와 로직의 분리기준 • 짧다면 통합하라 • 통합하기에 너무 길다면 
 팩토리 메서드나 테스트 데이터 생성기로 분리 • 이것도 쉽지 않다면 독립 파일로 남겨둬라
  38. 38. 가독성#7 매직넘버 대부분의 프로그래머가 동의하는 매직넘버를 피하자 game.roll(10, 12); assertThat(game.score(), is(equalTo(300))); -> 개선방법#1 roll( pins(10), times(12)); -> 개선방법#2 roll(TEN_PINS, TWELVE_TIMES);
  39. 39. 가독성#8 셋업 설교 • 셋업 메서드가 길고, 복잡하고 지루하다면? • 참고할만한 방법 3가지
 -셋업에서 핵심을 제외한 정보는 private 메소드로 추출
 -알맞은 서술적 이름을 사용
 -셋업 내의 추상화 수준을 통일 • 샘플코드는 다음장에…
  40. 40. 가독성#8 셋업 설교 셋업 설교는 짧은 테스트를 위한 너무 긴 준비작업을 뜻한다. @Before public void setUp() throws Exception { String tempDir = System.getProperty("java.io.tmpdir"); tempDir = new File(tempDir, "2014"); tempDir.mkdir(); String fileSeparator = System.propertiesSystem("file.separator"); List<String> listWorkFiles = new ArrayList<String>(); listWorkFiles.add( ..... fileSeparator ....... ); listWorkFiles.add( ..... fileSeparator ....... ); listWorkFiles.add( ..... fileSeparator ....... ); ..기타 등등 코드...
  41. 41. 가독성#8 셋업 설교 세부 정보를 추출하여 한결 명확해진 셋업의 모습 @Before public void setUp() throws Exception { tempDir = createTempDir("2014"); listWorkFiles = createWorkFileList(); .. .. }
  42. 42. 가독성#9 과잉보호 테스트 진짜 단언문 앞에 불필요한 단언문이 있는경우. Data data = project.getData(); assertNotNull(data); assertEquals(4, data.count());
  43. 43. 유지보수성
 Maintainability
  44. 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. 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. 46. 유지보수성#2 조건부 로직 • 테스트중에 실패가 확인되었다. • 어디서 실패가 나온건가? • 조건부 로직이 이를 방애한다.
  47. 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()); } } }
  48. 48. 유지보수성#2 조건부 로직 @Test public void returnAnIteratorForcontents() throws Exception { Dictionary dict = new Dictionary(); dict.add("A", new Long(3)); dict.add("B", "21"); assertContains(dict.iterator(), "A", 3L); assertContains(dict.iterator(), "B", "21"); } private void assertContain(Iterator i, Object key, Object value) { while (i.hasNext()) { Map.Entry entry = (Map.Entry) i.next(); if (key.equals(entry.getKey()) { assertEquals(value, entry.getValue()); return; } } fail("Iterator didn't contain " + key + " => " + value); }
  49. 49. 유지보수성#3 양치기 테스트 • 간헐적으로 실패하는 테스트. • 날짜, 시간에 따라 동작이 다르거나
 입출력, CPU 부하, 네트워크에 따라 다른 결과
  50. 50. 유지보수성#4 파손된 파일 경로 테스트를 순식간에 망가뜨릴 수 있는 절대 경로 new File(“C:workspacecatalog.xml”); -> 개선방법#1 new File("/workspace/catalog.xml"); -> 개선방법#2 new File(“./src/test/data/catalog.xml");
  51. 51. 유지보수성#5 끈질긴 임시 파일 • 가장 먼저.. 파일이 꼭 있어야 하는가? • 테스트에 사용한 임시파일이
 다음 테스트까지 남아 있다면? • @Before 메서드에서 파일을 삭제하라
 가능하면 임시 파일명도 유일하게 지어라 • File.createTempFile("catalog", ".xml", DirectoryPath);
 JVM프로세스가 종료될 때에나 지워지기 때문에 비추천
  52. 52. 유지보수성#6 잠자는 달팽이 • 메모리는 빠르지만
 디스크, Network은 상대적으로 느리다 • 또한 Thread.sleep() 역시 느.리.다. • 예제코드는 다음장에.. • Thread가 동작하는것에 대해
 충분한 시간(sleep())을 준다는것은 비효율. • java.util.concurrent패키지의
 CountDownLatch() 활용
  53. 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. 54. 유지보수성#8 파라미터화된 혼란 • 파라미터화된 테스트 패턴 이란?
 데이터를 아주 조금씩만 바꿔가며 수차례 반복적인 검사. • 나쁜냄새가 나는 코드 • JUnit이 제공하는 방법 • 여러 단언문을 하나의 메서드로 합치는 방법
  55. 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. 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. 57. 유지보수성#8 파라미터화된 혼란 여러 단언문을 하나의 메서드로 합치기 public void formatsPositiveIntegers() { assertEquals("I", format(1)); assertEquals("II", format(2)); assertEquals("IV", format(4)); assertEquals("V", format(5)); }
  58. 58. 유지보수성#9 메서드간 응집력 결핍 • OOP 의 개념중
 낮은 결합도, 높은 응집력 • 각각의 테스트 메소드가 일부 픽스처만 사용 • 픽스처(Fixture)란?
 테스트를 수행하는데 필요한 정보나 객체들 • 예제코드는 다음장에..
  59. 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); }
  60. 60. 신뢰성 Trustworthiness
  61. 61. 신뢰성#1 주석으로 변한 테스트 • 특정 테스트 메소드 전체가 주석이 되어 있다면? • 주석처리한 이유를 찾기 위해 아까운
 프로그래머의 시간을 좀먹게 한다. • 안쓰는건지?
 특수한 상황에서만 써야하는건지?
 혼란스러워진다. • 최고의 방법은 지워버리자!
  62. 62. 신뢰성#2 오해를 낳는 주석 • 실제 동작과 다른 주석이 있다면?
 이런 불일치를 그대로 믿고 진행한다면? • 주석 대신 더 적절한 변수명/메서드명 사용 • 주석이 사용된 부분을 의미있는 메소드명으로 분리
  63. 63. 유지보수성#3 절대 실패하지 않는 테스트 코드가 정상동작한다면 catch 블록이 예외를 잡아서 결과는 성공 코드가 비정상동작하여 예외가 발생하지 않으면 테스트 메서드는 정상 종료 @Test public void includeForMssingResourceFails() { try { new Environment().include("없는 자원"); } catch (IOException e) { assertThat(.e.getMessage(), contains("없는 자원")); } }
  64. 64. 유지보수성#3 절대 실패하지 않는 테스트 개선 코드 public void includeForMssingResourceFails() { try { new Environment().include("없는 자원"); fail(); } catch (IOException e) { assertThat(.e.getMessage(), contains("없는 자원")); } }
  65. 65. 유지보수성#3 절대 실패하지 않는 테스트 더욱 좋은방법은 @Test 애너테이션에 expected 속성 사용 @Test(expected = IOException.class) public void includeingMissingResourceFails() { new Environment().include("실존하지 않는 자원"); }
  66. 66. 신뢰성#4 지키지 못할 약속 • 아무 '일'도 안하는 테스트 • '무언가 일은 하지만, 정작 '검증'은 안하는 테스트 • 이름값 못하는 테스트
  67. 67. 신뢰성#4 지키지 못할 약속 아무 '일'도 안하는 테스트 @Test public void testFilteringObject() throws Exception { //List list........ //String result = create(........ //... //... //assertThat(........, .......); }
  68. 68. 신뢰성#4 지키지 못할 약속 '무언가 일은 하지만, 정작 '검증'은 안하는 테스트 테스트 메소드에 단언문이 없다면? Exception이 발생하지 않는 한 정상
  69. 69. 신뢰성#4 지키지 못할 약속 이름값 못하는 테스트 @Test public void zipBetweenTwoArraysProducesAHash() throws Exception { ... ... ... assertNotNull("We have a hash back", zipped.flatten(); }
  70. 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. 71. 신뢰성#6 플랫폼 편견 • JAVA가 제창하던
 Write Once, Run Anywhere
 
 하지만 실패.. • 플랫폼 편견이란 필요한 플랫폼을 모두
 다루지 못하는 것.

×