Dependency Injection - Extract Dependency Object

External Dependency 是系統中與被測試程式互動但你無法控制的物件。被測試程式受到 external dependency 行為影響可能有不同結果,為了保持 unit test 穩定,不會一下結果該是 A、一下結果該是 B,我們希望能掌控 external dependency──藉由 stub object 模擬 external dependency 的行為並將其 inject 到被測試程式中,基本步驟如下:

  1. 抽出 external dependency object 的 interface
  2. stub implement 該 interface 並實作 function
  3. inject stub 到被測試程式
    • Constructor Injection
    • Setter Injection
    • Extract and Override

本篇用例子說明步驟 1 跟 2:MyClass 是被測試程式,Foo 是 external dependency。(為了讓 code 短一點直接在 header 實作)

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

class MyClass {
public:
void DoSomething() {
Foo *foo = new Foo;
int x = foo->bar();
// do something...
delete foo;
};
};

首先幫 external dependency Foo 加 interface(C++ 沒有 interface 所以用所有 function 都是 pure virtual function 的 class 當 interface):

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 { // implement IFoo
public:
int bar() {
// do something...
};
};

被測試程式 MyClass 原本直接使用 Foo 的地方改用 interface:

MyClass.h
1
2
3
4
5
6
7
8
9
10
11
#include "Foo.h"

class MyClass {
public:
void DoSomething() {
IFoo *foo = new Foo; // 改用 IFoo
int x = foo->bar();
// do something...
delete foo;
};
};

接下來寫個假 class 實作 IFoo,實作相關 function 回傳假資料。

這個 class 可以以 Fake 開頭命名,表示它是類似某個 class 的假 class。以 Fake 開頭的 class 產生的 object 可以當作 stub 或 mock 使用。

FakeFoo.h
1
2
3
4
5
6
7
8
9
#pragma once
#include "IFoo.h"

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

這邊的例子是 C++,必須 extract interface。在非 strong type 語言,例如 PHP,如果原本 production code 的 dependency object 沒有 interface、只有 unit test 需要的時候,我會偷懶不 extract interface(喂),只讓 fake class 跟原本 class 有一樣 function,甚至只有 test case 會用到的 function。

有 fake class 後利用 dependency injection 將 stub object inject 進被測試程式,我們就能控制被測試程式的 external dependency,進而控制 test case 及預期被測試程式應有的行為。

以上是 dependency injection 的前置作業,那我們下回見~(咦)

系列文章