51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

手机号码,快捷登录

查看: 5149|回复: 9
打印 上一主题 下一主题

如何对数据访问的代码进行单元测试?

[复制链接]
  • TA的每日心情
    擦汗
    前天 09:07
  • 签到天数: 527 天

    连续签到: 4 天

    [LV.9]测试副司令

    跳转到指定楼层
    1#
    发表于 2019-12-9 11:25:20 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
    1测试积点
    如何对数据访问的代码进行单元测试?
    如果用MOCK对象要怎么做,如果我要测试取50条数据,那MOCK怎么做?工作量应该也很大啊?
    而且,如果我的代码事实上会出现SQLException(可能是我SQL错误,或违反数据库完整性约束),那MOCK不是测试不出来?
    而如果用直接操作数据库的办法,那性能呢?垃圾数据呢?是否是在开始时初始话数据,测试结束时再在[TearDown](NUnit)中把这个测试改变的数据还原?

    分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
    收藏收藏
    回复

    使用道具 举报

  • TA的每日心情
    擦汗
    2024-9-30 15:02
  • 签到天数: 751 天

    连续签到: 2 天

    [LV.10]测试总司令

    2#
    发表于 2019-12-10 09:23:55 | 只看该作者
    回复

    使用道具 举报

  • TA的每日心情

    2024-7-8 09:00
  • 签到天数: 943 天

    连续签到: 1 天

    [LV.10]测试总司令

    3#
    发表于 2019-12-10 09:28:02 | 只看该作者
    我觉得测sql语句是否正确或者DLINQ配置是否正确之类的也很重要的,有时发现数据层的错误要比业务层还多 不过MOCK是不大可能了,MOCK出一个有同步能力的数据库是会死人的 我的做法是数据库里放数据,每个测试都用using (TransactionScope...)包起来,打开MSDTC,测试完就回滚,能保证数据不变 单元测试不管性能,性能方面由压力测试负责
    回复

    使用道具 举报

  • TA的每日心情
    奋斗
    前天 10:10
  • 签到天数: 1516 天

    连续签到: 5 天

    [LV.Master]测试大本营

    4#
    发表于 2019-12-10 11:41:25 | 只看该作者
    可以循环的
    回复

    使用道具 举报

  • TA的每日心情
    奋斗
    前天 07:28
  • 签到天数: 2812 天

    连续签到: 5 天

    [LV.Master]测试大本营

    5#
    发表于 2019-12-10 12:07:52 | 只看该作者
    可以给出测试数据
    回复

    使用道具 举报

  • TA的每日心情
    奋斗
    前天 09:46
  • 签到天数: 991 天

    连续签到: 5 天

    [LV.10]测试总司令

    6#
    发表于 2019-12-10 14:39:15 | 只看该作者
    使用的测试数据中标记上测试
    回复

    使用道具 举报

  • TA的每日心情
    奋斗
    2019-12-31 08:59
  • 签到天数: 975 天

    连续签到: 1 天

    [LV.10]测试总司令

    7#
    发表于 2019-12-14 14:26:45 | 只看该作者
    单侧工具
    回复

    使用道具 举报

  • TA的每日心情
    慵懒
    2018-10-23 14:32
  • 签到天数: 1 天

    连续签到: 1 天

    [LV.1]测试小兵

    8#
    发表于 2020-5-22 11:16:44 | 只看该作者
    数据访问层在分层结构,比较常见. 有时可能是数据访问模块. 假设数据访问层后端是数据库,那我们如何测试他们的呢? 有时实际这种测试是集成测试了.
    有时数据库里还有一些逻辑,触发器,约束等. 个人十分不建议把业务逻辑放在数据库里实现. 最常见的数据库表的操作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

    回复

    使用道具 举报

  • TA的每日心情
    慵懒
    2018-10-23 14:32
  • 签到天数: 1 天

    连续签到: 1 天

    [LV.1]测试小兵

    9#
    发表于 2020-5-22 11:17:25 | 只看该作者
    当然,最标准的单元测试我们完全隔离数据库, 数据访问组件在单元测试中不应该访问数据库. 我们使用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对象来实现隔离数据库的访问

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

    回复

    使用道具 举报

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条

    小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

    GMT+8, 2024-11-17 06:36 , Processed in 0.066147 second(s), 21 queries .

    Powered by Discuz! X3.2

    © 2001-2024 Comsenz Inc.

    快速回复 返回顶部 返回列表