好代码是管出来的——.Net Core集成测试与数据驱动测试

时间:2022-02-04 16:08:11

  软件的单元测试关注是的软件最小可执行单元是否能够正常执行,但是软件是由一个个最小执行单元组成的集合体,单元与单元之间存在着种种依赖或联系,所以在软件开发时仅仅确保最小单元的正确往往是不够的,为了保证软件能够正确运行,单元与单元之间的集成测试是非常必要。
  另外上一篇文章只是介绍了如何使用xUnit.net对.Net Core程序进行简单(无参)的单元测试以及计算代码的覆盖率,但是在实际的测试工作中,往往会通过语句覆盖、条件/分支覆盖(白盒)方式以及等价类、边界值等(黑盒)方式来设计测试用例,这些用例的测试的关键点在于传入测试方法的参数是不同的,如果使用无参测试方法那么针对不同的测试用例就需要编写大量的测试方法,这是不现实的。
  本文将从以下几个方面,在单元测试的基础上介绍如何完成数据驱动测试以及代码依赖的集成测试:

集成测试简介

  集成测试简单的理解就是在单元测试的基础上,将各个单元根据其依赖关系集成起来,检查代码是否能够正确运行,集成测试根据软件开发方式的不同或者软件架构不同也有不同的集成方式,比如面向过程的设计方式和面向对象设计方式、单体架构以及微服务分布式架构等等。
  本文涉及到的仅仅是面向对象编程中类与类之间依赖的集成测试。

使用xUnit.Net实现简单的数据库集成测试

  数据库是大部分软件系统不可缺少的组件,所以在集成测试时与数据库集成是一种常见的测试场景,下面就开始介绍如何通过xUnit.Net来测试。
  1. 编写被测试代码:
  在被测试项目中引入EFCore以及相关组件:

  好代码是管出来的——.Net Core集成测试与数据驱动测试

  添加DBContext以及相应的数据操作代码:

  好代码是管出来的——.Net Core集成测试与数据驱动测试

  注:此处使用LocalDB作为数据库完成测试,如果使用SQL Server修改连接字符串即可。

  好代码是管出来的——.Net Core集成测试与数据驱动测试

  用户仓储中实现了用户添加以及根据姓名查询的方法。
  注:完成代码编写后,需要将实体代码迁移到数据库,先通过Add-Migration添加迁移代码,然后通过update-database命令将数据更新到数据库,迁移方法可参考:https://docs.microsoft.com/en-us/ef/core/get-started/full-dotnet/new-db
  2.编写测试代码:

  好代码是管出来的——.Net Core集成测试与数据驱动测试

  测试代码也非常简单,就是创建一个SQLServer的用户仓储实例,然后通过构造的方式将其注入到UserManager类型中使用,后续UserManager的创建、查询用户的方法将通过该仓储实现。
  3.测试运行结果:

  好代码是管出来的——.Net Core集成测试与数据驱动测试

可回归的数据库集成测试

  这里的回归指的是回归测试,回归测试是指当修改了旧的代码后需要重新对旧代码进行测试确认此次修改没有影响到已有代码。一般来说使用xUnit.Net实现的测试均是可回归的,因为测试代码不变,且测试代码可以重复执行,另外测试代码也被代码版本管理工具管理,当修改代码后重新执行所有测试方法即可。
  但是与数据库相关的测试又有所区别,数据库是数据持久化工具,对数据的每一次操作都会被持久化,假设有两个测试用例,第一是添加一条数据,第二是查询这条数据的数量是否为1,那么这两个测试在第一次运行的时候是没有问题的,但是再次运行时第一个用例可能主键冲突无法被添加,或者添加成功后第二个用例查询结果为2,导致测试失败,这样的测试视为不可回归。
  下面就介绍如何使用xUnit.Net实现数据库集成的回归测试:

