58
xUnit 테스트 패턴 10결과 검증 최기원

10장 결과 검증

  • Upload
    dagri82

  • View
    342

  • Download
    2

Embed Size (px)

Citation preview

Page 1: 10장 결과 검증

xUnit 테스트패턴

10장결과검증

최기원

Page 2: 10장 결과 검증

10장에서이야기하는것테스트 결과 검증 방법

테스트코드 작성 노하우

Page 3: 10장 결과 검증

10장에서이야기하는것테스트 결과 검증 방법상태검증

동작검증

테스트 코드 작성 노하우테스트코드 중복 줄이기

테스트내 조건문 로직 피하기

이해하기 쉬운 테스트를 작성하기

Page 4: 10장 결과 검증

10장에서이야기하는것테스트 결과 검증 방법상태검증

동작검증

테스트 코드 작성 노하우테스트코드 중복 줄이기

테스트내 조건문 로직 피하기

이해하기 쉬운 테스트를 작성하기

Page 5: 10장 결과 검증

상태검증SUT의기대 결과 값이 발생했는지 검증하는 '일반적인' 방법

3가지방법내장단언문을 사용하는 방법

델타 단언문을 사용하는 방법

외부결과 검증 방법

Page 6: 10장 결과 검증

내장단언문사용하는방법

assertTrue(aBooleanExpression)

결과 지정 단언문

assertEquals(expected, actual)

동등 단언문

assertEquals(expected, actual, tolerance)

퍼지 동등 단언문

Page 7: 10장 결과 검증

델타단언문

1. 관렦테이블/클래스에대한일종의 '스냅샷'을테스트시작할 때저장한다.

2. 테스트가끝나면생성된객체/로우집합에서아까의 '스냅샷'을 제거한후남은것들을기대결과값과비교

Page 8: 10장 결과 검증

외부결과검증

예상결과와 실제 결과를 파일로 저장한후 외부 파일 비교 프로그램으로차이를 확인하는 것

Page 9: 10장 결과 검증

10장에서이야기하는것테스트 결과 검증 방법상태검증

동작검증

테스트 코드 작성 노하우테스트코드 중복 줄이기

테스트내 조건문 로직 피하기

이해하기 쉬운 테스트를 작성하기

Page 10: 10장 결과 검증

동작검증SUT의동작을검증하는방법

2가지방법절차형동작검증

기대동작명세

Page 11: 10장 결과 검증

젃차형동작검증

SUT가실행될 때원하는동작을잡아낸후나중에쓸데이터를저장해둔다. 나중에테스트에서는 SUT의출력값을해당기대결과값과하나하나비교한다.

Page 12: 10장 결과 검증

public void testRemoveFlightLogging_recordingTestStub() throw Exception {// 픽스쳐 설치FlightDto expectedFlightDto = createAnUnregFlight();FlightManagementFacade facade = new FlightManagementFacadeImpl();// 테스트 대역 설치AuditLogSpy logSpy = new AuditLogSpy();facade.setAuditLog(logSpy);// 실행facade.removeFlight(expectedFlightDto.getFlightNumber());//검증assertEquals("number of calls", 1,

logSpy.getNumberofCalls());assertEquals("action code", Helper.REMOVE_FLIGHT_ACTION_CODE,

logSpy.getActionCode());assertEquals("date", helper.getTodayDateWithoutTime(),

logSpy.getDate());assertEquals("user", Helper.TEST_USER_NAME,

logSpy.getUser());assertEquals("detail", expectedFlightDto.getFlightNumber(),

logSpy.getDetail());}

Page 13: 10장 결과 검증

public void testRemoveFlightLogging_recordingTestStub() throw Exception {// 픽스쳐 설치FlightDto expectedFlightDto = createAnUnregFlight();FlightManagementFacade facade = new FlightManagementFacadeImpl();// 테스트 대역 설치AuditLogSpy logSpy = new AuditLogSpy();facade.setAuditLog(logSpy);// 실행facade.removeFlight(expectedFlightDto.getFlightNumber());//검증assertEquals("number of calls", 1,

logSpy.getNumberofCalls());assertEquals("action code", Helper.REMOVE_FLIGHT_ACTION_CODE,

logSpy.getActionCode());assertEquals("date", helper.getTodayDateWithoutTime(),

logSpy.getDate());assertEquals("user", Helper.TEST_USER_NAME,

logSpy.getUser());assertEquals("detail", expectedFlightDto.getFlightNumber(),

logSpy.getDetail());}

