鼎鼎知识库
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

3 роки тому
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. # .NET中的测试
  2. 测试类型
  3. - 单元测试(`Unit tests`):测试单个组件或方法,与数据库、文件、外界交互无关
  4. - 集成测试(Integration tests):测试多个组件或方法,与数据库、文件、外界交互有关
  5. - 压力测试(Load tests):测试系统对压力的承受程度,比如并发用户、并发请求等
  6. 测试框架
  7. - `xUnit`:来自`NUnit v2`的作者,包括了最新的测试技术和思想。属于`.NET基金会`下的项目。
  8. - `NUnit`:从`JUnit`演变而来,最新的版本增加了很多新功能。属于`.NET基金会`下的项目。
  9. - `MSTest`:微软的测试框架,支持在`.NET CLI`和`Visual Studio`中的扩展
  10. `.NET CLI`
  11. `.NET CLI`集成了`IDE`下的大部分功能,通过使用`dotnet test`命令运行。可以被用到持续集成交付管道中,也可以被用在测试自动化任务中。
  12. `IDE`
  13. 相比`.NET CLI`有更多的功能,比如`Live Unit Testing`是`IDE`独有的测试功能。
  14. # 单元测试的最佳实践
  15. 为什么需要单元测试
  16. - 相比功能测试更少的时间:功能测试需要一个懂领域知识的人,测试时间较长。单元测试不需要对被测试系统有太多了解,在极短的时间内完成测试。
  17. - 能更好地应对变化:只要有代码的更改,无论是新功能,还是老功能,跑一遍单元测试就可以做到
  18. - 测试的文档化:单元测试本身说明了给定输入下的输出
  19. - 反向促使代码解耦:耦合性很强的代码很难测试
  20. 好单元测试的特点
  21. - 快:毫秒级
  22. - 隔离的:不依赖于任何文件或数据库,可以单独跑通
  23. - 可重复的:如果在测试测时候没有改变条件,测试结果是一样的
  24. - 自检的:在没有人员介入的情况下判定测试成功或失败
  25. - 用时较少的:用时比写代码更少,如果发现写测试的时间较多,说明哪个地方有问题
  26. 测试覆盖率`Code coverage`
  27. 测试覆盖率不见得越大越好,只是表明有多少代码被测试了,需要在实际项目中把握测试覆盖率的度。
  28. ## 理解`Fake`, `Mock`, `Stub`
  29. - `Fake`:不确定是`Mock`还是`Stub`的时候用`Fake`
  30. - `Mock`:直接影响到测试结果就用`Mock`
  31. - `Stub`: 只是某个依赖的替代,不直接影响到测试结果
  32. 不好的
  33. ```
  34. var mockOrder = new MockOrder();
  35. var purchase = new Purchase(mockOrder);
  36. purchase.ValidateOrders();
  37. Assert.True(purchase.CanBeShipped);//测试结果与MockOrder没有直接关系
  38. ```
  39. 好的
  40. ```
  41. var stubOrder = new FakeOrder();//使用Fake可以转换成Mock或Stub
  42. var purchase = new Purchase(stubOrder);
  43. purchase.ValdiateOrders();
  44. Assert.True(purchase.CanBeShipped);//测试结果与stubOrder没有关系
  45. ```
  46. 好的
  47. ```
  48. var mockOrder = new FakeOrder();//使用Fake可以转换成Mock或Stub
  49. var purchase = new Purchase(mockOrder);
  50. purchase.ValidateOrders();
  51. Assert.True(mockOrder.Valdiated);//测试结果与mockOrder有直接关系
  52. ```
  53. > 命名。好的命名说明了测试意图。通常是:`被测方法名称_场景或条件_期待结果`
  54. 不好的
  55. ```
  56. [Fact]
  57. public void Test_Single(){}
  58. ```
  59. 好的
  60. ```
  61. [Fact]
  62. public void Add_SingleNumber_ReturnsSameNumber(){}
  63. ```
  64. ## 遵循`AAA`原则
  65. 不好的
  66. ```
  67. [Fact]
  68. public void Add_EmptyString_ReturnsZero()
  69. {
  70. //Arrange
  71. var stringCalculator = new StringCalculator();
  72. //Assert
  73. Assert.Equal(0, stringCalculator.Add(""));
  74. }
  75. ```
  76. 好的
  77. ```
  78. [Fact]
  79. public void Add_EmptyString_ReturnsZero()
  80. {
  81. //Arrange
  82. var stringCalculator = new StringCalculator();
  83. //Act
  84. var actual = stringCalculator.Add("");
  85. //Assert
  86. Assert.Equal(0, actual);
  87. }
  88. ```
  89. ## 保持输入简单原则
  90. 不好的
  91. ```
  92. [Fact]
  93. public void Add_SingleNumber_ReturnsSameNumber()
  94. {
  95. var stringCalculator = new StringCalculator();
  96. var actual = stringCalculator.Add("42");
  97. Assert.Equal(42, actual);
  98. }
  99. ```
  100. 好的
  101. ```
  102. [Fact]
  103. public void Add_SingleNumber_ReturnsSameNumber()
  104. {
  105. var stringCalculator = new StringCalculator();
  106. var actual = stringCalculator.Add("0");
  107. Assert.Equal(0, actual);
  108. }
  109. ```
  110. ## 避免使用系统保留字
  111. 不好的
  112. ```
  113. [Fact]
  114. public void Add_BigNumber_ThrowsException()
  115. {
  116. var stringCalculator = new StringCalculator();
  117. Action actual = () => stringCalculator.Add("1001");
  118. Assert.Throws<OverflowException>(actual);
  119. }
  120. ```
  121. 好的
  122. ```
  123. [Fact]
  124. void Add_MaximumSumResult_ThrowsOverflowException()
  125. {
  126. var stringCalculator = new StringCalculator();
  127. const string MAXIMUM_RESULT = "1001";//赋值给常量
  128. Action actual = () => stringCalculator.Add(MAXIMUM_RESULT);
  129. Assert.Throws<OverflowException>(actual);
  130. }
  131. ```
  132. ## 避免逻辑
  133. 不好的
  134. ```
  135. [Fact]
  136. public void Add_MultipleNumbers_ReturnsCorrectResults()
  137. {
  138. var stringCalculator = new StringCalculator();
  139. var expected = 0;
  140. var testCases = new[]
  141. {
  142. "0,0,0",
  143. "0,1,2",
  144. "1,2,3"
  145. };
  146. foreach (var test in testCases)
  147. {
  148. Assert.Equal(expected, stringCalculator.Add(test));
  149. expected += 3;
  150. }
  151. }
  152. ```
  153. 好的
  154. ```
  155. [Theory]
  156. [InlineData("0,0,0", 0)]
  157. [InlineData("0,1,2", 3)]
  158. [InlineData("1,2,3", 6)]
  159. public void Add_MultipleNumbers_ReturnsSumOfNumbers(string input, int expected)
  160. {
  161. var stringCalculator = new StringCalculator();
  162. var actual = stringCalculator.Add(input);
  163. Assert.Equal(expected, actual);
  164. }
  165. ```
  166. ## 帮助方法替代`setup`和`teadown`
  167. 不好的
  168. ```
  169. private readonly StringCalculator stringCalculator;
  170. public StringCalculatorTests()
  171. {
  172. stringCalculator = new StringCalculator();
  173. }
  174. ```
  175. ```
  176. [Fact]
  177. public void Add_TwoNumbers_ReturnsSumOfNumbers()
  178. {
  179. var result = stringCalculator.Add("0,1");
  180. Assert.Equal(1, result);
  181. }
  182. ```
  183. 好的
  184. ```
  185. [Fact]
  186. public void Add_TwoNumbers_ReturnsSumOfNumbers()
  187. {
  188. var stringCalculator = CreateDefaultStringCalculator();
  189. var actual = stringCalculator.Add("0,1");
  190. Assert.Equal(1, actual);
  191. }
  192. private StringCalculator CreateDefaultStringCalculator()
  193. {
  194. return new StringCalculator();
  195. }
  196. ```
  197. ## 避免一个测试方法中出现多个推断
  198. 不好的
  199. ```
  200. [Fact]
  201. public void Add_EdgeCases_ThrowsArgumentExceptions()
  202. {
  203. Assert.Throws<ArgumentException>(() => stringCalculator.Add(null));
  204. Assert.Throws<ArgumentException>(() => stringCalculator.Add("a"));
  205. }
  206. ```
  207. 好的
  208. ```
  209. [Theory]
  210. [InlineData(null)]
  211. [InlineData("a")]
  212. public void Add_InputNullOrAlphabetic_ThrowsArgumentException(string input)
  213. {
  214. var stringCalculator = new StringCalculator();
  215. Action actual = () => stringCalculator.Add(input);
  216. Assert.Throws<ArgumentException>(actual);
  217. }
  218. ```
  219. ## 测试公共方法而不是私有方法
  220. ```
  221. public string ParseLogLine(string input)
  222. {
  223. var sanitizedInput = TrimInput(input);
  224. return sanitizedInput;
  225. }
  226. private string TrimInput(string input)
  227. {
  228. return input.Trim();
  229. }
  230. [Fact]
  231. public void ParseLogLine_StartsAndEndsWithSpace_ReturnsTrimmedResult()
  232. {
  233. var parser = new Parser();
  234. var result = parser.ParseLogLine(" a ");
  235. Assert.Equals("a", result);
  236. }
  237. ```
  238. ## 使用接口获取静态值
  239. 有一个和日期相关的方法
  240. ```
  241. public int GetDiscountedPrice(int price)
  242. {
  243. if (DateTime.Now.DayOfWeek == DayOfWeek.Tuesday)
  244. {
  245. return price / 2;
  246. }
  247. else
  248. {
  249. return price;
  250. }
  251. }
  252. ```
  253. 不好的
  254. ```
  255. public void GetDiscountedPrice_NotTuesday_ReturnsFullPrice()
  256. {
  257. var priceCalculator = new PriceCalculator();
  258. var actual = priceCalculator.GetDiscountedPrice(2);
  259. Assert.Equals(2, actual)
  260. }
  261. public void GetDiscountedPrice_OnTuesday_ReturnsHalfPrice()
  262. {
  263. var priceCalculator = new PriceCalculator();
  264. var actual = priceCalculator.GetDiscountedPrice(2);
  265. Assert.Equals(1, actual);
  266. }
  267. ```
  268. 不管哪一天,总一个通不过。
  269. 好的。提炼出一个获取日期的接口。
  270. ```
  271. public interface IDateTimeProvider
  272. {
  273. DayOfWeek DayOfWeek();
  274. }
  275. ```
  276. ```
  277. public int GetDiscountedPrice(int price, IDateTimeProvider dateTimeProvider)
  278. {
  279. if (dateTimeProvider.DayOfWeek() == DayOfWeek.Tuesday)
  280. {
  281. return price / 2;
  282. }
  283. else
  284. {
  285. return price;
  286. }
  287. }
  288. ```
  289. ```
  290. public void GetDiscountedPrice_NotTuesday_ReturnsFullPrice()
  291. {
  292. var priceCalculator = new PriceCalculator();
  293. var dateTimeProviderStub = new Mock<IDateTimeProvider>();//模拟接口
  294. dateTimeProviderStub.Setup(dtp => dtp.DayOfWeek()).Returns(DayOfWeek.Monday);//模拟接口实现
  295. var actual = priceCalculator.GetDiscountedPrice(2, dateTimeProviderStub);
  296. Assert.Equals(2, actual);
  297. }
  298. public void GetDiscountedPrice_OnTuesday_ReturnsHalfPrice()
  299. {
  300. var priceCalculator = new PriceCalculator();
  301. var dateTimeProviderStub = new Mock<IDateTimeProvider>();
  302. dateTimeProviderStub.Setup(dtp => dtp.DayOfWeek()).Returns(DayOfWeek.Tuesday);
  303. var actual = priceCalculator.GetDiscountedPrice(2, dateTimeProviderStub);
  304. Assert.Equals(1, actual);
  305. }
  306. ```