Domanda

Con il codice, i moduli e i dati all'interno dello stesso database mi chiedo quali siano le migliori pratiche per progettare una suite di test per un'applicazione Microsoft Access (ad esempio per Access 2007).

Uno dei problemi principali con i moduli di test è che solo pochi controlli hanno un file hwnd handle e altri controlli ne ottengono solo uno su cui sono focalizzati, il che rende l'automazione piuttosto opaca poiché non è possibile ottenere un elenco di controlli su un modulo su cui agire.

Qualche esperienza da condividere?

È stato utile?

Soluzione

1.Scrivi codice testabile

Innanzitutto, smetti di scrivere la logica aziendale nel codice del tuo modulo.Non è quello il posto giusto.Non può essere testato adeguatamente lì.In effetti, non dovresti affatto testare il tuo modulo stesso.Dovrebbe essere una visione semplice e stupida che risponde all'interazione dell'utente e quindi delega la responsabilità di rispondere a tali azioni a un'altra classe che È testabile.

Come si fa a farlo?Familiarizzare con il Modello modello-vista-controller è un buon inizio.

Model View Controller diagram

Non è possibile farlo perfettamente in VBA a causa del fatto che otteniamo eventi o interfacce, mai entrambi, ma puoi avvicinarti molto.Considera questo semplice modulo che ha una casella di testo e un pulsante.

simple form with text box and button

Nel code-behind del modulo, avvolgeremo il valore di TextBox in una proprietà pubblica e genereremo nuovamente tutti gli eventi a cui siamo interessati.

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

Ora abbiamo bisogno di un modello con cui lavorare.Qui ho creato un nuovo modulo di classe denominato MyModel.Qui giace il codice che metteremo sotto test.Si noti che condivide naturalmente una struttura simile alla nostra visione.

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

Infine, il nostro controller collega tutto insieme.Il controller ascolta gli eventi del modulo, comunica le modifiche al modello e attiva le routine del modello.

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

Ora questo codice può essere eseguito da qualsiasi altro modulo.Ai fini di questo esempio, ho utilizzato un modulo standard.Ti incoraggio vivamente a crearlo tu stesso utilizzando il codice che ho fornito e vederlo funzionare.

Private controller As FormController

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

Quindi è fantastico e tutto ma cosa c'entra con i test?! Amico, è così qualunque cosa a che fare con i test.Ciò che abbiamo fatto è creare il nostro codice testabile.Nell'esempio che ho fornito, non c'è motivo nemmeno di provare a testare la GUI.L'unica cosa che dobbiamo veramente testare è il model.È lì che sta tutta la vera logica.

Quindi, passiamo al secondo passo.

2.Scegli un framework di unit test

