Pregunta

Con el código, los formularios y los datos dentro de la misma base de datos, me pregunto cuáles son las mejores prácticas para diseñar un conjunto de pruebas para una aplicación de Microsoft Access (por ejemplo, para Access 2007).

Uno de los principales problemas con los formularios de prueba es que sólo unos pocos controles tienen una hwnd handle y otros controles solo obtienen uno en el que tienen foco, lo que hace que la automatización sea bastante opaca ya que no se puede obtener una lista de controles en un formulario sobre los que actuar.

¿Alguna experiencia para compartir?

¿Fue útil?

Solución

1.Escribir código comprobable

Primero, deje de escribir lógica de negocios en el código de su formulario.Ese no es el lugar para eso.No se puede probar adecuadamente allí.De hecho, no deberías tener que probar tu formulario en absoluto.Debería ser una vista simple y tonta que responda a la interacción del usuario y luego delega la responsabilidad de responder a esas acciones a otra clase que es comprobable.

¿Cómo haces eso?Familiarizarse con el Patrón Modelo-Vista-Controlador es un buen comienzo.

Model View Controller diagram

no se puede hacer perfectamente en VBA debido al hecho de que obtenemos eventos o interfaces, nunca ambos, pero puedes acercarte bastante.Considere este formulario simple que tiene un cuadro de texto y un botón.

simple form with text box and button

En el código subyacente del formulario, incluiremos el valor del TextBox en una propiedad pública y volveremos a generar cualquier evento que nos interese.

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

Ahora necesitamos un modelo con el que trabajar.Aquí he creado un nuevo módulo de clase llamado MyModel.Aquí está el código que pondremos a prueba.Tenga en cuenta que, naturalmente, comparte una estructura similar a nuestra vista.

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

Finalmente, nuestro controlador lo conecta todo.El controlador escucha los eventos del formulario, comunica los cambios al modelo y activa las rutinas del modelo.

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

Ahora este código se puede ejecutar desde cualquier otro módulo.Para los propósitos de este ejemplo, he usado un módulo estándar.Le recomiendo encarecidamente que lo cree usted mismo utilizando el código que le proporcioné y que lo vea funcionar.

Private controller As FormController

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

Entonces, eso es genial y todo. pero ¿qué tiene que ver con las pruebas? Amigo, tiene todo que ver con las pruebas.Lo que hemos hecho es crear nuestro código. comprobable.En el ejemplo que he proporcionado, no hay ningún motivo para siquiera intentar probar la GUI.Lo único que realmente necesitamos probar es el model.Ahí es donde está toda la verdadera lógica.

Entonces, pasemos al paso dos.

2.Elija un marco de prueba unitario