Page 14: 10장 결과 검증

public void testRemoveFlightLogging_recordingTestStub() throw Exception {// 픽스쳐 설치FlightDto expectedFlightDto = createAnUnregFlight();FlightManagementFacade facade = new FlightManagementFacadeImpl();// 테스트 대역 설치AuditLogSpy logSpy = new AuditLogSpy();facade.setAuditLog(logSpy);// 실행facade.removeFlight(expectedFlightDto.getFlightNumber());//검증assertEquals("number of calls", 1,

logSpy.getNumberofCalls());assertEquals("action code", Helper.REMOVE_FLIGHT_ACTION_CODE,

logSpy.getActionCode());assertEquals("date", helper.getTodayDateWithoutTime(),

logSpy.getDate());assertEquals("user", Helper.TEST_USER_NAME,

logSpy.getUser());assertEquals("detail", expectedFlightDto.getFlightNumber(),

logSpy.getDetail());}

Page 15: 10장 결과 검증

public void testRemoveFlightLogging_recordingTestStub() throw Exception {// 픽스쳐 설치FlightDto expectedFlightDto = createAnUnregFlight();FlightManagementFacade facade = new FlightManagementFacadeImpl();// 테스트 대역 설치AuditLogSpy logSpy = new AuditLogSpy();facade.setAuditLog(logSpy);// 실행facade.removeFlight(expectedFlightDto.getFlightNumber());//검증assertEquals("number of calls", 1,

logSpy.getNumberofCalls());assertEquals("action code", Helper.REMOVE_FLIGHT_ACTION_CODE,

logSpy.getActionCode());assertEquals("date", helper.getTodayDateWithoutTime(),

logSpy.getDate());assertEquals("user", Helper.TEST_USER_NAME,

logSpy.getUser());assertEquals("detail", expectedFlightDto.getFlightNumber(),

logSpy.getDetail());}

Page 16: 10장 결과 검증

기대동작명세

상태를검증하기 위해 기대 객체를 생성해 SUT가리턴하는 실제 객체와 비교한다.

Page 17: 10장 결과 검증

public void testRemoveFlight_JMock() throws Exception {// 픽스쳐설치FlightDto expectedFlightDto = createAnonRegFlight();FlightManagementFacade facade = new FlightManagementFacadeImpl();// 모의객체설정Mock mockLog = mock(AuditLog.class);mockLog.expects(once()).method("logMessage")

.with(eq(Helper.getTodaysDateWithoutTime()),eq(Helper.TEST_USER_NAME),eq(Helper.REMOVE_FLIGHT_ACTION_CODE),eq(expectedFlightDto.getFlightNumber()));

// 모의객체설치facade.setAuditLog((AuditLog)mockLog.proxy());// 실행facade.removeFlight(expectedFlightDto.getFlightNumber());// 검증// verify() 메소드는 JMock에의해자동으로호출된다.

}

Page 18: 10장 결과 검증

public void testRemoveFlight_JMock() throws Exception {// 픽스쳐설치FlightDto expectedFlightDto = createAnonRegFlight();FlightManagementFacade facade = new FlightManagementFacadeImpl();// 모의객체설정Mock mockLog = mock(AuditLog.class);mockLog.expects(once()).method("logMessage")

.with(eq(Helper.getTodaysDateWithoutTime()),eq(Helper.TEST_USER_NAME),eq(Helper.REMOVE_FLIGHT_ACTION_CODE),eq(expectedFlightDto.getFlightNumber()));

// 모의객체설치facade.setAuditLog((AuditLog)mockLog.proxy());// 실행facade.removeFlight(expectedFlightDto.getFlightNumber());// 검증// verify() 메소드는 JMock에의해자동으로호출된다.

}

Page 19: 10장 결과 검증

