5. • 메소드 변경 시 매개 변수로 인한 의존 관계
발생 할 때
• 매개변수에 대해 인터페이스 추출을 사용할
수 없을 때
• 매개 변수를 Fake로 만들기 어려울 때
6. public class ARMDispatcher
{
public void populate(HttpServletRequest request) {
String [] values
= request.getParameterValues(pageStateName);
if (values != null && values.length > 0)
{
marketBindings.put(pageStateName +
getDateStamp(),
values[0]);
}
...
}
...
}
•문제 : 테스트 작성 시HttpServletRequest 클래스를 구현 하는 클래스 하나를 생성.
-> 구현 해야할 사항 많음
7. public class ARMDispatcher
{
public void populate(ParameterSource source) {
String values =
source.getParameterForName(pageStateName);
if (value != null) {
marketBindings.put(pageStateName +
getDateStamp(),
value);
}
...
}
}
•해결 : 매개변수를 Wrap 한 후, API 인터페이스 상에 있는 의존 관계 없앰
8. •Fake 클래스 : Test 루틴에 사용
class FakeParameterSource implements ParameterSource
{
public String value;
public String getParameterForName(String name) {
return value;
}
}
•Production클래스 : 기존 루틴에 사용.
class ServletParameterSource implements ParameterSource
{
private HttpServletRequest request;
public ServletParameterSource(HttpServletRequest request) {
this.request = request;
}
String getParameterValue(String name) {
String [] values = request.getParameterValues(name); • J2EE 소스 -> 로직 분리.
if (values == null || values.length < 1)
return null;
return values[0];
}
}
9. 1. 메소드의 매개변수에서 사용할 새로운 인터페이스 생성
(ParameterSource)
2. 새로운 인터페이스를 위한 클래스 생성(ServletParameterSource )
3. 테스트를 위한 Fake 클래스 생성 (FakeParameterSource )
4. 3번클래스를 매개변수에 전달하는 단순한 Test Case 작성
5. 새로운 매개변수를 사용하기 위해 메소드 안에 필요한 변경 을 가함.
6. Fake 클래스를 이용해서 테스트할 수 있는지 검증하기 위해 테스트
루틴 실행
11. • 사용 시점
• 메소드크기가 클 때
( 그 안에서 인스턴스 데이터와 메소드 사용 시)
• 방법
• 하나의 긴 메소드 -> 하나의 클래스로 변경
12. class GDIBrush class Renderer
{ {
public: public:
void draw(vector<point>& renderingRoots, Renderer(GBIBrush *brush, •함수 매개변수 그대로
ColorMatrix& colors, vector<point>&
가져옴(서명보전)
vector<point>& selection); renderingRoots,
... ColorMatrix &colors,
vector<point>& selection);
private: ...
void drawPoint(int x, int y, COLOR color); };
...
};
class Renderer
void GDIBrush::draw(vector<point>& renderingRoots, {
private:
ColorMatrix& colors,
vector<point>& selection) GDIBrush *brush; •인스턴스 변수 추가 및
{ vector<point>& renderingRoots;
for(vector<points>::iterator it = renderingRoots.begin();
it != renderingRoots.end();
ColorMatrix& colors;
vector<point>& selection; 초기화
++it) {
point p = *it; public:
... Renderer(GDIBrush *brush,
vector<point>& renderingRoots,
drawPoint(p.x, p.y, colors[n]); ColorMatrix& colors,
} vector<point>& selection)
... : brush(brush), renderingRoots(renderingRoots),
} colors(colors), selection(selection)
{}
};
13. class Renderer
{
private:
•draw 함수 추가
GDIBrush *brush;
vector<point>& renderingRoots; void Renderer::draw()
ColorMatrix& colors; {
vector<point>& selection; for(vector<points>::iterator it =
renderingRoots.begin();
public: it != renderingRoots.end();
Renderer(GDIBrush *brush, ++it) {
vector<point>& renderingRoots, point p = *it;
ColorMatrix& colors, ...
vector<point>& selection) drawPoint(p.x, p.y, colors[n]);
: brush(brush), renderingRoots(renderingRoots), }
colors(colors), selection(selection) ...
{} } •draw 함수의 Body를 멤버
함수인 draw 로 복사
void draw();
};
•컴파일러 의존 사용.(drawPooint에서 에러 발생)
14. class GDIBrush
class GDIBrush
{
{ •의존 관계가 있는
public:
public:
void draw(vector<point>&
void draw(vector<point>&
renderingRoots, drawPoint 함수를 Public으
renderingRoots,
ColorMatrix& colors,
로 수정
ColorMatrix& colors,
vector<point>& selection);
vector<point>& selection);
...
...
void drawPoint(int x, int y, COLOR color);
private:
•변수 인 경우 Getter 함수
private:
void drawPoint(int x, int y, COLOR color);
...
...
};
}; 생성함
void
GDIBrush::draw(vector<point>&
•큰 draw함수 -> Renderer 클래
renderingRoots,
스가 수행
ColorMatrix &colors,
vector<point>& selection)
{
Renderer
renderer(this, renderingRoots,
colors, selection);
renderer.draw();
}
15. class PointRenderer class Renderer
{ {
public: private:
virtual void drawPoint(int x, int y, COLOR color) = 0; PointRender *pointRenderer;
}; vector<point>& renderingRoots;
ColorMatrix& colors;
vector<point>& selection;
class GDIBrush : public PointRenderer
{ public:
public: Renderer(PointRenderer *renderer,
void drawPoint(int x, int y, COLOR color); vector<point>& renderingRoots,
... ColorMatrix& colors,
}; vector<point>& selection)
: pointRenderer(pointRenderer),
renderingRoots(renderingRoots),
colors(colors), selection(selection)
{}
void Renderer::draw()
{
void draw();
for(vector<points>::iterator it = renderingRoots.begin();
};
it != renderingRoots.end();
++it) {
point p = *it;
...
pointRenderer->drawPoint(p.x,p.y,colors[n]);
}
...
}
16. 1. 메소드 코드 넣을 새로운 클래스 한 개 생성(Renderer)
2. 새로운 클래스의 생성자 생성. (서명 보전 사용하여 매개변수 복사, 원본 메소드에서 인스턴
스 데이터나 내부 메소드 사용 시 원본 클래스에 대한 참조를 생성자에 대한 첫번째 매개변
수로 추가)
3. 생성자에 있는 모든 매개변수에 대해 멤버 변수 추가 및 초기화
4. 새로운 클래스 상에 빈 실행 메소드 생성
5. 메소드 안의 소스를 4번의 실행 메소드로 복사 -> 컴파일 -> 컴파일 의존 이용
6. 컴파일 에러 시 컴파일 되도록 함.
1. Private 함수 -> Public 함수
2. 변수일 경우 public Getter 함수 생성
7. 새로운 클래스가 컴파일 성공-> 원본 메소드로 돌아가 서 새로운 클래스의 인스턴스를 하
나 생성 -> 원본 메소드의 작업을 새로운 클래스에 위임
8. 필요 시 인터페이스 추출을 사용해서 원본 믈래스 상에 있는 의존 관계 청산
18. • 사용 시점
• 테스트 루틴의 정의만 다르게 주고 싶을 때
(기존의 정의와 의존 관계를 깰때)
• 방법
• 함수를 선언하고 다른 곳에서 그것을 정의
(C, C++ 언어 지원.)
19. class CLateBindingDispatchDriver : public CDispatchDriver
{
public:
•테스트 파일 내에 직접 정의
CLateBindingDispatchDriver ();
virtual ~CLateBindingDispatchDriver ();
#include "LateBindingDispatchDriver.h"
ROOTID GetROOTID (int id) const;
CLateBindingDispatchDriver::CLateBindingDispatchDriver() {}
void BindName (int id,
CLateBindingDispatchDriver::~CLateBindingDispatchDriver() {}
OLECHAR FAR *name);
...
ROOTID GetROOTID (int id) const { return ROOTID(-1); }
private:
void BindName(int id, OLECHAR FAR *name) {}
CArray<ROOTID, ROOTID& > rootids;
};
TEST(AddOrder,BOMTreeCtrl)
{
CLateBindingDispatchDriver driver;
CBOMTreeCtrl ctrl(&driver);
ctrl.AddOrder(COrderFactory::makeDefault());
LONGS_EQUAL(1, ctrl.OrderCount());
}
20. 1. 테스트 루틴을 위한 개별적인 실행 파일 생성
필요 ( 그렇지 않으면 링크 에러)
2. 멤버함수 선언 1개 에 대해 2개의 정의로 인한
유지보수 부담
3. 환경 설정 미비 시 디버거 혼란 상황 발생
==>최악의 의존 관계 상황이 아니라면 정의 완료
기법 권하지 않음. 어쩔 수 없을경우라도 초기 의
존 관계를 깨는 데만 사용 권장
22. • 사용 시점
• Global 변수 혹은 함수와 의존관계를 가진 문제
있는 코드를 테스트 하려 할 때
• 방법
• Global 변수 혹은 함수 -> 하나의 클래스로 캡슐
화 하여 기존 소스와 분리
23. // bool AGG230_activeframe[AGG230_SIZE]; // bool AGG230_suspendedframe[AGG230_SIZE];
bool AGG230_activeframe[AGG230_SIZE];
bool AGG230_suspendedframe[AGG230_SIZE];
class Frame
void AGGController::suspend_frame()
{ •전역 변수 -> 클래스 public
public:
멤버
{ // declare AGG230_SIZE as a constant
frame_copy(AGG230_suspendedframe, enum { AGG230_SIZE = 256 };
AGG230_activeframe);
bool AGG230_activeframe[AGG230_SIZE];
clear(AGG230_activeframe); bool AGG230_suspendedframe[AGG230_SIZE];
flush_frame_buffers(); };
}
void AGGController::flush_frame_buffers()
{ Frame frameForAGG230;
for (int n = 0; n < AGG230_SIZE; ++n) {
AGG230_activeframe[n] = false; // bool
AGG230_activeframe[AGG230_SIZE];
AGG230_suspendedframe[n] = false; // bool
} AGG230_suspendedframe[AGG230_SIZ
} E];
•Frame 클래스의 전역 인스턴스 생성
•전역 변수 주석처리
•빌드 -> 에러 처리는 다음장에
24. •에러난 모든 곳에 frameForAGG230. 을 추가
void AGGController::suspend_frame()
{
frame_copy(frameForAGG230.AGG230_suspendedframe,
frameForAGG230.AGG230_activeframe);
clear(frameForAGG230.AGG230_activeframe);
flush_frame_buffer();
}
•코드 분리 첫걸음
•시간이 지날 수록 코드 나아지도록 만듦
class Frame
{ class Frame
public: {
// declare AGG230_SIZE as a constant public:
enum { AGG230_SIZE = 256 }; enum { BUFFER_SIZE = 256 };
bool activebuffer[BUFFER_SIZE];
bool AGG230_activeframe[AGG230_SIZE]; bool suspendedbuffer[BUFFER_SIZE];
bool AGG230_suspendedframe[AGG230_SIZE]; };
};
Frame frame;
void AGGController::suspend_frame()
{ void AGGController::suspend_frame()
{
frame_copy(frameForAGG230.AGG230_suspendedframe frame_copy(frame.suspendedbuffer,
, frame.activebuffer);
frameForAGG230.AGG230_activeframe); clear(frame.activeframe);
clear(frameForAGG230.AGG230_activeframe); flush_frame_buffer();
flush_frame_buffer(); }
}
25. 1. 캡슐화 하고자 하는 전역 변수 혹은 함수를 식별
2. 새로운 클래스 생성
3. 전역물들을 새로운 클래스의 public 으로 복사.
(그 중 일부가 변수라면 클래스 안에서 초기화)
4. 전역변수 혹은 함수의 원본 선언문을 주석 처리
5. 새로운 클래스의 전역 인스턴스 선언
6. 빌드 (컴파일 의존 사용)
7. 에러 부분을 모두 새 클래스의 전역 인스턴스 이름으로 시작하도록
변경
8. Fakes를 사용하고자 하는 부근에서 정적 세터 도입, 생성자 매개변수
화 또는 메소드 매개변수화, 전역 참조를 게터로 대체를 사용.
27. • 사용 시점
• 인스턴스화 하기 힘든 클래스 내의 함수가 필요
할때
• 방법
• 멤버 함수 -> static 함수
(조건 : 기존 멤버 함수가 멤버함수 혹은 멤버 변
수 사용하지 않아야 함.)
28. // bool AGG230_activeframe[AGG230_SIZE]; // bool AGG230_suspendedframe[AGG230_SIZE];
class RSCWorkflow
{ public class RSCWorkflow {
... public void validate(Packet packet)
public void validate(Packet packet) throws InvalidFlowException {
validatePacket(packet);
throws InvalidFlowException { }
if (packet.getOriginator().equals( "MIA")
|| packet.getLength() > MAX_LENGTH public static void validatePacket(Packet packet)
|| !packet.hasValidCheckSum()) { throws InvalidFlowException {
throw new InvalidFlowException(); if (packet.getOriginator() == "MIA"
|| packet.getLength() <= MAX_LENGTH
} || packet.hasValidCheckSum()) {
... throw new InvalidFlowException();
} }
... ...
} }
...
}
•Protected로 만들어서 Test하위 클래스를 통해 접근 하도록 가
능.(namespace도 가능)
29. 1. 클래스의 정적 메소드에 접근하는 테스트 루
틴 하나 작성
2. 클래스의 메소드 -> 클래스의 정적 메소드 로
정의 이동
3. 빌드
4. 멤버 변수나 메소드에 접근하는 오류 시 접근
하는 멤버 변수나 메소드를 정적으로 만들 수
있는지 확인. 그렇지 않으면 다른 방식을 찾아
봄.
31. • 사용 시점
• 멤버함수 안에서 다른 클래스의 static 함수 호출 하
여 의존 관계가 발생할 때
• 멤버함수 안에서 전역 변수 혹은 함수를 호출하여
의존 관계가 발생할 때
• 방법
• 멤버함수내의 static혹은 전역 함수 호출 -> 필요한
static, 전역 함수를 호출 하는 가상 함수를 생성
32. // bool AGG230_activeframe[AGG230_SIZE]; // bool AGG230_suspendedframe[AGG230_SIZE];
public class PageLayout
public class PageLayout
{
{
private int id = 0; private int id = 0;
private List styles; private List styles;
private StyleTemplate template; private StyleTemplate template;
... ...
protected void rebindStyles() {
protected void rebindStyles() {
styles = formStyles(template, id);
styles = StyleMaster.formStyles(template, id); ...
... }
}
... protected List formStyles(StyleTemplate template,
int id) {
}
return StyleMaster.formStyles(template, id);
}
...
}
•의존관계를 깨지위해 테스트 시 위 함수 오버라이드
public class TestingPageLayout extends PageLayout {
protected List formStyles(StyleTemplate template,
int id) {
return new ArrayList();
}
...
}
33. 1. Static 함수 혹은 전역 변수, 함수와 의존 관계
가 있는 부분을 식별
2. 호출 되는 부분(static, 전역) 의 선언을 찾아서
서명 보전 사용
3. 클래스 상에 새로운 메소드 생성(2번의 서명
보전 사용)
4. 새로운 메소드에 대한 호출을 복사하여 기존
의 호출을 새로운 메소드에 대한 호출로 대체