No hay muchas opciones aquí.La mayoría de los marcos requieren la instalación de complementos COM, muchos textos repetitivos, sintaxis extraña, escribir pruebas como comentarios, etc.Por eso me involucré en construyendo uno yo mismo, por lo que esta parte de mi respuesta no es imparcial, pero intentaré ofrecer un resumen justo de lo que está disponible.

  1. unidad de cuentas

    • Funciona sólo en Access.
    • Requiere que escribas pruebas como un extraño híbrido de comentarios y código.(sin inteligencia para la parte del comentario.
    • Allá es Sin embargo, hay una interfaz gráfica para ayudarte a escribir esas pruebas de aspecto extraño.
    • El proyecto no ha visto ninguna actualización desde 2013.
  2. Unidad VB LiteNo puedo decir que lo haya usado personalmente.Está disponible, pero no ha recibido una actualización desde 2005.

  3. xlUnidadxlUnit no es horrible, pero tampoco es bueno.Es complicado y hay muchos códigos repetitivos.Es lo mejor de lo peor, pero no funciona en Access.Entonces, eso está descartado.

  4. Construye tu propio marco

    He estado allí y hecho eso.Probablemente sea más de lo que la mayoría de la gente quiere abordar, pero es completamente posible crear un marco de pruebas unitarias en código VBA nativo.

  5. Marco de pruebas unitarias del complemento Rubberduck VBE
    Descargo de responsabilidad:Soy uno de los co-desarrolladores.

    Soy parcial, pero este es, con diferencia, mi favorito del grupo.

    • Poco o ningún código de placa de caldera.
    • Intellisense está disponible.
    • El proyecto está activo.
    • Más documentación que la mayoría de estos proyectos.
    • Funciona en la mayoría de las principales aplicaciones de oficina, no sólo en Access.
    • Desafortunadamente, es un complemento COM, por lo que debe instalarse en su máquina.

3.Empezar a escribir pruebas

Entonces, volvamos a nuestro código de la sección 1.El único código que en realidad necesario probar era el MyModel.Reversed() función.Entonces, echemos un vistazo a cómo podría verse esa prueba.(El ejemplo dado utiliza Rubberduck, pero es una prueba simple y podría traducirse al marco de su elección).

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

Directrices para redactar buenos exámenes

  1. Pruebe sólo una cosa a la vez.
  2. Las buenas pruebas sólo fallan cuando se introduce un error en el sistema o los requisitos han cambiado.
  3. No incluya dependencias externas como bases de datos y sistemas de archivos.Estas dependencias externas pueden hacer que las pruebas fallen por motivos fuera de su control.En segundo lugar, ralentizan tus pruebas.Si sus pruebas son lentas, no las ejecutará.
  4. Utilice nombres de pruebas que describan lo que está probando la prueba.No te preocupes si se alarga.Lo más importante es que sea descriptivo.

Sé que la respuesta fue un poco larga y tardía, pero espero que ayude a algunas personas a comenzar a escribir pruebas unitarias para su código VBA.

Otros consejos

Aprecié las respuestas de Knox y David.Mi respuesta estará en algún punto entre las de ellos:solo hazlo formularios que no necesitan ser depurados!

Creo que los formularios deberían usarse exclusivamente como lo que son básicamente, es decir, interfaz gráfica. solo, lo que significa aquí que no es necesario depurarlos.El trabajo de depuración se limita entonces a los módulos y objetos de VBA, lo que es mucho más fácil de manejar.

Por supuesto, existe una tendencia natural a agregar código VBA a formularios y/o controles, especialmente cuando Access le ofrece estos fantásticos eventos "después de la actualización" y "al cambiar", pero definitivamente le aconsejo no para poner cualquier formulario o control de código específico en el módulo del formulario.Esto hace que el mantenimiento y la actualización sean muy costosos, ya que su código se divide entre módulos VBA y módulos de formularios/controles.

Esto no significa que no puedas usarlo más. AfterUpdate ¡evento!Simplemente coloque el código estándar en el evento, como este:

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

Dónde:

  • CTLAfterUpdate Es un procedimiento estándar que se ejecuta cada vez que se actualiza un control en un formulario.

  • CTLAfterUpdateMyForm es un procedimiento específico que se ejecuta cada vez que se actualiza un control en MyForm

Entonces tengo 2 módulos.El primero es

  • utilityFormEvents
    donde tendré mi evento genérico CTLAfterUpdate

El segundo es

  • MyAppFormEvents
    que contiene el código específico de todas las formas específicas de la aplicación MYAPP e incluye el procedimiento CTLAFTERUPDATEMYFORM.Por supuesto, CTLAFTERUPDATEMYFORMA podría no existir si no hay un código específico para ejecutarse.Es por eso que cambiamos el "error" a "reanudar" ...

Elegir una solución tan genérica significa mucho.Significa que está alcanzando un alto nivel de normalización del código (lo que significa un mantenimiento sencillo del código).Y cuando dice que no tiene ningún código específico de formulario, también significa que los módulos de formulario están completamente estandarizados y su producción puede ser automatizado:simplemente diga qué eventos desea gestionar a nivel de formulario/control y defina la terminología de sus procedimientos genéricos/específicos.
Escribe tu código de automatización, de una vez por todas.
Se necesitan unos días de trabajo pero se obtienen resultados interesantes.He estado usando esta solución durante los últimos 2 años y claramente es la correcta:Mis formularios se crean total y automáticamente desde cero con una "Tabla de formularios", vinculada a una "Tabla de controles".
Luego puedo dedicar mi tiempo a trabajar en los procedimientos específicos del formulario, si los hubiera.

La normalización del código, incluso con MS Access, es un proceso largo.¡Pero realmente vale la pena!

Otra ventaja de Access siendo una aplicación COM es que puedes crear un Aplicación .NET para ejecutar y probar una aplicación Access a través de Automatización.La ventaja de esto es que luego puede utilizar un marco de prueba más potente, como Unidad N para escribir pruebas de afirmación automatizadas en una aplicación de Access.

Por lo tanto, si domina C# o VB.NET combinado con algo como NUnit, podrá crear más fácilmente una mayor cobertura de prueba para su aplicación Access.

Aunque esa es una respuesta muy antigua:

Hay unidad de cuentas, un marco de prueba unitario especializado para Microsoft Access.

He sacado una página de La prueba de Python concepto e implementé un procedimiento DocTests en Access VBA.Obviamente, esta no es una solución de prueba unitaria en toda regla.Todavía es relativamente joven, así que dudo que haya solucionado todos los errores, pero creo que está lo suficientemente maduro como para liberarlo en la naturaleza.

Simplemente copie el siguiente código en un módulo de código estándar y presione F5 dentro del Sub para verlo en acción:

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

Copiar, pegar y ejecutar el código anterior desde un módulo llamado Módulo1 produce:

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

Algunas notas rápidas:

  • No tiene dependencias (cuando se usa desde Access)
  • Hace uso de Eval que es una función en el modelo de objetos Access.Application;esto significa tu podría Úselo fuera de Access, pero requeriría crear un objeto Access.Application y calificar completamente el Eval llamadas
  • Hay algunos idiosincrasias asociadas con Eval ser consciente de
  • Sólo se puede utilizar en funciones que devuelven un resultado que cabe en una sola línea.

A pesar de sus limitaciones, sigo pensando que ofrece bastante valor por su inversión.

Editar:Aquí hay una función simple con las "reglas más doctrinales" que la función debe satisfacer.

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

Diseñaría la aplicación para que se realice la mayor cantidad de trabajo posible en consultas y en subrutinas de vba, de modo que sus pruebas puedan consistir en completar bases de datos de prueba, ejecutar conjuntos de consultas de producción y vba en esas bases de datos y luego observar la salida y comparando para asegurarse de que el resultado sea bueno.Obviamente, este enfoque no prueba la GUI, por lo que podría aumentar las pruebas con una serie de scripts de prueba (aquí me refiero a un documento de Word que dice abrir formulario 1 y hacer clic en control 1) que se ejecutan manualmente.

Depende del alcance del proyecto como del nivel de automatización necesario para el aspecto de prueba.

Si está interesado en probar su aplicación Access a un nivel más granular, específicamente el código VBA en sí, entonces Unidad VB Lite es un excelente marco de pruebas unitarias para ese propósito.

Encuentro que hay relativamente pocas oportunidades para realizar pruebas unitarias en mis aplicaciones.La mayor parte del código que escribo interactúa con los datos de la tabla o el sistema de archivo, por lo que es fundamentalmente difícil realizar pruebas unitarias.Al principio, probé un enfoque que puede ser similar a la burla (suplantación de identidad), en la que creaba código que tenía un parámetro opcional.Si se usó el parámetro, entonces el procedimiento usaría el parámetro en lugar de recuperar datos de la base de datos.Es bastante fácil configurar un tipo definido por el usuario que tenga los mismos tipos de campo que una fila de datos y pasarlo a una función.Ahora tengo una forma de incorporar datos de prueba al procedimiento que quiero probar.Dentro de cada procedimiento había algún código que cambiaba la fuente de datos real por la fuente de datos de prueba.Esto me permitió utilizar pruebas unitarias en una variedad más amplia de funciones, utilizando mis propias funciones de prueba unitaria.Escribir una prueba unitaria es fácil, simplemente es repetitivo y aburrido.Al final, dejé las pruebas unitarias y comencé a utilizar un enfoque diferente.

Escribo aplicaciones internas para mí principalmente para poder permitirme esperar hasta que los problemas me encuentren en lugar de tener que tener un código perfecto.Si escribo aplicaciones para clientes, generalmente el cliente no es plenamente consciente de cuánto cuesta el desarrollo de software, por lo que necesito una forma económica de obtener resultados.Escribir pruebas unitarias se trata de escribir una prueba que envíe datos incorrectos a un procedimiento para ver si el procedimiento puede manejarlos adecuadamente.Las pruebas unitarias también confirman que los buenos datos se manejan adecuadamente.Mi enfoque actual se basa en escribir la validación de entrada en cada procedimiento dentro de una aplicación y generar una bandera de éxito cuando el código se haya completado exitosamente.Cada procedimiento de llamada comprueba el indicador de éxito antes de utilizar el resultado.Si ocurre un problema, se informa mediante un mensaje de error.Cada función tiene un indicador de éxito, un valor de retorno, un mensaje de error, un comentario y un origen.Un tipo definido por el usuario (fr para retorno de función) contiene los miembros de datos.Cualquier función dada puede completar solo algunos de los miembros de datos en el tipo definido por el usuario.Cuando se ejecuta una función, normalmente devuelve éxito = verdadero y un valor de retorno y, a veces, un comentario.Si una función falla, devuelve éxito = falso y un mensaje de error.Si una cadena de funciones falla, los mensajes de error se cambian pero el resultado es en realidad mucho más legible que un seguimiento de pila normal.Los orígenes también están encadenados para saber dónde ocurrió el problema.La aplicación rara vez falla e informa con precisión cualquier problema.El resultado es muchísimo mejor que el manejo de errores estándar.

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

Código explicado.eOutputFolder es una enumeración definida por el usuario como se muestra a continuación

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

Estoy usando Enum para pasar parámetros a funciones, ya que esto crea un conjunto limitado de opciones conocidas que una función puede aceptar.Las enumeraciones también proporcionan inteligencia al ingresar parámetros en funciones.Supongo que proporcionan una interfaz rudimentaria para una función.

'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 definido por el usuario, como FunctRet, también proporciona finalización de código, lo que ayuda.Dentro del procedimiento, normalmente almaceno los resultados internos en una variable interna anónima (fr) antes de asignar los resultados a la variable de retorno (GetOutputFolder).Esto hace que los procedimientos de cambio de nombre sean muy fáciles ya que sólo se han cambiado la parte superior e inferior.

En resumen, he desarrollado un marco con ms-access que cubre todas las operaciones que involucran VBA.La prueba está escrita permanentemente en los procedimientos, en lugar de una prueba unitaria de tiempo de desarrollo.En la práctica, el código todavía se ejecuta muy rápido.Tengo mucho cuidado de optimizar las funciones de nivel inferior que se pueden llamar diez mil veces por minuto.Además, puedo utilizar el código en producción a medida que se desarrolla.Si se produce un error, es fácil de usar y la fuente y el motivo del error suelen ser obvios.Los errores se informan desde el formulario de llamada, no desde algún módulo de la capa empresarial, que es un principio importante del diseño de aplicaciones.Además, no tengo la carga de mantener el código de prueba unitaria, lo cual es realmente importante cuando estoy desarrollando un diseño en lugar de codificar un diseño claramente conceptualizado.

Hay algunos problemas potenciales.La prueba no está automatizada y solo se detecta nuevo código incorrecto cuando se ejecuta la aplicación.El código no se parece al código VBA estándar (normalmente es más corto).Aún así, el enfoque tiene algunas ventajas.Es mucho mejor usar un controlador de errores solo para registrar un error, ya que los usuarios generalmente se comunicarán conmigo y me darán un mensaje de error significativo.También puede manejar procedimientos que funcionan con datos externos.JavaScript me recuerda a VBA, me pregunto por qué JavaScript es la tierra de los frameworks y VBA en ms-access no lo es.

Unos días después de escribir este post, encontré una artículo sobre The CodeProject eso se acerca a lo que he escrito arriba.El artículo compara y contrasta el manejo de excepciones y el manejo de errores.Lo que he sugerido anteriormente es similar al manejo de excepciones.

No lo he intentado, pero podrías intentarlo. publique sus formularios de acceso como páginas web de acceso a datos en algo como sharepoint o al igual que las páginas web y luego usar una herramienta como selenio para controlar el navegador con un conjunto de pruebas.

Obviamente, esto no es tan ideal como ejecutar el código directamente a través de pruebas unitarias, pero puede ayudarle en parte del camino.buena suerte

Access es una aplicación COM.Utilice COM, no la API de Windows.para probar cosas en Access.

El mejor entorno de prueba para una aplicación de Access es Access.Todos sus formularios/informes/tablas/códigos/consultas están disponibles, hay un lenguaje de secuencias de comandos similar a MS Test (bueno, probablemente no recuerde MS Test), existe un entorno de base de datos para guardar sus secuencias de comandos de prueba y sus resultados. y las habilidades que desarrolle aquí son transferibles a su aplicación.

Hay buenas sugerencias aquí, pero me sorprende que nadie haya mencionado el procesamiento centralizado de errores.Puede obtener complementos que permiten funciones rápidas/subplantillas y agregar números de línea (yo uso herramientas MZ).Luego envíe todos los errores a una única función donde pueda registrarlos.Luego, también puede interrumpir todos los errores estableciendo un único punto de interrupción.

MS ha dejado de utilizar las páginas de acceso a datos durante bastante tiempo y, en primer lugar, nunca funcionaron realmente (dependían de la instalación de los widgets de Office y funcionaban solo en IE, y solo mal en ese momento).

Es cierto que los controles de acceso que pueden enfocarse solo tienen un identificador de ventana cuando tienen el foco (y aquellos que no pueden enfocarse, como las etiquetas, nunca tienen un identificador de ventana).Esto hace que Access sea particularmente inapropiado para los regímenes de prueba basados ​​en identificadores de ventanas.

De hecho, me pregunto por qué desea realizar este tipo de pruebas en Access.Me parece su dogma básico de programación extrema, y ​​no todos los principios y prácticas de XP se pueden adaptar para trabajar con aplicaciones de Access: clavija cuadrada, orificio redondo.

Entonces, dé un paso atrás y pregúntese qué está tratando de lograr y considere que es posible que necesite utilizar métodos completamente diferentes a aquellos que se basan en enfoques que simplemente no pueden funcionar en Access.

O si ese tipo de prueba automatizada es válida o incluso útil con una aplicación de Access.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top