Non ci sono molte opzioni qui.La maggior parte dei framework richiede l'installazione di componenti aggiuntivi COM, molti boiler plate, sintassi strana, scrittura di test come commenti, ecc.Ecco perché sono stato coinvolto costruendone uno io stesso, quindi questa parte della mia risposta non è imparziale, ma cercherò di fornire un giusto riepilogo di ciò che è disponibile.

  1. Unità acc

    • Funziona solo in Access.
    • Richiede di scrivere test come uno strano ibrido di commenti e codice.(nessun IntelliSense per la parte dei commenti.
    • È un'interfaccia grafica per aiutarti a scrivere quei test dall'aspetto strano.
    • Il progetto non ha visto alcun aggiornamento dal 2013.
  2. Unità VB LiteNon posso dire di averlo utilizzato personalmente.È disponibile, ma non ha visto un aggiornamento dal 2005.

  3. xlUnitàxlUnit non è terribile, ma non è neanche buono.È goffo e ci sono molti codici di targa.È il meglio del peggio, ma non funziona in Access.Quindi questo è fuori.

  4. Costruisci la tua struttura

    Io ho ci sono stato e l'ho fatto.Probabilmente è più di quanto la maggior parte delle persone voglia affrontare, ma è completamente possibile creare un framework di unit test in codice VBA nativo.

  5. Framework di test unitari del componente aggiuntivo Rubberduck VBE
    Disclaimer:Sono uno dei co-sviluppatori.

    Sono di parte, ma questo è di gran lunga il mio preferito del gruppo.

    • Codice targa caldaia minimo o nullo.
    • L'Intellisense è disponibile.
    • Il progetto è attivo.
    • Più documentazione rispetto alla maggior parte di questi progetti.
    • Funziona con la maggior parte delle principali applicazioni per ufficio, non solo con Access.
    • Sfortunatamente è un componente aggiuntivo COM, quindi deve essere installato sul tuo computer.

3.Inizia a scrivere i test

Quindi, torniamo al nostro codice dalla sezione 1.L'unico codice che noi Veramente necessario per testare era il MyModel.Reversed() funzione.Quindi, diamo un'occhiata a come potrebbe essere il test.(L'esempio fornito utilizza Rubberduck, ma è un test semplice e potrebbe tradursi nel framework di tua scelta.)

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

Linee guida per scrivere buoni test

  1. Prova solo una cosa alla volta.
  2. I buoni test falliscono solo quando viene introdotto un bug nel sistema o i requisiti sono cambiati.
  3. Non includere dipendenze esterne come database e file system.Queste dipendenze esterne possono far fallire i test per motivi fuori dal tuo controllo.In secondo luogo, rallentano i test.Se i tuoi test sono lenti, non li eseguirai.
  4. Utilizzare nomi di test che descrivano ciò che il test sta testando.Non preoccuparti se diventa lungo.La cosa più importante è che sia descrittivo.

So che la risposta è stata un po' lunga e tardiva, ma spero che aiuti alcune persone a iniziare a scrivere test unitari per il loro codice VBA.

Altri suggerimenti

Ho apprezzato le risposte di Knox e David.La mia risposta sarà da qualche parte tra le loro:basta fare moduli che non necessitano di debug!

Penso che i moduli debbano essere utilizzati esclusivamente per quello che sono fondamentalmente, ovvero interfaccia grafica soltanto, il che significa che non è necessario eseguire il debug!Il lavoro di debug è quindi limitato ai moduli e agli oggetti VBA, il che è molto più semplice da gestire.

Esiste ovviamente una tendenza naturale ad aggiungere codice VBA ai moduli e/o ai controlli, specialmente quando Access ti offre questi fantastici eventi "dopo l'aggiornamento" e "in caso di modifica", ma ti consiglio vivamente non per inserire qualsiasi modulo o controllare il codice specifico nel modulo del modulo.Ciò rende molto costosi ulteriori interventi di manutenzione e aggiornamento, in cui il codice è suddiviso tra moduli VBA e moduli moduli/controlli.

Ciò non significa che non puoi più usarlo AfterUpdate evento!Basta inserire il codice standard nell'evento, in questo modo:

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

Dove:

  • CTLAfterUpdate è una procedura standard eseguita ogni volta che un controllo viene aggiornato in un modulo

  • CTLAfterUpdateMyForm è una procedura specifica eseguita ogni volta che un controllo viene aggiornato su MyForm

Ho quindi 2 moduli.Il primo è

  • utilityFormEvents
    dove avrò il mio evento generico CTLAfterUpdate

Il secondo è

  • MyAppFormEvents
    contenente il codice specifico di tutte le forme specifiche dell'applicazione MyApp e include la procedura CTLAFTERUPDATEMYFORM.Naturalmente, CtlafterUpdatemyForm potrebbe non esistere se non ci sono codice specifico da eseguire.Questo è il motivo per cui trasformiamo "On Error" in "riprendere successivo" ...

Scegliere una soluzione così generica significa molto.Significa che stai raggiungendo un elevato livello di normalizzazione del codice (ovvero manutenzione indolore del codice).E quando dici che non hai alcun codice specifico per il modulo, significa anche che i moduli del modulo sono completamente standardizzati e la loro produzione può essere automatizzato:basta dire quali eventi si desidera gestire a livello di modulo/controllo e definire la terminologia delle procedure generiche/specifiche.
Scrivi il tuo codice di automazione, una volta per tutte.
Richiede qualche giorno di lavoro ma dà risultati entusiasmanti.Ho usato questa soluzione negli ultimi 2 anni ed è chiaramente quella giusta:i miei moduli vengono creati completamente e automaticamente da zero con una "Tabella moduli", collegata a una "Tabella controlli".
Posso quindi dedicare il mio tempo a lavorare sulle procedure specifiche del modulo, se presenti.

La normalizzazione del codice, anche con MS Access, è un processo lungo.Ma ne vale davvero la pena!

Un altro vantaggio di L'accesso è un'applicazione COM è che puoi creare un file Applicazione .NET per eseguire e testare un'applicazione Access tramite l'automazione.Il vantaggio di ciò è che è possibile utilizzare un framework di test più potente come NUnità per scrivere test di asserzione automatizzati rispetto a un'app Access.

Pertanto, se sei esperto in C# o VB.NET combinato con qualcosa come NUnit, puoi creare più facilmente una maggiore copertura di test per la tua app Access.

Anche se questa è una risposta molto vecchia:

C'è Unità acc, un framework unit-test specializzato per Microsoft Access.

Ho tolto una pagina da Il doctest di Python concetto e implementato una procedura DocTests in Access VBA.Questa ovviamente non è una soluzione di test unitario completa.È ancora relativamente giovane, quindi dubito di aver risolto tutti i bug, ma penso che sia abbastanza maturo per essere rilasciato in natura.

Basta copiare il seguente codice in un modulo di codice standard e premere F5 all'interno del Sub per vederlo in azione:

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

Copiando, incollando ed eseguendo il codice precedente da un modulo denominato Modulo1 si ottiene:

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

Alcune brevi note:

  • Non ha dipendenze (se utilizzato da Access)
  • Si avvale di Eval che è una funzione nel modello a oggetti Access.Application;questo significa te Potevo usarlo al di fuori di Access ma richiederebbe la creazione di un oggetto Access.Application e la qualificazione completa del file Eval chiamate
  • Ci sono alcuni idiosincrasie associate a Eval essere a conoscenza di
  • Può essere utilizzato solo su funzioni che restituiscono un risultato che si adatta a una singola riga

Nonostante i suoi limiti, penso ancora che offra un bel rapporto qualità-prezzo.

Modificare:Ecco una semplice funzione con "regole doctest" che la funzione deve soddisfare.

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

Progetterei l'applicazione in modo che venga svolto quanto più lavoro possibile nelle query e nelle subroutine vba in modo che i test possano essere costituiti dal popolamento dei database di test, dall'esecuzione di serie di query di produzione e vba rispetto a tali database e quindi dall'osservazione dell'output e confrontare per assicurarsi che l'output sia buono.Questo approccio ovviamente non testa la GUI, quindi potresti aumentare il test con una serie di script di test (qui intendo come un documento word che dice apri modulo 1 e fai clic su controllo 1) che vengono eseguiti manualmente.

Dipende dall'ambito del progetto come dal livello di automazione necessario per l'aspetto del test.

Se sei interessato a testare la tua applicazione Access a un livello più granulare, in particolare il codice VBA stesso, allora Unità VB Lite è un ottimo framework per test unitari per questo scopo.

Trovo che ci siano relativamente poche opportunità per i test unitari nelle mie applicazioni.La maggior parte del codice che scrivo interagisce con i dati della tabella o con il sistema di archiviazione, quindi è fondamentalmente difficile eseguire test unitari.All'inizio ho provato un approccio che potrebbe essere simile al mocking (spoofing) in cui creavo codice con un parametro opzionale.Se fosse utilizzato il parametro, la procedura utilizzerebbe il parametro invece di recuperare i dati dal database.È abbastanza semplice impostare un tipo definito dall'utente che abbia gli stessi tipi di campo di una riga di dati e passarlo a una funzione.Ora ho un modo per inserire i dati di test nella procedura che voglio testare.All'interno di ogni procedura c'era del codice che sostituiva l'origine dati reale con l'origine dati di test.Ciò mi ha permesso di utilizzare i test unitari su una più ampia varietà di funzioni, utilizzando le mie funzioni di test unitari.Scrivere un test unitario è facile, è semplicemente ripetitivo e noioso.Alla fine ho rinunciato ai test unitari e ho iniziato a utilizzare un approccio diverso.

Scrivo applicazioni interne principalmente per me stesso, quindi posso permettermi di aspettare finché non mi trovano i problemi piuttosto che dover avere un codice perfetto.Se scrivo applicazioni per i clienti, generalmente il cliente non è pienamente consapevole di quanto costa lo sviluppo del software, quindi ho bisogno di un modo a basso costo per ottenere risultati.Scrivere unit test significa scrivere un test che inserisce dati errati in una procedura per vedere se la procedura è in grado di gestirli in modo appropriato.I test unitari confermano inoltre che i dati validi vengono gestiti in modo appropriato.Il mio approccio attuale si basa sulla scrittura della convalida dell'input in ogni procedura all'interno di un'applicazione e sull'attivazione di un flag di successo quando il codice è stato completato correttamente.Ogni procedura chiamante controlla il flag di successo prima di utilizzare il risultato.Se si verifica un problema, viene segnalato tramite un messaggio di errore.Ogni funzione ha un flag di successo, un valore restituito, un messaggio di errore, un commento e un'origine.Un tipo definito dall'utente (fr per la funzione return) contiene i membri dati.Qualsiasi funzione specificata popola solo alcuni dei membri dati nel tipo definito dall'utente.Quando viene eseguita una funzione, solitamente restituisce success = true e un valore restituito e talvolta un commento.Se una funzione fallisce, restituisce success = false e un messaggio di errore.Se una catena di funzioni fallisce, i messaggi di errore vengono modificati a margherita ma il risultato è in realtà molto più leggibile di una normale traccia dello stack.Anche le origini sono concatenate, quindi so dove si è verificato il problema.L'applicazione raramente si arresta in modo anomalo e segnala accuratamente eventuali problemi.Il risultato è decisamente migliore rispetto alla gestione degli errori standard.

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

Codice spiegato.eOutputFolder è un'enumerazione definita dall'utente come di seguito

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

Sto utilizzando Enum per passare parametri alle funzioni poiché ciò crea un insieme limitato di scelte note che una funzione può accettare.Le enumerazioni forniscono anche IntelliSense quando si immettono parametri nelle funzioni.Suppongo che forniscano un'interfaccia rudimentale per una funzione.

'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

Un tipo definito dall'utente come FunctRet fornisce anche il completamento del codice che aiuta.All'interno della procedura, solitamente memorizzo i risultati interni in una variabile interna anonima (fr) prima di assegnare i risultati alla variabile di ritorno (GetOutputFolder).Ciò rende le procedure di ridenominazione molto semplici poiché sono state modificate solo la parte superiore e quella inferiore.

Quindi, in sintesi, ho sviluppato un framework con MS-Access che copre tutte le operazioni che coinvolgono VBA.Il test è scritto in modo permanente nelle procedure, piuttosto che in un test unitario del tempo di sviluppo.In pratica, il codice funziona ancora molto velocemente.Sono molto attento a ottimizzare le funzioni di livello inferiore che possono essere chiamate diecimila volte al minuto.Inoltre, posso utilizzare il codice in produzione mentre viene sviluppato.Se si verifica un errore, è facile da usare e la fonte e il motivo dell'errore sono generalmente ovvi.Gli errori vengono segnalati dal modulo di chiamata, non da qualche modulo del livello aziendale, che è un principio importante della progettazione dell'applicazione.Inoltre, non ho l'onere di mantenere il codice di test unitario, il che è davvero importante quando evolvo un progetto piuttosto che codificare un progetto chiaramente concettualizzato.

Ci sono alcuni potenziali problemi.Il test non è automatizzato e il nuovo codice errato viene rilevato solo quando l'applicazione viene eseguita.Il codice non assomiglia al codice VBA standard (di solito è più breve).Tuttavia, l’approccio presenta alcuni vantaggi.È molto meglio che utilizzare un gestore di errori solo per registrare un errore poiché gli utenti di solito mi contattano e mi danno un messaggio di errore significativo.Può anche gestire procedure che funzionano con dati esterni.JavaScript mi ​​ricorda VBA, mi chiedo perché JavaScript sia la terra dei framework e VBA in MS-Access non lo sia.

Pochi giorni dopo aver scritto questo post, ho trovato un articolo su The CodeProject questo si avvicina a quanto ho scritto sopra.L'articolo confronta e contrappone la gestione delle eccezioni e la gestione degli errori.Ciò che ho suggerito sopra è simile alla gestione delle eccezioni.

Non l'ho provato, ma potresti tentare di farlo pubblica i tuoi moduli di accesso come pagine Web di accesso ai dati su qualcosa come SharePoint O proprio come le pagine web e quindi utilizzare uno strumento come selenio per guidare il browser con una serie di test.

Ovviamente questo non è l'ideale come guidare il codice direttamente attraverso i test unitari, ma potrebbe farti arrivare in parte.buona fortuna

L'accesso è un'applicazione COM.Utilizza COM, non l'API di Windows.per testare le cose in Access.

Il miglior ambiente di test per un'applicazione di Access è Access.Tutti i tuoi moduli/report/tabelle/codici/query sono disponibili, esiste un linguaggio di scripting simile a MS Test (Ok, probabilmente non ricordi MS Test), esiste un ambiente database per contenere gli script di test e i risultati dei test, e le competenze che acquisisci qui sono trasferibili alla tua applicazione.

Ci sono buoni suggerimenti qui, ma sono sorpreso che nessuno abbia menzionato l'elaborazione centralizzata degli errori.È possibile ottenere componenti aggiuntivi che consentono funzioni rapide/template secondari e l'aggiunta di numeri di riga (io utilizzo gli strumenti MZ).Quindi invia tutti gli errori a un'unica funzione dove puoi registrarli.È inoltre possibile interrompere tutti gli errori impostando un singolo punto di interruzione.

Le pagine di accesso ai dati sono state deprecate da MS per un bel po' di tempo e non hanno mai funzionato veramente (dipendevano dall'installazione dei widget di Office e funzionavano solo in IE, e solo male in quel caso).

È vero che i controlli di accesso che possono ottenere il focus hanno un handle di finestra solo quando hanno il focus (e quelli che non possono ottenere il focus, come le etichette, non hanno mai un handle di finestra).Ciò rende Access singolarmente inappropriato per i regimi di test basati sugli handle delle finestre.

In effetti, mi chiedo perché vuoi eseguire questo tipo di test in Access.Mi sembra il tuo dogma di base dell'Extreme Programming, e non tutti i principi e le pratiche di XP possono essere adattati per funzionare con le applicazioni di Access: piolo quadrato, foro rotondo.

Quindi, fai un passo indietro e chiediti cosa stai cercando di ottenere e considera che potresti dover utilizzare metodi completamente diversi da quelli basati su approcci che semplicemente non possono funzionare in Access.

O se quel tipo di test automatizzato sia valido o addirittura utile con un'applicazione Access.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top