< Return to Video

Testing and Refactoring Legacy Code

  • 0:02 - 0:07
    안녕하세요. 산드로 만쿠소 입니다.
  • 0:07 - 0:10
    이 스크린 케스트에선
  • 0:10 - 0:16
    테스트 코드가 없는 기존 코드에
  • 0:16 - 0:18
    먼저 테스트를 작성하고요
  • 0:18 - 0:21
    테스트 커버리지가 100%가 된 후에
  • 0:21 - 0:24
    리팩토링으로 개선하는 것을 보여드리겠습니다.
  • 0:24 - 0:26
    예제의 비즈니스 요구사항은
  • 0:26 - 0:29
    여행자를 위한 소셜 네트워킹 서비스 같은 것입니다.
  • 0:29 - 0:34
    콘텐츠를 보려면 로그인을 해야 하고
  • 0:34 - 0:36
    로그인 후에
  • 0:36 - 0:37
    다른 사람의 여행 일정을 보려면
  • 0:37 - 0:40
    그 사람과 친구인 경우에만 조회가 가능합니다.
  • 0:40 - 0:42
    페이스 북 처럼 말입니다.
  • 0:42 - 0:46
    그 사람과 친구라면 그 사람의 여행 목록을 볼 수 있고
  • 0:46 - 0:48
    친구가 아니라면 볼 수가 없습니다.
  • 0:49 - 0:51
    이 카타에는 규칙이 몇 개 있습니다.
  • 0:51 - 0:56
    테스트가 없는 코드는 수정을 할 수 없습니다.
  • 0:57 - 1:03
    따라서 기존 제품 코드를 수정하려면
  • 1:03 - 1:05
    먼저 기존 코드에 테스트 코드를 작성해야 합니다.
  • 1:05 - 1:10
    유일한 예외는 IDE의 리팩토링 기능을 사용하는 것 입니다.
  • 1:10 - 1:15
    절대 소스 코드에 타이핑해서 수정을 하면 안됩니다.
  • 1:16 - 1:20
    테스트를 작성해가는 순서가 있는데요
  • 1:20 - 1:24
    소스 코드가 이렇게 생겼다면요
  • 1:24 - 1:29
    여기 인덴트가 깊은 부분 말고
  • 1:30 - 1:34
    뎁스가 가장 얕은 부분부터 테스트를 작성할 것 입니다.
  • 1:35 - 1:40
    그 후에 그 다음으로 얕은 부분의
  • 1:40 - 1:41
    테스트를 작성하는 거지요.
  • 1:41 - 1:45
    이런 순서로 테스트를 작성하면
  • 1:46 - 1:48
    테스트를 작성함에 따라 제품 코드를 더 깊게 이해해 갈 수 있고
  • 1:48 - 1:54
    테스트 스윗도 점진적으로 만들어 갈 수 있습니다.
  • 1:54 - 1:58
    만약에 반대로
  • 1:58 - 2:00
    가장 깊은 부분부터 테스트를 작성하려면
  • 2:00 - 2:06
    처음부터 대상 코드를 모두 이해해야 하고
  • 2:07 - 2:09
    많은 양의 테스트 데이터를 준비해야 합니다.
  • 2:09 - 2:13
    그래서 코드의 얕은 부분에서 깊은 부분으로 테스트하는 것이 좋습니다.
  • 2:13 - 2:15
    이제 코드를 볼까요
  • 2:15 - 2:20
    TripService 클래스인데요
  • 2:21 - 2:24
    서버 단 코드겠지요
  • 2:25 - 2:27
    기본적으로
  • 2:27 - 2:29
    유저의 여행 목록을 조회합니다.
  • 2:29 - 2:31
    파라메터로 유저를 받고
  • 2:31 - 2:36
    세션 정보에서
  • 2:36 - 2:38
    로그인 유저 정보를 조회하고
  • 2:39 - 2:45
    로그인하지 않았다면 예외를 던집니다.
  • 2:45 - 2:51
    로그인 했다면
  • 2:51 - 2:57
    로그인 유저가 파라메터로 전달된 유저와 친구인지 확인하고
  • 2:57 - 3:00
    서로 친구라면
  • 3:00 - 3:04
    전달된 유저의 여행 목록을 조회합니다.
  • 3:04 - 3:08
    다 파악한 것은 아닙니다만
  • 3:08 - 3:11
    일단 테스트를 작성해보겠습니다.
  • 3:11 - 3:15
    코딩 하기에 편하게
  • 3:15 - 3:18
    에디터 화면을 배치하도록 하겠습니다.
  • 3:18 - 3:20
    저는 보통 화면을 둘로 나눠서
  • 3:20 - 3:22
    하나는 제품 코드를 두고
  • 3:22 - 3:23
    다른 하나는 테스트 코드를 띄웁니다.
  • 3:23 - 3:27
    화면을 계속 스위칭할 필요가 없어서
  • 3:27 - 3:31
    아주 편리해요.
  • 3:31 - 3:34
    맨 처음으로 할 것은
  • 3:34 - 3:36
    가장 얕은 부분을 찾는 겁니다.
  • 3:36 - 3:37
    그게 어떤 코드인지는 잊어도 되요
  • 3:37 - 3:39
    적어도 잠시 동안은요
  • 3:39 - 3:42
    레거시 코드에 작업을 할 때
  • 3:42 - 3:43
    맨 처음으로 할 것은요
  • 3:43 - 3:45
    레거시 코드가 뭘 하는지 파악하려면요
  • 3:45 - 3:46
    가장 얕은 부분을 찾는 것 입니다.
  • 3:46 - 3:49
    Logged user 변수가 Null이 아니라면
  • 3:49 - 3:51
    예외를 던지네요.
  • 3:51 - 3:54
    유저가 로그인 상태가 아니면 예외를 던져요.
  • 3:54 - 3:57
    테스트 코드로 실제로 그렇게 동작하는지 봅시다.
  • 3:57 - 4:13
    유저가 로그인 상태가 아니면 예외를 던진다
  • 4:13 - 4:17
    예외를 적어줍시다
  • 4:17 - 4:23
    UserNotLoggedInException이 나와야 하는 거지요
  • 4:24 - 4:27
    TripService 인스턴스를 만들어 봅시다
  • 4:33 - 4:35
    먼저 Import 하고
  • 4:35 - 4:37
    그 다음에
  • 4:37 - 4:42
    여행 서비스에서
  • 4:42 - 4:44
    유저의 여행 목록을 조회하고
  • 4:44 - 4:47
    일단 유저에는 null을 넣고요
  • 4:50 - 4:52
    지금 인피니테스트를 사용 중인데요
  • 4:52 - 4:56
    여기 빨간색으로 결과가 표시되지요
  • 4:56 - 4:59
    제가 코드를 저장할 때마다
  • 4:59 - 5:01
    자동으로 필요한 테스트를 실행합니다.
  • 5:01 - 5:03
    매번 손으로 돌릴 필요가 없어요.
  • 5:03 - 5:05
    그런데 원래는 이게..
  • 5:06 - 5:07
    녹색이어야 하는데요
  • 5:07 - 5:10
    뭔가 잘못된 거에요
  • 5:10 - 5:20
    그래서 테스트를 직접 돌리면
  • 5:20 - 5:23
    어떻게 되어야하냐면..
  • 5:23 - 5:28
    UserNotLoggedInException이 나와야 해요
  • 5:28 - 5:31
    UserNotLoggedInException이 나왔어야 하는데
  • 5:31 - 5:35
    의존 클래스 관련된 다른 종류의 예외가 생겼군요
  • 5:35 - 5:38
    어떻게 된거냐하면요
  • 5:41 - 5:47
    이 부분이 실행되었을 때에요
  • 5:49 - 5:55
    유저 세션의 로그인 유저를 조회하는데요
  • 5:56 - 5:58
    유저 세션 코드를 까서 보면
  • 5:58 - 6:01
    물론 이 건 연습을 위한 예제 코드라서 그래요
  • 6:01 - 6:03
    예외가 생기게 일부러 작성해놨습니다.
  • 6:03 - 6:06
    왜냐하면 단위테스트는 다른 클래스들을 호출하면 안되니까요
  • 6:06 - 6:11
    실수로 이 걸 실행시키면 바로 알 수 있게 하려고요.
  • 6:11 - 6:15
    만약에 실제 Http Session을 사용하고 있다면
  • 6:15 - 6:18
    실제 데이터 베이스를 조회하려고 할지도 몰라요
  • 6:18 - 6:20
    단위테스트하는데 그러면 안되지요.
  • 6:21 - 6:23
    이제 제대로 테스트하려면 이 코드를 사용하지 않게 만들어야 합니다.
  • 6:24 - 6:26
    문제는
  • 6:26 - 6:28
    이게 싱글톤이라
  • 6:28 - 6:33
    Mock으로 대체할 수가 없다는 점 입니다.
  • 6:33 - 6:38
    TripService 클래스를 손으로 수정하는 건 금지되어있으니
  • 6:39 - 6:42
    다른 방법을 찾아야겠지요.
  • 6:42 - 6:44
    UserSession을 Mock으로 대체할 방법을 찾아야 합니다.
  • 6:44 - 6:48
    처음에 말한 대로 코드를 수정하는 유일한 방법은
  • 6:48 - 6:51
    IDE의 리팩토링 기능을 이용하는 것 입니다.
  • 6:51 - 6:52
    코드에 직접 타이핑하는 것은 안됩니다.
  • 6:52 - 6:54
    그래서
  • 6:54 - 6:56
    연결점(Seam)을 만들려고 합니다.
  • 6:56 - 7:00
    연결점(Seam)은 클래스들이 이어지는 지점을 말합니다.
  • 7:00 - 7:03
    서로 분리되는 지점이기도 하지요.
  • 7:03 - 7:05
    이제 TripService와 UserSession 사이에
  • 7:05 - 7:06
    연결점을 만들겠습니다.
  • 7:06 - 7:09
    메소드를 하나 만드는데요
  • 7:10 - 7:15
    이름은 getLoggedInUser로 하고
  • 7:17 - 7:18
    접근제한자 protected로
  • 7:19 - 7:27
    이렇게 TripService가 다른 클래스에 접근하는 부분을 분리해서
  • 7:28 - 7:29
    연결점을 만들었습니다.
  • 7:30 - 7:34
    나중에 또 손보겠지만요
  • 7:34 - 7:45
    이제 TripService를 상속받는 TestableTripService 클래스를
  • 7:45 - 7:49
    만들 수 있겠지요
  • 7:52 - 7:54
    여기에서
  • 7:54 - 8:04
    getLoggedInUser 메소드를 오버라이드해서
  • 8:04 - 8:07
    null을 반환하게 만듭니다.
  • 8:11 - 8:18
    이제 테스트는 TripService 대신에 TestableTripService를 사용하는 겁니다.
  • 8:24 - 8:29
    테스트를 실행하면
  • 8:29 - 8:32
    보시는 데로 테스트가 통과합니다.
  • 8:32 - 8:35
    가끔 인피니테스트는 이상하게 동작할 때가 있어서
  • 8:36 - 8:39
    믿기 어려운 경우도 있습니다.
  • 8:39 - 8:45
    지금도 실패했다고 빨갛게 표시되었지만 실제로는 모두 정상이었지요.
  • 8:45 - 8:49
    이걸로도 충분하겠지만
  • 8:50 - 8:53
    테스트를 다시 돌리면.. 모두 통과합니다.
  • 8:54 - 8:59
    코드 커버리지를 확인하면
  • 9:00 - 9:06
    제일 얕은 부분의 코드가 실행되었는지 확인할 수 있습니다.
  • 9:06 - 9:09
    이 것도 팁이에요.
  • 9:09 - 9:11
    저는 레거시 코드로 작업을 할 때
  • 9:11 - 9:13
    항상 코드 커버리지를 확인합니다.
  • 9:13 - 9:15
    보통 코드 커버리지라고 하면
  • 9:15 - 9:17
    테스트 커버리지라고 생각하는 경우가 많은데요
  • 9:17 - 9:19
    저는 커버리지 숫자는 신경 쓰지 않습니다.
  • 9:19 - 9:23
    레거시 코드에 작업을 할 때에는
  • 9:23 - 9:28
    테스트하려는 코드가 실제로 실행되었는지가 중요합니다.
  • 9:28 - 9:31
    긍정 오류나 부정 오류 등을 방지해서
  • 9:31 - 9:33
    정확하게 작업할 수 있습니다.
  • 9:33 - 9:35
    좋습니다.
  • 9:35 - 9:42
    커버리지 표시는 지우고요.
  • 9:47 - 9:50
    그나저나 테스트 코드가 마땅치 않네요
  • 9:50 - 9:53
    이 테스트는요
  • 9:53 - 9:56
    유저가 로그인 상태가 아니면 예외를 던진다
  • 9:57 - 10:02
    이건 로그인하지 않은 유저에 대한 테스트인데요
  • 10:03 - 10:05
    로그인하지 않은 유저는 어디를 보면 알 수 있지요?
  • 10:05 - 10:08
    TestableTripService를 생성해서
  • 10:08 - 10:11
    Null을 인자로 메소드를 호출해서 예외가 생기는데요.
  • 10:11 - 10:15
    로그인 하지 않은 유저는 어디에 있을까요?
  • 10:17 - 10:19
    여기지요. 감춰져 있습니다.
  • 10:19 - 10:21
    그럼 유저 상태가 표현되도록
  • 10:21 - 10:24
    수정해야겠지요.
  • 10:24 - 10:27
    그나저나 이 부분은 조금 특이하지요?
  • 10:27 - 10:30
    자바에서는 조금 이상한 방식이지만
  • 10:30 - 10:32
    루비에서 종종 사용하는 방법입니다.
  • 10:32 - 10:34
    에디터에서 코드를 이렇게 접으면
  • 10:34 - 10:39
    이렇게 명세서처럼 표현이 됩니다.
  • 10:39 - 10:40
    그냥 명세서인 것처럼 읽는 거지요
  • 10:40 - 10:45
    "trip service should do something"
  • 10:45 - 10:50
    저는 이런 방식을 좋아합니다.
  • 10:50 - 10:56
    로그인 유저 정보가 전혀 안보이니
  • 10:56 - 10:58
    하나 만들도록 하겠습니다.
  • 10:59 - 11:06
    loggedInUser를 null로 만들고
  • 11:08 - 11:10
    멤버 변수로 만든 후에
  • 11:10 - 11:16
    로그인 유저를 조회할 때에
  • 11:18 - 11:23
    loggedInUser 필드를 반환하게 하는 거지요.
  • 11:24 - 11:29
    조금 더 좋아졌지요? 테스트 코드에 유저가 표현되고 있어요.
  • 11:32 - 11:35
    음.. Null은 좀 별로네요
  • 11:35 - 11:39
    여기 이 Null의 의미가 분명하지가 않아요.
  • 11:39 - 11:51
    유저가 로그인하지 않았다는 건
  • 11:51 - 11:55
    보통 웹 어플리케이션에서
  • 11:57 - 12:01
    로그인하지 않은 유저는 게스트지요.
  • 12:01 - 12:06
    여기에 비즈니스 개념을 적용해 볼 수 있겠네요
  • 12:06 - 12:11
    비즈니스 인력과 대화할 때 사용하는 표현들이요.
  • 12:11 - 12:14
    네 도메인 언어를 사용하기 시작했습니다.
  • 12:15 - 12:22
    이 건 여행 목록을 조회할 유저지요
  • 12:23 - 12:26
    Null 말고 더 명시적으로
  • 12:28 - 12:31
    Unused User라고 바꾸겠습니다.
  • 12:40 - 12:45
    어디 볼까요
  • 12:46 - 12:53
    이 번에는 인피니테스트가 제대로 동작할까요..
  • 12:54 - 12:55
    아니군요.
  • 12:55 - 12:58
    안타깝게도 인피니테스트를 믿기 어렵겠네요.
  • 13:02 - 13:05
    테스트 코드가 많이 개선되었어요.
  • 13:05 - 13:08
    그 사이에 인피니테스트가 정상으로 돌아왔네요.
  • 13:09 - 13:12
    말씀 드렸던 것처럼
  • 13:12 - 13:15
    이 건 유저가 로그인하지 않았으면 예외를 던지는 테스트인데요
  • 13:15 - 13:17
    현재 유저는 게스트
  • 13:17 - 13:19
    그리고 예외를 던지지요
  • 13:19 - 13:20
    좋습니다.
  • 13:20 - 13:22
    이제 코드 커버리지를 다시 돌려보겠습니다.
  • 13:22 - 13:23
    이제 코드 커버리지를 다시 돌려보겠습니다.
  • 13:30 - 13:32
    그 전에 JUnit을 실행하고요
  • 13:32 - 13:34
    그냥 확실하게 하는 거에요
  • 13:34 - 13:35
    좋습니다.
  • 13:35 - 13:37
    이제 코드 커버리지를 실행합니다.
  • 13:43 - 13:45
    자 이제
  • 13:45 - 13:47
    코드 커버리지를 보면요
  • 13:47 - 13:51
    다음으로 얕은 부분을 쉽게 찾을 수 있습니다.
  • 13:52 - 13:56
    이 For 반복문이겠지요
  • 13:57 - 14:00
    친구인 경우라면..
  • 14:00 - 14:02
    여기가 제일 깊은 부분이네요
  • 14:02 - 14:06
    이 부분이 실행되려면 이 값이 true 이어야 하니까요
  • 14:06 - 14:10
    그러니까 여기까지 실행되려면..
  • 14:10 - 14:14
    유저는 로그인 된 상태인데 조회하려는 유저와는 친구가 아닌 경우입니다.
  • 14:15 - 14:20
    그럼 이 시나리오를 검증하는 테스트를 작성하겠습니다.
  • 14:20 - 14:26
    커버리지는 그대로 두고요
  • 14:28 - 14:32
    테스트 이름은
  • 14:34 - 14:48
    유저가 서로 친구가 아닌 경우에는 여행 목록을 반환하지 않는다로 하겠습니다.
  • 14:48 - 14:54
    여기 코드를 가져와서요
  • 14:57 - 15:00
    이제는 로그인한 유저가 필요하지요
  • 15:00 - 15:04
    이름은 Registered User로 하고
  • 15:05 - 15:07
    멤버 변수를 만들겠습니다.
  • 15:14 - 15:24
    여행 목록이 필요하겠지요
  • 15:24 - 15:30
    이름은 friendTrips로 하고
  • 15:30 - 15:32
    import 하고
  • 15:43 - 15:45
    이제
  • 15:45 - 15:46
    제대로 된 유저가 필요하겠지요
  • 15:46 - 15:49
    여기에요
  • 15:49 - 15:51
    그러니까
  • 15:52 - 15:55
    이름은 friend로 하고
  • 15:56 - 16:00
    지금 친구가 필요하지요
  • 16:04 - 16:11
    친구를 추가해주고
  • 16:14 - 16:19
    브라질로 여행을 간다고 합시다
  • 16:22 - 16:25
    이제 추가한 친구를 멤버 변수로 만들겠습니다.
  • 16:41 - 16:45
    친구를 만들었지요
  • 16:45 - 16:49
    이 친구는 브라질로 여행을 가는 또 다른 친구가 있습니다.
  • 16:49 - 16:53
    하지만 현재 로그인한 유저와는
  • 16:53 - 16:56
    친구가 아니네요.
  • 16:56 - 17:02
    그래서 반환된 여행 목록은
  • 17:04 - 17:09
    크기가 0 이어야 합니다.
  • 17:11 - 17:14
    size() 대신에 isEmpty를 사용했어도
  • 17:14 - 17:16
    무방하겠지요.
  • 17:16 - 17:20
    인피니테스트는 잘 돌고 있군요
  • 17:20 - 17:25
    확실하게 JUnit을 직접 실행하겠습니다.
  • 17:25 - 17:27
    테스트는 통과했습니다.
  • 17:27 - 17:30
    이미 동작하던 코드들이니
  • 17:30 - 17:31
    당연히 테스트는 통과하겠지요.
  • 17:31 - 17:33
    지금은 코드가 어떻게 동작하는지 알아보려고 테스트를 작성하니까요
  • 17:33 - 17:35
    당연한 겁니다.
  • 17:35 - 17:37
    코드 커버리지를 다시 돌려봅시다.
  • 17:38 - 17:41
    커버리지를 보면 조금 더 진도가 나간 것을 알 수 있습니다.
  • 17:41 - 17:45
    보시는 것처럼 조금 더 깊이 들어갔어요
  • 17:45 - 17:46
    어떤 코드 였냐면
  • 17:46 - 17:49
    유저가 로그인 상태이긴 해도
  • 17:49 - 17:51
    서로 친구는 아닌 경우였지요
  • 17:52 - 17:57
    이 부분이 항상 false를 반환했지요
  • 17:57 - 17:59
    아직 끝난 게 아닙니다.
  • 17:59 - 18:02
    테스트에 중복된 부분들이 있으니
  • 18:02 - 18:04
    리팩토링을 해야 합니다.
  • 18:04 - 18:06
    중복 코드를 없애겠습니다.
  • 18:07 - 18:11
    @Before 메소드를 만들고요
  • 18:13 - 18:15
    로컬 변수를
  • 18:15 - 18:19
    멤버 변수로 바꾸겠습니다.
  • 18:19 - 18:21
    좋습니다.
  • 18:21 - 18:22
    좋아요.
  • 18:25 - 18:27
    위로 옮기겠습니다.
  • 18:36 - 18:37
    좋습니다.
  • 18:38 - 18:44
    이제 제일 깊은 부분을 테스트하겠습니다.
  • 18:44 - 18:46
    이 부분이 실행되려면
  • 18:46 - 18:52
    로그인 유저가 조회 대상인 유저와 친구이고
  • 18:53 - 18:56
    여행이 조회되는 시나리오가 필요합니다.
  • 19:00 - 19:08
    유저가 서로 친구인 경우에 친구의 여행 목록을 반환한다.
  • 19:09 - 19:11
    이 걸 테스트 해야 하지요.
  • 19:11 - 19:14
    이 부분을 옮겨서...
  • 19:23 - 19:25
    소스 코드는 절대 Copy & Paste 하지 마세요.
  • 19:25 - 19:27
    저는 절대 그러지 않습니다.
  • 19:27 - 19:29
    지금 본 것은 착각입니다.
  • 19:29 - 19:33
    소스 코드는 절대 Copy & Paste 하지 마세요.
  • 19:34 - 19:38
    더 추가해야 할 것은요
  • 19:39 - 19:46
    로그인 유저를 친구로 추가해주고
  • 19:47 - 19:49
    여행도 하나 더 추가합시다
  • 20:03 - 20:08
    인피니테스트에는 테스트가 실패했다고 나오지만요
  • 20:08 - 20:12
    유저들끼리 친구가 되었으니
  • 20:12 - 20:14
    테스트는 통과할 겁니다.
  • 20:14 - 20:15
    저장을 하면
  • 20:15 - 20:19
    여전히 빨간색이네요
  • 20:19 - 20:22
    테스트를 직접 실행하면
  • 20:22 - 20:25
    이런 단축키를 잘 못 눌렀네요
  • 20:25 - 20:28
    윈도우즈를 사용했었는데
  • 20:28 - 20:33
    맥이랑 인텔리제이랑 이클립스랑 바꾸다 보니 헷갈리네요.
  • 20:33 - 20:37
    그래도 역시 테스트가 실패합니다. 왜냐하면
  • 20:38 - 20:39
    화면 배치는 일단 돌려놓고
  • 20:40 - 20:42
    테스트가 여전히 실패하는 이유는
  • 20:42 - 20:47
    이 TripDao 때문입니다.
  • 20:47 - 20:49
    이건 Static 메소드이고
  • 20:50 - 20:53
    메소드를 실행하면 그냥
  • 20:53 - 20:55
    예외를 던지게 만들어놨습니다.
  • 20:55 - 20:57
  • 20:57 - 21:00
    TripDAO를 이렇게 만들어 놓은 이유는요
  • 21:00 - 21:01
    단위 테스트에서는 이런 DAO 같은 다른 클래스들을 실행시키면 안되는데
  • 21:01 - 21:07
    혹시 실수로 TripDAO를 실행한 경우 바로 알 수 있게 하려고 입니다.
  • 21:07 - 21:13
    실제 시스템의 DAO는 데이터베이스에 연결해서 데이터를 조회하겠지만
  • 21:13 - 21:17
    테스트에서는 그러면 안됩니다.
  • 21:17 - 21:22
    이제 UserSession에 했던 것과
  • 21:22 - 21:25
    같은 작업을 할 겁니다.
  • 21:25 - 21:30
    싱글톤이나 Static 메소드 호출 같은
  • 21:30 - 21:33
    강한 의존 관계를 떼어내는 거지요
  • 21:33 - 21:37
    메소드에서 객체를 생성하는 것도 똑 같습니다.
  • 21:37 - 21:39
    자 그러면
  • 21:40 - 21:42
    메소드를 추가하겠습니다.
  • 21:42 - 21:49
    이름은 tripsBy로 하고
  • 21:49 - 21:51
    접근제한자는 protected
  • 21:51 - 21:56
    이제 테스트 코드로 돌아가서
  • 22:02 - 22:04
    똑 같은 작업을 하겠습니다.
  • 22:17 - 22:20
    기본적으로
  • 22:20 - 22:22
    여행 목록을 반환할 건데요
  • 22:22 - 22:24
    조회할 유저를 받아서
  • 22:24 - 22:27
    이 테스트 유저는 여행 일정을 두 개 가지고 있으니까
  • 22:29 - 22:32
    그 걸 반환할 겁니다.
  • 22:32 - 22:35
    the user dot trips
  • 22:36 - 22:39
    바로 테스트 결과에 반영이 되었군요.
  • 22:40 - 22:44
    코드 커버리지를 확인하면
  • 22:44 - 22:45
    좋습니다.
  • 22:45 - 22:51
    이제 기존 코드의 대부분이
  • 22:51 - 22:54
    테스트 되었네요.
  • 22:54 - 23:00
    기존 코드를 상속 받은 부분은 테스트가 아니라고 할 수도 있겠지만
  • 23:00 - 23:02
    작은 연결점이니까 괜찮아요
  • 23:02 - 23:05
    작은 연결점이니까 괜찮아요
  • 23:05 - 23:07
    이게 최종 모습이 아니기도 하고요
  • 23:07 - 23:12
    조금 더 정리를 해 볼까요
  • 23:12 - 23:13
    보통은요..
  • 23:13 - 23:19
    이 건 여기에도 있고 저기에도 있네요
  • 23:19 - 23:25
    이런 중복 코드는 절대 좋지 않지요
  • 23:25 - 23:26
    이 건 남겨 두고요
  • 23:26 - 23:28
    이 건 이쪽으로 옮기겠습니다.
  • 23:30 - 23:32
    그러면 다른 모든 테스트에 공통이 되지요
  • 23:32 - 23:37
    로그인 된 유저가 필요한 모든 테스트에 유효합니다.
  • 23:37 - 23:40
    그런 후에
  • 23:40 - 23:42
    저장하고
  • 23:42 - 23:44
    테스트를 직접 돌리면
  • 23:44 - 23:46
    여전히 녹색이네요
  • 23:46 - 23:48
    좋습니다.
  • 23:48 - 23:52
    이 걸로 첫 단계가 완료되었습니다.
  • 23:52 - 23:56
    테스트가 다 작성되었으니 이제 리팩토링을 시작할 수 있게 되었습니다.
  • 23:57 - 24:02
    커밋하기 딱 좋은 시점이네요.
  • 24:02 - 24:06
    git status
  • 24:06 - 24:08
    git commit
  • 24:08 - 24:18
    unit tests for trip service
  • 24:18 - 24:20
    좋습니다.
  • 24:24 - 24:26
    그런데 이 테스트를 다시 보면요
  • 24:26 - 24:30
    레거시 코드는 종종..
  • 24:30 - 24:32
    지금은 아주 간단한 예제를 보고 있습니다만
  • 24:33 - 24:39
    훨씬 복잡한 메소드를 생각해보세요
  • 24:39 - 24:42
    예제에선 데이터 객체를 하나만 만들어도 충분했지만
  • 24:42 - 24:43
    예제에선 데이터 객체를 하나만 만들어도 충분했지만
  • 24:43 - 24:48
    코드를 제대로 테스트하려면 종종
  • 24:48 - 24:50
    거대하고 복잡한 테스트 데이터를 만들어야 하는 경우가 있습니다.
  • 24:50 - 24:53
    거대하고 복잡한 테스트 데이터를 만들어야 하는 경우가 있습니다.
  • 24:53 - 24:55
    그럼 테스트 데이터를 생성하는 코드를 정리해볼까요?
  • 24:55 - 24:58
    여기 저기에 중복 코드들이 있지요
  • 24:58 - 25:02
    빌더 패턴을 적용해보겠습니다.
  • 25:02 - 25:04
    빌더 패턴을 적용해보겠습니다.
  • 25:04 - 25:09
    기본적으로 이런 식으로 사용하게 되는 거지요
  • 25:09 - 25:12
    테스트용 유저를 생성하는데
  • 25:12 - 25:19
  • 25:24 - 25:35
    ANOTHER_USER, loggedInUser와 친구이고
  • 25:36 - 25:46
    브라질과 런던으로 여행을 가지요.
  • 25:47 - 25:50
    이런 식으로 테스트 데이터를 만드는 거에요.
  • 25:50 - 25:52
    가독성이 훨씬 좋습니다.
  • 25:52 - 25:55
    그럼 실제로 구현을 해볼까요
  • 25:55 - 25:58
    일단 build 메소드를 추가합시다.
  • 25:58 - 26:00
    빌더 패턴을 적용하는 거지요.
  • 26:02 - 26:05
    UserBuilder를 추가하고
  • 26:07 - 26:12
    이런 모양을 원하는 겁니다.
  • 26:12 - 26:16
    이 부분은 주석 처리하고요
  • 26:17 - 26:25
  • 26:25 - 26:28
  • 26:31 - 26:34
    aUser 메소드를 만들고요
  • 26:48 - 26:50
    메소드를 이어서 호출 할 수 있도록
  • 26:50 - 26:53
    계속 같은 오브젝트를 반환합니다
  • 26:53 - 26:55
    이러면 메소드를 계속 이어서 호출할 수 있게 됩니다.
  • 26:55 - 26:57
    UserBuilder를 생성해서 반환하고
  • 27:01 - 27:05
    다음 메소드를 만들어 봅시다
  • 27:08 - 27:10
    역시 UserBuilder를 반환하고요
  • 27:10 - 27:15
    두 개의 유저를 받고 있습니다.
  • 27:17 - 27:22
    항상 말 했듯이 프로그래밍에는 딱 3개의 숫자만 존재합니다.
  • 27:22 - 27:24
    0, 1, 그리고 많이 입니다.
  • 27:24 - 27:27
    그 밖에는 없어요.
  • 27:27 - 27:30
    인자를 여러 개를 받도록 변경합시다
  • 27:31 - 27:33
    필요한 수만큼 입력할 수 있도록
  • 27:33 - 27:37
    가변길이 인자를 사용하겠습니다.
  • 27:38 - 27:41
    이름은 friends 로 하고
  • 27:43 - 27:48
    friends 변수에 할당하고
  • 27:48 - 27:51
    계속 UserBuilder 인스턴스를 반환합니다.
  • 27:51 - 27:53
    이 것도 만들어 주고요
  • 27:53 - 27:58
    배열은 반드시 초기화 해야겠지요
  • 27:58 - 28:01
  • 28:05 - 28:08
  • 28:09 - 28:16
    좋습니다. 다음 메소드를 만들어 볼까요
  • 28:16 - 28:19
  • 28:19 - 28:22
    똑 같이 하는 겁니다.
  • 28:22 - 28:28
    메소드에 원하는 개 수만큼 여행 정보를 넣어 줄 수 있겠지요
  • 28:37 - 28:39
  • 28:39 - 28:41
    멤버 변수로 만들고요
  • 28:41 - 28:45
    저는 보통
  • 28:45 - 28:49
    읽기 좋은 모양으로 사용하는 코드를 작성하고
  • 28:49 - 28:52
    그거에 맞춰서 구현 코드를 생성해갑니다.
  • 28:53 - 28:57
    이 번엔 build 메소드를 생성합니다
  • 28:57 - 29:04
    맨 밑으로 옮기는 게 좋겠지요
  • 29:04 - 29:11
    build 메소드가 실제로 객체를 생성하게 됩니다.
  • 29:12 - 29:15
    유저를 생성하고
  • 29:16 - 29:17
    이 유저를 반환하게 하고
  • 29:17 - 29:19
    그리고
  • 29:20 - 29:23
    여행 목록을 추가하고
  • 29:23 - 29:29
    친구들을 추가합니다.
  • 29:32 - 29:38
    메소드 이름은 파라메터 이름과 함께 정하는 게 좋습니다.
  • 29:38 - 29:41
    읽기에도 좋고
  • 29:41 - 29:43
    이름에 중복을 피할 수도 있지요.
  • 29:43 - 29:46
    여행 정보부터 시작할까요
  • 29:46 - 29:50
  • 29:50 - 29:52
  • 29:52 - 29:56
  • 29:56 - 30:03
    여행을 추가하고
  • 30:04 - 30:07
    친구 목록도 똑같이 하겠습니다
  • 30:07 - 30:09
    메소드를 만들고
  • 30:11 - 30:12
  • 30:12 - 30:15
  • 30:15 - 30:17
  • 30:17 - 30:20
  • 30:20 - 30:22
  • 30:22 - 30:28
  • 30:29 - 30:31
    좋습니다.
  • 30:31 - 30:36
    인피니테스트 결과도 녹색이군요.
  • 30:36 - 30:40
    이제 이건 지우고요
  • 30:41 - 30:44
    여기에도 UserBuilder를 사용할 수 있겠지요
  • 30:44 - 30:45
    수정 합시다
  • 31:15 - 31:19
    좋고요. 이 건 주석 처리하고.
  • 31:19 - 31:22
    테스트를 다시 실행하면, 여전히 성공합니다.
  • 31:22 - 31:24
    좋습니다.
  • 31:25 - 31:30
    테스트 가독성이 조금 더 좋아졌습니다.
  • 31:30 - 31:34
    이제 빌더 클래스를 별도 파일로 빼겠습니다.
  • 31:34 - 31:35
  • 31:35 - 31:37
    Type To New File 기능을 사용해서
  • 31:39 - 31:41
    별도 클래스로 만들어서
  • 31:41 - 31:48
    일단 TripServiceTest와 같은 위치에 두겠습니다.
  • 31:48 - 31:49
    일단 TripServiceTest와 같은 위치에 두겠습니다.
  • 31:50 - 31:52
    이제 분리되었지요.
  • 31:52 - 31:56
    static import로 변경하고
  • 31:56 - 31:59
    이 것도 static import로 변경하고
  • 31:59 - 32:02
    코드 모양을 맞추겠습니다.
  • 32:02 - 32:05
    좋고요.
  • 32:05 - 32:09
    import를 정리하고요
  • 32:09 - 32:12
    사용하지 않는 import가 있으면 안되겠지요
  • 32:12 - 32:14
    좋습니다.
  • 32:14 - 32:17
    이제 테스트가 훨씬 마음에 드네요
  • 32:17 - 32:20
    테스트를 읽기가 훨씬 좋아졌네요.
  • 32:20 - 32:23
    이제 커밋을 해야겠지요?
  • 32:23 - 32:24
    봅시다
  • 32:24 - 32:26
  • 32:26 - 32:29
  • 32:30 - 32:31
  • 32:31 - 32:46
    UserBuilder created and used in TripServiceTest
  • 32:46 - 32:49
    좋습니다.
  • 32:53 - 32:56
    이제 진짜로 리팩토링을 시작할 수 있겠습니다.
  • 32:56 - 32:58
    아까 이야기 했던 게요
  • 32:58 - 33:00
    어디 있더라
  • 33:02 - 33:07
    테스트는 얕은 부분부터 깊은 부분으로 작성하는데요
  • 33:08 - 33:10
    하지만 리팩토링은 그 반대로 진행합니다.
  • 33:10 - 33:13
    가장 깊은 부분에서 시작해서
  • 33:14 - 33:17
    얕은 부분으로 가는 거지요.
  • 33:18 - 33:19
    왜냐하면
  • 33:19 - 33:22
    중간 부분부터 리팩토링을 하려면
  • 33:22 - 33:24
    코드를 전부 이해하고 있어야 하기 때문입니다.
  • 33:24 - 33:25
    일반적으로
  • 33:27 - 33:30
    레거시 코드는 글로벌 변수가 많고
  • 33:30 - 33:31
    의존관계도 복잡하고
  • 33:31 - 33:34
    변수도 여기 저기서 세팅하고 사용되는데요.
  • 33:34 - 33:37
    가장 깊은 부분부터 시작하면요
  • 33:37 - 33:40
    가장 깊은 부분은 뭔가에 의존하지 않습니다.
  • 33:40 - 33:43
    다른 곳에서 주는 정보를 사용해서
  • 33:43 - 33:45
    뭔가 하는 게 일반적입니다.
  • 33:45 - 33:48
    깊은 부분을 추출하면요
  • 33:48 - 33:53
    깊은 부분부터 리팩토링을 시작하면
  • 33:53 - 33:54
    코드가 점점 줄어들게 됩니다.
  • 33:54 - 33:57
    소스 코드를 볼까요
  • 33:57 - 34:00
    가장 깊은 부분은요
  • 34:00 - 34:02
    아까 본 것처럼 여기입니다.
  • 34:02 - 34:05
    이 코드가 실행되려면
  • 34:05 - 34:07
    이게 true 여야 하는데
  • 34:07 - 34:09
    여기에서 세팅되는군요.
  • 34:09 - 34:12
    이 예제 코드는요
  • 34:12 - 34:14
    가장 깊은 부분에서
  • 34:14 - 34:15
    하는 일이 별로 없어요
  • 34:15 - 34:18
    DAO를 호출하는 게 전부라서
  • 34:18 - 34:21
    특별히 리팩토링 할게 없습니다.
  • 34:21 - 34:25
    다음으로 깊은 부분은 여기인데요
  • 34:26 - 34:28
    네 이 부분이요
  • 34:28 - 34:30
    이 부분을 수정해보겠습니다.
  • 34:32 - 34:34
    레거시 코드에 작업을 할 때에
  • 34:34 - 34:36
    아주 중요한 것은
  • 34:36 - 34:41
    메소드가 이렇게 큰 원인을 파악하는 겁니다.
  • 34:41 - 34:44
    왜 이렇게 복잡할까요?
  • 34:44 - 34:51
    일반적으로 메소드가 하는 일이 너무 많아서입니다.
  • 34:51 - 34:53
    하는 일이 많으니 크기가 커지겠지요
  • 34:53 - 34:58
    처리 알고리즘을 개선하거나
  • 34:58 - 35:01
    메소드를 추출하는 것 보다 더 중요한 것은
  • 35:01 - 35:03
    정말 이 일을
  • 35:03 - 35:05
    이 메소드가 하는 게 적절한가를
  • 35:05 - 35:08
    지속적으로 생각해봐야 합니다.
  • 35:08 - 35:11
    이 경우에는
  • 35:13 - 35:14
    이 부분을 보면
  • 35:14 - 35:24
    유저 객체에 친구 정보를 요청하지요
  • 35:24 - 35:28
    유저 객체에서 받은 친구 목록을
  • 35:28 - 35:30
    하나 하나 검사해서
  • 35:30 - 35:34
    로그인 유저가 포함돼있는지를 확인하고 있습니다.
  • 35:34 - 35:36
    이 건 코드 스멜 중 Feature Envy 입니다.
  • 35:36 - 35:40
    TripService 클래스가 User 클래스의 역할을 탐내는 거죠
  • 35:40 - 35:42
    User 클래스가 되고 싶은 겁니다.
  • 35:42 - 35:48
    여기서 Feature Envy를 풀려면 어떻게 해야지요?
  • 35:48 - 35:53
    User 클래스가 친구 목록을 가지고 있다면
  • 35:53 - 35:55
    User 클래스에게 물어보면 됩니다.
  • 35:55 - 35:59
    "이봐 이 사람이랑 친구인가?" 라고요.
  • 35:59 - 36:03
    그럼 이 부분을 옮기겠습니다.
  • 36:03 - 36:05
    일단은요
  • 36:05 - 36:07
    유저 클래스를 봅시다.
  • 36:07 - 36:09
    어디 있지요?
  • 36:10 - 36:13
    이 건 닫고
  • 36:13 - 36:15
    여기 있네요.
  • 36:17 - 36:19
    유저 클래스의 테스트를 작성합시다.
  • 36:19 - 36:24
    이 건 유저 클래스고
  • 36:24 - 36:30
    테스트 케이스를 새로 만들고요
  • 36:30 - 36:35
    테스트 경로에 넣어야겠지요
  • 36:36 - 36:40
    테스트 클래스가 만들어 졌고요
  • 36:40 - 36:44
    이 쪽으로 옮기겠습니다.
  • 36:44 - 36:47
    네 이제 뭘 해야지요?
  • 36:47 - 36:53
    친구인지 확인하는 기능을 User 클래스로 옮길 겁니다.
  • 36:53 - 36:55
    그냥 User 클래스에게 물어보는 거지요.
  • 36:55 - 36:58
    "저 사람이랑 친구야?" 라고요.
  • 36:58 - 36:59
  • 36:59 - 37:02
    테스트 먼저 작성해야겠지요
  • 37:06 - 37:11
    유저들이 친구가 아니면 친구가 아니라고 해야 한다
  • 37:11 - 37:13
    친구가 아닌 경우부터 테스트하겠습니다.
  • 37:13 - 37:15
    제일 쉬운 테스트이니까요
  • 37:15 - 37:17
    가장 간단해요
  • 37:19 - 37:22
    UserBuilder를 사용해야겠지요
  • 37:22 - 37:35
  • 37:35 - 37:45
    Bob과 친구라고 합시다
  • 37:45 - 37:49
  • 37:50 - 37:54
    그렇다면
  • 37:55 - 38:13
    Paul과는 친구가 아니겠지요
  • 38:15 - 38:21
    보시다시피 Bob과 친구인 유저를 하나 만들고
  • 38:21 - 38:25
    유저에게 Paul과 친구냐고 물어보는 거지요
  • 38:25 - 38:26
    당연히 친구가 아니겠지요
  • 38:26 - 38:30
    Bob과 Paul을 생성하겠습니다
  • 38:38 - 38:40
    Paul도 만들어야지요
  • 38:49 - 38:53
    이 걸 User 클래스에 추가하려고요
  • 38:53 - 38:55
    생성하겠습니다.
  • 39:00 - 39:06
    이 테스트를 통과하기 위해 필요한 최소한의 구현은
  • 39:06 - 39:08
    false를 반환하는 거지요.
  • 39:08 - 39:10
    저장 하자마자
  • 39:10 - 39:15
    인피니테스트가 테스트가 성공했다고 알려주네요.
  • 39:15 - 39:16
    멋지군요.
  • 39:17 - 39:23
    유저끼리 친구인 경우도 테스트하겠습니다.
  • 39:23 - 39:35
    유저들이 친구인 경우 친구라고 알려야 한다.
  • 39:38 - 39:42
    이 코드를 가져오겠습니다
  • 39:42 - 39:44
    밑으로 내려서
  • 39:44 - 39:47
    어떻게 할거냐 하면
  • 39:47 - 39:51
    Paul과도 친구로 해주고
  • 39:51 - 39:58
    그러면 당연히
  • 39:58 - 40:00
    true가 되겠지요
  • 40:00 - 40:05
    테스트를 통과하지 못했군요
  • 40:06 - 40:08
    실제로 구현을 해야겠지요
  • 40:08 - 40:11
  • 40:13 - 40:14
    파라메터 이름으로 Paul은 맞지 않겠지요
  • 40:14 - 40:18
    anotherUser로 변경하고요
  • 40:26 - 40:32
    좋습니다. 메소드 구현이 되었네요.
  • 40:32 - 40:36
    테스트로 실행도 되고 있지요.
  • 40:36 - 40:38
    이 부분은 실행되지 않았지만
  • 40:38 - 40:40
    상관없지요.
  • 40:41 - 40:44
    다시 커밋하기에 좋은 타이밍이지요?
  • 40:46 - 40:48
  • 40:48 - 40:49
  • 40:49 - 40:51
  • 40:55 - 41:03
    isFriendsWith(user) method added to User
  • 41:04 - 41:06
    좋습니다.
  • 41:08 - 41:12
    다시 테스트로 돌아가서
  • 41:12 - 41:19
    커버리지 결과는 지우고요
  • 41:21 - 41:24
    이제 User 클래스에 새로운 행위가 추가되었습니다.
  • 41:25 - 41:26
    리팩토링을 할 때에는요
  • 41:26 - 41:29
    항상 테스트가 성공하는 상태로 작업할 수 있게 노력해야 합니다.
  • 41:29 - 41:31
    항상 테스트가 성공하는 상태로 작업할 수 있게 노력해야 합니다.
  • 41:31 - 41:33
    계획 없이 마구 고쳐버리면 안됩니다.
  • 41:34 - 41:36
    이것 저것 마구 고쳐서
  • 41:36 - 41:37
    테스트들이 마구 깨져버리면 안됩니다.
  • 41:37 - 41:40
    하나씩 수정해 가면서
  • 41:40 - 41:42
    테스트를 같이 실행해야 합니다.
  • 41:42 - 41:44
    인피니테스트를 사용하는 것도 좋은 방법입니다.
  • 41:44 - 41:47
    인피니테스트를 사용하는 것도 좋은 방법입니다.
  • 41:47 - 41:50
    코드를 저장할 때마다 테스트를 실행해서
  • 41:50 - 41:51
    바로 바로 결과를 알 수 있습니다.
  • 41:51 - 41:55
    뭔가 수정을 했는데
  • 41:55 - 41:57
    실수를 했다면
  • 41:57 - 42:00
    테스트 결과를 보고 바로 알아 챌 수 있습니다.
  • 42:00 - 42:04
    그러면 Ctrl+Z 한 번으로 복구할 수 있습니다.
  • 42:04 - 42:06
    Ctrl+Z를 누르고 저장만하면 됩니다.
  • 42:06 - 42:07
    바로 문제없던 상태로 돌아갈 수 있습니다.
  • 42:07 - 42:12
    이제 안전하게 리팩토링을 할 수 있게
  • 42:12 - 42:16
    가능한 작게 변경을 해보겠습니다.
  • 42:19 - 42:22
  • 42:22 - 42:29
    여기 불린 변수를 옮겨볼까요?
  • 42:29 - 42:34
    이 if 구문 안에서만 사용되고 있으니까요
  • 42:34 - 42:35
    또 다른 팁을 알려드리면요
  • 42:35 - 42:36
    레거시 코드로 작업을 하다 보면
  • 42:36 - 42:40
    변수들이 여기 저기에 선언된 경우가 있는데요
  • 42:40 - 42:42
    가능한 인접하게 모으세요
  • 42:42 - 42:48
    사용되는 위치에 가깝게 모으세요.
  • 42:48 - 42:52
    변수가 코드의 위 부분에서는 사용되지 않고
  • 42:52 - 42:53
    밑에서만 사용되고 있는데
  • 42:53 - 42:54
    맨 위에 선언되어있다면
  • 42:54 - 42:56
    사용되는 근처로 변수 선언을 옮기세요
  • 42:56 - 42:58
    그러면 연관된 코드들이 모여있게 되므로
  • 42:58 - 43:01
    코드를 분리하기에도 좋습니다.
  • 43:01 - 43:03
    이제
  • 43:03 - 43:10
    친구인지를 바로
  • 43:10 - 43:17
    알 수 있지요
  • 43:17 - 43:22
    loggedUser보다 loggedInUser가 낫군요
  • 43:22 - 43:24
    이렇게요
  • 43:24 - 43:25
    기존 코드에 작업을 하다가
  • 43:25 - 43:29
    이름이 이상하거나 적당하지 않으면
  • 43:29 - 43:30
    그 순간에 바로
  • 43:30 - 43:32
    이름을 바꾸세요.
  • 43:32 - 43:33
    좋습니다. 여기 보이죠
  • 43:33 - 43:38
    여기 초록색 상태바를 계속 보는 거에요
  • 43:38 - 43:39
    코드를 저장할 때마다
  • 43:39 - 43:41
    수정된 부분의 테스트가 실행돼서 결과가 표시됩니다.
  • 43:41 - 43:47
    이제 이 부분을 주석 처리할 수 있겠지요
  • 43:47 - 43:49
    저장을 하고요
  • 43:49 - 43:53
    테스트 결과를 보니 정상이네요.
  • 43:53 - 43:54
    이제 지우면 되겠네요.
  • 43:55 - 43:57
    이 것도 지우고요
  • 43:58 - 44:00
    그러면 아마도..
  • 44:10 - 44:13
    이 건 인라인하고요
  • 44:13 - 44:14
    좋습니다.
  • 44:15 - 44:16
    깔끔하네요.
  • 44:18 - 44:23
    이 것도 금방 바꿀 수 있겠지요
  • 44:23 - 44:26
    이 부분이요
  • 44:26 - 44:28
    Guard Clause 패턴을 적용할 수 있겠지요?
  • 44:28 - 44:30
    이 부분을 Guard Clause로 바꿀 수 있습니다.
  • 44:30 - 44:31
    Guard Clause는요
  • 44:31 - 44:33
    파라메터가 들어오자마자
  • 44:33 - 44:36
    유효성을 검증하는 것이지요.
  • 44:36 - 44:37
  • 44:37 - 44:40
    그러면
  • 44:40 - 44:45
    이 부분을 Guard Clause로 변경해서
  • 44:45 - 44:46
    이 if 문을 없애보겠습니다.
  • 44:48 - 44:53
    loggedInUser가 Null이면
  • 44:56 - 44:58
  • 45:00 - 45:03
    예외를 던지는 거지요.
  • 45:03 - 45:07
    이 게 위에 있어야겠지요.
  • 45:09 - 45:12
    저장을 하면 바로
  • 45:12 - 45:14
    녹색으로 표시되네요.
  • 45:14 - 45:20
    친절하게도 이클립스가 이 부분은 실행되지 않는 코드라고 알려주네요.
  • 45:20 - 45:24
    이제 if 문을 삭제할 수 있겠습니다.
  • 45:28 - 45:29
    저장하고
  • 45:30 - 45:31
    좋습니다.
  • 45:31 - 45:35
    지금 리팩토링하는 동안에 한 번도 테스트가 실패하지 않고 있지요.
  • 45:35 - 45:37
    지금 리팩토링하는 동안에 한 번도 테스트가 실패하지 않고 있지요.
  • 45:37 - 45:40
    혹시 실수를 해서 실패하는 테스트가 생겨도요
  • 45:40 - 45:43
    실제 업무 중에는 그럴 수도 있겠지요
  • 45:43 - 45:46
    Ctrl+Z를 누르면 되요.
  • 45:46 - 45:47
    금방 이전 상태로 되돌릴 수 있습니다.
  • 45:47 - 45:48
    그리고 뭐가 잘못된건지 생각하면 됩니다.
  • 45:48 - 45:51
    테스트를 유지할 수 없는 경우도 있겠지만
  • 45:51 - 45:54
    가능한 피해야 합니다.
  • 45:54 - 45:57
    이제 코드가 두 개의 블럭으로 구분되는군요.
  • 45:59 - 46:06
    첫 번째 블럭은 유저 정보를 검증하고요
  • 46:06 - 46:14
    두 번째 블럭은 친구의 여행 정보를 조회합니다.
  • 46:14 - 46:17
    여기서도 조금 더 개선이 가능하겠네요
  • 46:17 - 46:20
    어디 봅시다
  • 46:23 - 46:26
    레거시 코드에서 변수를 제거하는 것도
  • 46:26 - 46:29
    시도해 볼 만한 것 입니다.
  • 46:29 - 46:32
    변수 개 수가 적을 수록 코드는 더 쉬워지거든요.
  • 46:32 - 46:37
    조금은 중복이 되거나
  • 46:37 - 46:39
    메소드를 두 번 호출하게되서
  • 46:39 - 46:41
    약간의 성능이 희생되는 경우에도 가치가 있습니다.
  • 46:42 - 46:44
    변수를 줄이는 것은요
  • 46:44 - 46:47
    변수는 여러 곳에서 값이 세팅되고 참조되기 때문에
  • 46:47 - 46:50
    긴 코드를 파악하기 어렵게 합니다.
  • 46:50 - 46:52
    변수를 없애고
  • 46:52 - 46:56
    메소드를 반복해서 호출하면
  • 46:56 - 46:58
    코드를 이해하기 쉬워집니다.
  • 46:58 - 47:02
    그런 후에 꼭 필요한 경우에만 최적화 하면 됩니다.
  • 47:02 - 47:10
    예를 들어 이런 식으로요.
  • 47:16 - 47:18
    저장을 하면
  • 47:18 - 47:19
    문제 없지요?
  • 47:20 - 47:26
    이런 식으로도 변경할 수 있을 거에요
  • 47:26 - 47:27
  • 47:30 - 47:32
    문제 없고요
  • 47:33 - 47:35
  • 47:36 - 47:37
    이 건 필요 없지요.
  • 47:37 - 47:40
    그대로입니다.
  • 47:40 - 47:42
    이 변수는 이제 필요 없지요
  • 47:42 - 47:44
    이 것도 필요 없고요
  • 47:44 - 47:46
    테스트는 정상이에요
  • 47:46 - 47:49
    삼항 연산자를 써서
  • 47:49 - 47:53
    조금 더 간단하게 해볼까요
  • 47:53 - 47:58
    예를 들어서
  • 48:12 - 48:14
    저장을 하면
  • 48:14 - 48:15
    여전히 녹색이네요.
  • 48:15 - 48:16
    좋습니다.
  • 48:16 - 48:18
    그 다음에
  • 48:18 - 48:21
    이 변수는 두 군데에 사용되고 있는데요
  • 48:21 - 48:22
    인라인 시키겠습니다.
  • 48:22 - 48:24
  • 48:24 - 48:26
    둘 다 인라인 되었지요.
  • 48:26 - 48:27
    이런 식이지요.
  • 48:27 - 48:29
    변수를 없앴습니다.
  • 48:29 - 48:33
    물론 메소드를 두 번 호출하고 있어요
  • 48:33 - 48:35
    하지만 코드는 더 간단해졌지요.
  • 48:35 - 48:36
    물론 이렇게 해서 성능에 문제가 생기면
  • 48:37 - 48:40
    메소드 호출을 줄이도록 수정해야겠지요.
  • 48:40 - 48:45
    좋습니다. 훨씬 좋아졌네요.
  • 48:45 - 48:49
    이 부분은 마음에 들지않는군요..
  • 48:51 - 48:55
    메소드로 만들겠습니다.
  • 48:55 - 48:58
  • 49:02 - 49:04
    이렇게 하면 좋은 점은요
  • 49:06 - 49:08
    예를 들어서요
  • 49:10 - 49:12
    코드를 보면
  • 49:12 - 49:14
    테스트를 보면요
  • 49:15 - 49:19
    테스트 이름은 유저가 로그인하지 않은 경우에 예외를 던진다
  • 49:19 - 49:21
    로그인 된 유저를 조회한다
  • 49:21 - 49:22
    예외를 던진다
  • 49:22 - 49:27
    테스트는 유저가 친구가 아니면 여행 정보를 반환하지 않는다
  • 49:27 - 49:30
    유저가 로그인 유저와 친구라면
  • 49:30 - 49:31
    여행 정보가 없고
  • 49:31 - 49:34
    테스트는 유저가 친구들인 경우에 여행 정보를 조회한다
  • 49:34 - 49:36
    로그인 유저와 친구인 경우
  • 49:36 - 49:38
    유저의 여행 목록
  • 49:38 - 49:45
    이런 식으로 테스트에 사용하는 용어들이
  • 49:45 - 49:46
    제품 코드 구현에도 그대로 사용되게 됩니다.
  • 49:46 - 49:48
    제품 코드 구현에도 그대로 사용되게 됩니다.
  • 49:48 - 49:50
    꼭 이런 식으로 작성해보시길 바랍니다.
  • 49:50 - 49:55
    잠시 멈추고 커밋하겠습니다.
  • 49:55 - 49:57
  • 49:57 - 49:59
  • 49:59 - 50:02
  • 50:03 - 50:08
    TripService refactored
  • 50:09 - 50:10
    좋습니다.
  • 50:12 - 50:14
    훌륭해요.
  • 50:15 - 50:20
    이 정도면 충분하겠지요?
  • 50:20 - 50:22
    마무리할까요?
  • 50:22 - 50:23
    아닙니다.
  • 50:23 - 50:26
    아직 한참 멀었습니다.
  • 50:28 - 50:30
    여기 이 짧은 코드요
  • 50:31 - 50:35
    어마 어마한 문제점을 가지고 있습니다.
  • 50:36 - 50:39
    그 중 하나를 이야기하면요
  • 50:39 - 50:41
    우리는 지금까지
  • 50:41 - 50:46
    테스트가 없는 레거시 코드에
  • 50:46 - 50:48
    테스트를 작성했습니다.
  • 50:48 - 50:50
    그렇게 해서
  • 50:50 - 50:52
    문제 없이 코드를 수정할 수 있다는
  • 50:52 - 50:56
    자신감이 생겼어요. 대단한 일이지요.
  • 50:56 - 50:59
  • 50:59 - 51:02
    하지만 반대로 단점도 있는데요
  • 51:02 - 51:04
    기존 코드의 디자인이 나쁜 경우에는 어떻게 될까요?
  • 51:06 - 51:08
    기존 코드의 디자인이 나쁜데
  • 51:08 - 51:10
    그 위에 테스트를 작성해 놓으면
  • 51:10 - 51:13
    디자인을 수정할 수 없게 만들어 버립니다.
  • 51:13 - 51:15
    매우 주의해야 해요
  • 51:15 - 51:19
    원래 디자인 변경은 쉽게 하는 일이 아닌데요
  • 51:19 - 51:22
    그 위에 테스트들을 얹어버리면
  • 51:22 - 51:24
    더 변경하기 힘들게 됩니다.
  • 51:24 - 51:25
    디자인을 변경하는 순간
  • 51:25 - 51:27
    작성했던 테스트들도 같이 수정해야 하므로
  • 51:27 - 51:29
    주의해야 합니다.
  • 51:29 - 51:32
    그럼 이 디자인은 뭐가 문제일까요?
  • 51:32 - 51:35
    아마도
  • 51:36 - 51:41
    TripService는 서버단 코드이고
  • 51:41 - 51:43
    (MVC 중) 모델에 속할 겁니다.
  • 51:44 - 51:51
    그런데 User Session을 직접 참조하고 있네요
  • 51:51 - 51:57
    모델이 웹 프레임워크를 참조하면 안되지요
  • 51:57 - 52:01
    Http Session 같은 것들을 알고 있으면 안됩니다.
  • 52:02 - 52:05
    이 클래스는 User Session을 직접 참조하는 게 잘못된 겁니다.
  • 52:05 - 52:10
    해결을 해야 할텐데요
  • 52:10 - 52:12
    선택할 수 있는 하나의 방법은
  • 52:12 - 52:17
    로그인 유저를 메소드에 인자로 전달하는 겁니다.
  • 52:17 - 52:23
    이제 위험한 리팩토링을 시작하겠습니다.
  • 52:23 - 52:26
    오늘 연습이야 별 문제 없겠습니다만
  • 52:26 - 52:29
    실제 업무에서 할 때에는
  • 52:29 - 52:30
    주의를 기울이셔야 합니다.
  • 52:30 - 52:31
    조심스럽게
  • 52:31 - 52:33
    언제나 처럼
  • 52:33 - 52:35
    조금씩 작업하겠습니다.
  • 52:35 - 52:40
    메소드 파라메터로 로그인 유저를 전달하게 할 건데요
  • 52:40 - 52:43
    사용할 수 있는 방법은
  • 52:43 - 52:46
    리팩토링에서
  • 52:46 - 52:48
    메소드 시그니처를 수정해서
  • 52:48 - 52:51
    파라메터를 추가하겠습니다.
  • 52:51 - 52:53
  • 52:55 - 52:59
  • 52:59 - 53:01
    기본 값은 Null이고요
  • 53:02 - 53:04
    좋습니다.
  • 53:04 - 53:08
    이클립스의 리팩토링 기능은 매우 강력합니다.
  • 53:08 - 53:10
    인텔리제이도 그렇고요.
  • 53:10 - 53:13
    이 유저 파라메터는
  • 53:13 - 53:15
    사용되고 있지는 않지요.
  • 53:15 - 53:18
    테스트 코드를 보면
  • 53:18 - 53:23
    Null을 전달하는 것으로 변경되어 있네요.
  • 53:23 - 53:26
    멋지군요.
  • 53:26 - 53:29
    여기서 문제는요
  • 53:29 - 53:32
    이 메소드는 제품 코드이니
  • 53:32 - 53:34
    이미 사용되고 있을 거에요
  • 53:34 - 53:37
    아마 여러 군데서 사용 중이겠지요.
  • 53:37 - 53:45
    따라서 사용되는 모든 곳에서
  • 53:45 - 53:47
    Null을 넣어주고 있을 테니
  • 53:47 - 53:50
    모든 곳에서 전부 수정을 해줘야 합니다.
  • 53:50 - 53:52
    잊지 마시기 바랍니다.
  • 53:53 - 53:56
    일단 지금
  • 53:58 - 54:00
    해야 할 것은
  • 54:00 - 54:01
    테스트를 수정해야지요
  • 54:01 - 54:04
    여기 Null을 전달하는 게 보기 싫군요.
  • 54:04 - 54:05
    로그인 유저를 전달해야 하니
  • 54:05 - 54:08
    게스트를 넣어주고요
  • 54:08 - 54:10
  • 54:12 - 54:15
    인피니테스트 결과를 확인하고요
  • 54:15 - 54:22
    여기에는 로그인 된 유저를 넣어야지요
  • 54:22 - 54:24
    여기에는
  • 54:27 - 54:30
    네 여기에도 로그인 된 유저를 넣겠습니다.
  • 54:36 - 54:37
    좋습니다.
  • 54:37 - 54:39
    한 번 더 확인해보고요.
  • 54:39 - 54:41
    좋습니다.
  • 54:42 - 54:45
    이제 전달되는 파라메터를 사용해야겠지요
  • 54:47 - 54:48
    이제
  • 54:49 - 54:54
    전달된 걸 사용할거에요.
  • 54:54 - 54:59
    파라메터를 사용하게 변경할겁니다.
  • 54:59 - 55:04
    여기 메소드 호출을
  • 55:04 - 55:07
    파라메터로 변경하고요
  • 55:07 - 55:09
    저장을 하면..
  • 55:11 - 55:14
    테스트 결과는 정상입니다.
  • 55:14 - 55:16
    그러면 이 메소드는요
  • 55:16 - 55:19
    private으로 변경하면
  • 55:19 - 55:22
    사용 안 된다고 표시가 되지요.
  • 55:22 - 55:25
    삭제해도 안전하겠군요.
  • 55:26 - 55:30
    삭제한 후에
  • 55:30 - 55:32
    이 것도 삭제할 수 있겠지요.
  • 55:32 - 55:36
    다시 정상으로 돌아왔지요.
  • 55:36 - 55:37
    유저를 파라메터로 전달하게
  • 55:37 - 55:40
    리팩토링 했습니다.
  • 55:41 - 55:43
    이제 할 것은
  • 55:43 - 55:46
    이 loggedInUser 필드는
  • 55:46 - 55:49
    필요할 것 같지 않네요.
  • 55:49 - 55:55
    이렇게 바꾸면요
  • 55:56 - 55:59
    여기도요
  • 56:02 - 56:06
    그러면 이클립스가
  • 56:06 - 56:10
    이 변수가 사용되지 않는다고 알려주지요.
  • 56:10 - 56:14
    삭제할 수 있겠군요.
  • 56:14 - 56:17
    삭제하고요.
  • 56:17 - 56:19
    이 것도요
  • 56:19 - 56:23
    테스트도 정상입니다.
  • 56:23 - 56:26
    커밋을 해야겠네요.
  • 56:26 - 56:29
    오. 여기 경고가 있네요
  • 56:29 - 56:33
    사용하지 않는 import는 지워야지요
  • 56:33 - 56:35
    깔끔하게요.
  • 56:35 - 56:37
    장인정신은 중요합니다.
  • 56:37 - 56:39
    항상 깔끔하게 유지해야지요.
  • 56:40 - 56:42
    좋습니다. 잠시 멈추고요.
  • 56:42 - 56:44
  • 56:45 - 56:47
  • 56:50 - 57:01
    Logged in user passed in to TripService
  • 57:02 - 57:04
    좋습니다.
  • 57:06 - 57:09
    대충 마무리됐을까요.
  • 57:11 - 57:13
  • 57:13 - 57:17
    이 부분은 여전히 마음에 안 드네요.
  • 57:18 - 57:19
    TripDAO의
  • 57:19 - 57:21
    static 메소드를 직접 호출하고 있지요.
  • 57:21 - 57:25
    그래서 이런 식으로 해야만 했어요.
  • 57:25 - 57:30
    말했듯이 임시적인 방법인데요
  • 57:30 - 57:34
    제대로 한다면 TripDAO를 주입해서
  • 57:34 - 57:36
    멤버 변수를 사용하게 만들어야 하는데요
  • 57:36 - 57:39
    static 메소드라서
  • 57:39 - 57:41
    다른 것으로 대체하기가 어렵지요.
  • 57:41 - 57:43
    왜냐하면 일반적으로
  • 57:43 - 57:46
    인스턴스만 주입이 가능하고 클래스는 불가하기 때문에
  • 57:46 - 57:48
    Mock으로 대체할 수 없습니다.
  • 57:48 - 57:51
    그럼 조금 더 수정해서
  • 57:51 - 57:53
    이 부분을 없애고
  • 57:53 - 57:56
    TripDAO의 멤버 변수를 만들어 사용하게 해보겠습니다.
  • 57:56 - 57:58
    TripDAO 클래스를 열어
  • 57:58 - 57:59
    구현 내용을 보면요
  • 57:59 - 58:00
    지금 이 코드는
  • 58:00 - 58:02
    단순한 예제 코드라 예외를 던지고 끝나지만요
  • 58:02 - 58:05
    단순한 예제 코드라 예외를 던지고 끝나지만요
  • 58:06 - 58:10
    앞에 이야기한 것처럼
  • 58:10 - 58:12
    보통은 데이터베이스나
  • 58:12 - 58:13
    분산 캐시 같은
  • 58:13 - 58:18
    저장하는 무언가에 엑세스하겠지요.
  • 58:19 - 58:21
    자 이제
  • 58:22 - 58:29
    멤버 메소드를 만들건대요.
  • 58:30 - 58:35
    먼저 테스트를 작성해야겠지요.
  • 58:38 - 58:39
  • 58:43 - 58:46
  • 58:47 - 58:49
    테스트 폴더에 넣고
  • 58:52 - 58:54
    이 것도 흥미로운 기법이니
  • 58:56 - 58:57
    눈 여겨 보세요.
  • 58:58 - 59:03
    기존 Static 메소드와 동일한 기능을 하는
  • 59:03 - 59:09
    인스턴스 메소드를 만들 거에요.
  • 59:12 - 59:13
    똑 같이 동작하게요.
  • 59:16 - 59:18
    그냥 Static 키워드를 없애도 가능하겠지요.
  • 59:18 - 59:23
    여기 static 글자만 지워서요
  • 59:23 - 59:24
    물론 가능합니다만
  • 59:24 - 59:25
    그렇게 하면
  • 59:25 - 59:28
    여기 저기 수정할 게 많을 겁니다.
  • 59:28 - 59:32
    어플리케이션에서 이 메소드를 사용하는 클래스가
  • 59:32 - 59:35
    여러 개 있는 상황에서
  • 59:35 - 59:38
    이걸 인스턴스 메소드로 바꿔버리면
  • 59:38 - 59:40
    전부 컴파일 에러가 나겠지요.
  • 59:40 - 59:43
    그러면 일이 너무 커져버립니다.
  • 59:43 - 59:45
    우린 조금씩 안전하게 작업을 할거니까요
  • 59:46 - 59:48
    그래서
  • 59:49 - 59:51
    테스트를 만들겠습니다.
  • 59:51 - 59:53
    예제 DAO를 테스트하는 거니까
  • 59:53 - 59:55
    이 테스트가 검증하는 디테일 한 부분에는
  • 59:55 - 59:57
    집중하실 필요는 없습니다.
  • 59:57 - 60:01
    그냥 예제 DAO의 동작을 검증하겠습니다.
  • 60:01 - 60:20
    유저의 여행정보를 조회하면 예외를 발생하는 거지요
  • 60:21 - 60:26
    예외가 발생해야겠지요.
  • 60:28 - 60:30
    말씀드린대로
  • 60:30 - 60:34
    원래는 데이터를 조회해야겠지만
  • 60:34 - 60:36
    이 예제 코드에서는
  • 60:36 - 60:40
    바로 예외를 던지지요.
  • 60:40 - 60:42
    이거지요.
  • 60:42 - 60:45
    인스턴스 메소드를 사용하려고 하니까요
  • 60:45 - 60:48
    메소드를 호출하기만 하면 되겠지요
  • 60:48 - 60:49
    그러니까
  • 60:49 - 60:52
  • 60:55 - 61:06
  • 61:10 - 61:13
    인스턴스가 필요하지요
  • 61:15 - 61:16
  • 61:16 - 61:18
    메소드를 생성해서
  • 61:19 - 61:26
    기존 거랑 똑 같이 동작하게 할 겁니다
  • 61:33 - 61:35
    테스트는 실패하고 있지요. 당연합니다.
  • 61:35 - 61:37
    이제는요
  • 61:37 - 61:40
    기존 static 메소드랑 똑 같이 동작해야 하니까요
  • 61:40 - 61:43
  • 61:45 - 61:52
    이 안에서
  • 61:52 - 61:53
    기존 메소드를 호출합니다.
  • 61:54 - 61:56
    이렇게 하는 이유는요
  • 61:56 - 61:57
    이렇게 하는 이유는요
  • 61:57 - 62:03
    점진적으로 기존 코드들을 새 메소드를 사용하게 바꿔가려는 거지요.
  • 62:05 - 62:06
    여기에서
  • 62:06 - 62:09
    TripService가 인스턴스 메소드를 호출하게 하는 거지요
  • 62:09 - 62:11
    이렇게 시작해서
  • 62:11 - 62:13
    Static 메소드를 사용하는 클래스가 없어질 때까지
  • 62:13 - 62:19
    계속 바꿔나가는 겁니다.
  • 62:19 - 62:20
    그런 후에
  • 62:20 - 62:22
    이 Static 메소드의 내용을 복사해서
  • 62:22 - 62:24
    여기로 옮기고 Static 메소드는 지워버리면
  • 62:24 - 62:26
    끝이지요.
  • 62:26 - 62:27
    유용한 기법이에요.
  • 62:27 - 62:29
    다시 돌아가서
  • 62:29 - 62:35
    인스턴스 메소드가 완성됐습니다.
  • 62:35 - 62:38
    이제는
  • 62:38 - 62:44
    TripDAO를 인젝트 해야 하는데요
  • 62:44 - 62:46
  • 62:46 - 62:49
    방법은 여러 가지가 있습니다. 예를 들어
  • 62:49 - 62:51
    DI 프레임워크 없이 그냥 할 수도 있겠지요
  • 62:51 - 62:55
    그냥 TripService의 생성자에 TripDAO를 넣어주면 됩니다.
  • 62:55 - 62:57
    그렇게 하는 게
  • 62:57 - 62:59
    가장 간단한 방법입니다.
  • 62:59 - 63:03
    그래도 대부분의 자바 개발자들은
  • 63:03 - 63:06
    Spring, Mockito 등을 주로 사용하니까요
  • 63:06 - 63:08
    Mockito를 이용하는 방법을 보여드리겠습니다.
  • 63:08 - 63:11
    만약에 Mockito를 사용하지 않으면
  • 63:11 - 63:12
    그냥 심플하게
  • 63:12 - 63:17
    생성자를 만들어 TripDAO를 건네주면 됩니다.
  • 63:17 - 63:19
    그럼 Spring, Mockito를 사용해서
  • 63:19 - 63:22
    테스트하는 방법을 보여드리겠습니다.
  • 63:22 - 63:27
    이제 TripDAO를 인젝트 할 수 있도록
  • 63:27 - 63:31
    테스트를 변경하도록 하겠습니다.
  • 63:31 - 63:33
    이제
  • 63:33 - 63:36
    JUnit과 Mockito를 사용할 거니까
  • 63:36 - 63:37
  • 63:37 - 63:39
  • 63:42 - 63:47
    Mockito나 JUnit을 사용하지 않는 사람에게는
  • 63:47 - 63:48
    좀 복잡해 보일 겁니다.
  • 63:48 - 63:52
    그렇다면 관련 문서들을 읽어보시기 바랍니다.
  • 63:55 - 64:01
    지금까지는 TestableTripService을 사용하고 있었는데요
  • 64:01 - 64:03
    지금까지는 TestableTripService을 사용하고 있었는데요
  • 64:04 - 64:07
    이제 TripService를 바로 사용하겠습니다.
  • 64:07 - 64:09
    직접 생성하지 않고 주입 받아서요.
  • 64:09 - 64:13
    TestableTripService를 이제 지워야겠어요
  • 64:13 - 64:15
    더 이상 필요가 없습니다.
  • 64:16 - 64:19
    의존하는 것들은 Mock으로 바꿔서 테스트할 수 있게
  • 64:19 - 64:21
    조금씩 안전하게 작업하겠습니다.
  • 64:21 - 64:24
    TripService를 하나 더 만들고요
  • 64:24 - 64:26
    TripService를 하나 더 만들고요
  • 64:27 - 64:37
    일단 realTripService라고 하고
  • 64:37 - 64:38
    이건 진짜 TripService의 인스턴스에요
  • 64:38 - 64:41
    Mock을 만들고요
  • 64:52 - 64:55
    TripDAO의 Mock이요
  • 65:00 - 65:03
    Mockito 기능을 사용해서
  • 65:04 - 65:06
    TripService가
  • 65:06 - 65:10
    Mock을 사용하게 하겠습니다.
  • 65:11 - 65:21
    Mockito가 어떻게 동작하는지는 오늘 주제에서 조금 벗어나지만
  • 65:21 - 65:26
    간단하게 설명 드리면
  • 65:26 - 65:28
    실제 TripService 인스턴스가 생성될 때에
  • 65:28 - 65:30
    Mockito가 TripService 클래스를 분석해서
  • 65:31 - 65:35
    TripService가 사용하는 Mock들을 자동으로 주입시키는 겁니다.
  • 65:35 - 65:37
    Mock DAO는 여기에 있고요
  • 65:37 - 65:41
    이제 TripService가 이 Mock TripDAO가 필요하다는 것만 명시하면 됩니다.
  • 65:42 - 65:44
    일단 그 전에
  • 65:44 - 65:47
    이제 이 TripService의 인스턴스를요
  • 65:47 - 65:49
  • 65:49 - 65:49
  • 65:49 - 65:51
    조금씩 조금씩
  • 65:51 - 65:55
    realTripService로 바꾸고 저장..
  • 65:56 - 66:00
    테스트 결과는 정상이지요
  • 66:01 - 66:03
    지금 하려는 거는요
  • 66:03 - 66:06
    테스트에서 사용 중인 TestableTripService를
  • 66:06 - 66:07
    조금씩
  • 66:08 - 66:09
    TripService 인스턴스로 바꾸는 겁니다.
  • 66:10 - 66:11
  • 66:11 - 66:13
    이렇게 하면
  • 66:14 - 66:16
    정상이지요.
  • 66:16 - 66:17
    지금 테스트가 통과하는 이유는
  • 66:17 - 66:20
    TripDAO를 사용하지 않기 때문이에요.
  • 66:20 - 66:22
    이 코드를 사용하지 않는 테스트들인 거지요.
  • 66:24 - 66:31
    이 테스트도 바꾸면요..
  • 66:32 - 66:35
    실패하는 군요.
  • 66:35 - 66:37
    이거지요.
  • 66:37 - 66:39
    TripService 인스턴스에서
  • 66:39 - 66:44
    TripDAO의 Static 메소드를 호출했기 때문에
  • 66:44 - 66:48
    TripDAO의 Static 메소드를 호출했기 때문에
  • 66:48 - 66:49
    예외가 떨어진 거에요.
  • 66:49 - 66:51
    이제 실패한 테스트가 있으니
  • 66:51 - 66:54
    제품 코드를 수정해야겠네요.
  • 66:57 - 66:59
    그래서
  • 67:00 - 67:02
    제품 코드에서요
  • 67:02 - 67:04
    스프링인 경우지요
  • 67:04 - 67:06
  • 67:18 - 67:22
    import 하고
  • 67:22 - 67:25
    TripDAO는 XML에 스프링 빈으로 등록했다고 하고요
  • 67:25 - 67:27
    TripDAO는 XML에 스프링 빈으로 등록했다고 하고요
  • 67:27 - 67:32
  • 67:32 - 67:33
    좋습니다.
  • 67:35 - 67:37
    화면을 늘리고
  • 67:38 - 67:40
  • 67:40 - 67:42
    이러면 Mockito가
  • 67:42 - 67:44
    이 Mock TripDAO를
  • 67:44 - 67:47
    여기 TripService에 넣어줄 겁니다.
  • 67:49 - 67:52
    그래서 이 TripDAO를 사용하게 해야 하는데요
  • 67:52 - 67:55
    테스트는 실패하고 있지요?
  • 67:55 - 67:57
    이 TripDAO를 사용하려면
  • 67:57 - 68:02
    이거 대신에
  • 68:05 - 68:07
    일단 테스트를 돌리면요
  • 68:07 - 68:10
    차이점을 보려고요
  • 68:29 - 68:31
    실행하고
  • 68:33 - 68:35
    계속 단축키를 잘 못 누르네요
  • 68:35 - 68:40
    아직 실제 DAO를 호출해서 에러가 나고 있는 겁니다.
  • 68:40 - 68:45
    그래서 인스턴스 메소드를 호출하게 하고
  • 68:45 - 68:49
    기존 Static 메소드 대신에
  • 68:49 - 68:51
    인스턴스 메소드를 사용하게 해서
  • 68:52 - 68:56
    테스트를 실행하면
  • 68:56 - 69:00
    에러가 변했지요
  • 69:00 - 69:03
    이제 Mock DAO를 사용하고 있는 겁니다.
  • 69:03 - 69:07
    화면은 줄이고
  • 69:07 - 69:12
    이젠 Mock DAO가 실행된 겁니다.
  • 69:13 - 69:18
    MockDAO가 여행 정보 2개를 반환되었어야 하는데 아무것도 없었던 거지요.
  • 69:18 - 69:20
    원래 테스트 시나리오는
  • 69:20 - 69:22
    여행 정보 2개가 필요하니까요
  • 69:24 - 69:26
    이제..
  • 69:26 - 69:28
    이렇게 되는 이유는
  • 69:28 - 69:31
    TipService가 Mock TripDAO를 호출하는데
  • 69:31 - 69:33
    Mock TripDAO가 어떻게 동작해야 할지 설정하지 않아서 에요.
  • 69:33 - 69:38
    그러니까
  • 69:41 - 69:44
  • 69:44 - 69:49
  • 69:52 - 69:58
    이 메소드가 호출이 되면
  • 69:59 - 70:05
    친구의 여행 목록을 반환해야지요
  • 70:05 - 70:08
    지금 하는 건
  • 70:08 - 70:16
    Mock DAO가 여행 목록을 반환하게 하는 거지요
  • 70:16 - 70:19
    이 코드가 했던 것과 동일하게
  • 70:19 - 70:22
    Mock이 동작하도록 하는 겁니다.
  • 70:22 - 70:25
    이제 테스트가 통과하는군요
  • 70:27 - 70:31
    이제 이걸 private로 바꿀 수 있겠지요
  • 70:33 - 70:37
    이 메소드는 이제 필요가 없고요
  • 70:39 - 70:41
    전체 테스트를 다시 돌리면
  • 70:41 - 70:43
    모두 정상이군요.
  • 70:43 - 70:46
    이 클래스가 더 이상 필요 없다는 말이지요
  • 70:48 - 70:51
    그건 이 것도 필요 없다는 거고
  • 70:51 - 70:54
    이 게 필요 없으면 이 것도 필요 없지요
  • 70:54 - 70:57
    이제 이 필드는 사용되지 않네요
  • 70:57 - 70:59
    삭제하고요
  • 71:00 - 71:02
    그러면 이제
  • 71:02 - 71:04
    realTripService는 이름을 바꿔야겠지요
  • 71:04 - 71:06
    이제 real은 의미가 없으니까요
  • 71:06 - 71:07
    테스트를 작성할 때에
  • 71:07 - 71:14
    testee, mock 같은 이름을 사용하는 경우가 종종 있는데요
  • 71:14 - 71:15
    실제로 무엇인지를 표현하면 됩니다.
  • 71:16 - 71:20
    이건 TripService이니까 그냥 tripService로 충분합니다. 그렇지요?
  • 71:20 - 71:23
    좋습니다.
  • 71:24 - 71:27
    이제 Test Double 클래스들도 필요 없고
  • 71:27 - 71:30
    테스트도 읽기 편합니다.
  • 71:31 - 71:35
    테스트를 한 번 더 돌려서 확인하고요.
  • 71:35 - 71:38
    여기는 이렇게 바꿀 수 있겠지요.
  • 71:44 - 71:46
  • 71:47 - 71:49
    앞에 이야기한 것처럼
  • 71:49 - 71:54
    테스트 메소드들을 접으면
  • 71:55 - 72:00
    이렇게 되는 거지요.
  • 72:00 - 72:03
    유저가 로그인하지 않은 경우 예외를 던진다
  • 72:03 - 72:05
    여기에서 로그인 정보를 확인하지요
  • 72:06 - 72:09
    유저들이 친구가 아닌 경우 여행 목록을 반환하지 않는다
  • 72:09 - 72:13
    친구가 아닌 경우에는 빈 목록을 반환하고요
  • 72:14 - 72:18
    유저가 서로 친구인 경우에는
  • 72:18 - 72:19
    여행 정보를 반환하지요
  • 72:19 - 72:22
    로그인 유저와 친구인 경우에
  • 72:22 - 72:24
    여행 목록을 반환합니다.
  • 72:28 - 72:31
    이 getter 메소드는 좀 별로네요
  • 72:31 - 72:36
    getTripsByUser는요
  • 72:36 - 72:39
    getFriendTrips로
  • 72:40 - 72:42
    훨씬 낫지요?
  • 72:42 - 72:46
    이 변수도
  • 72:49 - 72:51
    friend가 좋겠네요
  • 72:52 - 72:54
    이런 식이지요
  • 72:56 - 72:59
    테스트가 잘 작성되어있으므로
  • 72:59 - 73:02
    이런 것들을 해볼 수가 있는 겁니다.
  • 73:03 - 73:07
    잠시 멈추고 커밋을 해볼까요.
  • 73:07 - 73:09
  • 73:10 - 73:12
  • 73:15 - 73:18
  • 73:19 - 73:27
    TripDAO injected into TripService
  • 73:28 - 73:31
    됐습니다.
  • 73:31 - 73:32
    이런 식으로
  • 73:33 - 73:35
    레거시 코드를 안전하게 리팩토링 할 수 있습니다.
  • 73:35 - 73:37
    레거시 코드를 안전하게 리팩토링 할 수 있습니다.
  • 73:39 - 73:41
    처음 TripService 코드가
  • 73:47 - 73:50
    여기에 있을 텐데요.
  • 73:50 - 73:54
    처음에는 이런 코드로 시작했던 겁니다.
  • 73:54 - 74:03
    처음에는 이런 코드로 시작했던 겁니다.
  • 74:03 - 74:06
    지금은
  • 74:06 - 74:09
    스크린 캐스트를 녹화하고 있으니
  • 74:09 - 74:13
    한 시간 정도 걸렸는데요
  • 74:13 - 74:16
    여기에 사용된 기법들이 익숙한 경우에는요
  • 74:16 - 74:20
    저 같은 경우에는 평소에는 20분 정도 걸립니다.
  • 74:20 - 74:22
  • 74:22 - 74:24
    뭔가 새로운 기술을 알게 되면
  • 74:24 - 74:26
    연습을 하세요
  • 74:26 - 74:29
    능숙하게 사용할 때 까지요
  • 74:29 - 74:32
    다시 한 번 말씀 드리면
  • 74:32 - 74:35
    레거시 코드에 작업을 할 때에는
  • 74:35 - 74:40
    깊은 부분부터 작업을 하면 안됩니다.
  • 74:40 - 74:42
    항상 얕은 부분부터 시작을 하세요.
  • 74:42 - 74:44
    얕은 부분에서 시작해서 깊은 부분으로요.
  • 74:44 - 74:47
    얕은 부분에서 테스트를 작성하기 시작해야 합니다
  • 74:47 - 74:51
    그러면 작성하는 테스트로 제품 코드를 이해해갈 수 있고
  • 74:51 - 74:55
    테스트 데이터를 만들어 가면서
  • 74:55 - 74:58
    점점 더 깊은 부분을 테스트할 수 있게 됩니다.
  • 74:58 - 75:00
    반대로
  • 75:00 - 75:01
    리팩토링을 할 때에는
  • 75:01 - 75:03
    가장 깊은 부분부터 시작합니다.
  • 75:03 - 75:05
    깊은 부분부터 메소드를 추출하고
  • 75:05 - 75:08
    행위를 다른 클래스로 옮기다 보면
  • 75:08 - 75:11
    어느 순간부터 코드가
  • 75:11 - 75:14
    줄어들기 시작할 겁니다.
  • 75:14 - 75:17
  • 75:19 - 75:24
    읽기 좋고 관리하기 좋은 코드를 작성하세요.
  • 75:24 - 75:26
    도메인 언어가 코드에 사용되도록 하세요.
  • 75:26 - 75:28
    코드에 녹아들 게 하세요.
  • 75:28 - 75:30
  • 75:30 - 75:32
  • 75:32 - 75:37
    도메인 언어가 테스트와 제품 코드에 녹아들 게 하세요
  • 75:37 - 75:43
    BA와 이야기하는 용어가 테스트 코드 제품 코드에도 그대로 사용되게 하세요.
  • 75:43 - 75:46
    심플한 것을 추구하고
  • 75:46 - 75:49
    사용하는 프레임워크나 단축키 등도 알아두세요
  • 75:49 - 75:51
    신속하게 작업하도록 노력하고
  • 75:52 - 75:56
    개발 환경에도 공을 들여서 단축키 등으로 간단하게
  • 75:56 - 75:58
    메소드나 클래스를 만들거나
  • 75:58 - 76:00
    리팩토링을 할 수 있어야 합니다.
  • 76:00 - 76:04
    항상 조금씩 점진적으로 작업하고요
  • 76:04 - 76:05
    자주 커밋하세요.
  • 76:05 - 76:07
    Git같은 툴을 사용하면
  • 76:08 - 76:10
    실수를 하더라도
  • 76:10 - 76:13
    Ctrl+Z 나 git reset hard 등으로
  • 76:13 - 76:15
    이 전 커밋으로 돌아갈 수 있습니다.
  • 76:15 - 76:17
    문제가 없었던 상태로요.
  • 76:17 - 76:19
    용기를 가지세요.
  • 76:19 - 76:22
    오늘 여러 가지 기법들을 배웠는데요
  • 76:22 - 76:27
    오늘 여러 가지 기법들을 배웠는데요
  • 76:27 - 76:31
    이제 레거시 코드의 99%에 테스트 작성이 가능할 겁니다.
  • 76:31 - 76:33
    그러니 용기를 내서
  • 76:33 - 76:36
    오늘 배운 것들을 활용하세요.
  • 76:37 - 76:40
    머문 자리는 머물기 전보다 깨끗하게 하고 떠나세요.
  • 76:40 - 76:41
    보이 스카웃 규칙이지요.
  • 76:41 - 76:43
    깨진 창문 이론도 아시지요.
  • 76:43 - 76:44
    코드 냄새가 나면요.
  • 76:44 - 76:48
    코드가 그다지 잘 작성된 게 아니라면
  • 76:48 - 76:49
    바로 리팩토링 하세요.
  • 76:49 - 76:51
    바로 수정하세요.
  • 76:51 - 76:53
    유익한 시간이 됐으면 좋겠습니다.
  • 76:54 - 76:56
    이상입니다.
  • 76:56 - 77:01
    이 카타는 제 깃헙에 있습니다.
  • 77:01 - 77:04
    제 깃헙을 방문해보세요.
  • 77:04 - 77:06
    이상입니다. 유익한 시간이 되었으면 좋겠습니다.
  • 77:06 - 77:07
    감사합니다.
Title:
Testing and Refactoring Legacy Code
Description:

more » « less
Video Language:
English
Duration:
01:17:08

Korean subtitles

Revisions