¿La mejor forma de probar una aplicación de MS Access?

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 (digamos para Access 2007).

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

¿Alguna experiencia para compartir?

1. Escribe el código testable

Primero, deje de escribir lógica comercial en el código de su formulario. Ese no es el lugar para eso. No puede ser probado apropiadamente allí. De hecho, realmente no debería tener que probar su formulario en absoluto. Debería ser una vista muda, muda y simple, que responda a la interacción del usuario y luego delegue la responsabilidad de responder a esas acciones en otra clase que sea comprobable.

¿Cómo haces eso? Familiarizarse con el patrón Model-View-Controller es un buen comienzo.

Diagrama de Model View Controller

No se puede hacer perfectamente en VBA debido a que obtenemos eventos o interfaces, nunca ambos, pero puedes acercarte mucho. Considere esta forma simple que tiene un cuadro de texto y un botón.

forma simple con cuadro de texto y botón

En el código del formulario que se encuentra detrás, ajustaremos el valor del TextBox en una propiedad pública y volveremos a generar los eventos que nos interesen.

 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 para 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 eventos de formulario y 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 que construya esto usted mismo utilizando el código que he proporcionado y que funcione.

 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 hacer que nuestro código sea comprobable . En el ejemplo que proporcioné, no hay ninguna razón para intentar siquiera probar la GUI. Lo único que realmente necesitamos probar es el model . Ahí es donde está toda la lógica real.

Entonces, en el paso dos.

2. Elija un marco de prueba de la unidad