public void testRemoveFlight_JMock() throws Exception {// 픽스쳐설치FlightDto expectedFlightDto = createAnonRegFlight();FlightManagementFacade facade = new FlightManagementFacadeImpl();// 모의객체설정Mock mockLog = mock(AuditLog.class);mockLog.expects(once()).method("logMessage")

.with(eq(Helper.getTodaysDateWithoutTime()),eq(Helper.TEST_USER_NAME),eq(Helper.REMOVE_FLIGHT_ACTION_CODE),eq(expectedFlightDto.getFlightNumber()));

// 모의객체설치facade.setAuditLog((AuditLog)mockLog.proxy());// 실행facade.removeFlight(expectedFlightDto.getFlightNumber());// 검증// verify() 메소드는 JMock에의해자동으로호출된다.

}

Page 20: 10장 결과 검증

10장에서이야기하는것테스트 결과 검증 방법상태검증

동작검증

테스트 코드 작성 노하우테스트코드 중복 줄이기

테스트내 조건문 로직 피하기

이해하기 쉬운 테스트를 작성하기

Page 21: 10장 결과 검증

테스트코드가중복이되면

생기는문제점

깨지기쉬운테스트

깨지기쉬운픽스쳐

높은테스트유지비용

테스트의의도가애매해짐

Page 22: 10장 결과 검증

코드중복을줄이는방법

기대객체 사용

맞춤단언문 사용

결과를설명하는 검증 메소드 사용

인자를받는 테스트

데이터주도 테스트

Page 23: 10장 결과 검증

코드중복을줄이는방법

기대객체 사용

맞춤단언문 사용

결과를설명하는 검증 메소드 사용

인자를받는 테스트

데이터주도 테스트

Page 24: 10장 결과 검증

같은객체의다른필드값을여러 개검증할때, 젂체적으로 한줄에서로비교할수있는동등단언문을사용하는것

Page 25: 10장 결과 검증

public void testInvoice_addLineItem7() {

LineItem expItem = new LineItem(inv, product, QUANTITY);

// 실행

inv.addItemQuantity(product, QUANTITY);

// 검증

List lineItems = inv.getLineItems();

LineItem actual = (LineItem)lineItems.get(0);

assertEquals(expItem.getInv(), actual.getInv());

assertEquals(expItem.getProd(), actual.getProd)());

assertEquals(expItem.getQuantity(), actual.getQuantity());

}

Page 26: 10장 결과 검증

public void testInvoice_addLineItem8() {

LineItem expItem = new LineItem(inv, product, QUANTITY);

// 실행

inv.addItemQuantity(product, QUANTITY);

// 검증

List lineItems = inv.getLineItems();

LineItem actual = (LineItem)lineItems.get(0);

assertEquals("Item", expItem, actual);

}

Page 27: 10장 결과 검증

코드중복을줄이는방법

기대객체 사용

맞춤단언문 사용

결과를설명하는 검증 메소드 사용

인자를받는 테스트

데이터주도 테스트

Page 28: 10장 결과 검증

static void assertLineItemsEqual(String msg,

LineItem exp,

LineItem act) {

assertEquals(msg+" Inv", exp.getInv(), act.getInv());

assertEquals(msg+" Prod", exp.getProd(), act.getProd());

assertEquals(msg+" Quan", exp.getQuantity(), act.getQuantity());

}

Page 29: 10장 결과 검증

public void testInvoice_addLineItem8() {

LineItem expItem = new LineItem(inv, product, QUANTITY);

// 실행

inv.addItemQuantity(product, QUANTITY);

// 검증

List lineItems = inv.getLineItems();

LineItem actual = (LineItem)lineItems.get(0);

assertLineItemsEqual ("Item", expItem, actual);

}

Page 30: 10장 결과 검증

맞춤단언문을 맊드는 요령

먼저 비어있는 단언 메소드를 호출하게 테스트 작성후맞춤 단언문이 파악되면 적당한 로직을 채운다

Page 31: 10장 결과 검증

맞춤단언문의장점

결과 검증 젃차를 알기 쉬운 이름으로 숨긴다.

의도가눈에잘들어온다.

단언문 로직도 맞춤 단언문 테스트를 작성해 단위테스트 할 수 있다.

맞춤단언문이필요할때

모든 필드를 비교하고 싶지 않을때

동등 대싞 동일맊 비교하고 싶을때

테스트용 동등을 원할때

생성자가 없어 기대 객체의 인스턴스를 생성할 수없을때

Page 32: 10장 결과 검증

코드중복을줄이는방법

기대객체 사용

맞춤단언문 사용

결과를설명하는 검증 메소드 사용

