如何在VB.NET中模拟迭代式数据库查询并保留原有功能?
解决方案:通过依赖注入模拟迭代式数据检索
要在保留原有功能的前提下实现单元测试友好的重构,核心是抽象数据访问层并通过依赖注入替换硬编码的实例化,具体步骤如下:
1. 抽象DataAccess层为接口
首先为DataAccess类定义对应的接口,覆盖函数中用到的核心方法和关联类型:
' 抽象资源类型枚举 Public Enum ReTypes DG ' 保留原有其他枚举值 End Enum ' 抽象资源子项接口 Public Interface IRegResourceChild Property ID As Integer Property SignalID As Integer End Interface ' 抽象资源接口 Public Interface IRegResource Property Type As ReTypes Property Children As IEnumerable(Of IRegResourceChild) End Interface ' 核心数据访问接口 Public Interface IDataAccess Sub InitializeFromMDB(connectionString As String, dbName As String) Function GetRegResource(reID As Integer, signalID As Integer, effDayUtc As Date) As IRegResource End Interface ' 让原有DataAccess类实现该接口 Public Class DataAccess Implements IDataAccess ' 保留原有InitializeFromMDB实现 Public Sub InitializeFromMDB(connectionString As String, dbName As String) Implements IDataAccess.InitializeFromMDB ' 原有代码逻辑 End Sub ' 调整原有GetRegResource实现,返回IRegResource类型 Public Function GetRegResource(reID As Integer, signalID As Integer, effDayUtc As Date) As IRegResource Implements IDataAccess.GetRegResource ' 原有代码逻辑,返回实现了IRegResource的实例 End Function End Class
2. 修改函数以支持依赖注入
为原Shared函数新增IDataAccess可选参数,移除内部硬编码的New DataAccess实例化逻辑,同时保留原有调用兼容性:
' 修改后的函数签名,新增IDataAccess可选参数 Public Shared Function CalcDailyScoresByDate(ByVal dayUtc As Date, ByVal ReID As Integer, ByVal dailyScores As DataTable, Optional ByVal regDs As IDataAccess = Nothing ) As DailyDataCollection Dim timeUtc As Date = dayUtc Dim srvSet As ServiceSettings = New ServiceSettings(dayUtc) Dim ept As Date = TimeZoneInfo.ConvertTimeFromUtc(timeUtc, TimeZoneInfo.Local) ept = ept.AddDays(1) Dim effDayUtc As Date = TimeZoneInfo.ConvertTimeToUtc(ept) Dim daily As New DailyDataCollection Try #Region "Region DataAccess" ' 生产环境自动创建默认实现,保证原有逻辑不变 If regDs Is Nothing Then regDs = New DataAccess() Try regDs.InitializeFromMDB(srvSet.MyConnectionString, "MyDb") Catch ex As Exception MyUtil.ErrorLog("RegDS.InitializeFromMDB " & ex.Message) End Try End If For Each rw In dailyScores.Rows Dim drw As New DailyData drw.ReID = rw(0) drw.SignalID = rw(1) drw.MyDay = effDayUtc drw.dailyScore = rw(2) ' 使用注入的IDataAccess实例调用数据检索方法 Dim rsc = regDs.GetRegResource(drw.ReID, drw.SignalID, effDayUtc) If rsc.Type = ReTypes.DG Then For Each child In rsc.Children Dim childDaily As New DailyData childDaily.ReID = child.ID childDaily.SignalID = child.SignalID childDaily.myDay = effDayUtc childDaily.dailyScore = drw.dailyScore daily.Add(childDaily) Next End If Next dailyScores.Dispose() dailyScores = Nothing srvSet = Nothing #End Region Catch ex As Exception MyUtil.ErrorLog("CalcDailyScoresByDate: " & ex.Message) End Try Return daily End Function
3. 单元测试中模拟迭代数据检索
使用Mock框架(如Moq)创建IDataAccess的模拟实例,预设GetRegResource方法根据不同参数返回模拟资源,从而测试迭代逻辑:
Imports Moq Imports Microsoft.VisualStudio.TestTools.UnitTesting <TestClass> Public Class DailyScoresTests <TestMethod> Public Sub CalcDailyScoresByDate_WithDGTypeResource_AddsChildRecords() ' 1. 准备测试用DataTable Dim testDayUtc = Date.UtcNow Dim testReID = 1 Dim testDataTable = New DataTable() testDataTable.Columns.AddRange({ New DataColumn("ReID", GetType(Integer)), New DataColumn("SignalID", GetType(Integer)), New DataColumn("DailyScore", GetType(Decimal)) }) testDataTable.Rows.Add(100, 200, 85.5) ' 2. 模拟资源子项 Dim mockChild = New Mock(Of IRegResourceChild)() mockChild.Setup(Function(c) c.ID).Returns(300) mockChild.Setup(Function(c) c.SignalID).Returns(400) ' 3. 模拟DG类型资源 Dim mockResource = New Mock(Of IRegResource)() mockResource.Setup(Function(r) r.Type).Returns(ReTypes.DG) mockResource.Setup(Function(r) r.Children).Returns(New List(Of IRegResourceChild) From {mockChild.Object}) ' 4. 模拟数据访问层,预设检索方法返回值 Dim mockDataAccess = New Mock(Of IDataAccess)() mockDataAccess.Setup(Function(d) d.GetRegResource(100, 200, It.IsAny(Of Date))) _ .Returns(mockResource.Object) ' 5. 调用目标函数 Dim result = CalcDailyScoresByDate(testDayUtc, testReID, testDataTable, mockDataAccess.Object) ' 6. 验证结果 Assert.AreEqual(1, result.Count) Assert.AreEqual(300, result(0).ReID) Assert.AreEqual(400, result(0).SignalID) End Sub End Class
关键说明
- 完全保留原有生产逻辑:生产环境调用时可省略
regDs参数,函数自动创建并初始化真实DataAccess实例。 - 覆盖所有分支测试:通过Mock可以轻松模拟不同参数下的返回结果,测试非DG类型、空Children集合等场景。
- 最小侵入式修改:仅新增接口和参数,无需大幅改动原有业务逻辑。
内容的提问来源于stack exchange,提问作者user2284452
相关产品推荐
相关产品推荐

