
测试
单元测试
单元测试是通过把一个应用程序拆分成可测试的足够小的部分,然后把每一部分与其它所有功能隔离开,单独对这一部分进行测试。而这个“可测试的足够小的部分”就称之为“单元“,在C语言中一个单元可以是一个函数,在C#中单元测试可以是一个类。 如果所有的单元都能够像我们所预料的正常工作,那么把他们合并起来就能够保证至少不会出现很严重的错误。
- 可重复运行的
- 持续长期有效,并且返回一致的结果
- 在内存中运行,没有外部依赖组件(比如说真实的数据库,真实的文件存储等)
- 快速返回结果
- 一个测试方法只测试一个问题
测试驱动开发(TDD): 理解需求–>针对需求写单元测试–> 编写代码让单元测试通过。 最开始是叫测试先行(TFD: Test First Development) ,后来就发展成我们熟知的"测试驱动开发"了。
测试驱动开发最大的好处是,让开发人员更好的理解需求,甚至是挖掘需求之后再进行开发。 当然,我们不可能一次性把所有的测试代码都写出来之后再写代码,这是一个重复迭代的过程
TDD
TDD指的是Test Drive Development,很明显的意思是测试驱动开发,也就是说我们可以从测试的角度来检验整个项目。大概的流程是先针对每个功能点抽象出接口代码,然后编写单元测试代码,接下来实现接口,运行单元测试代码,循环此过程,直到整个单元测试都通过。这一点和敏捷开发有类似之处。
TDD的好处自然不用多说,它能让你减少程序逻辑方面的错误,尽可能的减少项目中的bug,开始接触编程的时候我们大都有过这样的体验,可能你觉得完成得很完美,自我感觉良好,但是实际测试或者应用的时候才发现里面可能存在一堆bug,或者存在设计问题,或者更严重的逻辑问题,而TDD正好可以帮助我们尽量减少类似事件的发生。而且现在大行其道的一些模式对TDD的支持都非常不错,比如MVC和MVP等。
但是并不是所有的项目都适合TDD这种模式的,我觉得必须具备以下几个条件。
首先,项目的需求必须足够清晰,而且程序员对整个需求有足够的了解,如果这个条件不满足,那么执行的过程中难免失控。当然,要达到这个目标也是需要做一定功课的,这要求我们前期的需求分析以及HLD和LLD都要做得足够的细致和完善。
其次,取决于项目的复杂度和依赖性,对于一个业务模型及其复杂、内部模块之间的相互依赖性非常强的项目,采用TDD反而会得不尝失,这会导致程序员在拆分接口和写测试代码的时候工作量非常大。另外,由于模块之间的依赖性太强,我们在写测试代码的时候可能不采取一些桥接模式来实现,这样势必加大了程序员的工作量。
需求越不明确越需要TDD.如果需求都很明确,说明这东西以前做过不知几百次了, 那还写测试干什么啊,直接把以前的改改就用了呗.
TDD是测试驱动开发,ATDD是验收测试驱动开发,都是关于测试的,是与所开发的系统紧密联系的。而BDD则不同,前面提到过BDD不是关于测试的,着重关注需求、关注客户的业务价值,所描述的需求用例是可以独立于软件系统存在的,因为客户的业务是始终存在的,不取决于是否有软件系统来支撑。
BDD
BDD指的是Behavior Drive Development,也就是行为驱动开发。这里的B并非指的是Business,实际上BDD可以看作是对TDD的一种补充,当然你也可以把它看作TDD的一个分支。因为在TDD中,我们并不能完全保证根据设计所编写的测试就是用户所期望的功能。BDD将这一部分简单和自然化,用自然语言来描述,让开发、测试、BA以及客户都能在这个基础上达成一致。因为测试优先的概念并不是每个人都能接受的,可能有人觉得系统太复杂而难以测试,有人认为不存在的东西无法测试。所以,我们在这里试图转换一种观念,那便是考虑它的行为,也就是说它应该如何运行,然后抽象出能达成共识的规范。如果你用过JBehave之类的BDD框架,你将会更好的理解其中具体的流程。
DDD
DDD指的是Domain Drive Design,也就是领域驱动开发。这是一种非常好的思想,在我们刚开始学习程序,甚至刚开始学习三层架构的时候,我们曾经面临过很多疑惑,比如如何来实现我们的数据层?后来我们开始学习MVC,MVP等架构,如何设计Model层又成了我们的新问题。我们见过太多这种情况,Model变成了单纯的数据容器,也就是我们经常说的贫血模式。DDD实际上也是建立在这个基础之上,因为它关注的是Service层的设计,着重于业务的实现,因此不可避免的以贫血模式为基础而存在。但是它最大的特别是将分析和设计结合起来,不再使他们处于分裂的状态,这对于我们正确完整的实现客户的需求,以及建立一个具有业务伸缩性的模型,是有很大帮助的。
单元测试
https://www.cnblogs.com/cuiyansong/p/4521124.html
总之推荐xUnit。
xUnit
测试的三个阶段:AAA
Arrange
: 在这里做一些先决的设定。例如创建对象实例,数据,输入等。
Act
: 在这里执行生产代码并返回结果。例如调用方法或者设置属性。
Assert
:在这里检查结果,会产生测试通过或者失败两种结果。
[Fact]
[Trait("Category","New")] // 分组
public void BeNewWhenCreated()
{
_output.WriteLine("第一个测试");
// Arrange
var patient = new Patient();
// Act
var result = patient.IsNew;
// Assert
Assert.True(result);
}
public class CalculatorTestData
{
private static readonly List<object[]> Data = new List<object[]>
{
new object[]{ 1,2,3},
new object[]{ 1,3,4},
new object[]{ 2,4,6},
new object[]{ 0,1,1},
};
public static IEnumerable<object[]> TestData => Data;
}
// 使用[Theory],可以写有构造参数的测试方法
[Theory]
[MemberData(nameof(CalculatorTestData.TestData),MemberType =typeof(CalculatorTestData))]
public void ShouldAddEquals2(int operand1, int operand2, int expected)
{
//Arrange
var sut = new Calculator(); //sut-system under test
//Act
var result = sut.Add(operand1, operand2);
//Assert
Assert.Equal(expected, result);
}
模拟
为了让单元测试可以完全脱离外部组件,我们需要用到一些Mock对象和Stub对象,而Moq是一个开源的Mock类框架可以帮助我们实现这些功能 。我们上面代码中用到的MockRepository是我们自己用List封装的一个IRepository实例,支持增删改查,相当于我们把数据持久化于内存中。
MOQ
https://github.com/moq/moq4
Moq(发音为“ Mock-you”或“ Mock” 模拟你)是唯一从头开始开发的.NET充分利用.NET Linq表达式树和lambda表达式的模拟库,这使其成为最高效,类型安全和可操作的库。提供重构友好的模拟库。它支持模拟接口和类。它的API非常简单明了,不需要任何有关模拟概念的知识或经验。
该库主要是为那些目前不使用任何模拟库(或对其他实现的复杂性不满意)并且通常手动编写自己的模拟(或多或少具有“幻想性”)的开发人员创建的。在这种情况下,大多数开发人员也碰巧很务实,并且坚持使用状态(或经典)TDD。感觉是从其他模拟库进入的障碍有点高,并且有可能使用更简单,更轻巧和优雅的方法。 Moq充分利用了优雅而紧凑的C#和VB语言功能(统称为LINQ)来实现所有这些功能(它们不仅仅用于查询,正如首字母缩写所暗示的)。
Moq被设计为一种非常实用,通俗易懂且直截了当的方法,可以快速设置测试的依赖关系。它的API设计甚至可以帮助新手用户摆脱“成功陷阱”,并避免最常见的误用/滥用模拟行为。
快速入门: https://github.com/Moq/moq4/wiki/Quickstart
用法:
var mock = new Mock<IFoo>();
创建一个模拟的IFoo对象
Setup
装配模拟对象里的方法并设定参数和对应的返回值
using Moq;
public interface IFoo
{
bool DoSomething(string value);
string DoSomethingString(string value);
bool TryParse(string value, out string outputValue);
bool Submit(ref Bar bar);
}
public class Bar
{
public virtual Baz Baz { get; set; }
public virtual bool Submit() { return false; }
}
public class Baz
{
public virtual string Name { get; set; }
}
/////////////////////模拟///////////////////////////////
// 模拟一个实现IFoo接口的对象
var mock = new Mock<IFoo>();
// 配置对象里的方法, 此处是DoSomething方法,传入”ping“时返回true
mock.Setup(foo => foo.DoSomething("ping")).Returns(true);
mock.Object.DoSomething("pin");// false
mock.Object.DoSomething("ping");// true
var outString = "ack";
// TryParse将返回true,out参数将返回“ack” ,lazy evaluated
mock.Setup(foo => foo.TryParse("ping", out outString)).Returns(true);
var instance = new Bar();
// 仅当调用的引用参数是同一个实例时匹配
mock.Setup(foo => foo.Submit(ref instance)).Returns(true);
// 返回一个值时访问调用参数,并将参数小写输出
mock.Setup(x => x.DoSomethingStringy(It.IsAny<string>()))
.Returns((string s) => s.ToLower());
mock.Object.DoSomethingStringy("ABC");//返回 abc
断言
FluentAssertions
Fluentassertions:相对于.NET测试工具本身提供的Assert,Fluentassertions提供基于链式构建的一些更人性、易懂的方法来帮助写出更好理解的单元测试代码 。 Fluentassertions是一组.NET扩展方法,可以更自然地指定TDD或BDD样式的单元测试的预期结果。
https://fluentassertions.com/introduction
using FluentAssertions;
object theObject = null;
theObject.Should().BeNull("because the value is null");
// 抛异常: Expected theObject not to be <null> because 此处可以备注原因.
theObject.Should().NotBeNull("此处可以备注原因");
theObject = "whatever";
theObject.Should().BeOfType<string>("because a {0} is set", typeof(string));
theObject.Should().BeOfType(typeof(string), "because a {0} is set", typeof(string));
// 要断言两个对象相等(通过其Object.Equals的实现),请使用
string otherObject = "whatever";
theObject.Should().Be(otherObject, "because they have the same values");
theObject.Should().NotBe(otherObject);
// 如果要确保两个对象不仅在功能上相等,而且引用内存中的完全相同的对象,请使用以下两种方法。
theObject = otherObject;
theObject.Should().BeSameAs(otherObject);
theObject.Should().NotBeSameAs(otherObject);
string actual = "ABCDEFGHI";
actual.Should().StartWith("AB").And.EndWith("HI").And.Contain("EF").And.HaveLength(9);
IEnumerable numbers = new[] { 1, 2, 3 };
numbers.Should().OnlyContain(n => n > 0);
numbers.Should().HaveCount(4, "because we thought we put four items in the collection");
string username = "dennis";
// 引发异常: Expected username to be "jonas" with a length of 5, but "dennis" has a length of 6, differs near "den" (index 0).
username.Should().Be("jonas");
// 使用一个范围,批量处理异常,会在AssertionScope Dispose时一起触发
//https://github.com/fluentassertions/fluentassertions/blob/master/Tests/Shared.Specs/AssertionScopeSpecs.cs
using (new AssertionScope())
{
// Expected value to be 10, but found 5.
5.Should().Be(10);
// Expected string to be "Expected" with a length of 8, but "Actual" has a length of 6, differs near "Act" (index 0).
"Actual".Should().Be("Expected");
}
var ex = new ArgumentException();
ex.Should().BeAssignableTo<Exception>("because it is an exception");
ex.Should().NotBeAssignableTo<DateTime>("because it is an exception");
var dummy = new Object();
dummy.Should().Match(d => (d.ToString() == "System.Object"));
dummy.Should().Match<string>(d => (d == "System.Object"));
dummy.Should().Match((string d) => (d == "System.Object"));
// 基于[Flags]的枚举具有特殊的支持
regexOptions.Should().HaveFlag(RegexOptions.Global);
regexOptions.Should().NotHaveFlag(RegexOptions.CaseInsensitive);
// 对象转换为它的派生类之一。
customer.Animals.First().As<Human>().Height.Should().Be(178);
DateTime? theDate = null;
theDate.Should().NotHaveValue();
theDate.Should().BeNull();
// ExecutionTime执行时间
Action someAction = () => Thread.Sleep(100);
someAction.ExecutionTime().Should().BeLessOrEqualTo(200.Milliseconds());
await someAsyncWork.Should().CompleteWithinAsync(100.Milliseconds());
Fluent Assertions 支持许多不同的单元测试框架。只需在单元测试项目中添加对相应测试框架程序集的引用即可。Fluent Assertions 将自动找到相应的程序集,并将其用于引发特定于框架的异常。
<configuration>
<appSettings>
<!-- Supported values: nunit, xunit, mstest, mspec, mbunit and gallio -->
<add key="FluentAssertions.TestFramework" value="xunit"/>
</appSettings>
</configuration>
基础断言
字符串断言
数字类型
注意,Should().Be()和Should().NotBe()不可用于浮点数和双精度数。浮点变量是不准确的,永远不应该进行相等比较。而是使用Should().BeInRange()
方法或下面专门为浮点或十进制变量设计的方法。
float value = 3.1415927F;
// 这将验证浮点值在3.139和3.141之间。
value.Should().BeApproximately(3.14F, 0.01F); // Approximately 大约
float value = 3.5F;
// 这将验证浮点数的值不在2.0到3.0之间。
value.Should().NotBeApproximately(2.5F, 0.5F);
value.Should().BeOneOf(new[] { 3, 6});
日期
// 04/22/2010 22:15:00
var theDatetime = 22.April(2010).At(22, 15).AsLocal();
theDatetime.Should().Be(1.March(2010).At(22, 15));
theDatetime.Should().BeAfter(1.February(2010));
theDatetime.Should().BeBefore(2.March(2010));
集合
字典
异常
事件监控
类型,方法,属性
typeof(MyBaseClass).Should().BeAbstract();
typeof(InjectedClass).Should().NotBeStatic();
集成测试
- 利用真实的外部依赖(采用真实的数据库,外部的Web Service,文件存储系统等)
- 在一个测试里面可能会多个问题(数据库正常确,配置,系统逻辑等)
- 可以在运行较长时间之后才返回测试结果
webapplicationfactory和TestSever
ASP.NET Core的东西
TestServer
行为测试
SpecFlow
https://specflow.org/
https://docs.specflow.org/projects/specflow/en/latest/Getting-Started/Getting-Started-With-An-Example.html?utm_source=website&utm_medium=example&utm_campaign=getting_started
SpecFlow是用于行为驱动开发,验收测试驱动开发和示例规范的第一的 .NET开源框架。
使用易于理解的Gherkin语法(Given-When-Then)用您的母语编写示例。发现业务需求并在您的团队和利益相关者之间建立共识。
端到端测试
在执行集成和端到端测试自动化的同时,也非常合适去实践现有测试库提供的功能。由应用程序UI驱动的API级测试需要有消除不必要的编码负担的组件,这样能让与被测应用程序的交互变得更容易。因而,测试人员就不会受到连接到应用程序、发送请求、接收结果响应这些编码工作的困扰。
这种类型的几个重要测试组件有:Selenium(可用于主要语言)、protractor(特定于 JavaScript)、Karate DSL(Java 特定的 API 级集成测试)。
Selenium
封装了各种工具和库,可实现Web浏览器自动化。Selenium专门为W3C WebDriver规范提供了基础结构 —与所有主要Web浏览器兼容的平台和与语言无关的编码接口。