testingMS Access应用程序的最佳方法是什么?

使用同一个数据库中的代码,表单和数据我想知道什么是为Microsoft Access应用程序(比如说Access 2007)devise一套testing的最佳实践。

testing表单的一个主要问题是只有less数控件具有hwnd句柄,而其他控件只能得到他们关注的一个控件,这使得自动化非常不透明,因为您无法获取表单上的控件列表来执行操作。

任何经验分享?

1.编写可testing的代码

首先,停止将业务逻辑写入您的表单代码中。 这不是它的地方。 在那里无法正确testing。 事实上,你真的不应该根本不需要testing你的表单。 它应该是一个愚蠢的简单视图,它响应用户交互,然后将响应这些操作的责任委托给另一个可testing的类。

你如何做到这一点? 熟悉模型 – 视图 – 控制器模式是一个好的开始。

模型视图控制器图

在VBA中不能完美的完成,因为事实上我们既可以获得事件也可以获得接口,但是从来没有,但是可以变得非常接近。 考虑这个简单的表单,它有一个文本框和一个button。

简单的形式与文本框和按钮

在后面的表单代码中,我们将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 

现在我们需要一个模型来处理。 在这里我创build了一个名为MyModel的新类模块。 这里是我们将要testing的代码。 请注意,它自然与我们的观点具有相似的结构。

 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 

现在这个代码可以从任何其他模块运行。 为了这个例子的目的,我使用了一个标准模块。 我强烈build议你使用我提供的代码自己构build它,并看看它的function。

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

所以,这是伟大的, 但它与testing有什么关系呢? 朋友,这与testing有关。 我们所做的是使我们的代码可testing 。 在我提供的例子中,甚至没有理由尝试testingGUI。 我们真正需要testing的唯一东西就是model 。 这就是所有真正的逻辑。

所以,第二步。

2.select一个unit testing框架

