시작하자 단위테스트

58
단위테스트 최용은 시작하자! SpringCamp 2014

Upload: yongeun-choi

Post on 28-Jun-2015

2.063 views

Category:

Software


0 download

DESCRIPTION

springcamp 2014(5월31일)에서 발표한 자료 입니다.

TRANSCRIPT

Page 1: 시작하자 단위테스트

단위테스트최용은

시작하자

!

SpringCamp 2014

Page 2: 시작하자 단위테스트

단위 테스트란?

Page 3: 시작하자 단위테스트

사전적 의미

1) 제품의 성능이나 상태 따위를 일정한 기준에 따라 검사함

2) 일정한 기준에 따라 검사하다.

1) 사물의 길이, 넓이, 무게 등을 수치로 나타낼 때, 기본이 되는 기준

2) 하나의 집단 조직 등을 구성하는 기본적인 한덩어리.

단위

테스트

출처 : Daum 사전

Page 4: 시작하자 단위테스트

위키백과

유닛 테스트(unit test)

컴퓨터 프로그래밍에서 소스코드의 특정 모듈이 의도된 대로 정확히 작동하는지 검

증하는 절차다.

즉, 모든 함수와 메소드에 대한 테스트 케이스를 작성하는 절차를 말한다.

…..

Page 5: 시작하자 단위테스트

그림으로 보면..

출처 : http://martinfowler.com/bliki/UnitTest.html

Page 6: 시작하자 단위테스트

목적?

출처 : wikitree.co.kr/main/news_view.php?id=70077

Page 7: 시작하자 단위테스트

목 적!신 뢰

Page 8: 시작하자 단위테스트

단위테스트를 작성하지 않은 이유 ?● 단위테스트가 무엇인지 모른다.

● 들어봤지만, 주변에 작성하는 사람이 없다.

● 작성하고자 하지만, 너무 어렵다.

○ 어디서 어떻게 시작해야할지 모르겠다.

● 작성할 줄 알지만,

○ 귀찮다. -> 습관을 들여야한다...

○ 필요 없다. -> 천재...

Page 9: 시작하자 단위테스트

오 해기능 추가 시간이 너~ 무 오래 걸린다.

Page 10: 시작하자 단위테스트

오 해기능 추가 시간이 너~ 무 오래 걸린다.

테스트 코드량이 많아져서, 별로...

Page 11: 시작하자 단위테스트

오 해기능 추가 시간이 너~ 무 오래 걸린다.

테스트 코드량이 많아져서, 별로...

단위 테스트만 작성하면 버그 따윈 없어!

Page 12: 시작하자 단위테스트

단위 테스트를 작성해 볼까요?

Page 13: 시작하자 단위테스트

단위 테스트를 도와 주는 도구 for JAVA

● JUnit○ (자바) 단위 테스트 프레임워크의 표준○ http://junit.org/

● Mocking Framework○ Mockito○ easyMock○ JMock○ ...

Page 14: 시작하자 단위테스트

단순하게 생각하자.

예상하고,

Page 15: 시작하자 단위테스트

단순하게 생각하자.

예상하고,

실행하고,

Page 16: 시작하자 단위테스트

단순하게 생각하자.

예상하고,

실행하고,

검증하라!

Page 17: 시작하자 단위테스트

예 : 상품 판매 금액/수량 계산하기class SaleResult

private final long productId;private long totalSalePrice;private long totalSaleCount;

public SaleResult(long productId) { this.productId = productId;}

public void calculate(List<Sale> sales) { for( Sale sale : sales ) { totalSalePrice += sale.getSalePrice(); totalSaleCount += sale.getSaleCount(); }}

// getter/setter 생략

class Sale

private long id;private long productId;private long salePrice;private long saleCount;

public Sale(long id, long productId, long salePrice, long saleCount) { this.id = id; this.productId = productId; this.salePrice = salePrice; this.saleCount = saleCount;}// getter/setter 생략

Page 18: 시작하자 단위테스트

SaleResult 테스트class SaleResult

private final long productId;private long totalSalePrice;private long totalSaleCount;

public SaleResult(long productId) { this.productId = productId;}

public void calculate(List<Sale> sales) { for( Sale sale : sales ) { totalSalePrice += sale.getSalePrice(); totalSaleCount += sale.getSaleCount(); }}