No hay muchas opciones aquí. La mayoría de los marcos requieren la instalación de complementos COM, mucha placa de caldera, syntax extraña, pruebas de escritura como comentarios, etc. Es por eso que me involucré en la creación de uno yo mismo , así que esta parte de mi respuesta no es imparcial, pero lo intentaré para dar un resumen justo de lo que está disponible.

  1. AccUnit

    • Funciona solo en Access.
    • Requiere que escriba pruebas como un extraño híbrido de comentarios y código. (no intellisense para la parte de comentario.
    • 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 Lite No puedo decir que la haya usado personalmente. Está disponible, pero no ha visto una actualización desde 2005.

  3. xlUnit xlUnit no es horrible, pero tampoco es bueno. Es torpe y hay un montón de código de placa de caldera. Es lo mejor de lo peor, pero no funciona en Access. Entonces, eso está fuera.

  4. Crea tu propio marco

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

  5. El marco de prueba de la unidad del complemento Rubberduck VBE
    Descargo de responsabilidad: soy uno de los co-desarrolladores .

    Soy parcial, pero este es de lejos 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 Office, no solo en Access.
    • Desafortunadamente, es un complemento COM, por lo que debe instalarse en su máquina.

3. Comience a escribir pruebas

Por lo tanto, volvamos a nuestro código de la sección 1. El único código que realmente necesitamos para probar es la función MyModel.Reversed() . Entonces, echemos un vistazo a cómo se vería esa prueba. (El ejemplo dado utiliza Rubberduck, pero es una prueba simple y podría traducirse en el 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 

Pautas para escribir buenas pruebas

  1. Solo prueba una cosa a la vez.
  2. Las buenas pruebas solo 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 razones fuera de su control. En segundo lugar, disminuyen tus pruebas. Si tus pruebas son lentas, no las ejecutarás.
  4. Use nombres de prueba que describan lo que la prueba está probando. No te preocupes si se alarga. Es más importante 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.

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

Creo que los formularios deben usarse exclusivamente como lo que son básicamente, es decir, solo como interfaz gráfica, lo que significa que no es necesario depurarlos. El trabajo de depuración se limita a sus módulos y objetos VBA, 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 excelentes eventos “después de la Actualización” y “en cambio”, pero definitivamente le aconsejo que no ponga ningún formulario o controle código específico. en el módulo del formulario. Esto hace que el mantenimiento y la actualización sean muy costosos, donde el código se divide entre los módulos de VBA y los módulos de formularios / controles.

¡Esto no significa que no puedas usar más este evento AfterUpdate ! Simplemente ponga el código estándar en el evento, así:

 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

Tengo entonces 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, CTLAfterUpdateMyForm podría no existir si no hay un código específico para ejecutar. Es por eso que activamos el “En error” para “reanudar el próximo” …

Elegir una solución tan genérica significa mucho. Significa que está alcanzando un alto nivel de normalización de código (es decir, mantenimiento sin problemas del código). Y cuando dice que no tiene ningún código específico, también significa que los módulos de formulario están completamente estandarizados y su producción puede automatizarse : simplemente diga qué eventos desea gestionar en el nivel de formulario / control, y defina su terminología de procedimientos generics / específicos.
Escriba su código de automatización, una vez por todas.
Toma unos días de trabajo pero da resultados emocionantes. He estado usando esta solución durante los últimos 2 años y es claramente la correcta: mis formularios se crean completa y automáticamente desde cero con una “Tabla de formularios”, vinculada a una “Tabla de controles”.
Entonces puedo dedicar mi tiempo a trabajar en los procedimientos específicos del formulario, si corresponde.

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

Otra ventaja de que Access es una aplicación COM es que puede crear una aplicación .NET para ejecutar y probar una aplicación de Access a través de la automatización . La ventaja de esto es que luego puede usar un marco de prueba más poderoso como NUnit para escribir pruebas automáticas de afirmación contra una aplicación de Access.

Por lo tanto, si dominas C # o VB.NET combinados con algo así como NUnit, entonces puedes crear más fácilmente una mayor cobertura de prueba para tu aplicación de Access.

Aunque esa es una respuesta muy antigua:

Existe AccUnit , un marco especializado de pruebas de unidades para Microsoft Access.

Saqué una página del concepto más preciso de Python e implementé un procedimiento de DocTests en Access VBA. Obviamente, esta no es una solución de pruebas de unidades en toda regla. Todavía es relativamente joven, así que dudo que haya resuelto todos los errores, pero creo que es lo suficientemente maduro como para salir al air libre.

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 Module1 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 que podría usarlo fuera de Access pero requeriría la creación de un objeto Access.Application y la calificación completa de las llamadas Eval
  • Hay algunas idiosincrasias asociadas con Eval para tener en cuenta
  • Solo se puede usar en funciones que devuelven un resultado que se ajusta a una sola línea

A pesar de sus limitaciones, sigo creyendo que ofrece un buen rendimiento por tu dinero.

Editar : Aquí hay una función simple con “reglas de doctest” que la función debe cumplir.

 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 tener tanto trabajo como sea posible en consultas y en subrutinas vba, de modo que sus pruebas podrían estar compuestas de bases de datos de prueba, ejecutando conjuntos de consultas de producción y vba contra esas bases de datos y luego mirando la salida y comparando para asegurarse de que la salida sea buena. Este enfoque no prueba la GUI obviamente, por lo que podría boost 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 scope del proyecto como el nivel de automatización necesario para el aspecto de prueba.

Si está interesado en probar su aplicación de Access a un nivel más detallado, específicamente el código VBA, la unidad VB Lite es un gran marco de prueba de unidades para tal fin.

Encuentro que hay relativamente pocas oportunidades para 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 de probar. Al principio, probé un enfoque que puede ser similar a la burla (suplantación) donde creé un 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 buscar 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 manera de obtener datos de prueba en el procedimiento que quiero probar. Dentro de cada procedimiento, había algún código que intercambiaba la fuente de datos real para la fuente de datos de prueba. Esto me permitió usar pruebas unitarias en una variedad más amplia de funciones, usando mis propias funciones de prueba de unidades. La prueba de unidad de escritura es fácil, solo es repetitiva y aburrida. Al final, abandoné las pruebas unitarias y empecé a usar un enfoque diferente.

Escribo aplicaciones internas para mí principalmente, así que puedo 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 está al tanto de cuánto cuesta el desarrollo de software, así que necesito una forma de obtener resultados a bajo costo. Escribir pruebas unitarias tiene que ver con escribir una prueba que envía datos incorrectos en un procedimiento para ver si el procedimiento puede manejarlo de manera adecuada. Las pruebas unitarias también confirman que los buenos datos se manejan de manera adecuada. Mi enfoque actual se basa en escribir validación de entrada en cada procedimiento dentro de una aplicación y generar una bandera de éxito cuando el código se ha completado con éxito. Cada procedimiento de llamada busca el indicador de éxito antes de usar el resultado. Si ocurre un problema, se informa por medio de 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 el retorno de la función) contiene los miembros de datos. Cualquier función dada llena muchos de los miembros de datos en el tipo definido por el usuario. Cuando se ejecuta una función, por lo general devuelve success = true y un valor de retorno y, a veces un comentario. Si una función falla, devuelve success = false y un mensaje de error. Si una cadena de funciones falla, los mensajes de error se cambian en margarita, pero el resultado es en realidad mucho más legible que un seguimiento de stack normal. Los orígenes también están encadenados, así que sé dónde ocurrió el problema. La aplicación rara vez se cuelga 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. 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 

Código explicado eOutputFolder es un Enum definido 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 las funciones, ya que esto crea un conjunto limitado de opciones conocidas que una función puede aceptar. Los enumeraciones también proporcionan intellisense al ingresar parámetros en las 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 la finalización de código que ayuda. Dentro del procedimiento, generalmente almacé los resultados internos en una variable interna anónima (fr) antes de asignar los resultados a la variable de devolución (GetOutputFolder). Esto hace que los procedimientos de cambio de nombre sean muy fáciles ya que solo se deben cambiar la parte superior e inferior.

Entonces, en resumen, he desarrollado un framework con ms-access que cubre todas las operaciones que involucran a VBA. La prueba está escrita permanentemente en los procedimientos, en lugar de una prueba de unidad de tiempo de desarrollo. En la práctica, el código todavía corre muy rápido. Tengo mucho cuidado para optimizar las funciones de nivel inferior que se pueden llamar diez mil veces por minuto. Además, puedo usar el código en producción mientras 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 en la capa empresarial, que es un principio importante del diseño de la aplicación. Además, no tengo la carga de mantener el código de pruebas unitarias, que es realmente importante cuando estoy desarrollando un diseño en lugar de codificar un diseño claramente conceptualizado.

Hay algunos problemas potenciales. Las pruebas no son automáticas y el nuevo código incorrecto solo se detecta cuando se ejecuta la aplicación. El código no se parece al código VBA estándar (generalmente es más corto). Aún así, el enfoque tiene algunas ventajas. Es mucho mejor que usar un controlador de errores solo para registrar un error ya que los usuarios generalmente me contactarán y 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 esta publicación, encontré un artículo sobre The CodeProject que se acerca mucho a lo que escribí 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 he intentado esto, pero podría intentar publicar sus formularios de acceso como páginas web de acceso a datos en algo parecido a compartir o simplemente como páginas web y luego usar una herramienta como el selenium para conducir el navegador con un conjunto de pruebas.

Obviamente, esto no es tan ideal como conducir el código directamente a través de pruebas unitarias, pero puede que te lleve a una parte del camino. buena suerte

El acceso es una aplicación COM. Use COM, no API de Windows. para probar cosas en Access.

El mejor entorno de prueba para una aplicación de acceso es acceso. Todos sus Formularios / Informes / Tablas / Código / Consultas están disponibles, existe un lenguaje de scripts similar al de MS Test (vale, probablemente no recuerde MS Test), existe un entorno de base de datos para guardar los scripts de prueba y los resultados de las pruebas, y las habilidades que construyes aquí son transferibles a tu aplicación.

Aquí hay buenas sugerencias, pero me sorprende que nadie mencionó el procesamiento centralizado de errores. Puede obtener complementos que permiten funciones rápidas / plantillas secundarias y para agregar números de línea (utilizo herramientas MZ). A continuación, envíe todos los errores a una única función donde pueda registrarlos. También puede romper todos los errores configurando un único punto de interrupción.

Las páginas de acceso a datos han sido desaprobadas por MS por bastante tiempo, y nunca funcionaron en primer lugar (dependían de que se instalaran los widgets de Office, y funcionaran solo en IE, y solo de mala manera).

Es cierto que los controles de Acceso que pueden obtener el foco solo tienen un identificador de ventana cuando tienen el foco (y aquellos que no pueden enfocarse, como las tags, nunca tienen un identificador de ventana en absoluto). Esto hace que Access sea singularmente inadecuado para los regímenes de prueba manejados por la ventana.

De hecho, me pregunto por qué quieres hacer este tipo de pruebas en Access. Me parece que es un dogma básico de Extreme Programming, y no todos los principios y prácticas de XP se pueden adaptar para trabajar con aplicaciones Access: Square Peg, Round Hole.

Por lo tanto, 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 los que se basan en los 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.