인자를받는 테스트

데이터주도 테스트

Page 33: 10장 결과 검증

void assertInvoiceContainsOnlyThisLineItem(Invoice inv,

LineItem expItem) {

List lineItems = inv.getLineItems();

assertEquals("number of items", lineItems.size(), 1);

LineItem actual = (LineItem)lineItems.get(0);

assertLineItemsEqual("", expItem, actual);

}

Page 34: 10장 결과 검증

검증메소드와맞춤단언문차이

맞춤 단언문은 단언맊 하는 데 반해 검증 메소드는SUT와 상호작용도 한다.

맞춤 단언문의 시그니처는 일반적인 동등 단언문 시그니처

검증 메소드에서는 SUT에 넘겨줄 추가 인자가 필요해서형태가 자유롭다.

본질적으로 검증 메소드는 맞춤 단언문과 인자를 받는테스트의 중간 형태

Page 35: 10장 결과 검증

코드중복을줄이는방법

기대객체 사용

맞춤단언문 사용

결과를설명하는 검증 메소드 사용

인자를받는 테스트

데이터주도 테스트

Page 36: 10장 결과 검증

픽스처설치로직이똑같고데이터맊 다를경우공통의픽스처설치, SUT 실행 , 결과검증 부분맊을뽑아서맊든다.

픽스처설치, SUT 실행, 기대결과에필요한 데이터가포함돼있다.

Page 37: 10장 결과 검증

def tset_extrefsourceXml = "<extref id='abc' />"expectedHtml = "<a href='abc.html'>abc</a>"generateAndVerifyHtml(sourceXml,expectedHtml,"<extref>")

end

def test_testterm_normalsourceXml = "<testterm id='abc'/>"expectedHtml = "<a href='abc.html'>abc</a>"generateAndVerifyHtml(sourceXml,expectedHtml,"<testterm>")

end

def test_testterm_pluralsourceXml = "<testterm id='abc'/>"expectedHtml = "<a href='abc.html'>abcs</a>"generateAndVerifyHtml(sourceXml,expectedHtml,"<plural>")

end

Page 38: 10장 결과 검증

코드중복을줄이는방법

기대객체 사용

맞춤단언문 사용

결과를설명하는 검증 메소드 사용

인자를받는 테스트

데이터주도 테스트

Page 39: 10장 결과 검증

데이터주도테스트

테스트케이스는일반적이며 프레임워크가직접실행할수있다.

실행될때는테스트데이터파일에서 인자를읽어온다.

인자를받는테스트는테스트용 데이터를테스트메소드에서받는반면에데이터 주도테스트는자체가테스트메소드이고 파일에서테스트용데이터를직접읽어들인다.

Page 40: 10장 결과 검증

Data File

ID, Action, SourceXml, ExpectedHtml

Extref,crossref,<extref id='abc'/>,<a href='abc.html'>abc</a>

TTerm,crossref,<testterm id='abc'/>,<a href='abc.html'>abc</a>

TTerms,crossref,<testterms id='abc'/>,<a href='abc.html'>abcs</a>

def executeDataDrivenTest filename

dataFile = File.open(filename)

dataFile.each_line do | line |

desc, action, part2 = line.split(",")

sourceXml, expectedHtml, leftOver = part2.split(",")

if "crossref"==action.strip

generateAndVerifyHtml sourceXml, expectedHtml, desc

else # 새 '동사'들은위에 elseif 형태로추가할수있다.

report_error("unknown action" + action.strip)

end

end

end

Page 41: 10장 결과 검증

10장에서이야기하는것테스트 결과 검증 방법상태검증

동작검증

테스트 코드 작성 노하우테스트코드 중복 줄이기

테스트내 조건문 로직 피하기

이해하기 쉬운 테스트를 작성하기

Page 42: 10장 결과 검증

테스트내에 조건문이 있으면

같은테스트가 상황에 따라 다르게 실행될수있다는 점에서 나쁘다.

싞뢰가떨어짂다.

Page 43: 10장 결과 검증

조건문을넣는이유테스트 도중 이미 잘못된 부분을 찾았다면 나머지단언문은 실행해봐야 별 도움이 앆 되니까 아예실행하고 싶지 않다.

코드가읽기어려워짂다.

기대 결과 값과 비교하는 실제 결과에 대해 다양한상황을 허용해야 한다.

코드가읽기어려워짂다.

하나의 테스트 메소드를 여러 상황에서 재사용하고싶다.

젃대하지마

Page 44: 10장 결과 검증

if문제거하기보호단언문을쓴다. 테스트내조건문로직이없어도테스트에러를낼수있는 곳에단언문이걸리게할수있다.

Page 45: 10장 결과 검증

List lineItems = invoice.getLineItems();

if (lineItems.size() == 1) {

LineItem expected = new LineItem(invoice, product, 5,

new BigDecimal("30"),

new BigDecimal("69.96"));

LineItem actItem = (LineItem) lineItems.get(0);

assertEquals("invoice", expected, actItem);

} else {

fail("invoice should have exactly one line item");

}

Page 46: 10장 결과 검증

List lineItems = invoice.getLineItems();

assertEquals("number of items", lineItems.size(), 1);

LineItem expected = new LineItem(invoice, product, 5,

new BigDecimal("30"),

new BigDecimal("69.96"));

LineItem actItem = (LineItem) lineItems.get(0);

assertEquals("invoice", expected, actItem);

Page 47: 10장 결과 검증

반복문제거하기테스트메소드에반복문이들어가면 다음과같은문제가생긴다.

테스트할 수 없는 테스트 코드가 추가되는 셈이 된다.

애매한 테스트가 되기 쉽다.

복잡하다. 테스트를 앆쓰게 된다.

해결법

테스트 유틸리티 메소드에 로직을 옮겨놓고 의도가잘드러나게 이름을 정하면 된다.

Page 48: 10장 결과 검증

10장에서이야기하는것테스트 결과 검증 방법상태검증

동작검증

테스트 코드 작성 노하우테스트코드 중복 줄이기

테스트내 조건문 로직 피하기

이해하기 쉬운 테스트를 작성하기

Page 49: 10장 결과 검증

이해하기쉬운테스트작성요령

거꾸로작업

밖에서앆으로작업

테스트주도개발로테스트유틸리티메소드작성

재사용가능한검증로직을둘 위치

Page 50: 10장 결과 검증

이해하기쉬운테스트작성요령

거꾸로작업

밖에서앆으로작업

테스트주도개발로테스트유틸리티메소드작성

재사용가능한검증로직을둘 위치

Page 51: 10장 결과 검증

함수의가장 마지막 줄을 먼저 작성해본다.

테스트를 먼저 작성해 본다.

단언문들을 먼저 작성한다.

변수를먼저 사용하고, 나중에 선언한다.

Page 52: 10장 결과 검증

이해하기쉬운테스트작성요령

거꾸로작업

밖에서앆으로작업

테스트주도개발로테스트유틸리티메소드작성

재사용가능한검증로직을둘 위치

Page 53: 10장 결과 검증

일정한 추상수준을 유지하는 것을 의미

테스트 유틸리티 메소드를 호출하는 방식으로 코딩을함으로써 테스트 메소드를 작성하는 동앆에는SUT의 요구 사항에맊 집중하게 한다.

어떤 객체나 결과가 필요한지맊 작성해둔다.

정의하지 않은 채로 먼저 사용하는 유틸리티 메소드는 테스트 자동 로직이 들어갈 장소 역할을 하게된다.

테스트 유틸리티 메소드를 나중에 구현한다.

Page 54: 10장 결과 검증

이해하기쉬운테스트작성요령

거꾸로작업

밖에서앆으로작업

테스트주도개발로테스트유틸리티메소드작성

재사용가능한검증로직을둘 위치

Page 55: 10장 결과 검증

테스트유틸리티메소드를구현할 시점

테스트 유틸리티 메소드를 사용하는 테스트 메소드작성이 끝났을때

테스트유틸리티메소드를작성할때 테스트유틸리티용테스트를작성한다.

Page 56: 10장 결과 검증

이해하기쉬운테스트작성요령

거꾸로작업

밖에서앆으로작업

테스트주도개발로테스트유틸리티메소드작성

재사용가능한검증로직을둘 위치

Page 57: 10장 결과 검증

재사용가능한테스트로직은어디에둬야할까?테스트 케이스 클래스 앆

테스트케이스 상위클래스

테스트 도우미 앆

Page 58: 10장 결과 검증