1.关系型数据库
插件:Microsoft.EntityFrameworkCore.InMemory
Microsoft.EntityFrameworkCore.InMemory 是一个用于测试的“临时内存数据库”,让你在不连接真实数据库的情况下,测试 EF Core 的功能。
使用时就是用具体这个框架里面已经自带的数据库方法,去操作这个临时的数据库。
要手动的给这个数据库的设置参数(见configDictionary 部分),如果 真实的数据库有相关参数的话,如图:
2. 非关系型数据库
测试方法一:[Fact]
前言:如果用Xunit 测试框架,准备工作,配置moq,模拟依赖行为,FluentAssertions 包 判定测试结果。
比如 MongoDB,直接建一个新的 MongoDB。
1. 把测试关联的那些属性,方法等变成 virtual, 可以后续在 test里面 overwrite。
并且可以 {get,set}
2. test unit 里面要去和原项目关联。 (project reference)
3. 如果数据有相关的配置 settings,必须显性的继承参数属性,不然数据库没有办法取到设置参数,并且手动设置这写参数的值,用 option方法
完整unit 测试截图:
AutoMapper依赖隔离说明:
注意: 在这个项目中,BookService
使用AutoMapper进行DTO与Entity之间的转换。由于我们Mock了IMapper
接口,AutoMapper不会执行真实的映射逻辑,因此需要手动创建两个测试对象来模拟完整的数据流程:
mappedBook
- 模拟DTO转Entity后,保存到数据库前的状态
Book mappedBook = new Book("test", "description", 1); // 无ID,因为还没存入数据库
createdBook
- 模拟保存到数据库后的状态
Book createdBook = new Book("test", "description", 1) { Id = 1 }; // 有ID,数据库自动生成
测试方法二:[Theory]
通过 Xunit 测试框架 传递 inline data 到测试方法上,设置几个可能会报错的参数组合
注意: _bookService
的调用被延迟了,通过 Func<Task>
将方法调用包装成委托(delegate),实现延迟执行,等到 func.Should()
调用时才真正触发。
如果直接写 await _bookService.CreateBook(badRequest)
,方法会立即执行并抛异常,测试框架还没来得及准备捕获异常,程序就已经崩溃了
总结:建真实的数据库,使这个测试避免了复杂的配置接口等等,直接可以用数据库的context,同时只mock 需要的方法或者属性,使测试更加简单,高效
面试的点:
如果面试官问 在项目中有什么挑战,解决了哪些问题,那咋们可以围绕下面的内容展开说
描述问题背景:
"我们之前的项目遇到了一个头疼的问题。就是写单元测试特别难受,因为我们的代码直接连MongoDB,每次跑测试都得连真实的数据库。结果就是测试跑得特别慢,一套下来要30秒,而且还经常出问题,可能上一个测试改了数据,下一个测试就挂了,特别不稳定。"
思考解决方案的过程
"我当时就想,这样下去不行啊,得想个办法。我看了几个方案:
-
用Repository模式吧,确实对测试友好,但是要改的代码太多了,有点担心改出别的bug
-
试了试InMemory Database,但是发现MongoDB的内存版本跟真实环境还是有差别,不太放心
-
后来想到用Virtual + Mock的方式,改动最小"
最后怎么解决的:
"最后我选了virtual这个方案,其实挺简单的: 就是把MongoDB Context里面的方法加个virtual关键字,然后测试的时候继承一个新的Context类,把需要的方法重写一下,返回我们准备好的测试数据,这样就不用真的去连数据库了。
这个方案最大的好处就是现有的代码基本不用动,只是加个关键字的事。"
结果:
"效果还挺明显的:
-
测试速度快了很多,从30秒直接降到3秒,快了10倍
-
团队的同事也更愿意写测试了,因为跑得快嘛,覆盖率从30%升到了70%
-
而且这个方案比较好理解,新同事也容易上手"
总结:
"这件事让我感触挺深的。有时候不一定要用最高大上的方案,关键是要适合团队的实际情况。我们的方案可能不是最完美的,但是解决了问题,而且大家都能接受,我觉得这就够了。"