// getter/setter 생략

class SaleResultTest

@Test public void calculate() throws Exception { //given long productId = 1l; List<Sale> sales = new ArrayList<>(); sales.add(new Sale(1l, 1l, 10000l, 1l)); sales.add(new Sale(2l, 1l, 10000l, 1l)); SaleResult saleResult = new SaleResult(productId);

//when saleResult.calculate(sales);

//then assertThat(saleResult.getTotalSalePrice(), is(20000l)); assertThat(saleResult.getTotalSaleCount(), is (2l));}

Page 19: 시작하자 단위테스트

given / when / thenlong productId = 1l;List<Sale> sales = new ArrayList<>();sales.add(new Sale(1l, 1l, 10000l, 1l));sales.add(new Sale(2l, 1l, 10000l, 1l));SaleResult saleResult = new SaleResult(productId);

saleResult.calculate(sales);

assertThat(saleResult.getTotalSalePrice(), is(20000l));assertThat(saleResult.getTotalSaleCount(), is (2l));

given

when

then

Page 20: 시작하자 단위테스트

long productId = 1l;

List<Sale> sales = new ArrayList<>();sales.add(new Sale(1l, 1l, 10000l, 1l));sales.add(new Sale(2l, 1l, 10000l, 1l));

SaleResult saleResult = new SaleResult(productId);

예상하기 (준비하기)

given

productId 1

totalSalePrice 0

totalSaleCount 0

SaleResult

Page 21: 시작하자 단위테스트

실행하기

saleResult.calculate(sales);when

public void calculate(List<Sale> sales) { for( Sale sale : sales ) { totalSalePrice += sale.getSalePrice(); totalSaleCount += sale.getSaleCount(); }}

productId 1

totalSalePrice 20000

totalSaleCount 2

SaleResult

Page 22: 시작하자 단위테스트

검증하기

assertThat(saleResult.getTotalSalePrice(), is(20000l));

assertThat(saleResult.getTotalSaleCount(), is (2l));

then

productId 1

totalSalePrice 20000

totalSaleCount 2

SaleResult

Page 23: 시작하자 단위테스트

예제의 느낌

Page 24: 시작하자 단위테스트

하지만 현실은

Page 25: 시작하자 단위테스트

단위테스트가 어려운 이유들..● 소프트웨어는 객체들이 얽히고, 설켜서, 복잡하다.

● 제어하기 힘든 것들

○ 영속성(Persistency)

○ 시간(Time)

○ 임의성(Randomness)

○ 네트워크(Network)

○ 인프라(Infra)

○ ...

Page 26: 시작하자 단위테스트

테스트 더블

진짜 협력 객체

테스트더블

테스트더블

테스트더블

출처 : Effective Unit Testing

테스트 대상 코드와 협력 객체를 분리

● 테스트 작성 시 테스트 대상 코드와 상호작용하는 객체

테스트 대상 코드

Page 27: 시작하자 단위테스트

테스트 더블 종류

스파이 객체스텁 객체 페이크 객체 목 객체

테스트 더블

출처 : Effective Unit Testing

Page 28: 시작하자 단위테스트

테스트 더블 사용하기

● 테스트 대상 코드에서 어떻게 테스트 더블을 사용하지?

Page 29: 시작하자 단위테스트

테스트 더블 사용하기

● 테스트 대상 코드에서 어떻게 테스트 더블을 사용하

지?

○ 의존성 주입 (Dependency Injection, DI)

■ 객체간 종속성을 소스코드에서 설정하지 않고,

외부에서 주입하도록 하는 디자인 패턴 중 하나

Page 30: 시작하자 단위테스트

테스트 더블 사용하기

● 의존성 주입 (Dependency Injection, DI)

○ 적용 유형

■ 생성자 주입

■ 세터(Setter)를 통한 주입

■ 인터페이스(Interface)를 통한 주입

Page 31: 시작하자 단위테스트

테스트 더블 사용하기● 의존성 주입 (Dependency Injection, DI)

○ 인터페이스를 통한 주입

<<interface>>Ram

SamsungRam HynixRam

MotherBoard1

4

Page 32: 시작하자 단위테스트

테스트 더블 사용하기● 의존성 주입 (Dependency Injection, DI)

class MotherBoard

private Ram ram;

public MotherBoard(Ram ram) { this.ram = ram;}// 생성자 주입

class MotherBoard

private Ram ram;

public void setRam(Ram ram) { this.ram = ram;}// 세터(setter) 주입

class MotherBoard

Ram ram = new HynixRam();

// 안티 패턴

MotherBoard b = new MotherBoard(new SamsungRam());

MotherBoard b= new MotherBoard();b.setRam(new HynixRam());MotherBoard b= new MotherBoard();

Page 33: 시작하자 단위테스트

테스트 더블을 이용해서 영속성 단위 테스트 해보자!

Page 34: 시작하자 단위테스트

영속성에 대한 대처

SaleService

SaleResult <<interface>>SaleRepository

<<interface>>SaleResultRepository

<<create>>1

11

1

● 상품의 판매 금액/수량을 계산 하여, DB에 저장하기

Page 35: 시작하자 단위테스트

영속성에 대한 대처

SaleService

SaleResult <<interface>>SaleRepository

<<interface>>SaleResultRepository

<<create>>1

11

1

● 상품의 판매 금액/수량을 계산 하여, DB에 저장하기 테스트 더블

진짜 협력객체

테스트 대상코드

Page 36: 시작하자 단위테스트

SaleResult 저장하기class SaleService

private final SaleRespository saleRespository;private final SaleResultRepository saleResultRepository;public SaleService(saleRespository,saleResultRepository) { this.saleRespository = saleRespository; this.saleResultRepository = saleResultRepository;}

public void generateSaleResult(long productId) { List<Sale> sales = saleRespository.findAllByProductId(productId); if(sales.isEmpty()) { throw new RuntimeException("sales is empty"); } SaleResult saleResult = new SaleResult(productId); saleResult.calculate(sales);

saleResultRepository.save(saleResult);}

class SaleServiceTest

@Before public void setUp() throws Exception { saleRespositoryMock = mock(SaleRespository.class); saleResultRepositoryMock = mock(SaleResultRepository.class); saleService = new SaleService(saleRespositoryMock, saleResultRepositoryMock);

}@Test public void generateSaleResult() throws Exception { //given List<Sale> sales = new ArrayList<>(); sales.add(new Sale(1l, 1l, 10000l, 1l)); given(saleRespositoryMock.findAllByProductId(productId)).willReturn(sales); //when saleService.generateSaleResult(productId);

//then verify(saleRespositoryMock, times(1)).findAllByProductId(productId); verify(saleResultRepositoryMock,times(1)).save(any(SaleResult.class));

}

Page 37: 시작하자 단위테스트

SaleResult 저장하기class SaleService

private final SaleRespository saleRespository;private final SaleResultRepository saleResultRepository;public SaleService(saleRespository, saleResultRepository) { this.saleRespository = saleRespository; this.saleResultRepository = saleResultRepository;}

public void generateSaleResult(long productId) { List<Sale> sales = saleRespository.findAllByProductId(productId); if(sales.isEmpty()) { throw new RuntimeException("sales is empty"); } SaleResult saleResult = new SaleResult(productId); saleResult.calculate(sales);

saleResultRepository.save(saleResult);}

class SaleServiceTest

@Before public void setUp() throws Exception { saleRespositoryMock = mock(SaleRespository.class); saleResultRepositoryMock = mock(SaleResultRepository.class); saleService = new SaleService(saleRespositoryMock, saleResultRepositoryMock);

}@Test public void generateSaleResult() throws Exception { //given List<Sale> sales = new ArrayList<>(); sales.add(new Sale(1l, 1l, 10000l, 1l)); given(saleRespositoryMock.findAllByProductId(productId)).willReturn(sales); //when saleService.generateSaleResult(productId);

//then verify(saleRespositoryMock, times(1)).findAllByProductId(productId); verify(saleResultRepositoryMock,times(1)).save(any(SaleResult.class));

}

Mockito

Page 38: 시작하자 단위테스트

given / when / thensetUp();List<Sale> sales = new ArrayList<>();sales.add(new Sale(1l, 1l, 10000l, 1l)); given( saleRespositoryMock.findAllByProductId(productId) ).willReturn(sales);

saleService.generateSaleResult(productId);

verify(saleRespositoryMock, times(1) ).findAllByProductId(productId);

verify(saleResultRepositoryMock, times(1) ).save(any(SaleResult.class));

given

when

then

Page 39: 시작하자 단위테스트

@Before public void setUp() throws Exception { saleRespositoryMock = mock(SaleRespository.class); saleResultRepositoryMock = mock(SaleResultRepository.class); saleService = new SaleService(saleRespositoryMock, saleResultRepositoryMock);

}

List<Sale> sales = new ArrayList<>();sales.add(new Sale(1l, 1l, 10000l, 1l)); given( saleRespositoryMock.findAllByProductId(productId) ).willReturn(sales);

SaleService 생성자 주입

given

Page 40: 시작하자 단위테스트

@Before public void setUp() throws Exception { saleRespositoryMock = mock(SaleRespository.class); saleResultRepositoryMock = mock(SaleResultRepository.class); saleService = new SaleService(saleRespositoryMock, saleResultRepositoryMock);

}

List<Sale> sales = new ArrayList<>();sales.add(new Sale(1l, 1l, 10000l, 1l)); given( saleRespositoryMock.findAllByProductId(productId) ).willReturn(sales);

given(호출_메서드).willReturn(던져줄 값)

given

Page 41: 시작하자 단위테스트

예상된 값 나오기

saleService.generateSaleResult(productId);

when

public void generateSaleResult(long productId) { List<Sale> sales = saleRespository.findAllByProductId(productId); if(sales.isEmpty()) { throw new RuntimeException("sales is empty"); } SaleResult saleResult = new SaleResult(productId); saleResult.calculate(sales);

saleResultRepository.save(saleResult);}

Page 42: 시작하자 단위테스트

예상된 값 나오기

saleService.generateSaleResult(productId);

when

public void generateSaleResult(long productId) { List<Sale> sales = saleRespository.findAllByProductId(productId); if(sales.isEmpty()) { throw new RuntimeException("sales is empty"); } SaleResult saleResult = new SaleResult(productId); saleResult.calculate(sales);

saleResultRepository.save(saleResult);}

given( saleRespositoryMock.findAllByProductId(productId) ).willReturn(sales);

Page 43: 시작하자 단위테스트

saleService.generateSaleResult

saleService.generateSaleResult(productId);

when

public void generateSaleResult(long productId) { List<Sale> sales = saleRespository.findAllByProductId(productId); if(sales.isEmpty()) { throw new RuntimeException("sales is empty"); } SaleResult saleResult = new SaleResult(productId); saleResult.calculate(sales);

saleResultRepository.save(saleResult);}

Page 44: 시작하자 단위테스트

검증

verify(saleRespositoryMock, times(1)).findAllByProductId(productId);

verify(saleResultRepositoryMock, times(1)).save(any(SaleResult.class));

then

Page 45: 시작하자 단위테스트

임의성을 제어 해보자!

Page 46: 시작하자 단위테스트

임의성 제어하기● 예 : 숫자야구 게임

● 상황

○ 숫자야구 게임 실행시 내부에서 임의로 정답을 생성

■ 단위 테스트 작성을 위해서는 개발자가 정답을 알아야

Page 47: 시작하자 단위테스트

숫자야구 게임 정답 가져오기

class BaseBallGame

public BaseBallResult play(String number) { int strike = 0, ball = 0; for(int i = 0 ; i < getNumber().length() ; i ++ ) { //strike, ball 구하는 구현체 if( getNumber().charAt(i) == number.charAt(j)) { ….. } } return new BaseBallResult(strike,ball);}

private String getNumber() {

List<String> numbers = Lists.newArrayList("1", "2", "3", "4",

"5", "6", "7", "8", "9");

Random random = new Random(System.nanoTime());

Collections.shuffle(numbers, random);

return numbers.get(0) + numbers.get(1) + numbers.get(2);

}

AS - IS

Page 48: 시작하자 단위테스트

숫자야구 게임 정답 가져오기

class BaseBallGame

public BaseBallResult play(String number) { int strike = 0, ball = 0; for(int i = 0 ; i < getNumber().length() ; i ++ ) { //strike, ball 구하는 구현체 if( getNumber().charAt(i) == number.charAt(j)) { ….. } } return new BaseBallResult(strike,ball);}

private String getNumber() {

List<String> numbers = Lists.newArrayList("1", "2", "3", "4",

"5", "6", "7", "8", "9");

Random random = new Random(System.nanoTime());

Collections.shuffle(numbers, random);

return numbers.get(0) + numbers.get(1) + numbers.get(2);

}

AS - IS

getNumber()를 원하는 값이 나오게 할 수가 없네..

Page 49: 시작하자 단위테스트

숫자야구 게임 정답 가져오기

class BaseBallGame

public BaseBallResult play(String number) { int strike = 0, ball = 0; for(int i = 0 ; i < getNumber().length() ; i ++ ) { //strike, ball 구하는 구현체 if( getNumber().charAt(i) == number.charAt(j)) { ….. } } return new BaseBallResult(strike,ball);}

private String getNumber() {

List<String> numbers = Lists.newArrayList("1", "2", "3", "4",

"5", "6", "7", "8", "9");

Random random = new Random(System.nanoTime());

Collections.shuffle(numbers, random);

return numbers.get(0) + numbers.get(1) + numbers.get(2);

}

AS - IS 테스트 할 수가 없어..

http://www.freeimages.com/photo/776061

Page 50: 시작하자 단위테스트

숫자야구 게임 정답 가져오기

BaseBallGame <<interface>>BaseBallNumber

11

● 해결책 ?

○ 정답을 만들어주는 녀석을 인터페이스로 주입받아서 처리하자

RandomBaseBallNumber

Page 51: 시작하자 단위테스트

숫자야구 게임 정답 가져오기

class BaseBallGame

private BaseBallNumber baseballNumber;

public void setBaseBallNumber(baseballNumber) { this.baseballNumber = baseballNumber;}public BaseBallResult play(String number) { ... if( getNumber().charAt(i) == number.charAt(j)) { ….. } …. return new BaseBallResult(strike,ball);}

private String getNumber() {

return baseballNumber.getNumber();

}

TO - BE

Page 52: 시작하자 단위테스트

숫자야구 게임 정답 가져오기

class BaseBallGame

private BaseBallNumber baseballNumber;

public void setBaseBallNumber(baseballNumber) { this.baseballNumber = baseballNumber;}public BaseBallResult play(String number) { ... if( getNumber().charAt(i) == number.charAt(j)) { ….. } …. return new BaseBallResult(strike,ball);}

class BaseBallGameTest

BaseBallGame game;//setUp에서 game 생성

@Test public void givenNumber_assertBaseBall() { baseBallNumber = new BaseBallNumber(){ @Override public String getNumber() { return "123"; } }; game.setBaseBallNumber(baseBallNumber);

//검증 ..

}

TO - BE

Page 53: 시작하자 단위테스트

좋은 정보

숫자야구게임 TDD- 최범균

http://www.youtube.com/watch?v=960hX13PDuk

Page 54: 시작하자 단위테스트

테스트 작성 흐름

실패한 테스트 작성

테스트 성공

리팩토링

Page 55: 시작하자 단위테스트

자, 이제부터 단위테스트를

작성해보아요 ^_^

Page 56: 시작하자 단위테스트

참조● Effective Unit Testing - 라쎄 코스켈라● UnitTest by Martin Fowler

○ http://martinfowler.com/bliki/UnitTest.html● 숫자야구게임 TDD - 최범균

○ http://www.youtube.com/watch?v=960hX13PDuk● TDD Live (springcamp2013) - 최범균

○ http://www.slideshare.net/madvirus/tdd-live-spring-camp-2013?qid=8c55f248-4151-42e9-977a-5ca334e0eabb&v=default&b=&from_search=1

● 유닛테스트 - 위키백과○ http://ko.wikipedia.org/wiki/%EC%9C%A0%EB%8B%9B_%ED%85%8C%EC%8A%A4%

ED%8A%B8● 의존성 주입 - 위키백과

○ http://ko.wikipedia.org/wiki/%EC%9D%98%EC%A1%B4%EC%84%B1_%EC%A3%BC%EC%9E%85

Page 57: 시작하자 단위테스트

Q&A(email : [email protected])

Page 58: 시작하자 단위테스트

THANKS

Maldives 팀

최범균님

박용권님

양완수님