簡體   English   中英

測試MS Access應用程序的最佳方法?

[英]Best way to test a MS Access application?

使用同一數據庫中的代碼,表單和數據,我想知道為Microsoft Access應用程序設計一套測試的最佳實踐是什么(比如Access 2007)。

測試表單的主要問題之一是只有少數控件具有hwnd句柄,而其他控件只能獲得一個焦點,這使得自動化非常不透明,因為您無法獲取表單上的控件列表來執行操作。

有經驗可以分享嗎?

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加載項,大量的樣板,奇怪的語法,寫測試作為評論等等。這就是為什么我自己參與構建一個 ,所以這部分答案不公正,但我會嘗試對可用的內容進行公平的總結。

  1. AccUnit

    • 僅適用於Access。
    • 要求您將測試編寫為注釋和代碼的奇怪組合。 (評論部分沒有智能感知。
    • 一個圖形界面來幫你寫那些奇怪的看着測試雖然。
    • 該項目自2013年以來未見任何更新。
  2. VB Lite Unit我不能說我親自使用它。 它在那里,但自2005年以來沒有看到更新。

  3. xlUnit xlUnit並不糟糕,但它也不好。 它很笨重,並且有很多鍋爐板代碼。 這是最糟糕的,但它在Access中不起作用。 所以,那就是了。

  4. 建立自己的框架

    去過那里並做到了 它可能比大多數人想要進入的更多,但完全有可能在Native VBA代碼中構建單元測試框架。

  5. Rubberduck VBE加載項的單元測試框架
    免責聲明:我是其中一個共同開發者

    我有偏見,但這是迄今為止我最喜歡的一群。

    • 很少甚至沒有鍋爐板代碼。
    • 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

編寫良好測試指南

  1. 一次只測試一件事。
  2. 只有在系統中引入了錯誤或需求發生變化時,良好的測試才會失敗。
  3. 不要包含外部依賴項,例如數據庫和文件系統。 這些外部依賴項可能會因為您無法控制的原因而導致測試失敗。 其次,它們會減慢您的測試速度。 如果你的測試很慢,你就不會運行它們。
  4. 使用描述測試測試內容的測試名稱。 如果它變長,不要擔心。 最重要的是它是描述性的。

我知道答案有點長,而且很晚,但希望它可以幫助一些人開始為他們的VBA代碼編寫單元測試。

我很欣賞諾克斯和大衛的答案。 我的答案將介於他們之間:只需制作不需要調試的表單

我認為表單應該專門用作它們的基本內容, 僅指圖形界面,這意味着它們不必調試! 然后,調試作業僅限於您的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,代碼規范化也是一個漫長的過程。 但這真的值得痛苦!

Access作為COM應用程序的另一個優點是,您可以創建.NET應用程序以通過Automation運行和測試Access應用程序 這樣做的好處是,您可以使用更強大的測試框架(如NUnit)來編寫針對Access應用程序的自動斷言測試。

因此,如果您熟練使用C#或VB.NET以及NUnit之類的東西,那么您可以更輕松地為Access應用程序創建更大的測試覆蓋率。

雖然這是一個非常古老的答案:

AccUnit ,一個專門用於Microsoft 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

從名為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

我將設計應用程序以在查詢和vba子例程中完成盡可能多的工作,以便您的測試可以由填充測試數據庫,運行生產查詢集和對這些數據庫的vba組成,然后查看輸出和比較以確保輸出良好。 這種方法顯然不會對GUI進行測試,因此您可以使用一系列測試腳本來擴充測試(這里我的意思是像開放式1的單詞文檔,然后單擊控件1),這些腳本是手動執行的。

它取決於項目的范圍,作為測試方面所需的自動化水平。

我發現在我的應用程序中進行單元測試的機會相對較少。 我編寫的大多數代碼都與表數據或文件系統交互,因此從根本上難以進行單元測試。 在早期,我嘗試了一種類似於模擬(欺騙)的方法,其中我創建了具有可選參數的代碼。 如果使用了該參數,則該過程將使用該參數而不是從數據庫中獲取數據。 設置用戶定義的類型非常容易,該類型具有與一行數據相同的字段類型並將其傳遞給函數。 我現在有辦法將測試數據輸入到我想要測試的過程中。 在每個過程中都有一些代碼可以替換測試數據源的真實數據源。 這使我能夠使用我自己的單元測試功能對更廣泛的功能進行單元測試。 寫單元測試很簡單,只是重復和無聊。 最后,我放棄了單元測試並開始使用不同的方法。

我主要為自己編寫內部應用程序,因此我可以等到問題找到我而不是必須擁有完美的代碼。 如果我為客戶編寫應用程序,通常客戶並不完全了解軟件開發成本,因此我需要一種低成本的方法來獲得結果。 編寫單元測試就是編寫一個測試,在一個過程中推送壞數據,以查看過程是否可以正確處理它。 單元測試還確認正確處理好的數據。 我當前的方法是基於將輸入驗證寫入應用程序中的每個過程,並在代碼成功完成時引發成功標志。 在使用結果之前,每個調用過程都會檢查成功標志。 如果出現問題,則通過錯誤消息報告。 每個函數都有一個成功標志,一個返回值,一個錯誤消息,一個注釋和一個原點。 用戶定義的類型(函數返回的fr)包含數據成員。 任何給定的函數都只填充用戶定義類型中的某些數據成員。 運行函數時,它通常會返回success = true和返回值,有時還會返回注釋。 如果函數失敗,則返回success = false和錯誤消息。 如果一系列函數失敗,則會更改錯誤消息,但結果實際上比正常的堆棧跟蹤更具可讀性。 起源也是鏈接的,所以我知道問題出在哪里。 該應用程序很少崩潰並准確報告任何問題。 結果是比標准錯誤處理好得多。

Public Function GetOutputFolder(OutputFolder As eOutputFolder) As  FunctRet

        '///Returns a full path when provided with a target folder alias. e.g. '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將參數傳遞給函數,因為這會創建函數可以接受的一組有限的已知選項。 枚舉在向函數輸入參數時也提供智能感知。 我想它們為函數提供了一個基本的接口。

'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

用戶定義的類型(如FunctRet)也提供了有用的代碼完成功能。 在該過程中,我通常將內部結果存儲到匿名內部變量(fr),然后將結果分配給返回變量(GetOutputFolder)。 這使得重命名過程非常簡單,因為只需更改頂部和底部。

總而言之,我開發了一個帶有ms-access的框架,涵蓋了涉及VBA的所有操作。 測試永久寫入程序,而不是開發時間單元測試。 在實踐中,代碼仍然運行得非常快。 我非常小心地優化可以每分鍾調用一萬次的低級功能。 此外,我可以在開發過程中使用生產中的代碼。 如果發生錯誤,則用戶友好,錯誤的來源和原因通常很明顯。 從調用表單報告錯誤,而不是從業務層中的某個模塊報告,這是應用程序設計的重要原則。 此外,我沒有維護單元測試代碼的負擔,這在我開發設計而不是編寫清晰概念化設計時非常重要。

有一些潛在的問題。 測試不是自動化的,只有在運行應用程序時才會檢測到新的錯誤代碼。 代碼看起來不像標准VBA代碼(通常更短)。 不過,這種方法有一些優點。 使用錯誤處理程序來記錄錯誤要好得多,因為用戶通常會聯系我並給我一個有意義的錯誤消息。 它還可以處理與外部數據一起使用的過程。 JavaScript讓我想起了VBA,我想知道為什么JavaScript是框架的土地,而ms-access中的VBA則不然。

寫完這篇文章幾天后,我發現了一篇關於The CodeProject文章 ,它與我上面寫的內容很接近。 本文比較和對比了異常處理和錯誤處理。 我上面提到的內容類似於異常處理。

如果您有興趣在更細粒度級別測試Access應用程序,特別是VBA代碼本身,那么VB Lite Unit就是一個很好的單元測試框架。

這里有很好的建議,但我很驚訝沒有人提到集中式錯誤處理。 您可以獲得允許快速功能/子模板和添加行號的插件(我使用MZ工具)。 然后將所有錯誤發送到您可以記錄它們的單個函數。 然后,您還可以通過設置單個斷點來中斷所有錯誤。

我沒有嘗試過,但您可以嘗試將訪問表單作為數據訪問網頁發布到sharepointweb頁面 ,然后使用selenium等工具通過一系列測試來驅動瀏覽器。

顯然,這不像通過單元測試直接驅動代碼那樣理想,但它可能會讓你成為一部分。 祝好運

Access是一個COM應用程序。 使用COM,而不是Windows API。 在Access中測試東西。

Access應用程序的最佳測試環境是Access。 您可以使用所有表單/報表/表格/代碼/查詢,有類似於MS Test的腳本語言(好的,您可能不記得MS Test),有用於保存測試腳本和測試結果的數據庫環境,您在此處構建的技能可以轉移到您的應用程序中。

數據訪問頁面已被MS棄用了相當長的一段時間,並且從未真正起作用(它們依賴於正在安裝的Office Widgets,並且僅在IE中工作,並且只是非常糟糕)。

確實,可以獲得焦點的Access控件在有焦點時只有一個窗口句柄(那些無法獲得焦點的控件,如標簽,根本就沒有窗口句柄)。 這使得Access非常不適合窗口句柄驅動的測試機制。

實際上,我懷疑你為什么要在Access中進行這種測試。 這聽起來像你的基本極限編程教條,並不是XP的所有原理和實踐都可以適用於Access應用程序 - 方形釘,圓孔。

所以,退后一步,問問自己你想要完成什么,並認為你可能需要使用完全不同的方法,而不是基於在Access中無法使用的方法。

或者這種自動化測試是否完全有效,甚至對Access應用程序是否有用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM