---
# System prepended metadata

title: Working With Legacy Code

---

Working With Legacy Code
==========

一個系統中的業務流程，往往會與多個物件有所關聯．
![](https://i.imgur.com/ZWlnIB5.png)
```Controller的action handler依賴service物件, service又依賴repository物件, repostiory物件又依賴database.```

要做單元測試前，可能必須要先準備好這些所有物件，
然後把一群物件都放入測試當值中，但它們彼此依賴又環環相扣．

要測試handler, 卻事先要準備好service, service又需要先準備好repository, repository又需要db; 所以測試一個物件, 要先準備好除了自己之外的QQ

## 單元測試的前提
1. 快速的定位出錯誤點
2. 花費的時間夠快，需要的測試環境不需太複雜(不需要準備好DB, SMTP...)

## 感測 &　分離
### 感測Sensing
適用場景:
```We can't access values our code computes```

Solution:
```break dependencies to sense the value```

### 分離Separation
適用場景:
```we can't even get a piece of code into a test harness to run```

Solution:
```break dependencies to separate the code```


## Dependency-Breaking Techniques

解依賴的三個基本技巧
![](https://i.imgur.com/fHkaJ9F.png)

### 參數化方法Parameterize Method
```go=
func Method1() {
    mResult := new(Result);
    
    mReuslt.xxxx();
    ....
}
```
這樣子Method1高度耦合於Result物件, 我們無法感測到Result的值.
可以選擇```參數化方法```來讓Method1來感測到Result的值.
白話文, Result的值與內容是我們可以完全掌控的.

```go=
func Method1(mResult Result) {
    mReuslt.xxxx();
    ....
}
```
### 參數調配Adapt Parameter
當無法對一個引述的型別使用介面提取, 或者該引述難以偽裝時.
```go=
type ARMDispatcher struct {
    marketBinding []string
}
func NewARMDispatcher() *ARMDispatcher {
    return &ARMDispatcher{
        marketBinding = make([]string, 0)
    }
}
//HttpsParameters 是原生的Http參數型別, 難以偽裝與測試;
// 且過程中還需要感測其值
func (a *ARMDispatcher) Populate(parameters HttpsParameters) {
    var values []string = parameters.GetCipherSuites()
    if values != nil && len(values) > 0 {
        a.marketBinding = append(a.marketBinding, values[0])
    }
}
```
1. 替Populate用到的HttpsParameters類型新增interface, 且簡單能表達意圖就好
2. 為新interface建立一個產品程式碼的實現類型
3. 為新interface建立一個測試程式碼的偽造類型
4. 寫一個簡單測試用例, 將偽裝物件傳給該目標方法
5. 修改原來方法使其能使用新的引數型別
6. 執行測試, 確保work
```go=
type ParameterSource interface {
    GetParameterValue() string
}

type HttpParameterSource struct {
    mHttpsParameters HttpsParameters
}
func NewHttpParameterSource(httpsParameters HttpsParameters) *HttpParameterSource {
    return &HttpParameterSource{
        mHttpsParameters = httpsParameters
    }
}

func (p *ParameterSource) GetParameterValue() string {
    var values []string = p.mHttpsParameters.GetParameterValue()
    if values != nil && len(values) > 0 {
        return values[0]
    }
    return "";
}
```
```go=
type ARMDispatcher struct {
    marketBinding []string
}
func NewARMDispatcher() *ARMDispatcher {
    return &ARMDispatcher{
        marketBinding = make([]string, 0)
    }
}

func (a *ARMDispatcher) Populate(parameterSource ParameterSource) {
    var values string = parameters.GetParameterValue()
    if values != nil {
        a.marketBinding = append(a.marketBinding, values)
    }
}
```

```go=
type FakeParameterSource  struct {
    values string
}

func NewFakeParameterSource(values string) *FakeParameterSource {
    return &FakeParameterSource{
        values = values
    }
}
func (p *FakeParameterSource) GetParameterValue() string {
   return values
}

func TestARMDispatcherPopulate(t *testing.T) {
    armDispatcher := &ARMDispatcher{}
    fakeParameterSource  := NewFakeParameterSource("Hello World")
    armDispatcher.Populate(fakeParameterSource)
    assert.Equal(t,1, len(armDispatcher.marketBinding)  )
    assert.Equal(t, "Hello World", armDispatcher.marketBinding[0])
}
```

Adapt Parameter通常都會修改道原來的簽章, 為了把意圖通用化.

https://gunnarpeipman.com/refactoring-adapt-parameter/

### 介面提取Extract Interface
Extract Interface並沒有要一次提取依賴類別上所有Public methods.(記得SOLID中的ISP嗎)
可以考慮一步一步地提取出所需要的.

```go=
type PaydayTransaction struct {
    transactionLog *TransactionLog 
}

func NewPaydayTransaction(transactionLog *TransactionLog) *PaydayTransaction {
    return &PaydayTransaction{
        transactionLog : transactionLog
    }
}

func (p *PaydayTransaction) Run() {
    p.transactionLog.SaveTransaction()
}


type TransactionLog  struct { }

func NewTransactionLog() *TransactionLog {
    return &TransactionLog{}
}

func (p *TransactionLog) SaveTransaction() {
    // call db
}

func (p *TransactionLog) RecordError(code int) {
    // log error
}

```
1. 建立一個新interface, 取有意義的命名,  暫時別加入任何方法
2. 建立一個目標類別來實作該interface
3. 把想使用偽造物件的地方, 通通從原來類別,改成引用這新interface
4. 按下編譯, 編譯器會說缺少什麼方法, 再慢慢新增對應方法
```go=
type ITransactionRecorder interface {
    SaveTransaction()
}

```
```go=
type PaydayTransaction struct {
    transactionRecorder ITransactionRecorder 
}

func NewPaydayTransaction(transactionRecorder ITransactionRecorder) *PaydayTransaction {
    return &PaydayTransaction{
        transactionRecorder : transactionRecorder
    }
}

func (p *PaydayTransaction) Run() {
    p.transactionRecorder.SaveTransaction()
}
```

```go=
type FakeTransactionLog  struct { 
    IsSave bool
}

func NewFakeTransactionLog() ITransactionRecorder{
    return &TransactionLog{
        IsSave : false
    }
}

func (p *FakeTransactionLog) SaveTransaction() {
    // do something
    p.IsSave = true
}

func (p *FakeTransactionLog) RecordError(code int) {
    // log error
}

func TestPayday(t *testing.T) {
    fakeTransactionLog  := NewFakeTransactionLog()
    paydayTransaction  := NewPaydayTransaction(fakeTransactionLog)
    paydayTransaction.Run()
    assert.Equal(t,true, fakeTransactionLog.IsSave  )
}
```

https://docs.microsoft.com/zh-tw/visualstudio/ide/reference/extract-interface?view=vs-2019

其他好用技巧
但不是物件導向語言未必能實作
### Extract and Override

### Pull Up Feature
1. Pull Up Field
2. Pull Up Method
3. Pull Up Constructor Body
### Push Down Dependency
1. Push Down Method
2. Push Down Field
https://refactoring.guru/push-down-method

https://faculty.csbsju.edu/jschnepf/CS230/Slides/Refactoring.pdf

# Test Doubles
Test doubles : 一個概念名詞, 描述測試中所有non-production ready的依賴物件. 
讓依賴物件看起來的樣貌跟行為非常相似release-intended的版本, 但內容裡卻是簡化非常多的版本.
Test doubles有5種, 但大致區分成兩大類
![](https://i.imgur.com/EPVfoXu.png)


- Stub : 協助模擬資料的交互與感測
- Mock : 有助於模擬與檢查, 其交互對象的內部狀態(呼叫次數,執行多久,非同步的執行狀態...)

![](https://i.imgur.com/oLU0ZzJ.png)
![](https://i.imgur.com/DWE0HvW.png)


[reference](https://enterprisecraftsmanship.com/posts/when-to-mock/)
[reference2](https://www.softwaretestingmagazine.com/knowledge/unit-testing-fakes-mocks-and-stubs/)