test driven development short lesson

35
This report is written by Sunny Kwak. ([email protected] , sunnykwak.tistory.com) 2016.03.07 Test Driven Development (mini-lesson) Version 0.2

Upload: -

Post on 22-Jan-2017

798 views

Category:

Software


0 download

TRANSCRIPT

This report is written by Sunny Kwak. ([email protected], sunnykwak.tistory.com)

2016.03.07

Test Driven Development

(mini-lesson)

Version 0.2

2Test Driven Development – Video Shop

Test Driven Development 접근하기

Test Driven Development

( 살살 ) 접근하기

3Test Driven Development – Video Shop

Test Driven Development 접근하기

프로그래밍은 주어진 과제를 해결하는 것이다 .

최초 과제 혹은 요구사항은 대체로 단순하다 .

하지만 , 점점 요구가 복잡해진다 . ( 간혹 처음부터 조건이 까다로운 경우도… )

일단 쏴서 맞추어 봐라 . 그래서 시키는 대로 했다 .

점점 앉아쏴 , 서서 쏴 , 업드려 쏴… 설마 덤블링 하면서 쏘라고 하나 ?

목표를 쏴라 ~

4Test Driven Development – Video Shop

Test Driven Development 접근하기

Non TDD

문제를 더 작은 문제로 쪼갠다 .각각의 작은 문제를 해결하기 위한 모듈 ( 객체 ) 들과 각 모듈간의 관계 ( 상속 , 합성 ) 를 정의한다 .그리고 , 문제를 해결하기 위한 코드를 작성한다 . ( 원칙은 그렇다고 한다 .)

문제를 더 작은 문제로 쪼갠다 .하나의 컴포넌트를 선언하고 , 작은 문제 하나를 푼다 .그리고 , 또 다른 문제를 해결하기 위해 기존 컴포넌트에 코드를 계속 추가한다 .

필요한 기능을 추가할 수록 점점 스파게티 코드가 되어간다 . 리팩토링을 해서 코드를 개선하기도 하지만…

테스트가 점점 어려워진다 . 하나의 컴포넌트가 여러가지 이상의 기능을 수행하면서

최초에 테스트 했던 기능이 동작하지 않는다 .

그리고 , 점점 억울해진다 . “ 아니 , 이거 원래 잘 동작하던 건데요 ? 미치겠네 ..”

선배도 답답하고 , 고객은 열받고 , 개발자는 집에 가고 싶을 뿐이고 ~

사실은 ?

5Test Driven Development – Video Shop

Test Driven Development 접근하기

Test Driven Development!

작은 목표 혹은 문제를 선정한다 .문제에 대한 답을 예상한다 . 그리고 , 예상 결과를 검사하는 코드를 먼저 작성한다 .결과를 반환하는 컴포넌트를 선언하고 , 간단히 결과를 생성하는 실행 코드를 작성한다 .위 과정을 반복하면서 필요하다면 , 리팩토링을 하며 컴포넌트를 추가하거나 구조를 변경한다 .

그래서 ?

요구사항 혹은 조건이 추가되거나 , 기능을 추가했을 때 이전에 동작하던 기능이 잘 동작하는지 테스트 케이스가 자동으로 확인해 준다 .

대체로 잘 동작하는 상태로 업무가 진행된다 . 자동화된 테스트를 통해 문제 여부를 빠르게 확인할 수 있어서 테스트 시간과 심리적 부담이 감소한다 .

늘 동작하는 코드를 보면서 작업하게 되니 . 한참 만들어 놓고 , 동작 안하는 코드를 보고 절망할 일이 없다 . 코드 분량이 늘어나도 적극적으로 코드를 수정할 수 있게 된다 .

정시 퇴근이 ( 어쩌면 ) 가능할지도 모른다 .

문제의 범위를 크게 잡지 말고 , 조금씩 기능을 개선하며 리듬을 타야 한다 .

6Test Driven Development – Video Shop

퀴즈 - VideoShop

Test Driven Development

퀴즈 풀기

7Test Driven Development – Video Shop

퀴즈 - VideoShop

고객

비디오테이프

대여 서비스

Video Shop

고객은 이름을 가지고 있다 . 고객은 한번에 여러 개의 비디오를 대여할 수 있고 , 각각의 대여 기간은 일정하지 않다 .

