Go程式中的init()

在Go語言的世界裡,func init() {} 是一個適合用來初始化整個package的執行者,它會在package 第一次被import時,自動的執行其中之內容,並且它只會被執行一次…

不過使用上也必須特別注意,因為當某個package需要init 時,就代表它有一些package level variable,而這些package level variable 若是被一些內部的function 或 method 使用時,就可能造成一些side effect。

簡而言之,若使用的不好的話,是很容易造成一些anit pattern的…

舉例來說像下面這段範例程碼碼

package foo


type handler interface {
    shouldHandle() bool
    handleFoo() error
    handlerBar() error
}

type handlerImpl struct {}

func (h handlerImpl) shouldHandle() bool {
    return true
}

func (h handlerImpl) handleFoo() error {
    return nil
}

func (h handlerImpl) handleBar() error {
    return nil
}

var defaulHandler handler

func init() {
    defaultHandler = handlerImpl{}
}


func HandleFoo() error {
    if defaultHandler.shouldHandle() {
        return defaultHandler.handleFoo()
    }
    return errors.New("not allowed")
}

func HandleBar() error {
    if defaultHandler.shouldHandle() {
        return defaultHandler.handlerBar()
    }
    return errors.New("not allowed")
}

在上面這個範例中,HandleFoo, HandleBar都會先call defaultHandler.shouldHandle來看是不是需要處理接下來的流程,如果是的話,再執行各自的handle function。

如果要分別測試HandleFoo, HandleBar的話,我們就要想辦法mock defaultHandler 。 但因為defaultHandler是在init中被指派的,所以就上面的程式碼來看,是沒有直接可以測試的方式…

但如果我們不在init中指派defaultHandler,而是改用下面的範例來做的話,就可以進一步解決測試上的問題…

func Init(concreteHandler handler) {
    defaultHandler = concreteHandler
}

以這段程式碼來看,如果我們要測試HandleFoo, HandleBar的話,我們只需要在每一個unit test之前,先執行Init()來代入mocked 過的 concreteHandler就可以測試了。

不過這樣的作法是不是就沒有問題了???

很不幸的,透過Init來解決測試的問題還是會有一些副作用;由於這兩個HandleFooHandleBar 都同時用到handler.shouldHandle(),所以這也代表如果我們同時在測試這兩個function時,就會有可能發生下面的race issue:

  • test 1: 測試HandleFoo時,代入mockDefaultHandler, 並且把shouldHandle(),mock成回傳值為true.
  • test 2: 測試HandleBar時,代入mockDefaultHandler, 並且把shouldHandle(),mock成回傳值為false.

由於go test預設是會同時用多個cpu cores來執行所有的測試,這樣就會有機率性地遇到上述的問題,並發生測試結果不如預期情況…

當然如果我們直接使用go test -p 1來執行unit tests 的話就沒有race 的問題了,不過同樣的就造成測試時間變長的問題了。

寫到這邊的一些想法:

  • 如果沒有必要的話,盡量不要使用init,因為會使用init的情況通常代表這個package 有些package level variables 需要被初始化。
  • 盡量避免在func中直接使用package level variables,因為它會造成那些使用的func不易被測試。

references:

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *