51Testing软件测试论坛

标题: 如何对数据访问的代码进行单元测试? [打印本页]

作者: 测试积点老人    时间: 2019-12-9 11:25
标题: 如何对数据访问的代码进行单元测试?
如何对数据访问的代码进行单元测试?
如果用MOCK对象要怎么做,如果我要测试取50条数据,那MOCK怎么做?工作量应该也很大啊?
而且,如果我的代码事实上会出现SQLException(可能是我SQL错误,或违反数据库完整性约束),那MOCK不是测试不出来?
而如果用直接操作数据库的办法,那性能呢?垃圾数据呢?是否是在开始时初始话数据,测试结束时再在[TearDown](NUnit)中把这个测试改变的数据还原?

作者: bellas    时间: 2019-12-10 09:23
参考下这个链接https://www.cnblogs.com/harlanc/p/7007145.html
作者: 梦想家    时间: 2019-12-10 09:28
我觉得测sql语句是否正确或者DLINQ配置是否正确之类的也很重要的,有时发现数据层的错误要比业务层还多 不过MOCK是不大可能了,MOCK出一个有同步能力的数据库是会死人的 我的做法是数据库里放数据,每个测试都用using (TransactionScope...)包起来,打开MSDTC,测试完就回滚,能保证数据不变 单元测试不管性能,性能方面由压力测试负责
作者: qqq911    时间: 2019-12-10 11:41
可以循环的
作者: jingzizx    时间: 2019-12-10 12:07
可以给出测试数据
作者: litingting0214    时间: 2019-12-10 14:39
使用的测试数据中标记上测试
作者: Miss_love    时间: 2019-12-14 14:26
单侧工具
作者: 旺达    时间: 2020-5-22 11:16
数据访问层在分层结构,比较常见. 有时可能是数据访问模块. 假设数据访问层后端是数据库,那我们如何测试他们的呢? 有时实际这种测试是集成测试了.
有时数据库里还有一些逻辑,触发器,约束等. 个人十分不建议把业务逻辑放在数据库里实现. 最常见的数据库表的操作create, read, update和delete(简称CRUD)
, 例如我们需要测试某个Add方法,在这个测试方法完成后, 希望这条测试数据清除掉. 这样做是 为了不影响其它的测试方法也访问同一数据库对象.
    首先,我们可以使用.net 2.0中提供的TransactionScope类, 下面的代码基于MsTest的单元测试:

   1:      [TestClass]
   2:      public class DbTestBase
   3:      {
   4:          private TransactionScope scope;
   5:   
   6:          [TestInitialize]
   7:          public void SetUp()
   8:          {
   9:              this.scope = new TransactionScope();
  10:          }
  11:         
  12:          [TestCleanup]
  13:          public void TearDown()
  14:          {
  15:              this.scope.Dispose();
  16:          }
  17:      }
上面代码我们看到在标记TestInitialize特性SetUp方法中创建TransactionScope的实例,在TestCleanup特性TearDown方法中调用TransactionScope的Dispose方法.  然后我们继承这个测试基类:

   1:      [TestClass]
   2:      public class DateBaseTesting : DbTestBase
   3:      {
   4:          /// <summary>
   5:          /// Test Insert record to database
   6:          /// </summary>
   7:          /// <seealso cref="http://www.cnblogs.com/wintersun"/>
   8:          /// <remarks>Any database modification will be roll back</remarks>
   9:          [TestMethod]
  10:          public void TestAddWithEmployeeRepository()
  11:          {
  12:              //arrange
  13:              var employee = this.CreateNewEmployee();
  14:              var employRepository = RepositoryHelper.GetEmployeeRepository();
  15:   
  16:              //act
  17:              employRepository.Add(employee);
  18:              employRepository.Save();
  19:   
  20:              //assert
  21:              var employeelist =
  22:                 employRepository.Repository.Find(e => e.EmployeeID == employee.EmployeeID);
  23:              Assert.IsNotNull(employeelist);
  24:              CollectionAssert.AreEqual(new List<Employee>() { employee }, employeelist.ToList());
  25:          }
  26:   
  27:   
  28:          private Employee CreateNewEmployee()
  29:          {
  30:              var employee = new Employee
  31:              {
  32:                  ManagerID = 2,
  33:                  ContactID = 3,
  34:                  Title = "Developer",
  35:                  BirthDate = new DateTime(1965, 1, 1, 0, 0, 0),
  36:                  HireDate = DateTime.Now,
  37:                  Gender = "M",
  38:                  MaritalStatus = "M",
  39:                  ModifiedDate = DateTime.Now,
  40:                  NationalIDNumber = "2",
  41:                  rowguid = new Guid(),
  42:                  CurrentFlag = true,
  43:                  VacationHours = 2,
  44:                  SickLeaveHours = 3,
  45:                  SalariedFlag = false,
  46:                  LoginID = "myworkbase\\peter"
  47:              };
  48:              return employee;
  49:          }
  50:   
  51:      }
