在單元測試中,可通過兩種方式來驗證代碼是否正確地工作。一種是基於結果狀態的測試,一種是基於交互行為的測試。
測試結果與測試行為之間有什么區別呢?
基於結果狀態的測試,也就意味着我們需要驗證被測試代碼需要返回正確的結果。
1 [TestMethod] 2 public void TestSortNumberResult() 3 { 4 IShellSorter<int> shellSorter = new ShellSorter<int>(); 5 IBubbleSorter<int> bubbleSorter = new BubbleSorter<int>(); 6 7 NumberSorter numberSorter = new NumberSorter(shellSorter, bubbleSorter); 8 int[] numbers = new int[] { 3, 1, 2 }; 9 numberSorter.Sort(numbers); 10 11 // 驗證返回值是否已經被正確排序。 12 // 只要返回值正確即可,並不關心使用了哪個算法。 13 CollectionAssert.AreEqual(new int[] { 1, 2, 3 }, numbers); 14 }
基於交互行為的測試,也就意味着我們需要驗證被測試代碼是否正確合理地調用了某些方法。
1 [TestMethod] 2 public void TestUseCorrectSortingAlgorithm() 3 { 4 IShellSorter<int> mockShellSorter = Substitute.For<IShellSorter<int>>(); 5 IBubbleSorter<int> mockBubbleSorter = Substitute.For<IBubbleSorter<int>>(); 6 7 NumberSorter numberSorter = new NumberSorter(mockShellSorter, mockBubbleSorter); 8 int[] numbers = new int[] { 3, 1, 2 }; 9 numberSorter.Sort(numbers); 10 11 // 驗證排序器是否使用冒泡排序算法。 12 // 如果排序器未使用冒泡排序算法,或者使用了該算法但傳遞了錯誤的參數,則驗證失敗。 13 mockBubbleSorter.Received().Sort(Arg.Is<int[]>(numbers)); 14 }
第二種測試方法可能會得出較好的代碼覆蓋率,但它卻沒有告訴我們排序結果是否正確,而只是確認調用了 bubbleSorter.Sort() 方法。所以交互行為測試並不能證明代碼可以正確工作。這也就是在大多數情況下,我們需要測試結果和狀態,而不是測試交互和行為。
通常來說,如果程序的正確性不能僅僅靠程序的輸出結果來決定,而還需要判斷結果是怎么產生的,在這種條件下,我們就需要對交互和行為進行測試。在上面的示例中,你可能想在得到正確測試結果的前提下,額外的再測試下交互行為,因為可能確認正確地使用了某種算法非常重要,例如某些算法在給定條件下運行速度更快,否則的話測試交互行為的意義並不大。
通常在什么條件下需要對交互行為進行測試呢?
這里給出兩種較適合的場景:
- 假設被測試代碼需要調用了一個方法,但可能由於其被調用的次數不同,或者被調用的順序不同,而導致產生了不同的結果,或者出現了其他類似時間延遲、多線程死鎖等副作用。例如該方法負責發送郵件,我們需要確認只調用了一次郵件發送函數。或者例如該方法的不同調用順序會產生不同的線程鎖控制,導致死鎖。在類似這些情況下,測試交互行為可以有效地幫助你確認方法調用是否正確。
- 假設我們在測試一個UI程序,其中已經通過抽象將UI渲染部分與UI邏輯部分隔離,可以考慮是某種MVC或MVVM模式。那么在我們測試 Controller 或 ViewModel 層時,如果有的話,可能只關心 View 上的哪些方法被調用了,而並不關系具體該方法內部是如何渲染的,所以此處測試與 View 的交互就比較合適。類似的,對於 Model 層也一樣。
完整代碼

1 [TestClass] 2 public class UnitTestTwoWays 3 { 4 public interface IShellSorter<T> 5 where T : IComparable 6 { 7 void Sort(T[] list); 8 } 9 10 public interface IBubbleSorter<T> 11 where T : IComparable 12 { 13 void Sort(T[] list); 14 } 15 16 public class ShellSorter<T> : IShellSorter<T> 17 where T : IComparable 18 { 19 public void Sort(T[] list) 20 { 21 int inc; 22 23 for (inc = 1; inc <= list.Length / 9; inc = 3 * inc + 1) ; 24 25 for (; inc > 0; inc /= 3) 26 { 27 for (int i = inc + 1; i <= list.Length; i += inc) 28 { 29 T t = list[i - 1]; 30 int j = i; 31 32 while ((j > inc) && (list[j - inc - 1].CompareTo(t) > 0)) 33 { 34 list[j - 1] = list[j - inc - 1]; 35 j -= inc; 36 } 37 38 list[j - 1] = t; 39 } 40 } 41 } 42 } 43 44 public class BubbleSorter<T> : IBubbleSorter<T> 45 where T : IComparable 46 { 47 public void Sort(T[] list) 48 { 49 int i, j; 50 bool done = false; 51 52 j = 1; 53 while ((j < list.Length) && (!done)) 54 { 55 done = true; 56 57 for (i = 0; i < list.Length - j; i++) 58 { 59 if (list[i].CompareTo(list[i + 1]) > 0) 60 { 61 done = false; 62 T t = list[i]; 63 list[i] = list[i + 1]; 64 list[i + 1] = t; 65 } 66 } 67 68 j++; 69 } 70 } 71 } 72 73 public interface INumberSorter 74 { 75 void Sort(int[] numbers); 76 } 77 78 public class NumberSorter : INumberSorter 79 { 80 private IShellSorter<int> _shellSorter; 81 private IBubbleSorter<int> _bubbleSorter; 82 83 public NumberSorter( 84 IShellSorter<int> shellSorter, 85 IBubbleSorter<int> bubbleSorter) 86 { 87 _shellSorter = shellSorter; 88 _bubbleSorter = bubbleSorter; 89 } 90 91 public void Sort(int[] numbers) 92 { 93 _bubbleSorter.Sort(numbers); 94 } 95 } 96 97 [TestMethod] 98 public void TestSortNumberResult() 99 { 100 IShellSorter<int> shellSorter = new ShellSorter<int>(); 101 IBubbleSorter<int> bubbleSorter = new BubbleSorter<int>(); 102 103 NumberSorter numberSorter = new NumberSorter(shellSorter, bubbleSorter); 104 int[] numbers = new int[] { 3, 1, 2 }; 105 numberSorter.Sort(numbers); 106 107 // 驗證返回值是否已經被正確排序。 108 // 只要返回值正確即可,並不關心使用了哪個算法。 109 CollectionAssert.AreEqual(new int[] { 1, 2, 3 }, numbers); 110 } 111 112 [TestMethod] 113 public void TestUseCorrectSortingAlgorithm() 114 { 115 IShellSorter<int> mockShellSorter = Substitute.For<IShellSorter<int>>(); 116 IBubbleSorter<int> mockBubbleSorter = Substitute.For<IBubbleSorter<int>>(); 117 118 NumberSorter numberSorter = new NumberSorter(mockShellSorter, mockBubbleSorter); 119 int[] numbers = new int[] { 3, 1, 2 }; 120 numberSorter.Sort(numbers); 121 122 // 驗證排序器是否使用冒泡排序算法。 123 // 如果排序器未使用冒泡排序算法,或者使用了該算法但傳遞了錯誤的參數,則驗證失敗。 124 mockBubbleSorter.Received().Sort(Arg.Is<int[]>(numbers)); 125 } 126 }
關於單元測試 mocking 技術,請參考《NSubstitute完全手冊》。