Dependency Injection - Extract and Override

Extract and Override 是另一種 injection 方式,它幾乎不會改變程式的語意(增加 constructor 的參數或者其他 public 介面等等),寫起來乾淨漂亮。它適合用於模擬回傳值或回傳 stub 或 mock object,不適合用在確認被測試程式與 dependency object 的互動。

Override factory method to inject stub

  1. 在被測試 class 加入可被繼承並 override 的 factory method 來取得 dependency object。
  2. 在測試裡新增一個 class 繼承被測試 class,override 該 factory method 回傳 stub object,接著使用測試 class 進行測試。

一樣用例子來看,MyClass 是被測試 class,Foo 是 external dependency。

首先是 Foo 相關的 class:

IFoo.h
1
2
3
4
5
6
7
8
#pragma once
class IFoo {
public:
virtual ~IFoo() = 0;
virtual int bar() = 0;
};

inline IFoo::~IFoo() { }
Foo.h
1
2
3
4
5
6
7
8
9
#pragma once
#include "IFoo.h"

class Foo : public IFoo {
public:
int bar() {
// do something...
};
};
FakeFoo.h
1
2
3
4
5
6
7
8
9
#pragma once
#include "IFoo.h"

class FakeFoo : public IFoo {
public:
int bar() {
return 1; // fake something...
};
};

下面 MyClassgetFoo() 是取得 Foo 的 factory method,是 protectedvirtual 讓 derived class override。MyClass 其他部份程式都以 getFoo() 取得 instance 使用。

MyClass.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include "Foo.h"

class MyClass {
public:
MyClass() : m_pFoo(NULL) {
};

~MyClass() {
if (m_pFoo) {
delete m_pFoo;
}
};

void DoSomething() {
getFoo()->bar(); // 使用 factory method 取得 instance
};

protected:
virtual IFoo* getFoo() { // 可被 override 的 factory method
if (m_pFoo == NULL) {
m_pFoo = new Foo(); // 使用一般 dependency object
}
return m_pFoo;
};

protected:
IFoo *m_pFoo;
};

至於要有 member 存 dependency object 還是 factory method 直接 return new Foo(),我想是依使用情況跟語言而定。用 member 存的好處是不用 new 很多次,對大物件是好的,壞處則是 class 裡可能會有地方直接使用 member 而非 factory method。另一方面,沒有 garbage collection 的語言像 C++,就得存下來不然一直 new 會 leak 啊。

最後是 production 以及 test code。

在 unit test 裡加一個繼承 MyClass 的 class TestableMyClass,override getFoo() 回傳 stub object。一般 production code 使用 MyClass,unit test 使用 TestableMyClass 進行對 MyClass 的測試。

main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include "MyClass.h"
#include "FakeFoo.h"

class TestableMyClass : public MyClass { // 繼承被測試 class
protected:
virtual IFoo *getFoo() { // override factory method
if (m_pFoo == NULL) {
m_pFoo = new FakeFoo(); // stub object
}
return m_pFoo;
};
};

int main() {
// Production code
MyClass *obj = new MyClass;
obj->DoSomething();
delete obj;

// Test code
MyClass *obj2 = new TestableMyClass;
obj2->DoSomething();
delete obj2;

return 0;
}

如果一個 interface 的 stub 依據測試情境有很多個,例如這邊有 FakeFooForAFakeFooForB,會需要多個測試 class 繼承 MyClass、override factory method 回傳不同 stub,或者對 TestableMyClass 做 inject,讓它能回傳不同的 stub。

模擬假結果

除了在 derived class 裡 override factory method 回傳 stub 外,也可以直接回傳假結果,不需要多抽一個 interface 跟製作 stub。

Foo.h
1
2
3
4
5
6
7
#pragma once
class Foo { // 不需要 interface IFoo
public:
bool check() {
// Do some check
}
};
MyClass.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include "Foo.h"

class MyClass {
public:
void DoSomething() { // 被測試 function
if (checkSth()) {
// Do something
}
};

protected:
virtual bool checkSth() { // 可被 override 的 function
Foo foo; // 使用真正的 dependency
return foo.check();
}
};
main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include "MyClass.h"

class TestableMyClass : public MyClass {
protected:
virtual bool checkSth() { // override 直接回傳假結果
return true;
}
};

int main() {
// Production code
MyClass *obj = new MyClass;
obj->DoSomething();

// Test code
MyClass *obj2 = new TestableMyClass;
obj2->DoSomething();

return 0;
}

系列文章

延伸閱讀