这里没有太多的select。 大多数框架需要安装COM加载项,大量的锅炉板,奇怪的语法,写作testing作为评论等,这就是为什么我参与了自己build设一个 ,所以这部分我的答案是不公正的,但我会尝试对可用内容进行公正的总结。

  1. AccUnit

    • 仅在Access中有效。
    • 要求您将testing写成注释和代码的混合体。 (评论部分没有智能感知。
    • 有一个graphics界面,可以帮助你编写那些奇怪的外观testing。
    • 该项目自2013年以来没有任何更新。
  2. VB Lite的单位我不能说我亲自使用它。 它在那里,但自2005年以来没有看到更新。

  3. xlUnit xlUnit不是很糟糕,但也不好。 这是笨重的,有很多锅炉代码。 这是最糟糕的,但在Access中不起作用。 所以,那已经结束了

  4. build立你自己的框架

    我去过那里 。 这可能比大多数人想要的更多,但是在Native VBA代码中构buildunit testing框架是完全可能的。

  5. Rubberduck VBE插件的unit testing框架
    免责声明:我是共同开发者之一

    我有偏见,但这是我最喜欢的一群。

    • 几乎没有锅炉板代码。
    • 智能感知可用。
    • 该项目是积极的。
    • 比大多数这些项目更多的文档。
    • 它适用于大多数主要的办公应用程序,而不仅仅是Access。
    • 不幸的是,它是一个COM加载项,所以它必须安装到你的机器上。

3.开始写testing

所以,回到第一部分的代码。我们真正需要testing的唯一代码是MyModel.Reversed()函数。 那么,让我们来看看那个testing可能是什么样子。 (给出的例子使用了Rubberduck,但这是一个简单的testing,可以转化为您select的框架。)

 '@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 

编写好的testing指南

  1. 一次只testing一件事情。
  2. 好的testing只有在系统出现错误或需求改变时才会失败。
  3. 不要包含数据库和文件系统等外部依赖项。 这些外部依赖可能会使testing失败,原因不在您的控制范围之内。 其次,他们减慢你的testing。 如果你的testing很慢,你不会运行它们。
  4. 使用描述testing正在testing的testing名称。 不要担心,如果它很长。 这是最重要的,它是描述性的。

我知道答案有点长,但迟到了,但希望它能帮助一些人开始为他们的VBA代码编写unit testing。

我赞赏诺克斯和大卫的答案。 我的答案会在他们之间的某个地方:只是使表单不需要被debugging

我认为这些表格应该被完全用作基本的,意思是graphics界面,这意味着它们不必被debugging! debugging工作然后限制在你的VBA模块和对象上,这样处理起来更容易。

当然,将VBA代码添加到表单和/或控件当然是一种自然的趋势,特别是当Access为您提供这些“Update”和“on change”事件之后,但我绝对build议您不要放置任何forms或控制特定的代码在窗体的模块中。 这使得进一步的维护和升级非常昂贵,在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应用程序的所有特定forms的特定代码,并包含CTLAfterUpdateMyForm过程。 当然,如果没有特定的代码运行,CTLAfterUpdateMyForm可能不存在。 这就是为什么我们把“开启错误”改为“继续下一个”…

select这样的通用解决scheme意味着很多。 这意味着你正在达到高级别的代码规范化(意味着代码的无痛维护)。 而当你说没有任何forms特定的代码时,这也意味着表单模块是完全标准化的,而且他们的生产可以是自动化的 :只要说出你想要在表单/控制级别pipe理哪些事件,并定义你的通用/特定程序术语。
一劳永逸地编写你的自动化代码。
这需要几天的工作,但它会产生令人兴奋的结果。 过去两年来,我一直在使用这个解决scheme,而且显然是正确的:我的表单完全自动地从头开始用“表格表”链接到“控制表”。
如果有的话,我可以把时间花在表格的具体程序上。

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

Access作为COM应用程序的另一个优点是,您可以创build一个.NET应用程序来通过自动化运行和testingAccess应用程序 。 这样做的好处是,你可以使用更强大的testing框架(如NUnit)来编写针对Access应用程序的自动声明testing。

因此,如果你精通C#或VB.NET,再加上类似NUnit的东西,那么你可以更容易地为你的Access应用程序创build更大的testing覆盖率。

我已经从Python的doctest概念中抽出一个页面,并在Access VBA中实现了一个DocTests过程。 这显然不是一个完整的unit testing解决scheme。 它还比较年轻,所以我怀疑我已经弄清了所有的错误,但是我觉得它已经足够成熟,可以放到野外了。

只需将下面的代码复制到一个标准的代码模块中,然后在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之外使用它,但它需要创build一个Access.Application对象并完全限定Eval调用
  • 有一些与Eval相关的特质需要注意
  • 它只能用于返回符合一行的结果的函数

尽pipe有其局限性,但我仍然认为它为你提供了相当大的压力。

编辑 :这是一个简单的function与“doctest规则”function必须满足。

 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 

我将devise应用程序在查询和vba子例程中尽可能多地完成工作,以便您的testing可以由填充testing数据库,运行生产查询集和vba对这些数据库组成,然后查看输出和比较,以确保输出是好的。 这种方法不能明显地testingGUI,所以你可以通过一系列testing脚本来扩展testing(这里我的意思就像是一个开放的表单1的单词文档,点击控制1)手动执行。

这取决于项目的范围,作为testing方面所需的自动化水平。

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

AccUnit是Microsoft Access的一个专门的unit testing框架。

如果你有兴趣testing你的Access应用程序在更细粒度的级别,特别是VBA代码本身,那么VB Lite Unit是一个伟大的unit testing框架。

我发现在我的应用程序中进行unit testing的机会相对较less。 我写的大部分代码与表格数据或文件系统交互,所以基本上难以进行unit testing。 早期,我尝试了一种可能类似于嘲笑(欺骗)的方法,在那里我创build了具有可选参数的代码。 如果使用参数,那么过程将使用该参数而不是从数据库中获取数据。 build立一个与一行数据具有相同字段types的用户定义types并将其传递给一个函数是很容易的。 我现在有一种将testing数据导入我想testing的过程的方法。 在每个过程中,有一些代码将testing数据源的实际数据源换出。 这使我可以使用我自己的unit testingfunction在更广泛的function上使用unit testing。 编写unit testing很容易,只是重复和无聊。 最后,我放弃了unit testing,并开始使用不同的方法。

我主要为自己编写内部应用程序,所以我可以等到问题find我,而不必完善代码。 如果我为客户编写应用程序,通常客户并不完全知道软件开发的成本,所以我需要一种低成本的方式来获得结果。 编写unit testing就是编写一个testing程序,在程序中推送不良数据,以查看程序是否可以正确处理。 unit testing也证实了正确的数据处理。 我目前的方法是基于将inputvalidation写入应用程序中的每个过程,并在代码成功完成时产生成功标志。 每个调用过程在使用结果之前检查成功标志。 如果发生问题,则通过错误消息报告。 每个函数都有一个成功标志,一个返回值,一个错误信息,一个注释和一个起源。 用户定义的types(用于函数返回的fr)包含数据成员。 任何给定的函数都只能填充用户定义types中的一些数据成员。 当一个函数运行时,它通常会返回success = true和一个返回值,有时还会有一个注释。 如果函数失败,则返回success = false并显示错误消息。 如果一连串的函数失败了,错误信息就会被雏菊改变,但结果实际上比正常的堆栈跟踪更可读。 起源也被链接,所以我知道问题发生的地方。 该应用程序很less崩溃,并准确地报告任何问题。 结果是比标准的error handling好多了。

 Public Function GetOutputFolder(OutputFolder As eOutputFolder) As FunctRet '///Returns a full path when provided with a target folder alias. eg 'temp' folder Dim fr As FunctRet Select Case OutputFolder Case 1 fr.Rtn = "C:\Temp\" fr.Success = True Case 2 fr.Rtn = TrailingSlash(Application.CurrentProject.path) fr.Success = True Case 3 fr.EM = "Can't set custom paths – not yet implemented" Case Else fr.EM = "Unrecognised output destination requested" End Select exitproc: GetOutputFolder = fr End Function 

代码解释。 eOutputFolder是一个用户定义的枚举,如下所示

 Public Enum eOutputFolder eDefaultDirectory = 1 eAppPath = 2 eCustomPath = 3 End Enum 

我使用Enum来传递参数给函数,因为这会创build一个function可以接受的已知select的有限集合。 将参数input到函数中时,枚举也提供智能感知。 我想他们提供了一个function的基本接口。

 'Type FunctRet is used as a generic means of reporting function returns Public Type FunctRet Success As Long 'Boolean flag for success, boolean not used to avoid nulls Rtn As Variant 'Return Value EM As String 'Error message Cmt As String 'Comments Origin As String 'Originating procedure/function End Type 

用户定义的types,如FunctRet也提供了代码完成,这有助于。 在这个过程中,我通常在将结果赋给返回variables(GetOutputFolder)之前将内部结果存储到一个匿名内部variables(fr)中。 这使得重命名过程变得非常简单,因为只有顶部和底部被更改。

总而言之,我开发了一个带有ms-access的框架,涵盖了涉及VBA的所有操作。 testing永久写入程序,而不是开发时间unit testing。 在实践中,代码仍然运行得非常快。 我非常小心地优化可以被称为每分钟一万次的低级function。 此外,我可以使用生产中的代码,因为它正在开发。 如果发生错误,这是用户友好的,错误的来源和原因通常是显而易见的。 从调用表中报告错误,而不是来自业务层中的某个模块,这是应用程序devise的重要原则。 此外,我没有维护unit testing代码的负担,这在我进行devise而不是编写明确的概念化devise时非常重要。

有一些潜在的问题。 testing不是自动的,只有在运行应用程序时才能检测到新的错误代码。 代码看起来不像标准的VBA代码(它通常比较短)。 不过,这种方法有一些优点。 使用error handling程序只是logging一个错误是好得多,因为用户通常会与我联系并给我一个有意义的错误信息。 它也可以处理使用外部数据的程序。 JavaScript让我想起了VBA,我想知道为什么JavaScript是框架的土地,VBA在ms-access中不是。

在写这篇文章几天后,我发现了一篇关于CodeProject的文章,与我上面写的很接近。 文章比较和比较exception处理和error handling。 我上面提到的类似于exception处理。

我没有尝试过,但是可以尝试将访问表单作为数据访问网页发布到像Sharepoint或Web页面 ,然后使用一个工具(如selenium)来驱动浏览器进行一系列testing。

显然,这不像通过unit testing直接驱动代码那样理想,但它可能会让你成为一部分。 祝你好运

Access是一个COM应用程序。 使用COM,而不是Windows API。 在Access中testing的东西。

Access应用程序的最佳testing环境是Access。 所有的表单/报表/表格/代码/查询都可用,有一个类似于MS Test的脚本语言(好吧,你可能不记得MS Test),还有数据库环境来保存你的testing脚本和testing结果,你在这里build立的技能可以转移到你的应用程序。

这里有好的build议,但我很惊讶没有人提到集中式的error handling。 你可以得到允许快速function/子模板和添加行号的插件(我使用MZ工具)。 然后将所有错误发送到一个可以logging它们的function。 您也可以通过设置一个断点来解决所有错误。

数据访问页已被MS弃用了相当长的一段时间,从来没有真正的工作(他们是依赖于正在安装的Office Widgets,只能在IE浏览器,只有很差)。

确实,可以获得焦点的Access控件只有在拥有焦点的情况下才具有窗口句柄(而那些不能获得焦点的types,比如标签,根本就没有窗口句柄)。 这使得Access非常不适合窗口驱动的testing机制。

事实上,我质疑你为什么要在Access中进行这种testing。 这听起来像你的基本的极限编程教条,并不是所有的原则和XP的做法可以适应与Access应用程序 – 方形挂钩,圆孔。

所以,退后一步,问自己想要完成什么,并考虑到可能需要使用完全不同的方法,而不是那些基于无法在Access中工作的方法。

或者这种自动化testing是否对Access应用程序有效或甚至有用。