使用构造方法初始化数据库

  要实现数据库集成测试的可回归性,其实最主要的问题在于每次执行完单元测试后数据库的状态都是不同的,那么解决这个问题在每次执行测试前将数据库初始化是否就可以解决了呢?
  看下面代码:

  好代码是管出来的——.Net Core集成测试与数据驱动测试

  首先为测试类添加构造方法,在构造方法中初始化数据库。

  好代码是管出来的——.Net Core集成测试与数据驱动测试

  然后在测试方法中添加用户数量测试断言,当添加完成用户后,应该一共存在1个用户。
  执行测试方法:

  好代码是管出来的——.Net Core集成测试与数据驱动测试

  无论执行多少次测试都是通过的,这意味着每次添加用户后用户数量均为1,这样就达到了测试的可回归性。
  但是真实的开发过程中,不可能只有一个测试类型,而且如果为每一个测试类型都添加初始化数据库的代码,那么这些单元测试将会执行的非常慢(数据库的初始化非常耗时),那么要如何解决这个问题?

使用Fixture共享测试上下文

  Fixture是xUnit.Net中的一项特性,它可以用来共享测试上下文,Fixture可以译为固定装置,换句话说就是把一个内容固定住,使该内容可以在被指定的范围内使用。xUnit.Net中Fixture有两种类型,分别是ClassFixture和CollectionFixture,它们对应的是在一个测试类型里面共享和一个测试类型集合*享上下文,Fixture的作用有很多,下面将介绍如何使用Fixture来共享数据库被初始化这一状态(不需要重复初始化):
  1. 创建一个Fixture类型:
  Fixture实际上是一个普通的C#类型,唯一需要注意的是它使用构造方法初始化数据、使用Dispose来释放或者清理数据,简单的一个Fixture类型如下:

  好代码是管出来的——.Net Core集成测试与数据驱动测试

  2. 为单个类型使用ClassFixture:

  好代码是管出来的——.Net Core集成测试与数据驱动测试

  从代码中可以看到通过构造方法初始化的代码已经被注释了,实现IClassFixture接口后,再次执行测试方法,可以看到测试仍然是可回归的。
  3. 为多个类型使用ICollectionFixture:
  首先需要在DatabaseFixture的基础上添加一个实现ICollectionFixture的类型:

  好代码是管出来的——.Net Core集成测试与数据驱动测试

  两个要点分别是需要使用CollectionDefinition特性,然后类型需要实现ICollectionFixture<T>接口。
  在测试类型上通过Collection特性,指定之前定义的Collection名称来应用该CollectionFixture:

  好代码是管出来的——.Net Core集成测试与数据驱动测试

  然后多次执行测试方法,可以发现测试仍然是可回归的:

  好代码是管出来的——.Net Core集成测试与数据驱动测试

  注:在共享测试上下数据时(包括共享数据库数据),可能会因为测试方法执行顺序导致测试失败,但是xUnit.Net中没有提供按顺序执行实现方式,默认是按照方法命名,如果需要按照一定顺序执行测试,可参考官方的例子:https://github.com/xunit/samples.xunit/tree/master/TestOrderExamples

测试数据的管理与初始化

  涉及到数据库的测试,很可能在测试之前数据库就会存在一些已有的测试数据,使用xUnit.Net来编写测试代码时,可以将测试数据的初始化放到Fixture类型中,但是要如何对这些数据进行管理呢?一般来说使用SQL脚本来管理数据库的测试数据是一种不错的方式,因为这样测试数据与代码解耦,维护数据时仅需要维护SQL脚本文件即可。
  下面就介绍一下如何在xUnit.Net中使用SQL脚本文件管理以及初始化数据:
  1.创建一个SQL脚本:

  好代码是管出来的——.Net Core集成测试与数据驱动测试

  注:需要将文件的“复制到输出目录”设置为“总是复制”或者“有更新复制”:

  好代码是管出来的——.Net Core集成测试与数据驱动测试

  2.在Fixture类型初始化数据库时,执行SQL脚本初始化数据:
  下面代码首先去Data目录下查找.sql的所有文件,然后依次执行这些SQL脚本。

  好代码是管出来的——.Net Core集成测试与数据驱动测试

  3.修改测试代码(因为脚本添加了2条数据,所以期望值应该是3条):

  好代码是管出来的——.Net Core集成测试与数据驱动测试

  4.执行测试:

  好代码是管出来的——.Net Core集成测试与数据驱动测试  

  测试成功。

