# 为什么测试这么重要

`人非圣贤孰能无过`,归根结底,代码是人写的,`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<myitem> 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`测试