# 为什么测试这么重要 `人非圣贤孰能无过`,归根结底,代码是人写的,`Bug`终究是无法避免的。测试的目的是为了尽早发现问题,尽量减少`Bug`数量。 # 测试类型 ## `Smoke test` - 程序员自己测试 ## `Unit Tests` - 通常由程序员做 - 快,几毫秒 - 独立 - 一次测试一种行为 - 不依赖于外界,比如使用`Moq`和`nSubstitute` ## `Integration Tests` - 通常由程序员做 - 相对较慢 - 依赖于外部实现,比如依赖数据库、外部服务 ## `Functional Tests` - 从用户的角度测试功能,可能是内测人员 - 可以手动测试,也有自动测试框架 ## `Subcutaneous Tests` - 最接近`UI`下的测试 - 通常由程序员做 - 适合逻辑在后端业务层,比如前后端分离的后端业务层 ## `Load Tests` - 通常由程序员做 - 模拟程序的负载 - 通常查看各项性能指标 `Stress Tests` - 通常由程序员做 - 通常查看`CPU`,`Network`,`Memory`指标 ![t3](F:\SourceCodes\DDWiki\专题\后端\测试\t3.png) # 单元测试的要和不要 要 - 要间接测试私有方法 - 要符合条件/不符合条件的输入 - 容易出问题的代码要单元测试,比如包含正则表达式 - 很难被捕捉的异常要单元测试,比如路由、算法 不要 - 不要100%的覆盖率,没太必要 - 不要给依赖组件的运行时错误进行单元测试 - 不要在数据库架构、外部服务方面进行单元测试 - 性能测试不通过单元测试 - 代码生成器生成的代码不需要单元测试 - 如果测试代码远大于被测试代码不需要单元测试 # 一定要让测试不通过 不好的 ``` [Test] public void ShouldAddTwoNumbers() { var calculator = new Calculator(); var result = calculator.Sum(10, 11); Assert.Equal(21, result); } // The method to test in class Calculator ... public int Sum(int x, int y) { throw new NotImplementedException(); } ``` 好的 ``` [Test] public void ShouldAddTwoNumbers() { var calculator = new Calculator(); var result = calculator.Sum(10, 11); Assert.Equal(21, result); } // The method to test in class Calculator ... public int Sum(int x, int y) { return 0;//返回一个值让不通过 } ``` # 通过单元测试消除`Bug` 如果通过单元测试发现一个`Bug`, 即单元测试显示红灯。重构代买,单元测试通过,显示绿色。于是,单元测试起到了帮助代码重构的作用。 # 流行的测试框架 - `NUnit`: 是`.NET`开源的、被认可的测试框架,有`UI` - `XUnit`: 来之`NUnit`作者,最新的,鼓励`TDD`的开发方式,甚至`.NET Core`团队也在使用 - `MSTest`: 微软的测试框架,无法在`build server`跑`CI/CD` # 持续集成服务器 监控源代码,一旦有变化,检查、构建、自动测试、发送报告等。 # 测试项目的文件结构 ![t4](F:\SourceCodes\DDWiki\专题\后端\测试\t4.png) # 命名 - `MethodName_StateUnderTest_ExpectedBehavior` ``` isAdult_AgeLessThan18_False withdrawMoney_InvalidAccount_ExceptionThrown admitStudent_MissingMandatoryFields_FailToAdmit ``` - `MethodName_ExpectedBehavior_StateUnderTest` - `test[Feature being tested]` ``` testIsNotAnAdultIfAgeLessThan18 testFailToWithdrawMoneyIfAccountIsInvalid ``` - `Feature to be tested` ``` IsNotAnAdultIfAgeLessThan18 FailToWithdrawMoneyIfAccountIsInvalid ``` - `Should_ExpectedBehaviour_When_StateUnderTest` ``` Should_ThrowException_When_AgeLessThan18 Should_FailToWithdrawMoney_ForInvalidAccount ``` - `When_StateUnderTest_Expect_ExpectedBehavior` ``` When_AgeLessThan18_Expect_isAdultAsFalse When_InvalidAccount_Expect_WithdrawMoneyToFail ``` - `Given_Preconditions_When_StateUnderTest_Then_ExpectedBehavior` ``` Given_UserIsAuthenticated_When_InvalidAccountNumberIsUsedToWithdrawMoney_Then_TransactionsWillFail ``` # `AAA` ``` [TestMethod] public void TestRegisterPost_ValidUser_ReturnsRedirect() { // Arrange AccountController controller = GetAccountController(); RegisterModel model = new RegisterModel() { UserName = "someUser", Email = "goodEmail", Password = "goodPassword", ConfirmPassword = "goodPassword" }; // Act ActionResult result = controller.Register(model); // Assert RedirectToRouteResult redirectResult = (RedirectToRouteResult)result; Assert.AreEqual("Home", redirectResult.RouteValues["controller"]); Assert.AreEqual("Index", redirectResult.RouteValues["action"]); } ``` # 数据量很大很难测试时用单元测试 被测代码,数量越大越难测,很有必要使用单元测试。 ``` public decimal CalculateTotal(List items) { decimal total = 0.0m; foreach(MyItem i in items) { total += i.UnitPrice * (i - i.Discount); } return total; } ``` # 保持被测方法不要过于复杂 # `Test Explorer`的使用 ![t5](F:\SourceCodes\DDWiki\专题\后端\测试\t5.png) ![t6](F:\SourceCodes\DDWiki\专题\后端\测试\t6.png) # 对被测代码复杂逻辑的封装 不好 ``` while ((ActiveThreads > 0 || AssociationsQueued > 0) && (IsRegistered || report.TotalTargets <= 1000 ) && (maxNumPagesToScan == -1 || report.TotalTargets < maxNumPagesToScan) && (!CancelScan)) ``` 好的 ``` while (!HasFinishedInitializing (ActiveThreads, AssociationsQueued, IsRegistered, report.TotalTargets, maxNumPagesToScan, CancelScan)) ``` # 使用`Selenium`进行`Web`测试