上面的TestAddWithEmployeeRepository中场景是数据访问层基于EntityFramework的Repository模式, 这里的操作是先是创建实体,然后是提交.  实际中可以是您的任何代码块,ADO.NET或其他的数据访问组件. 当我们执行这个单元测试后,这个TransactionScope将被释放. 之前插入的那条记录将被清除.
      假设你不想用基类, 只是简单在某个方法中, 可以这样做:

   1:          [TestMethod]
   2:          public void TestWrapTransactionScope()
   3:          {
   4:              WrapTransactionScope(() => TestAddWithEmployeeRepository());
   5:          }
   6:   
   7:          /// <summary>
   8:          /// Wraps the transaction scope for unit testing
   9:          /// </summary>
  10:          /// <param name="action">The action method</param>
  11:          /// <remarks>author http://www.cnblogs.com/wintersun </remarks>
  12:          public void WrapTransactionScope(Action action)
  13:          {
  14:              using (var scope = new TransactionScope())
  15:              {
  16:                  action();
  17:              }
  18:          }
      上面的代码演示了, 我们用Action委托实现另一个方法包装一下目标方法使其在TransactionScope块中.
      我们还可以使用xUnit 类库 这实现同样的操作, 这里使用是xUnit 1.8版本. 还有MbUnit,NUnit的[RollBack]特性与这个类似. 如果您还不熟悉怎么使用xUnit请先看这里介绍. 下面的代码使用 xUnit 代码变得更加简洁:

   1:      /// <summary>
   2:      /// Database unit testing with xUnit demo
   3:      /// </summary>
   4:      /// <remarks>http://wintersun.cnblogs.com </remarks>
   5:      public class TestDbWithxUnit
   6:      {
   7:          [Fact]
   8:          [AutoRollback]
   9:          public void Test_Add_One_Enity()
  10:          {
  11:              //arrange
  12:              var employee = this.CreateNewEmployee();
  13:              var employRepository = RepositoryHelper.GetEmployeeRepository();
  14:   
  15:              //act
  16:              employRepository.Add(employee);
  17:              employRepository.Save();
  18:   
  19:              //assert
  20:              var employeelist =
  21:                 employRepository.Repository.Find(e => e.EmployeeID == employee.EmployeeID);
  22:              Assert.NotNull(employeelist);
  23:              Assert.Equal(new List<Employee> { employee }, employeelist.ToList());
  24:          }
  25:  }
上面的代码我们只需在方法上加一个AutoRollback的Attribute就可以了, 注意你先需要引用xunit.extensions.dll


作者: 旺达    时间: 2020-5-22 11:17
当然,最标准的单元测试我们完全隔离数据库, 数据访问组件在单元测试中不应该访问数据库. 我们使用Mock框架来实现对数据库的隔离, 如下代码演示了我们使用Moq v4.0.20926 和 xUnit 来实现的单元测试:

   1:          [Fact]
   2:          public void TestWithMoq()
   3:          {
   4:              //arrange
   5:              var mock = new Mock<IRepository<Employee>>();
   6:              Employee employee = this.CreateNewEmployee();
   7:              var list = new List<Employee>() { employee };
   8:   
   9:              mock.Setup(ep => ep.Add(It.IsAny<Employee>()))
  10:                  .Callback(()=>Console.WriteLine("Add()"));
  11:              mock.Setup(ep => ep.Find(It.IsAny<Expression<Func<Employee, bool>>>()))
  12:                  .Returns(list);
  13:              mock.Setup(ep => ep.Save())
  14:               .Callback(() => Console.WriteLine("Save()"));
  15:              var employeeRespository = mock.Object;
  16:            
  17:              //act
  18:              employeeRespository.Add(employee);
  19:              employeeRespository.Save();
  20:   
  21:              //verify method
  22:              mock.Verify(ep => ep.Add(It.IsAny<Employee>()));
  23:              mock.Verify(ep => ep.Save());
  24:   
  25:              //assert
  26:              var employeelist =
  27:                employeeRespository.Find(e => e.EmployeeID == employee.EmployeeID);
  28:              Assert.NotNull(employeelist);
  29:              Assert.Equal(new List<Employee> { employee }, employeelist.ToList());
  30:          }
Employee是实体类, IRepository<T>是一个统一数据访问interface, 实现martinfowler的Repository模式;  上面的代码Mock了其中的Add,Save方法, 在调用后并做了Verify,确认之前Mock的方法有被调用到,  最后的部分才是Assert语句块. 代码很简单.

除了使用Mock框架, 我们还可以使用依赖注入容器来注入一个具体的Fake对象实现对访问数据库的隔离, 我们使用Unity 2.1 来实现:

   1:          [Fact]
   2:          public void TestWithDIContainer()
   3:          {
   4:              //arrange
   5:              var employee = this.CreateNewEmployee();
   6:              var list = new List<Employee>() { employee };
   7:              var employeeRespository = GetRepositoryInstance("for testing");
   8:   
   9:              //act
  10:              employeeRespository.Add(employee);
  11:              employeeRespository.Save();
  12:   
  13:              //assert
  14:              var employeelist =
  15:                employeeRespository.Find(e => e.EmployeeID == employee.EmployeeID);
  16:              Assert.NotNull(employeelist);
  17:              Assert.Equal(new List<Employee> { employee }, employeelist.ToList());
  18:          }
  19:   
  20:          private IRepository<Employee> GetRepositoryInstance(string name)
  21:          {
  22:              //Initial container
  23:              var container = new UnityContainer();
  24:              container.RegisterType<IRepository<Employee>, EFRepository<Employee>>();
  25:              container.RegisterType<IRepository<Employee>, FakeRepository<Employee>>("for testing");
  26:              //....
  27:              return container.Resolve<IRepository<Employee>>(name);
  28:          }
上面的代码为了演示的简便, 我们实现创建容器注册对象在一个private方法中; 您也可以使用其它IOC/DI的容器. FakeRepository<Employee>是一个实现了IRepository<T>的具体类, 在其内部可以在内存中操作实体,或是其它方式. 具体看您的需求了.

以上所有是我们介绍对数据访问层或数据库进行单元测试的解决方法:

1. 使用TransactionScope类实现测试方法回滚
2. 使用xUnit类库的AutoRollback特性实现自动回滚
3. 使用Moq框架来实现隔离数据库的访问
4. 使用Unity容器注入Fake对象来实现隔离数据库的访问

希望对您做单元测试有帮助.






欢迎光临 51Testing软件测试论坛 (http://bbs.51testing.com/) Powered by Discuz! X3.2