当前位置:  开发笔记 > 编程语言 > 正文

测试MS Access应用程序的最佳方法?

如何解决《测试MSAccess应用程序的最佳方法?》经验,为你挑选了5个好方法。

使用同一数据库中的代码,表单和数据,我想知道为Microsoft Access应用程序设计一套测试的最佳实践是什么(比如Access 2007).

测试表单的主要问题之一是,只有少数控件具有hwnd句柄,而其他控件只能获得一个焦点,这使得自动化非常不透明,因为您无法获取表单上的控件列表.

有经验可以分享吗?



1> RubberDuck..:
1.编写可测试代码

首先,停止将业务逻辑写入您的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

现在我们需要一个模型来使用.在这里,我创建了一个名为的新类模块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

最后,我们的控制器将它们连接在一起.控制器侦听表单事件并将更改传递给模型并触发模型的例程.

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

现在,此代码可以从任何其他模块运行.出于本示例的目的,我使用了标准模块.我强烈建议您使用我提供的代码自己构建它并查看它的功能.

Private controller As FormController

Public Sub Run()
    Set controller = New FormController
    controller.Run
End Sub

那么,那很好,除了与测试有什么关系之外呢?!朋友,它具有的一切做测试.我们所做的就是让我们的代码可以测试.在我提供的示例中,甚至没有理由尝试测试GUI.我们真正需要测试的唯一一件事是model.这就是所有真实逻辑的所在.

所以,继续第二步.

2.选择单元测试框架

这里没有很多选择.大多数框架都需要安装COM加载项,大量的样板,奇怪的语法,写测试作为评论等等.这就是为什么我自己参与构建一个,所以这部分答案不公正,但我会尝试对可用的内容进行公平的总结.

    AccUnit

    仅适用于Access.

    要求您将测试编写为注释和代码的奇怪组合.(评论部分没有智能感知.

    这里一个图形界面,以帮助你写那些奇怪的看着测试虽然.

    该项目自2013年以来未见任何更新.

    VB Lite Unit 我不能说我亲自使用它.它在那里,但自2005年以来没有看到更新.

    xlUnit xlUnit并不糟糕,但它也不好.它很笨重,还有很多锅炉板代码.这是最糟糕的,但它在Access中不起作用.所以,那就是了.

    建立自己的框架

    我去过那里并做到了.它可能比大多数人想要进入的更多,但完全有可能在Native VBA代码中构建单元测试框架.

    Rubberduck VBE Add-In的单元测试框架
    免责声明:我是其中一个共同开发者.

    我有偏见,但这是迄今为止我最喜欢的一群.

    很少甚至没有锅炉板代码.

    Intellisense可用.

    该项目很活跃.

    比大多数这些项目更多的文档.

    它适用于大多数主要的办公应用程序,而不仅仅是Access.

    遗憾的是,它是一个COM加载项,因此必须安装到您的计算机上.

3.开始编写测试

所以,回到第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

编写良好测试指南

    一次只测试一件事.

    只有在系统中引入了错误或需求发生变化时,良好的测试才会失败.

    不要包含外部依赖项,例如数据库和文件系统.这些外部依赖项可能会因为您无法控制的原因而导致测试失败.其次,它们会减慢您的测试速度.如果你的测试很慢,你就不会运行它们.

    使用描述测试测试内容的测试名称.如果它变长,不要担心.最重要的是它是描述性的.


我知道答案有点长,而且很晚,但希望它可以帮助一些人开始为他们的VBA代码编写单元测试.



2> Philippe Gro..:

我很欣赏诺克斯和大卫的答案.我的答案将介于他们之间:只需制作不需要调试的表单!

我认为表单应该专门用作它们的基本内容,仅指图形界面,这意味着它们不必调试!然后,调试作业仅限于您的VBA模块和对象,这样更容易处理.

当然有一种将VBA代码添加到表单和/或控件的自然趋势,特别是当Access为您提供了这些伟大的"更新后"和"更改后"事件时,但我绝对建议您不要放置任何表单或控制特定代码在表单的模块中.这使得进一步维护和升级变得非常昂贵,其中您的代码在VBA模块和表单/控件模块之间分配.

这并不意味着你不能再使用这个AfterUpdate活动了!只需将标准代码放入事件中,如下所示:

Private Sub myControl_AfterUpdate()  
    CTLAfterUpdate myControl
    On Error Resume Next
    Eval ("CTLAfterUpdate_MyForm()")
    On Error GoTo 0  
End sub

哪里:

CTLAfterUpdate 是每次在表单中更新控件时运行的标准过程

CTLAfterUpdateMyForm 是每次在MyForm上更新控件时运行的特定过程

我有2个模块.第一个是

utilityFormEvents
我将在哪里获得CTLAfterUpdate通用事件

第二个是

MyAppFormEvents
包含MyApp应用程序的所有特定形式的特定代码,并包括CTLAfterUpdateMyForm过程.当然,如果没有特定的代码可以运行,CTLAfterUpdateMyForm可能不存在.这就是为什么我们将"On error"变为"resume next"......

选择这样的通用解决方案意味着很多.这意味着您正在达到高级别的代码规范化(意味着无痛的代码维护).当你说你没有任何特定于表单的代码时,它也意味着表单模块是完全标准化的,并且它们的生产可以自动化:只需说明你想在表单/控件级别管理哪些事件,并定义你的通用/特定程序术语.
一劳永逸地编写自动化代码.
这需要几天的工作,但它会带来令人兴奋的结果.在过去的两年里,我一直在使用这个解决方案,它显然是正确的:我的表单是从头开始完全自动创建的"表格表",链接到"控制表".
然后,我可以花时间研究表单的特定过程(如果有的话).

即使使用MS Access,代码规范化也是一个漫长的过程.但这真的值得痛苦!



3> Ray Vega..:

Access作为COM应用程序的另一个优点是,您可以创建.NET应用程序以通过Automation运行和测试Access应用程序.这样做的好处是,您可以使用更强大的测试框架(如NUnit)来编写针对Access应用程序的自动断言测试.

因此,如果您熟练使用C#或VB.NET以及NUnit之类的东西,那么您可以更轻松地为Access应用程序创建更大的测试覆盖率.


我实际上已经这样做了.我强烈推荐这种方式,因为你拥有.net的所有优势来测试你的Access/VBA应用程序.

4> mwolfe02..:

我从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

从名为Module1的模块复制,粘贴和运行上述代码会产生:

Module: 3 - 1 evaluates to:  2  Expected:  0 
Tests passed:  1  of  2

一些快速说明:

它没有依赖关系(从Access中使用时)

它使用的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


@ David-W-Fenton能够以自动方式测试代码非常有用,如果您在其他地方可能已经破坏某些地方的地方进行更改.通过以系统方式运行测试,您可以验证您的代码是否全局一致:失败的测试会突出显示在手动UI测试人员或最终用户偶然发现之前可能无法看到的问题.代码测试并不是为了测试所有内容,而是仅用于测试代码.它也有它的缺点(测试中断,增加了创建测试的时间),但对于大型项目来说这是值得的.
@David:它验证了*logic*的正确性.当然,编译不行.

5> paulroho..:

虽然这是一个非常古老的答案:

有AccUnit,一个专门的Microsoft Access单元测试框架.

推荐阅读
mobiledu2402851173
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有