2016-08-04 26 views
0

這讓我瘋了。假設我有以下功能:如何重寫此選擇語句以保證100%的測試覆蓋率?

func Map(quit <-chan struct{}, dst chan<- interface{}, src <-chan interface{}, f func(interface{}) interface{} { 
    for { 
     select { 
     case v, ok := <- src: 
      if !ok { 
       return 
      } 
      select { 
      case dst <- f(v): 
      case <-quit: 
       return 
      } 
     case <-quit: 
      return 
     } 
    } 
} 

它發送f起SRC接收(V)上的DST爲每個值v直到src或退出關閉和空的或接收到的值從退出。

現在,假設我要編寫一個測試,這表明它可以被取消:

func TestMapCancel(t *testing.T) { 
    var wg sync.WaitGroup 

    quit := make(chan struct{}) 
    success := make(chan struct{}) 
    wg.Add(3) 

    src := // channel providing arbitrary values until quit is closed 
    dst := make(chan interface{}) 

    // mapper 
    go func() { 
     defer wg.Done() 
     defer close(dst) 
     Map(quit, dst, src, double) 
    }() 

    // provide a sink to consume values from dst until quit is closed 
    timeout(quit, 10*time.Millisecond) 
    wait(success, &wg) 

    select { 
    case <-success: 
    case <-time.After(100 * time.Millisecond): 
     t.Error("cancellation timed out") 
    } 
} 

這裏的未定義功能不是非常重要。請假設他們工作。 timeout在指定的時間後關閉其通道參數,waitwg.Wait()後關閉其通道參數。

問題是,這將不會提供100%的覆蓋範圍,因爲如果兩者都準備好發送/接收,則在(僞)隨機選擇統一的選擇案例。的Map以下版本沒有這個問題,但是從潛在不定阻擋遭受如果上游信道(SRC)沒有閉合:

func Map(quit <-chan struct{}, dst chan<- interface{}, src <-chan interface{}, f func(interface{}) interface{}) { 
    for v := range src { 
     select { 
     case dst <- f(v): 
     case <-quit: 
      return 
     } 
    } 
} 

我可以排序的解決這個通過重寫測試重複幾次循環,以便每個分支都有機會隨機選擇。我已經嘗試了10次迭代,並且所有測試都通過了100%覆蓋率(除此之外還有其他測試)。但它讓我大吃一驚,我似乎無法寫出一個最好的兩全其美的版本,如果上游渠道沒有關閉,並且保證給出100%的測試覆蓋率(不只是可能)。

對我有什麼啓發?

P.S.如果您好奇,爲什麼「不阻止上游通道未關閉」很重要,這只是另一點OCD。這個函數被導出,這意味着如果客戶端代碼行爲不當,我的代碼會出錯。我希望它比第一個版本更有彈性。

回答

0

編輯:我大量編輯這個答案,因爲它仍然不正確。這似乎工作得很好。

好吧,所以我感覺很不好意思。當我看着這個時,我一定被燒燬了。以確定性的方式遍歷這些選擇語句是完全可能的:

func TestMapCancel(t *testing.T) { 
    src := make(chan interface{}) 
    quit := make(chan struct{}) 
    done := make(chan struct{}) 

    go func() { 
     defer close(done) 
     Map(quit, nil, src, double) 
    }() 

    close(quit) 

    select { 
    case <-done: 
    case <-time.After(100 * time.Millisecond): 
     t.Error("quitting pre-send failed") 
    } 

    src = make(chan interface{}) 
    quit = make(chan struct{}) 
    done = make(chan struct{}) 

    go func() { 
     defer close(done) 
     Map(quit, nil, src, double) 
    }() 

    src <- 1 
    close(quit) 

    select { 
    case <-done: 
    case <-time.After(100 * time.Millisecond): 
     t.Error("quitting pre-send failed") 
    } 
} 
+0

我會將它分成多個測試函數。此外,還有兩個你沒有測試的退出情況:從src'接收,然後發送'dst' _then_關閉'quit',並退出,因爲src'已關閉。 https://play.golang.org/p/KI9OJLsHdc – Kaedys

+0

這些已經在其他測試功能中處理過了。只是這個特定的人給了我適合,因爲我沒有意識到我可以通過將通道設置爲零來確定性地遍歷select語句,所以他們永遠不會收到。 – burfl

+0

說實話,甚至不需要設置它們爲零。簡單地讓他們沒有緩衝的頻道,你不叫接收在機械上是相同的渠道爲零。如果您創建了一個無緩衝的'dst'頻道並將它交給'Map',並且從來不打算做一個'<-dst',那麼您的測試根本不會改變。 – Kaedys