10장 결과 검증

Preview:

Citation preview

xUnit 테스트패턴

10장결과검증

최기원

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

테스트코드 작성 노하우

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

동작검증

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

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

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

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

동작검증

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

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

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

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

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

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

외부결과 검증 방법

내장단언문사용하는방법

assertTrue(aBooleanExpression)

결과 지정 단언문

assertEquals(expected, actual)

동등 단언문

assertEquals(expected, actual, tolerance)

퍼지 동등 단언문

델타단언문

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

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

외부결과검증

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

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

동작검증

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

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

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

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

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

기대동작명세

젃차형동작검증

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

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());}

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());}

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());}

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());}

기대동작명세

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

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에의해자동으로호출된다.

}

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에의해자동으로호출된다.

}

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에의해자동으로호출된다.

}

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());

}

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);

}

코드중복을줄이는방법

기대객체 사용

맞춤단언문 사용

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

인자를받는 테스트

데이터주도 테스트

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());

}

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);

}

맞춤단언문을 맊드는 요령

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

맞춤단언문의장점

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

의도가눈에잘들어온다.

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

맞춤단언문이필요할때

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

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

테스트용 동등을 원할때

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

코드중복을줄이는방법

기대객체 사용

맞춤단언문 사용

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

인자를받는 테스트

데이터주도 테스트

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);

}

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

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

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

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

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

코드중복을줄이는방법

기대객체 사용

맞춤단언문 사용

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

인자를받는 테스트

데이터주도 테스트

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

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

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

코드중복을줄이는방법

기대객체 사용

맞춤단언문 사용

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

인자를받는 테스트

데이터주도 테스트

데이터주도테스트

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

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

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

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

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

동작검증

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

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

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

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

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

싞뢰가떨어짂다.

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

코드가읽기어려워짂다.

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

코드가읽기어려워짂다.

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

젃대하지마

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

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");

}

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);

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

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

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

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

해결법

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

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

동작검증

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

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

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

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

거꾸로작업

밖에서앆으로작업

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

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

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

거꾸로작업

밖에서앆으로작업

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

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

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

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

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

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

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

거꾸로작업

밖에서앆으로작업

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

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

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

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

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

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

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

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

거꾸로작업

밖에서앆으로작업

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

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

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

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

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

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

거꾸로작업

밖에서앆으로작업

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

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

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

테스트케이스 상위클래스

테스트 도우미 앆

Recommended