비디오는 3 가지 유형이 있다 . ( 영화 , 스포츠 , 다큐멘터리 ) 각각의 비디오는 독립적인 일일 대여요금이 부여되며 , 유형에 따라 할인율이 틀리다 . 비디오 대여 시 유형에 따라 다른 포인트를 제공한다 .

비디오 대여가 발생할 때 , 포인트를 적립하고 누적 포인트를 조회할 수 있어야 한다 . 상세 대여 내역 ( 종류 , 제목 , 가격 등 ) 을 조회할 수 있어야 한다 . 대여 기간에 따라 요금을 계산할 수 있어야 한다 .

기타 조건

TDD 의 순서를 따라서 개발할 것 . 테스트할 목록을 작성하라 .

8Test Driven Development – Video Shop

퀴즈 - VideoShop

문제가 너무 커 보이는데요 ?

어디서 부터 접근해야 할까요 ?

문제를 먼저 쪼개는 작업이 필요하지 않나요 ?

자칫 엉뚱한 곳부터 풀다가 헤매는 건 아닐까요 ?

저는 TDD 를 처음 경험해 보는데 조언 해주실 건 없나요 ?

어디서 시작하나

상관 없는 게 TDD 입니다 .

두려워하지 마시고 일단

시작해 보세요 !

Just Do IT!

9Test Driven Development – Video Shop

퀴즈 - VideoShop

그래서 , 그냥 순서대로 풀었습니다 ! - 첫번째 문제 혹은 태스크 ‘고객은 이름을 가지고 있다 .’

- 이게 전부는 아니죠… 새로운 고객을 등록하고 , 이름으로 검색할 수 있어야 합니다 .- 그래서 , 아래와 같이 작업했습니다 .

개발 환경 - 이클립스를 실행한 후 워크스페이스를 만들고 , VideoShop 프로젝트를 생성했습니다

패키지 선언 - 테스트 코드와 실행 코드는 다른 패키지에 위치 시켜야 한답니다 . - 그래서 , com.acme.videoshop 패키지와 test.acme.video.shop 으로 분리했습니다 .

테스트 프레임워크 설정 - 다양한 도구가 있지만 , 이클립스에 기본으로 포함되어 있는 Junit 을 선택했습니다 .

테스트 클래스 선언 - test.acme.videoshop. CustomerTest 클래스를 작성합니다 .

10Test Driven Development – Video Shop

퀴즈 - VideoShop

고객 관리를 테스트 하기 위한 CustomerTest 클래스 작성 .

package test.acme.videoshop;

import com.acme.videoshop.customer.Customer;import com.acme.videoshop.customer.CustomerManager;

import junit.framework.TestCase;

public class CustomerTest extends TestCase {

private static final String CUSTOMER_NAME = "Sunny Kwak";

public void testCustomer() {

Customer customer = CustomerManager.register( CUSTOMER_NAME ); assertNotNull(customer); customer = CustomerManager.lookup(CUSTOMER_NAME); assertNotNull(customer); customer = CustomerManager.lookup(CUSTOMER_NAME + "."); assertNull(customer); }}

11Test Driven Development – Video Shop

퀴즈 - VideoShop

Customer & CustomerManager

Customer

+name+Customer(String name)+getName()

CustomerManager

+customerMap+register(String customerName)+lookup(String customerName)

고객 객체를 직접 생성하지 않고 , CustomerManager 클래스의 register() 메소드를 통해 간접 생성 .

고객의 이름 검색 또한 lookup() 메소드를 통해 수행 .

- 고객 관리에 대한 모든 책임을 CustomerManager 에 위임 .

- 고객 정보를 저장소 등으로 보내거나 , 읽어오게끔 변경하더라도 CustomerManager 외부에서는 변경 사항을 알아차리지 못하게 됨 .

- 이를 통해 정보 은폐 (information hiding) 을 시도함 .

12Test Driven Development – Video Shop

퀴즈 - VideoShop

Customer & CustomerManager

Customer

+name+Customer(String name)+getName()

CustomerManager

+customerMap+register(String customerName)+lookup(String customerName)

- Customer 객체를 CustomerManager 의 register() 메소드 내부에서 생성하고 있습니다 .

- 고객에 대한 정보 ( 항목 ) 가 늘어날 수도 있으니 Customer 객체를 생성한 후에 register() 메소드에 Cus-tomer 객체를 파라미터로 전달하는 건 어떨까요 ?

