С кодом, формами и данными внутри одной и той же базы данных мне интересно, какие лучшие методы для разработки набора тестов для приложения Microsoft Access (скажем, для Access 2007).
Одна из основных проблем с тестовыми формами заключается в том, что только несколько элементов управления имеют дескриптор hwnd
, а другие элементы управления только получают то, что у них есть фокус, что делает автоматизацию довольно непрозрачной, поскольку вы не можете получить список элементов управления в форме действуйте дальше.
Можно ли поделиться опытом?
Во-первых, прекратите писать бизнес-логику в свой код формы. Это не место для этого. Там он не может быть надлежащим образом протестирован. Фактически, вам действительно не нужно тестировать свою форму вообще. Это должно быть мертвое немое простое представление, которое отвечает на взаимодействие с пользователем, а затем делегирует ответственность за ответ на эти действия другому классу, который является проверяемым.
Как вы это делаете? Хорошим началом является знакомство с Model-View-Controller pattern.
В 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
. Это где вся настоящая логика.
Итак, на второй шаг.
Здесь не так много вариантов. Большинство фреймворков требуют установки надстроек COM, большого количества плиты котла, странного синтаксиса, написания тестов в виде комментариев и т.д. Поэтому я включил сам построил, поэтому эта часть моего ответа не является беспристрастной, но я постараюсь дать справедливое резюме того, что доступно.
VB Lite Unit Я не могу сказать, что я лично использовал его. Он там, но не видел обновления с 2005 года.
xlUnit xlUnit не ужасно, но это тоже не хорошо. Он неуклюжий и там много кодовых табличек. Это лучший из худших, но он не работает в Access. Итак, это.
Создайте собственную инфраструктуру
Я был и сделал это. Вероятно, это больше, чем большинство людей хотят войти, но вполне возможно построить платформу Unit Testing в коде Native VBA.
Rubberduck VBE Модуль тестирования модулей расширения VBE
Отказ от ответственности: я являюсь одним из разработчиков.
Я смещен, но это, безусловно, мой любимый кусок.
Итак, вернемся к нашему коду из раздела 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
Я знаю, что ответ был немного длинным, и поздно, но, надеюсь, это помогает некоторым людям начать писать модульные тесты для своего кода 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
Второй -
MyAppFormEvents
Выбор такого общего решения означает много. Это означает, что вы достигаете высокого уровня нормализации кода (что означает безболезненное обслуживание кода). И когда вы говорите, что у вас нет кода конкретной формы, это также означает, что модули формы полностью стандартизованы, а их производство может быть автоматизировано: просто скажите, какие события вы хотите управлять в форме/уровень управления и определите терминологию общих или конкретных процедур.
Напишите код автоматизации один раз для всех.
Это занимает несколько дней работы, но это дает захватывающие результаты. Я использую это решение в течение последних двух лет, и он, безусловно, правильный: мои формы полностью и автоматически создаются с нуля с помощью "Таблицы форм", связанной с "Таблицей элементов управления".
Затем я могу провести время над конкретными процедурами формы, если таковые имеются.
Нормализация кода, даже с MS Access, является длительным процессом. Но это действительно стоит боль!
Я взял страницу из Концепция python и реализовал процедуру DocTests в Access VBA. Это, очевидно, не полномасштабное решение для тестирования модулей. Он все еще относительно молод, поэтому я сомневаюсь, что я разработал все ошибки, но я думаю, что он достаточно зрелый, чтобы выпустить в дикую природу.
Просто скопируйте следующий код в стандартный модуль кода и нажмите F5 внутри Sub, чтобы увидеть его в действии:
'>>> 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
Несколько быстрых заметок:
Eval
, который является функцией в объектной модели Access.Application; это означает, что вы можете использовать его вне Access, но для этого потребуется создать объект Access.Application и полностью квалифицировать вызовы Eval
Eval
, чтобы быть в курсеНесмотря на свои ограничения, я все еще думаю, что это дает вам немного шума для вашего доллара.
Изменить. Вот простая функция с "правилами доктрины", которые должна удовлетворять функция.
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
Еще одно преимущество Доступ к COM-приложению заключается в том, что вы можете создать приложение .NET для запуска и тестирования приложения Access через Automation. Преимущество этого заключается в том, что тогда вы можете использовать более мощную среду тестирования, такую как NUnit, чтобы писать автоматические тесты assert против приложения Access.
Поэтому, если вы владеете либо С#, либо VB.NET в сочетании с чем-то вроде NUnit, тогда вы можете более легко создать более обширное покрытие для вашего приложения Access.
Хотя это очень старый ответ:
Существует AccUnit, специализированная платформа для тестирования Microsoft Access.
Я бы разработал приложение для максимально возможной работы в запросах и подпрограммах vba, чтобы ваше тестирование могло состоять из заполнения тестовых баз данных, выполнения наборов производственных запросов и vba в отношении этих баз данных, а затем просмотра выход и сравнение, чтобы убедиться, что выход хорош. Этот подход не проверяет графический интерфейс, поэтому вы можете увеличить тестирование с помощью ряда тестовых сценариев (здесь я имею в виду как текстовый документ, в котором говорится, что открытая форма 1 и элемент управления кликом 1) выполняются вручную.
Это зависит от объема проекта как уровня автоматизации, необходимого для аспекта тестирования.
Я считаю, что относительно небольшого числа возможностей для модульного тестирования в моих приложениях. Большая часть кода, который я пишу, взаимодействует с табличными данными или файловой системой, в основном не соответствует unit test. Раньше я пробовал подход, который может быть похож на насмешку (spoofing), где я создал код с необязательным параметром. Если этот параметр использовался, тогда процедура будет использовать этот параметр вместо того, чтобы извлекать данные из базы данных. Очень просто настроить тип, определенный пользователем, который имеет те же типы полей, что и ряд данных, и передать это функции. Теперь у меня есть способ получить тестовые данные в процедуре, которую я хочу протестировать. Внутри каждой процедуры был некоторый код, который заменил реальный источник данных для источника тестовых данных. Это позволило мне использовать модульное тестирование для более широкого спектра функций, используя собственные функции тестирования модулей. Письмо unit test легко, оно просто повторяется и скучно. В конце концов, я отказался от модульных тестов и начал использовать другой подход.
Я пишу собственные приложения для себя в основном, поэтому я могу позволить себе подождать, пока проблемы не найдут меня, а не будут иметь совершенный код. Если я пишу приложения для клиентов, как правило, клиент не полностью осознает, сколько стоит разработка программного обеспечения, поэтому мне нужен недорогой способ получения результатов. Тестирование на блокнот - это все, что нужно для написания теста, который подталкивает плохие данные к процедуре, чтобы убедиться, что процедура может обрабатывать ее надлежащим образом. Модульные тесты также подтверждают, что хорошие данные обрабатываются надлежащим образом. Мой текущий подход основан на написании проверки ввода в каждой процедуре в приложении и повышении флага успеха, когда код успешно завершен. Каждая вызывающая процедура проверяет флаг успеха перед использованием результата. Если проблема возникает, она сообщается посредством сообщения об ошибке. Каждая функция имеет флаг успеха, возвращаемое значение, сообщение об ошибке, комментарий и источник. Пользовательский тип (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 - это пользовательский Enum, как указано ниже
Public Enum eOutputFolder
eDefaultDirectory = 1
eAppPath = 2
eCustomPath = 3
End Enum
Я использую Enum для передачи параметров в функции, поскольку это создает ограниченный набор известных вариантов, которые может принять функция. Enums также обеспечивают intellisense при вводе параметров в функции. Я полагаю, что они обеспечивают рудиментарный интерфейс для функции.
'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-доступом, которая охватывает все операции, связанные с VBA. Тестирование постоянно записывается в процедуры, а не время разработки unit test. На практике код все еще работает очень быстро. Я очень осторожен, чтобы оптимизировать функции нижнего уровня, которые можно назвать десять тысяч раз в минуту. Кроме того, я могу использовать код в производстве по мере его разработки. Если ошибка возникает, она удобна для пользователя, и источник и причина ошибки обычно очевидны. Ошибки сообщаются из вызывающей формы, а не из некоторого модуля бизнес-уровня, что является важным принципом разработки приложения. Кроме того, у меня нет бремени поддержания кода модульного тестирования, что очень важно, когда я разрабатываю дизайн, а не кодирую концептуальный дизайн.
Есть некоторые потенциальные проблемы. Тестирование не автоматизировано, и новый плохой код обнаруживается только при запуске приложения. Код не похож на стандартный код VBA (он обычно короче). Тем не менее, этот подход имеет некоторые преимущества. Намного лучше использовать обработчик ошибок для регистрации ошибки, поскольку пользователи обычно свяжутся со мной и дадут мне значимое сообщение об ошибке. Он также может обрабатывать процедуры, которые работают с внешними данными. JavaScript напоминает мне VBA, интересно, почему JavaScript - это земля фреймворков, а VBA в ms-доступе - нет.
Через несколько дней после написания этого сообщения я нашел статью статьи CodeProject, которая приближается к тому, что я написал выше. В статье сравниваются и сравниваются обработка исключений и обработка ошибок. То, что я предложил выше, сродни обработке исключений.
Если вам интересно тестировать ваше приложение Access на более гранулированном уровне, в частности, сам код VBA, тогда VB Lite Unit - отличное модульное тестирование рамки для этой цели.
Здесь есть хорошие предложения, но я удивлен, что никто не упомянул о централизованной обработке ошибок. Вы можете получать добавочные файлы, которые позволяют быстро выполнять функции/вспомогательные шаблоны и добавлять номера строк (я использую MZ-инструменты). Затем отправьте все ошибки в одну функцию, где вы можете их зарегистрировать. Вы также можете разбить все ошибки, установив одну точку останова.
Доступ - это COM-приложение. Используйте COM, а не Windows API. для проверки вещей в Access.
Лучшей тестовой средой для Access Access является Access. Все ваши формы/отчеты/таблицы/код/запросы доступны, есть язык сценариев, аналогичный MS Test (хорошо, вы, вероятно, не помните MS Test), есть среда базы данных для хранения тестовых скриптов и результатов тестов, и навыки, которые вы создаете здесь, могут быть переданы в ваше приложение.
Я не пробовал это, но вы могли бы попытаться опубликовать ваши формы доступа в качестве веб-страниц для доступа к данным как-то вроде sharepoint или как и веб-страницы, а затем используйте инструмент, например selenium для запуска браузера с набором тестов.
Очевидно, что это не так идеально, как управление кодом непосредственно через модульные тесты, но это может помочь вам в этом. удачи
Страницы доступа к данным устарели MS в течение некоторого времени и никогда не работали в первую очередь (они зависели от установленных виджета Office и работали только в IE, и только плохо тогда).
Верно, что элементы управления Access, которые могут получить фокус, имеют только дескриптор окна, когда они имеют фокус (и те, которые не могут получить фокус, например метки, никогда не имеют дескриптора окна). Это делает доступ уникальным несовместимым с режимами тестирования, управляемыми окнами.
В самом деле, я задаю вопрос, почему вы хотите сделать такое тестирование в Access. Это звучит для меня как ваша основная догма экстремального программирования, и не все принципы и практики XP могут быть адаптированы для работы с приложениями Access - квадратной привязкой, круглым отверстием.
Итак, отступите назад и спросите себя, что вы пытаетесь выполнить, и подумайте, что вам может потребоваться использовать совершенно разные методы, чем те, которые основаны на подходах, которые просто не могут работать в Access.
Или действительно ли это автоматическое тестирование действительно или вообще полезно в приложении Access.