Ren*_*uis 41 database ms-access vba unit-testing
使用同一数据库中的代码,表单和数据,我想知道为Microsoft Access应用程序设计一套测试的最佳实践是什么(比如Access 2007).
测试表单的主要问题之一是,只有少数控件具有hwnd
句柄,而其他控件只能获得一个焦点,这使得自动化非常不透明,因为您无法获取表单上的控件列表.
有经验可以分享吗?
Rub*_*uck 18
首先,停止将业务逻辑写入您的Form的代码中.那不是它的地方.它无法在那里进行适当的测试.事实上,你真的不应该自己测试你的表单.这应该是响应用户交互,然后响应这些动作到另一个类,委托责任死哑巴简单的观点是测试.
你是怎样做的?熟悉模型 - 视图 - 控制器模式是一个良好的开端.
它不能在VBA中完美地完成,因为我们得到了事件或接口,而不是两者,但你可以非常接近.考虑这个带有文本框和按钮的简单表单.
在后面的表单代码中,我们将TextBox的值包装在公共属性中,并重新引发我们感兴趣的任何事件.
Public Event OnSayHello()
Public Event AfterTextUpdate()
Public Property Let Text(value As String)
Me.TextBox1.value = value
End Property
Public Property Get Text() As String
Text = Me.TextBox1.value
End Property
Private Sub SayHello_Click()
RaiseEvent OnSayHello
End Sub
Private Sub TextBox1_AfterUpdate()
RaiseEvent AfterTextUpdate
End Sub
Run Code Online (Sandbox Code Playgroud)
现在我们需要一个模型来使用.在这里,我创建了一个名为的新类模块MyModel
.这就是我们将要测试的代码.请注意,它自然地与我们的视图共享类似的结构.
Private mText As String
Public Property Let Text(value As String)
mText = value
End Property
Public Property Get Text() As String
Text = mText
End Property
Public Function Reversed() As String
Dim result As String
Dim length As Long
length = Len(mText)
Dim i As Long
For i = 0 To length - 1
result = result + Mid(mText, (length - i), 1)
Next i
Reversed = result
End Function
Public Sub SayHello()
MsgBox Reversed()
End Sub
Run Code Online (Sandbox Code Playgroud)
最后,我们的控制器将它们连接在一起.控制器侦听表单事件并将更改传递给模型并触发模型的例程.
Private WithEvents view As Form_Form1
Private model As MyModel
Public Sub Run()
Set model = New MyModel
Set view = New Form_Form1
view.Visible = True
End Sub
Private Sub view_AfterTextUpdate()
model.Text = view.Text
End Sub
Private Sub view_OnSayHello()
model.SayHello
view.Text = model.Reversed()
End Sub
Run Code Online (Sandbox Code Playgroud)
现在,此代码可以从任何其他模块运行.出于本示例的目的,我使用了标准模块.我强烈建议您使用我提供的代码自己构建它并查看它的功能.
Private controller As FormController
Public Sub Run()
Set controller = New FormController
controller.Run
End Sub
Run Code Online (Sandbox Code Playgroud)
那么,那很好,除了与测试有什么关系之外呢?!朋友,它具有的一切做测试.我们所做的就是让我们的代码可以测试.在我提供的示例中,甚至没有理由尝试测试GUI.我们真正需要测试的唯一一件事是model
.这就是所有真实逻辑的所在.
所以,继续第二步.
这里没有很多选择.大多数框架都需要安装COM加载项,大量的样板,奇怪的语法,写测试作为评论等等.这就是为什么我自己参与构建一个,所以这部分答案不公正,但我会尝试对可用的内容进行公平的总结.
VB Lite Unit 我不能说我亲自使用它.它在那里,但自2005年以来没有看到更新.
xlUnit xlUnit并不糟糕,但它也不好.它很笨重,还有很多锅炉板代码.这是最糟糕的,但它在Access中不起作用.所以,那就是了.
建立自己的框架
我去过那里并做到了.它可能比大多数人想要进入的更多,但完全有可能在Native VBA代码中构建单元测试框架.
Rubberduck VBE Add-In的单元测试框架
免责声明:我是其中一个共同开发者.
我有偏见,但这是迄今为止我最喜欢的一群.
所以,回到第1节中的代码.我们真正需要测试的唯一代码就是MyModel.Reversed()
函数.那么,让我们来看看测试的样子.(给出的示例使用Rubberduck,但这是一个简单的测试,可以转换为您选择的框架.)
'@TestModule
Private Assert As New Rubberduck.AssertClass
'@TestMethod
Public Sub ReversedReversesCorrectly()
Arrange:
Dim model As New MyModel
Const original As String = "Hello"
Const expected As String = "olleH"
Dim actual As String
model.Text = original
Act:
actual = model.Reversed
Assert:
Assert.AreEqual expected, actual
End Sub
Run Code Online (Sandbox Code Playgroud)
我知道答案有点长,而且很晚,但希望它可以帮助一些人开始为他们的VBA代码编写单元测试.
Phi*_*ier 17
我很欣赏诺克斯和大卫的答案.我的答案将介于他们之间:只需制作不需要调试的表单!
我认为表单应该专门用作它们的基本内容,仅指图形界面,这意味着它们不必调试!然后,调试作业仅限于您的VBA模块和对象,这样更容易处理.
当然有一种将VBA代码添加到表单和/或控件的自然趋势,特别是当Access为您提供了这些伟大的"更新后"和"更改后"事件时,但我绝对建议您不要放置任何表单或控制特定代码在表单的模块中.这使得进一步维护和升级变得非常昂贵,其中您的代码在VBA模块和表单/控件模块之间分配.
这并不意味着你不能再使用这个AfterUpdate
活动了!只需将标准代码放入事件中,如下所示:
Private Sub myControl_AfterUpdate()
CTLAfterUpdate myControl
On Error Resume Next
Eval ("CTLAfterUpdate_MyForm()")
On Error GoTo 0
End sub
Run Code Online (Sandbox Code Playgroud)
哪里:
CTLAfterUpdate
是每次在表单中更新控件时运行的标准过程
CTLAfterUpdateMyForm
是每次在MyForm上更新控件时运行的特定过程
我有2个模块.第一个是
utilityFormEvents
第二个是
MyAppFormEvents
选择这样的通用解决方案意味着很多.这意味着您正在达到高级别的代码规范化(意味着无痛的代码维护).当你说你没有任何特定于表单的代码时,它也意味着表单模块是完全标准化的,并且它们的生产可以自动化:只需说明你想在表单/控件级别管理哪些事件,并定义你的通用/特定程序术语.
一劳永逸地编写自动化代码.
这需要几天的工作,但它会带来令人兴奋的结果.在过去的两年里,我一直在使用这个解决方案,它显然是正确的:我的表单是从头开始完全自动创建的"表格表",链接到"控制表".
然后,我可以花时间研究表单的特定过程(如果有的话).
即使使用MS Access,代码规范化也是一个漫长的过程.但这真的值得痛苦!
Access作为COM应用程序的另一个优点是,您可以创建.NET应用程序以通过Automation运行和测试Access应用程序.这样做的好处是,您可以使用更强大的测试框架(如NUnit)来编写针对Access应用程序的自动断言测试.
因此,如果您熟练使用C#或VB.NET以及NUnit之类的东西,那么您可以更轻松地为Access应用程序创建更大的测试覆盖率.
我从Python的doctest概念中获取了一个页面,并在Access VBA中实现了DocTests过程.这显然不是一个完整的单元测试解决方案.它仍然相对年轻,所以我怀疑我已经解决了所有的错误,但我认为它足够成熟,可以释放到野外.
只需将以下代码复制到标准代码模块中,然后在Sub中按F5即可查看其中的操作:
'>>> 1 + 1
'2
'>>> 3 - 1
'0
Sub DocTests()
Dim Comp As Object, i As Long, CM As Object
Dim Expr As String, ExpectedResult As Variant, TestsPassed As Long, TestsFailed As Long
Dim Evaluation As Variant
For Each Comp In Application.VBE.ActiveVBProject.VBComponents
Set CM = Comp.CodeModule
For i = 1 To CM.CountOfLines
If Left(Trim(CM.Lines(i, 1)), 4) = "'>>>" Then
Expr = Trim(Mid(CM.Lines(i, 1), 5))
On Error Resume Next
Evaluation = Eval(Expr)
If Err.Number = 2425 And Comp.Type <> 1 Then
'The expression you entered has a function name that '' can't find.
'This is not surprising because we are not in a standard code module (Comp.Type <> 1).
'So we will just ignore it.
GoTo NextLine
ElseIf Err.Number <> 0 Then
Debug.Print Err.Number, Err.Description, Expr
GoTo NextLine
End If
On Error GoTo 0
ExpectedResult = Trim(Mid(CM.Lines(i + 1, 1), InStr(CM.Lines(i + 1, 1), "'") + 1))
Select Case ExpectedResult
Case "True": ExpectedResult = True
Case "False": ExpectedResult = False
Case "Null": ExpectedResult = Null
End Select
Select Case TypeName(Evaluation)
Case "Long", "Integer", "Short", "Byte", "Single", "Double", "Decimal", "Currency"
ExpectedResult = Eval(ExpectedResult)
Case "Date"
If IsDate(ExpectedResult) Then ExpectedResult = CDate(ExpectedResult)
End Select
If (Evaluation = ExpectedResult) Then
TestsPassed = TestsPassed + 1
ElseIf (IsNull(Evaluation) And IsNull(ExpectedResult)) Then
TestsPassed = TestsPassed + 1
Else
Debug.Print Comp.Name; ": "; Expr; " evaluates to: "; Evaluation; " Expected: "; ExpectedResult
TestsFailed = TestsFailed + 1
End If
End If
NextLine:
Next i
Next Comp
Debug.Print "Tests passed: "; TestsPassed; " of "; TestsPassed + TestsFailed
End Sub
Run Code Online (Sandbox Code Playgroud)
从名为Module1的模块复制,粘贴和运行上述代码会产生:
Module: 3 - 1 evaluates to: 2 Expected: 0
Tests passed: 1 of 2
Run Code Online (Sandbox Code Playgroud)
一些快速说明:
Eval
是Access.Application对象模型中的一个函数; 这意味着您可以在Access之外使用它,但它需要创建一个Access.Application对象并完全限定Eval
调用Eval
需要注意尽管有其局限性,我仍然认为它为你的降压提供了相当多的帮助.
编辑:这是一个简单的功能与"doctest规则"功能必须满足.
Public Function AddTwoValues(ByVal p1 As Variant, _
ByVal p2 As Variant) As Variant
'>>> AddTwoValues(1,1)
'2
'>>> AddTwoValues(1,1) = 1
'False
'>>> AddTwoValues(1,Null)
'Null
'>>> IsError(AddTwoValues(1,"foo"))
'True
On Error GoTo ErrorHandler
AddTwoValues = p1 + p2
ExitHere:
On Error GoTo 0
Exit Function
ErrorHandler:
AddTwoValues = CVErr(Err.Number)
GoTo ExitHere
End Function
Run Code Online (Sandbox Code Playgroud)