- 만일 구조를 바꾸게 된다면 , CustomerManager 가 Customer 객체를 생성하는 경우와 그렇지 않을 경우 장단점은 무엇일까요 ?

확실한 답을 얻기 위해 질문하는 것이 아닙니다 . 어차피 정답이 없을 수도 없지만 ,생각하는 습관을 들이고 , 남의 의견을 경청하기 위한 훈련입니다 .멍청한 질문이 되지 않을까 두려워 하면 아무 것도 배우지 못합니다 !

13Test Driven Development – Video Shop

퀴즈 - VideoShop

그리고 , 두번째 태스크를 선택 했습니다 . - 내용은 거의 유사합니다 . ‘ 비디오 상품 관리’

- 비디오 유형에 따라 분리해 등록하고 , 제목으로 검색할 수 있어야 합니다 .- 비디오 테이프마다 대여료가 다르다고 하는군요 .

테스트 케이스 클래스 생성 - VideoTapeTest 클래스를 작성합니다 .

테스트 메소드 생성 - testVideo() 메소드를 작성했습니다 . - 비디오 테이프를 등록하고 , 정상적으로 등록되었는지 등록된 내용을 검사합니다 . - 비디오 테이프를 등록 한후 타이틀로 검색하고 , 존재하는지 검사합니다 .

실행 코드 작성 - VideoCatalog, Video 클래스를 생성합니다 .

14Test Driven Development – Video Shop

퀴즈 - VideoShop

비디오 목록 관리를 테스트 하기 위한 VideoTest 클래스 .package test.acme.videoshop;import com.acme.videoshop.video.Video;import com.acme.videoshop.video.VideoCatalog;import junit.framework.TestCase;public class VideoTapeTest extends TestCase { public static String STAR_WARS = "StarWars"; public void testVideo() { Video item = VideoCatalog.register(STAR_WARS, VideoCatalog.VIDEO_TYPE.MOVIE, 100); assertNotNull(item); assertEquals(item.getTitle(), STAR_WARS); assertEquals(item.getType(), VideoCatalog.VIDEO_TYPE.MOVIE); assertEquals(item.getRent(), 100); item = VideoCatalog.lookup(STAR_WARS); assertNotNull(item); assertEquals(item.getTitle(), STAR_WARS); assertEquals(item.getType(), VideoCatalog.VIDEO_TYPE.MOVIE); assertEquals(item.getRent(), 100); item = VideoCatalog.lookup(STAR_WARS + "."); assertNull(item); } }

15Test Driven Development – Video Shop

퀴즈 - VideoShop

Video & VideoCatalog

비디오 객체를 직접 생성하지 않고 , VideoCatalog 클래스의 register() 메소드를 통해 간접 생성 .

비디오 검색 또한 lookup() 메소드를 통해 수행 .

- 비디오 유형을 표현하기 위해서 상속하는 방법이 있지만 , 불필요한 클래스 추가라고 판단되어 enum 선언

public enum VIDEO_TYPE { MOVIE, SPORTS, DOCUMENTARY };

Video

+title+type+rent+Video(title, type, rent)+getTitle()+getType()+getRent()

VideoCatalog

+catalog+register(title, type, rent)+lookup()

16Test Driven Development – Video Shop

퀴즈 - VideoShop

Video & VideoCatalog

- VIDEO_TYPE enum 상수를 코드로 변경하는 것이 낫지 않을까요 ?

- VIDEO_TYPE enum 은 Video 클래스 혹은 VideoCatalog 클래스 어느 쪽에 포함하는 게 나을까 ?

- Video 클래스를 상속해서 , 스포츠 , 다큐멘터리 , 영화 클래스를 따로 생성하는 경우와 그렇지 않은 경우의 차이는 무얼까요 ?

Video

+title+type+rent+Video(title, type, rent)+getTitle()+getType()+getRent()

VideoCatalog

+catalog+register(title, type, rent)+lookup()

17Test Driven Development – Video Shop

퀴즈 - VideoShop

이제 좀 까다로운 문제입니다 . - 고객과 비디오를 등록하고 조회할 수 있으니까 다음 문제는 ...

- 고객이 비디오를 대여하는 프로세스를 테스트합니다 .- 가장 어려운 고비였습니다 ! 여러분은 ?

대여는 누가 하나요 ? - 당연히 고객이 대여하죠 . 그래서 Customer 클래스에 choose() 메소드를 추가 했습니다 .