数据驱动测试简介

  文章最开始提到过,不管是白盒的覆盖测试还是黑盒的边界/等价测试,它们最直接的体现就是“参数”,传入被测试方法的参数。而这些参数实际上就是“测试用例”,为每一个测试用例编写一个测试方法是不合理的,所以需要一种方法通过变换参数来完成测试,并且尽可能的重用测试代码。这就是数据驱动测试。

基于xUnit.Net的数据驱动测试

Theory&InlineData

  Theory和InlineData是xUnit.Net中提供的数据驱动(参数化)测试的功能特性,下面是一个theory以及InlineData的应用示例:

  好代码是管出来的——.Net Core集成测试与数据驱动测试

  运行结果:

  好代码是管出来的——.Net Core集成测试与数据驱动测试 

  通过InlineData就可以轻易的将测试用例的数据应用到测试方法上,并完成期望的测试。但是当添加新的测试用例时就需要对代码文件进行修改,测试用例放置在代码中管理也不是非常方便,有没有一种可以独立管理测试用例数据的方法呢?

基于excel的数据驱动测试

  xUnit.Net没有提供内置的Excel数据加载功能,但是它的拓展性很好,可以通过实现DataAttribute实现一个Theory的数据源提供器:

  好代码是管出来的——.Net Core集成测试与数据驱动测试

  1. 实现DataAttribute抽象类,实现Excel的Theory数据提供器:
  下面是实现Excel数据加载的核心方法:

  好代码是管出来的——.Net Core集成测试与数据驱动测试

  好代码是管出来的——.Net Core集成测试与数据驱动测试

  好代码是管出来的——.Net Core集成测试与数据驱动测试

  以上方法需要注意以下几点:

  • 两个主体:Excel文件名称和测试的方法的MethodInfo,前者用于读取Excel文件,后者用来获取参数信息。
  • 通过MethodInfo获取测试方法的参数列表包括参数类型。
  • 读取Excel数据并于测试方法的参数列表匹配,简单来说就是Excel中每一行的数据就是对应测试方法的参数,有多少行数据测试方法就会被执行多少次。
  • 当Excel中数据读取为double或者float但是测试方法需要int时,需要进行转换。

  注:此处使用的Excel文件读取组件为:https://github.com/ExcelDataReader/ExcelDataReader

  DataAttribute完整代码见:https://github.com/yqszt/xUnitTestDemo/blob/master/XUnitTestProject/ExcelDataAttribute.cs

  2.使用ExcelDataAttribute:

  好代码是管出来的——.Net Core集成测试与数据驱动测试

  3. 在AdditionTestCase.xls中添加测试用例数据:

  好代码是管出来的——.Net Core集成测试与数据驱动测试

  注:需要将AdditionTestCase.xls的复制到输出目录属性设置为“总是复制”或“复制新文件”。

  4.执行测试方法:

  好代码是管出来的——.Net Core集成测试与数据驱动测试

  测试成功。

小结

  本文主要介绍了.Net Core使用xUnit.Net实现了与数据的集成测试并通过Fixture的方式实现了数据库的初始化,保证了测试的可回归性。另外还介绍了如何使用xUnit.Net来实现数据驱动测试,数据驱动测试可以简单地通过添加用例数据的方式来提高测试质量,还可以便捷的对测试用例进行管理。

本文示例代码:https://github.com/yqszt/xUnitTestDemo

参考:

  https://en.wikipedia.org/wiki/Integration_testing
  https://en.wikipedia.org/wiki/Data-driven_testing
  https://xunit.github.io/docs/getting-started-dotnet-core#write-first-theory
  http://dotnetliberty.com/index.php/2015/12/31/fast-asp-net-5-integration-testing-with-xunit/
  https://github.com/MisterJames/GenFu
  http://ikeptwalking.com/writing-data-driven-tests-using-xunit/
  https://wandering.life/data-driven-testing-xunit-vs2017/
  https://github.com/ExcelDataReader/ExcelDataReader
  http://xunitpatterns.com/Testing%20With%20Databases.html
  https://xunit.github.io/docs/shared-context

本文链接:https://www.cnblogs.com/selimsong/p/9306221.html

好代码是管出来的——浅谈.Net Core的代码管理方法与落地(更新中...)