测试类型
Unit tests
):测试单个组件或方法,与数据库、文件、外界交互无关测试框架
xUnit
:来自NUnit v2
的作者,包括了最新的测试技术和思想。属于.NET基金会
下的项目。NUnit
:从JUnit
演变而来,最新的版本增加了很多新功能。属于.NET基金会
下的项目。MSTest
:微软的测试框架,支持在.NET CLI
和Visual Studio
中的扩展.NET CLI
.NET CLI
集成了IDE
下的大部分功能,通过使用dotnet test
命令运行。可以被用到持续集成交付管道中,也可以被用在测试自动化任务中。
IDE
相比.NET CLI
有更多的功能,比如Live Unit Testing
是IDE
独有的测试功能。
为什么需要单元测试
好单元测试的特点
测试覆盖率Code coverage
测试覆盖率不见得越大越好,只是表明有多少代码被测试了,需要在实际项目中把握测试覆盖率的度。
Fake
, Mock
, Stub
Fake
:不确定是Mock
还是Stub
的时候用Fake
Mock
:直接影响到测试结果就用Mock
Stub
: 只是某个依赖的替代,不直接影响到测试结果不好的
var mockOrder = new MockOrder();
var purchase = new Purchase(mockOrder);
purchase.ValidateOrders();
Assert.True(purchase.CanBeShipped);//测试结果与MockOrder没有直接关系
好的
var stubOrder = new FakeOrder();//使用Fake可以转换成Mock或Stub
var purchase = new Purchase(stubOrder);
purchase.ValdiateOrders();
Assert.True(purchase.CanBeShipped);//测试结果与stubOrder没有关系
好的
var mockOrder = new FakeOrder();//使用Fake可以转换成Mock或Stub
var purchase = new Purchase(mockOrder);
purchase.ValidateOrders();
Assert.True(mockOrder.Valdiated);//测试结果与mockOrder有直接关系
命名。好的命名说明了测试意图。通常是:
被测方法名称_场景或条件_期待结果
不好的
[Fact]
public void Test_Single(){}
好的
[Fact]
public void Add_SingleNumber_ReturnsSameNumber(){}
AAA
原则不好的
[Fact]
public void Add_EmptyString_ReturnsZero()
{
//Arrange
var stringCalculator = new StringCalculator();
//Assert
Assert.Equal(0, stringCalculator.Add(""));
}
好的
[Fact]
public void Add_EmptyString_ReturnsZero()
{
//Arrange
var stringCalculator = new StringCalculator();
//Act
var actual = stringCalculator.Add("");
//Assert
Assert.Equal(0, actual);
}
不好的
[Fact]
public void Add_SingleNumber_ReturnsSameNumber()
{
var stringCalculator = new StringCalculator();
var actual = stringCalculator.Add("42");
Assert.Equal(42, actual);
}
好的
[Fact]
public void Add_SingleNumber_ReturnsSameNumber()
{
var stringCalculator = new StringCalculator();
var actual = stringCalculator.Add("0");
Assert.Equal(0, actual);
}
不好的
[Fact]
public void Add_BigNumber_ThrowsException()
{
var stringCalculator = new StringCalculator();
Action actual = () => stringCalculator.Add("1001");
Assert.Throws<OverflowException>(actual);
}
好的
[Fact]
void Add_MaximumSumResult_ThrowsOverflowException()
{
var stringCalculator = new StringCalculator();
const string MAXIMUM_RESULT = "1001";//赋值给常量
Action actual = () => stringCalculator.Add(MAXIMUM_RESULT);
Assert.Throws<OverflowException>(actual);
}
不好的
[Fact]
public void Add_MultipleNumbers_ReturnsCorrectResults()
{
var stringCalculator = new StringCalculator();
var expected = 0;
var testCases = new[]
{
"0,0,0",
"0,1,2",
"1,2,3"
};
foreach (var test in testCases)
{
Assert.Equal(expected, stringCalculator.Add(test));
expected += 3;
}
}
好的
[Theory]
[InlineData("0,0,0", 0)]
[InlineData("0,1,2", 3)]
[InlineData("1,2,3", 6)]
public void Add_MultipleNumbers_ReturnsSumOfNumbers(string input, int expected)
{
var stringCalculator = new StringCalculator();
var actual = stringCalculator.Add(input);
Assert.Equal(expected, actual);
}
setup
和teadown
不好的
private readonly StringCalculator stringCalculator;
public StringCalculatorTests()
{
stringCalculator = new StringCalculator();
}
[Fact]
public void Add_TwoNumbers_ReturnsSumOfNumbers()
{
var result = stringCalculator.Add("0,1");
Assert.Equal(1, result);
}
好的
[Fact]
public void Add_TwoNumbers_ReturnsSumOfNumbers()
{
var stringCalculator = CreateDefaultStringCalculator();
var actual = stringCalculator.Add("0,1");
Assert.Equal(1, actual);
}
private StringCalculator CreateDefaultStringCalculator()
{
return new StringCalculator();
}
不好的
[Fact]
public void Add_EdgeCases_ThrowsArgumentExceptions()
{
Assert.Throws<ArgumentException>(() => stringCalculator.Add(null));
Assert.Throws<ArgumentException>(() => stringCalculator.Add("a"));
}
好的
[Theory]
[InlineData(null)]
[InlineData("a")]
public void Add_InputNullOrAlphabetic_ThrowsArgumentException(string input)
{
var stringCalculator = new StringCalculator();
Action actual = () => stringCalculator.Add(input);
Assert.Throws<ArgumentException>(actual);
}
public string ParseLogLine(string input)
{
var sanitizedInput = TrimInput(input);
return sanitizedInput;
}
private string TrimInput(string input)
{
return input.Trim();
}
[Fact]
public void ParseLogLine_StartsAndEndsWithSpace_ReturnsTrimmedResult()
{
var parser = new Parser();
var result = parser.ParseLogLine(" a ");
Assert.Equals("a", result);
}
有一个和日期相关的方法
public int GetDiscountedPrice(int price)
{
if (DateTime.Now.DayOfWeek == DayOfWeek.Tuesday)
{
return price / 2;
}
else
{
return price;
}
}
不好的
public void GetDiscountedPrice_NotTuesday_ReturnsFullPrice()
{
var priceCalculator = new PriceCalculator();
var actual = priceCalculator.GetDiscountedPrice(2);
Assert.Equals(2, actual)
}
public void GetDiscountedPrice_OnTuesday_ReturnsHalfPrice()
{
var priceCalculator = new PriceCalculator();
var actual = priceCalculator.GetDiscountedPrice(2);
Assert.Equals(1, actual);
}
不管哪一天,总一个通不过。
好的。提炼出一个获取日期的接口。
public interface IDateTimeProvider
{
DayOfWeek DayOfWeek();
}
public int GetDiscountedPrice(int price, IDateTimeProvider dateTimeProvider)
{
if (dateTimeProvider.DayOfWeek() == DayOfWeek.Tuesday)
{
return price / 2;
}
else
{
return price;
}
}
public void GetDiscountedPrice_NotTuesday_ReturnsFullPrice()
{
var priceCalculator = new PriceCalculator();
var dateTimeProviderStub = new Mock<IDateTimeProvider>();//模拟接口
dateTimeProviderStub.Setup(dtp => dtp.DayOfWeek()).Returns(DayOfWeek.Monday);//模拟接口实现
var actual = priceCalculator.GetDiscountedPrice(2, dateTimeProviderStub);
Assert.Equals(2, actual);
}
public void GetDiscountedPrice_OnTuesday_ReturnsHalfPrice()
{
var priceCalculator = new PriceCalculator();
var dateTimeProviderStub = new Mock<IDateTimeProvider>();
dateTimeProviderStub.Setup(dtp => dtp.DayOfWeek()).Returns(DayOfWeek.Tuesday);
var actual = priceCalculator.GetDiscountedPrice(2, dateTimeProviderStub);
Assert.Equals(1, actual);
}