포인트를 적립해야 할 것 같습니다 . - 요금은 나중에 정산하지만 , 포인트는 대여하는 시점에서 적립되겠죠 . - 그런데 , 적립된 누적 포인트와 대여 중인 비디오를 통해서 얻은 포인트를 구분해야 합니다 . - 적립된 포인트를 어디선가 관리해야 하는 겁니다 .

그리고 , 대여 중인 비디오 수를 알아야 합니다 . - 그러니까 대여 중인 비디오 내역을 어디선가 관리를 해야 하는거죠 . - 다행스럽게도 과거의 대역 내역은 조회할 필요가 없다고 합니다 .

계산은 언제 ? - 태스크를 너무 크게 만들지 말라고 해서 , 일단 대여하고 , 대여 결과를 확인하는 것 까지로 한정 했습니다 .

18Test Driven Development – Video Shop

퀴즈 - VideoShop

비디오 대여를 테스트 하기 위한 RentTest 클래스 .package test.acme.videoshop;import java.util.List;import com.acme.videoshop.counter.VideoRental;import com.acme.videoshop.customer.Customer;import com.acme.videoshop.customer.CustomerManager;import com.acme.videoshop.util.DateUtils;import com.acme.videoshop.video.Video;import com.acme.videoshop.video.VideoCatalog;import junit.framework.TestCase;public class RentTest extends TestCase { private static final String CUSTOMER_NAME = "Sunny Kwak"; public static String STAR_WARS = "StarWars"; public static String NBA_STARS = "NBA All Stars"; public static String THE_EARTH = "The Earth"; public void setUp() { CustomerManager.register(CUSTOMER_NAME); VideoCatalog.register(STAR_WARS, VideoCatalog.VIDEO_TYPE.MOVIE, 100); VideoCatalog.register(THE_EARTH, VideoCatalog.VIDEO_TYPE.DOCUMENTARY, 200); VideoCatalog.register(NBA_STARS, VideoCatalog.VIDEO_TYPE.SPORTS, 300); }(continued…)

19Test Driven Development – Video Shop

퀴즈 - VideoShop

비디오 대여를 테스트 하기 위한 RentTest 클래스 .

public void testRent() { Video movie, sports, documentary; // 영화 비디오 대여 ( 어제 ) Customer customer = CustomerManager.lookup(CUSTOMER_NAME); movie = VideoCatalog.lookup(STAR_WARS); assertNotNull(movie); customer.choose(movie, DateUtils.minusDays(1)); // 적립 포인트 1, 발생 포인트 1, 대여 수 1 assertEquals(customer.getResevePoint(), 1); assertEquals(customer.getIssuePoint(), 1); assertEquals(customer.rentCount(), 1); // 스포츠 비디오 대여 ( 그저께 ) sports = VideoCatalog.lookup(NBA_STARS); assertNotNull(sports); customer.choose(sports, DateUtils.minusDays(2)); // 적립 포인트 3, 발생 포인트 2, 대여 수 2 assertEquals(customer.getResevePoint(), 3); assertEquals(customer.getIssuePoint(), 3); assertEquals(customer.rentCount(), 2);(continued…)

20Test Driven Development – Video Shop

퀴즈 - VideoShop

비디오 대여를 테스트 하기 위한 RentTest 클래스 .

// 다큐멘터리 비디오 대여 ( 오늘 ) documentary = VideoCatalog.lookup(THE_EARTH); assertNotNull(documentary); customer.choose(documentary, DateUtils.getToday());

// 적립 포인트 4, 발생 포인트 4, 대여 수 3 assertEquals(customer.getResevePoint(), 4); assertEquals(customer.getIssuePoint(), 4); assertEquals(customer.rentCount(), 3);

// 대여정보 : 비디오 ( 종류 + 제목 + 가격 ), 대여기간 리스트 // int totalCharge = 0; List<VideoRental> rentalList = customer.getRentalList(); for( VideoRental rental : rentalList ) { Video video = rental.getVideo(); System.out.print( "Type : " + VideoCatalog.getTypeAsString(video.getType()) ); System.out.print( ", Title : " + video.getTitle() ); System.out.print( ", Rent : " + video.getRent() ); System.out.print( ", Rent Date : " + rental.getRentDate() ); System.out.println( ", Term : " + DateUtils.daysBetween(rental.getRentDate(), DateUtils.getToday()) ); } }}

(end…)

21Test Driven Development – Video Shop

퀴즈 - VideoShop

DateUtils, Customer, VideoRental & Video

현재 날짜 , 며칠 전 날짜 , 며칠 후 날짜 등을 얻기 위해 DateUtils 정적 클래스를 만들었습니다 .

- 날짜는 DateTime 타입을 사용하는 방안도 있지만 , 문자열로 관리하는 formatting 하기 편합니다 .- 기간 등을 계산하는 함수에서는 Joda Time API (http://joda-time.sourceforge.net) 를 사용했습니다 .- 만일 , 음력을 계산해야 하는 경우가 있다면 , IBM ICU 오픈소스 라이브러리를 사용하시면 좋습니다 .

적립 포인트 속성은 Customer 클래스 내부에 포함 시켰습니다 .

비디오 대여 목록을 표현하기 위해서 VideoRental 클래스를 만들고 , List 인터페이스를 이용해 Customer 클래스 내에 동적 배열로 선언했습니다 .

DateUtils

+getToday()+plusDays(int)+minusDays(int)

Customer

+rentalList+reservePoint+choose()+getIssuePoint()+getReservePoint()+rentCount()+getRentalList()

VideoRental

+video+rentDate+getCharge()+getPoint()+getRentDate()+getVideo()

Video

22Test Driven Development – Video Shop

퀴즈 - VideoShop

Customer 클래스 : 비디오 대여

/** * 비디오 대여 . * * @param video 대여 비디오 * @param rentDate 대여 일자 */ public void choose(Video video, String rentDate) { prepareList(); VideoRental rental = new VideoRental(video, rentDate); rentalList.add( rental ); reservePoint += rental.getPoint(); }

private void prepareList() { if( rentalList == null ) { rentalList = new ArrayList<VideoRental>(); } }

23Test Driven Development – Video Shop

퀴즈 - VideoShop

Customer 클래스 : 적립 포인트 반환 및 발생 포인트

/** * * @return 적립 포인트 반환 */ public int getResevePoint() { return reservePoint; }

/** * * @return 현재 대여 중인 비디오로 인해 발생한 포인트 */ public int getIssuePoint() { int issuePoint = 0; for( VideoRental rental : rentalList ) { issuePoint += Counter.getPoint(rental.getVideo()); } return issuePoint; }

24Test Driven Development – Video Shop

퀴즈 - VideoShop

VideoRental, Counter 클래스 : 비디오 별 포인트 반환

public class VideoRental {

public int getPoint() { return Counter.getPoint(video); }}

public class Counter {

public static int getPoint(Video video) { return video.getType() == VideoCatalog.VIDEO_TYPE.SPORTS ? 2 : 1; }}

25Test Driven Development – Video Shop

퀴즈 - VideoShop

DateUtils, Customer, VideoRental & Video

- Customer 클래스 내부에 대여 내역과 적립 포인트를 포함시켰는데 , 별도 클래스로 분리하는 건 어떨까요 ?

DateUtils

+getToday()+plusDays(int)+minusDays(int)

Customer

+rentalList+reservePoint+choose()+getIssuePoint()+getReservePoint()+rentCount()+getRentalList()

VideoRental

+video+rentDate+getCharge()+getPoint()+getRentDate()+getVideo()

Video

26Test Driven Development – Video Shop

퀴즈 - VideoShop

거의 마지막에 도착한 것 같습니다 . - 마지막 태스크는 고객이 비디오를 반납했을 때 , 요금을 정산하는 프로세스입니다 .

- 대충하느라 테스트 클래스를 따로 만들지 않았습니다 . RentTest 클래스의 testRent() 메소드 변경

- 별도의 메소드를 작성하지 않고 , testRent() 메소드에 코드를 추가했습니다 .

테스트는 꼼꼼하게… - 검사 조건을 따져 봤습니다 . 비디오 유형이 최소 3 가지 , 각각 할인해주는 경우와 할인 없는 경우가 있습니

다 . - 게다가 오늘 오전에 빌리고 , 오후에 반납하는 경우는 24 시간을 넘지 않는데 이런 경우도 하루 요금을

받아야죠 .

그런데 반납하는 프로세스는 개발하지 않았습니다 !

27Test Driven Development – Video Shop

퀴즈 - VideoShop

비디오 요금 계산을 테스트 하기 위한 RentTest 클래스 .

public void testRent() { // ---- 요금 계산을 해보자 ...! ---- // 스포츠는 장기대여 할인이 없다 . ( 이게 제일 쉬우니 이것부터 ) // 2 일전 대여 , 일일요금 300 원 for( VideoRental rental : rentalList ) { Video video = rental.getVideo(); if( video.getType() == VideoCatalog.VIDEO_TYPE.SPORTS ) { // 오늘 반납 시 , 2 * 300 = 600 원 assertEquals( 600, rental.getCharge(DateUtils.getToday())); // 내일 반납 시 , 3 * 300 = 900 원 assertEquals( 900, rental.getCharge(DateUtils.plusDays(1))); } } // 영화는 대여기간이 2 일 이상되면 3 일째 부터는 대여요금이 1/2 로 할인된다 . // 어제 대여 , 일일 요금 100 원 for( VideoRental rental : rentalList ) { Video video = rental.getVideo(); if( video.getType() == VideoCatalog.VIDEO_TYPE.MOVIE ) { // 오늘 반납 시 , 1 * 100 = 100 원 assertEquals( 100, rental.getCharge(DateUtils.getToday())); // 내일 반납 시 , 2 * 100 = 200 원 assertEquals( 200, rental.getCharge(DateUtils.plusDays(1))); // 모레 반납 시 , (2 * 100) + (1 * 100 / 2) = 250 assertEquals( 250, rental.getCharge(DateUtils.plusDays(2))); } } (continued…)

28Test Driven Development – Video Shop

퀴즈 - VideoShop

비디오 요금 계산을 테스트 하기 위한 RentTest 클래스 .

// 다큐멘타리는 3 일 이상 대여하면 4 일째 부터는 1/3 로 할인된다 . // 오늘 대여 , 일일 요금 200 원 for( VideoRental rental : rentalList ) { Video video = rental.getVideo(); if( video.getType() == VideoCatalog.VIDEO_TYPE.DOCUMENTARY ) { // 오늘 반납 시 , 1 * 200 = 200 원 assertEquals( 200, rental.getCharge(DateUtils.getToday())); // 내일 반납 시 , 1 * 200 = 200 원 assertEquals( 200, rental.getCharge(DateUtils.plusDays(1))); // 2 일 후 반납 시 , (2 * 200) = 400 assertEquals( 400, rental.getCharge(DateUtils.plusDays(2))); // 4 일 후 반납 시 , (3 * 200) + (1 * 200 / 3) = 667 assertEquals( 666, rental.getCharge(DateUtils.plusDays(4))); } } // ---- 요금 계산을끝내자 ...! ---- (continued…)

29Test Driven Development – Video Shop

퀴즈 - VideoShop

비디오 요금 계산을 테스트 하기 위한 RentTest 클래스 . // 전체 요금 계산 (4 일 후 반납 시 , 6 일간 대여 ) String restoreDate = DateUtils.plusDays(4); int totalCharge = 0; for( VideoRental rental : rentalList ) { int eachCharge = rental.getCharge(DateUtils.plusDays(4)); Video video = rental.getVideo(); // 다큐멘터리 오늘 대여 , 일일요금 200 원 // 4 일 대여 요금 : (3 * 200) + (1 * 200 / 3) = 667 if( video.getType() == VideoCatalog.VIDEO_TYPE.DOCUMENTARY ) { assertEquals( eachCharge, 666 ); } // 영화 어제 대여 , 일일요금 100 원 // 5 일 대여 요금 : (2 * 100) + (3 * 100 / 2) = 350 else if( video.getType() == VideoCatalog.VIDEO_TYPE.MOVIE ) { assertEquals( eachCharge, 350 ); } // 스포츠 2 일전 대여 , 일일요금 300 원 // 6 일 대여 요금 : 6 * 300 = 1800 else if( video.getType() == VideoCatalog.VIDEO_TYPE.SPORTS) { assertEquals( eachCharge, 1800 ); } totalCharge += eachCharge; } // 전체 요금 : 666 + 350 + 1800 = 2816 assertEquals(totalCharge, 2816);(end…)

30Test Driven Development – Video Shop

퀴즈 - VideoShop

비디오 요금 계산을 테스트 하기 위한 RentTest 클래스 . // 전체 요금 계산 (4 일 후 반납 시 , 6 일간 대여 ) String restoreDate = DateUtils.plusDays(4); int totalCharge = 0; for( VideoRental rental : rentalList ) { int eachCharge = rental.getCharge(DateUtils.plusDays(4)); Video video = rental.getVideo(); // 다큐멘터리 오늘 대여 , 일일요금 200 원 // 4 일 대여 요금 : (3 * 200) + (1 * 200 / 3) = 667 if( video.getType() == VideoCatalog.VIDEO_TYPE.DOCUMENTARY ) { assertEquals( eachCharge, 666 ); } // 영화 어제 대여 , 일일요금 100 원 // 5 일 대여 요금 : (2 * 100) + (3 * 100 / 2) = 350 else if( video.getType() == VideoCatalog.VIDEO_TYPE.MOVIE ) { assertEquals( eachCharge, 350 ); } // 스포츠 2 일전 대여 , 일일요금 300 원 // 6 일 대여 요금 : 6 * 300 = 1800 else if( video.getType() == VideoCatalog.VIDEO_TYPE.SPORTS) { assertEquals( eachCharge, 1800 ); } totalCharge += eachCharge; } // 전체 요금 : 666 + 350 + 1800 = 2816 assertEquals(totalCharge, 2816);(end…)

31Test Driven Development – Video Shop

퀴즈 - VideoShop

Counter 클래스

요금 및 포인트 계산을 Counter 클래스에 전담 시켰습니다 .

기존의 경험 상 업무 규칙은 한 곳에 몰아 두는 것이 좋은 것 같습니다 .

- 요금 할인 / 할증 규칙은 비디오 유형 별로 바뀌지 않고 , 시기 (특정 계절에 따른 이벤트 ) 혹은 집합 조건 (2 종 선택 시 하나 무료 ) 등 외적인 조건에 따라 바뀌는 경우가 많습니다 .

DateUtils

+getToday()+plusDays(int)+minusDays(int)

Customer

+rentalList+reservePoint+choose()+getIssuePoint()+getReservePoint()+rentCount()+getRentalList()

VideoRental+video+rentDate+getCharge()+getPoint()+getRentDate()+getVideo()

Video

Counter

+getCharge()+getPoint()

32Test Driven Development – Video Shop

퀴즈 - VideoShop

VideoRental 클래스 : 요금 반환

public class VideoRental {

public int getCharge(String restoreDate) { return Counter.getCharge(video, rentDate, restoreDate); }}

public static int getCharge(Video video, String rentDate, String restoreDate) {

int days = DateUtils.daysBetween(rentDate, restoreDate); if (days == 0) days = 1;

int charge = 0; // 다큐멘타리는 3 일 이상 대여하면 4 일째 부터는 1/3 로 할인된다 . if (video.getType() == VideoCatalog.VIDEO_TYPE.DOCUMENTARY) { if( days < 4 ) { charge = days * video.getRent(); } else { charge = (3 * video.getRent()) + ((days-3) * video.getRent() / 3); } }

(continued…)

33Test Driven Development – Video Shop

퀴즈 - VideoShop

VideoRental 클래스 : 요금 반환

// 영화는 대여기간이 2 일 이상되면 3 일째 부터는 대여요금이 1/2 로 할인된다 . else if (video.getType() == VideoCatalog.VIDEO_TYPE.MOVIE) { if( days < 3 ) { charge = days * video.getRent(); } else { charge = (2 * video.getRent()) + ((days-2) * video.getRent() / 2); } } // 스포츠는 장기대여 할인이 없다 . else if (video.getType() == VideoCatalog.VIDEO_TYPE.SPORTS) { charge = video.getRent() * days; } return charge; }

(end…)

34Test Driven Development – Video Shop

퀴즈 - VideoShop

DateUtils, Customer, VideoRental & Video

- 규칙이 변경 되는 상황일 발생하는 것을 예상한다면 , Counter와 VideoRental 사이에 Factory 패턴을 적용하면 어떨까요 ?

DateUtils

+getToday()+plusDays(int)+minusDays(int)

Customer

+rentalList+reservePoint+choose()+getIssuePoint()+getReservePoint()+rentCount()+getRentalList()

VideoRental+video+rentDate+getCharge()+getPoint()+getRentDate()+getVideo()

Video

Counter

+getCharge()+getPoint()

35Test Driven Development – Video Shop

퀴즈 - VideoShop

Test Driven Development

[Video Shop]

봐주셔서 감사드립니다 .

날카로운 지적 부탁 드립니다 .