Лучший способ протестировать приложение MS Access?
-
09-06-2019 - |
Вопрос
Поскольку код, формы и данные находятся в одной базе данных, мне интересно, как лучше всего разработать набор тестов для приложения Microsoft Access (скажем, для Access 2007).
Одна из основных проблем с формами тестирования заключается в том, что лишь некоторые элементы управления имеют hwnd
дескриптор и другие элементы управления получают только тот, у которого есть фокус, что делает автоматизацию довольно непрозрачной, поскольку вы не можете получить список элементов управления в форме, чтобы действовать над ними.
Есть опыт, которым можно поделиться?
Решение
1.Напишите тестируемый код
Во-первых, перестаньте писать бизнес-логику в коде вашей формы.Это не место для этого.Там его невозможно протестировать должным образом.На самом деле вам вообще не нужно тестировать саму форму.Это должно быть совершенно тупое простое представление, которое реагирует на взаимодействие с пользователем, а затем делегирует ответственность за реагирование на эти действия другому классу, который является проверяемый.
Как ты это делаешь?Знакомство с Шаблон Модель-Представление-Контроллер это хорошее начало.
Это невозможно сделать в совершенстве в 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
Так что это здорово и все но какое это имеет отношение к тестированию?! Друг, есть все иметь дело с тестированием.Мы сделали наш код проверяемый.В приведенном мной примере нет никаких причин даже пытаться протестировать графический интерфейс.Единственное, что нам действительно нужно проверить, это model
.Вот где вся настоящая логика.
Итак, переходим ко второму шагу.
2.Выберите фреймворк модульного тестирования
Здесь не так уж много вариантов.Большинство платформ требуют установки надстроек COM, большого количества шаблонов, странного синтаксиса, написания тестов в виде комментариев и т. д.Вот почему я ввязался в строю один сам, поэтому эта часть моего ответа не является беспристрастной, но я постараюсь дать объективное описание того, что доступно.
-
- Работает только в Access.
- Требует от вас написания тестов в виде странного гибрида комментариев и кода.(нет смысла для части комментариев.
- Там является графический интерфейс, который поможет вам писать эти странно выглядящие тесты.
- Проект не видел обновлений с 2013 года.
Модуль VB LiteНе могу сказать, что лично использовал.Он существует, но не обновлялся с 2005 года.
кслюнитxlUnit не так уж и плох, но и не хорош.Это неуклюже, и в нем много шаблонного кода.Это лучшее из худшего, но оно не работает в Access.Итак, это закончилось.
Создайте свою собственную структуру
у меня есть был там и сделал это.Вероятно, это больше, чем хотелось бы большинству людей, но вполне возможно создать среду модульного тестирования в собственном коде VBA.
Платформа модульного тестирования надстройки Rubberduck VBE
Отказ от ответственности:Я один из соавторов.Я предвзят, но это, безусловно, мой фаворит из всех.
- Практически нет кода котла.
- IntelliSense доступен.
- Проект активен.
- Больше документации, чем у большинства этих проектов.
- Он работает в большинстве основных офисных приложений, а не только в Access.
- К сожалению, это надстройка COM, поэтому ее необходимо установить на ваш компьютер.
3.Начни писать тесты
Итак, вернемся к нашему коду из раздела 1.Единственный код, который мы Действительно необходимо проверить, было ли MyModel.Reversed()
функция.Итак, давайте посмотрим, как может выглядеть этот тест.(В приведенном примере используется Rubberduck, но это простой тест, который можно перенести в среду по вашему выбору.)
'@TestModule
Private Assert As New Rubberduck.AssertClass
'@TestMethod
Public Sub ReversedReversesCorrectly()
Arrange:
Dim model As New MyModel
Const original As String = "Hello"
Const expected As String = "olleH"
Dim actual As String
model.Text = original
Act:
actual = model.Reversed
Assert:
Assert.AreEqual expected, actual
End Sub
Рекомендации по написанию хороших тестов
- Тестируйте только одну вещь за раз.
- Хорошие тесты терпят неудачу только тогда, когда в системе появилась ошибка или изменились требования.
- Не включайте внешние зависимости, такие как базы данных и файловые системы.Эти внешние зависимости могут привести к сбою тестов по причинам, не зависящим от вас.Во-вторых, они замедляют ваши тесты.Если ваши тесты медленные, вы не будете их запускать.
- Используйте названия тестов, которые описывают, что тестирует тест.Не волнуйтесь, если это затянется.Самое главное, чтобы оно было описательным.
Я знаю, что ответ был немного длинным и запоздалым, но, надеюсь, он поможет некоторым людям начать писать модульные тесты для своего кода VBA.
Другие советы
Я оценил ответы Нокса и Дэвида.Мой ответ будет где-то между их ответами:просто сделай формы, которые не требуют отладки!
Я считаю, что формы следует использовать исключительно такими, какие они есть, то есть графическим интерфейсом. только, то есть их не нужно отлаживать!Тогда задание по отладке ограничивается вашими модулями и объектами VBA, что намного проще.
Конечно, существует естественная тенденция добавлять код VBA в формы и/или элементы управления, особенно когда Access предлагает вам эти замечательные события «после обновления» и «при изменении», но я определенно советую вам нет чтобы поместить любую форму или определенный код управления в модуль формы.Это делает дальнейшее обслуживание и обновление очень дорогостоящим, поскольку ваш код разделен между модулями VBA и модулями форм/элементов управления.
Это не значит, что вы больше не можете использовать это AfterUpdate
событие!Просто добавьте в событие стандартный код, например:
Private Sub myControl_AfterUpdate()
CTLAfterUpdate myControl
On Error Resume Next
Eval ("CTLAfterUpdate_MyForm()")
On Error GoTo 0
End sub
Где:
CTLAfterUpdate
стандартная процедура, запускаемая каждый раз при обновлении элемента управления в формеCTLAfterUpdateMyForm
это определенная процедура, запускаемая каждый раз при обновлении элемента управления в MyForm
У меня тогда 2 модуля.Первый из них
utilityFormEvents
где у меня будет общее событие CTLAfterUpdate
Второй
MyAppFormEvents
содержащий конкретный код всех конкретных форм приложения MYAPP и включающего процедуру CtlafterUpdatemyform.Конечно, ctlafterupdatemyform может не существовать, если нет конкретного кода для запуска.Вот почему мы поворачиваем «на ошибку», чтобы «возобновить следующее» ...
Выбор такого универсального решения означает очень многое.Это означает, что вы достигаете высокого уровня нормализации кода (то есть безболезненного обслуживания кода).И когда вы говорите, что у вас нет какого-либо кода, специфичного для формы, это также означает, что модули форм полностью стандартизированы, и их производство может быть автоматизированный:просто скажите, какими событиями вы хотите управлять на уровне формы/элемента управления, и определите терминологию общих/конкретных процедур.
Напишите свой код автоматизации раз и навсегда.
Работа занимает несколько дней, но дает потрясающие результаты.Я использую это решение последние 2 года, и оно явно правильное:мои формы полностью и автоматически создаются с нуля с помощью «Таблицы форм», связанной с «Таблицей элементов управления».
Затем я могу потратить свое время на работу над конкретными процедурами формы, если таковые имеются.
Нормализация кода, даже при использовании MS Access, — длительный процесс.Но это действительно стоит боли!
Еще одно преимущество Доступ является COM-приложением заключается в том, что вы можете создать Приложение .NET для запуска и тестирования приложения Access с помощью автоматизации..Преимущество этого в том, что тогда вы можете использовать более мощную среду тестирования, например НУнит для написания автоматических тестов утверждения для приложения Access.
Таким образом, если вы владеете C# или VB.NET в сочетании с чем-то вроде NUnit, вам будет проще создать большее тестовое покрытие для вашего приложения Access.
Хотя это очень старый ответ:
Есть АккЮнит, специализированная платформа модульного тестирования для Microsoft 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
Несколько быстрых заметок:
- Он не имеет зависимостей (при использовании из Access)
- Он использует
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
Я бы спроектировал приложение так, чтобы в запросах и подпрограммах vba выполнялось как можно больше работы, чтобы ваше тестирование могло состоять из заполнения тестовых баз данных, выполнения наборов рабочих запросов и vba для этих баз данных, а затем просмотра выходных данных и сравнивая, чтобы убедиться, что результат хороший.Очевидно, что этот подход не тестирует графический интерфейс, поэтому вы можете дополнить тестирование серией тестовых сценариев (здесь я имею в виду текстовый документ, в котором написано «Открыть форму 1» и «Управление щелчком мыши 1»), которые выполняются вручную.
Это зависит от масштаба проекта, а также от уровня автоматизации, необходимого для аспекта тестирования.
Если вы заинтересованы в тестировании вашего приложения Access на более детальном уровне, в частности, самого кода VBA, тогда Модуль VB Lite — отличная среда модульного тестирования для этой цели.
Я обнаружил, что в моих приложениях относительно мало возможностей для модульного тестирования.Большая часть кода, который я пишу, взаимодействует с данными таблиц или файловой системой, поэтому его принципиально сложно протестировать.Вначале я пробовал подход, который может быть похож на издевательство (подделку), когда я создавал код с необязательным параметром.Если параметр использовался, процедура будет использовать его вместо извлечения данных из базы данных.Довольно легко настроить определяемый пользователем тип, который имеет те же типы полей, что и строка данных, и передать его в функцию.Теперь у меня есть способ ввести тестовые данные в процедуру, которую я хочу протестировать.Внутри каждой процедуры был некоторый код, который заменял реальный источник данных источником тестовых данных.Это позволило мне использовать модульное тестирование для более широкого спектра функций, используя мои собственные функции модульного тестирования.Написание модульного теста легко, это просто однообразие и скучность.В конце концов я отказался от модульных тестов и начал использовать другой подход.
Я пишу собственные приложения в основном для себя, чтобы позволить себе подождать, пока проблемы сами не найдут меня, вместо того, чтобы иметь идеальный код.Если я пишу приложения для клиентов, обычно клиент не полностью осознает, сколько стоит разработка программного обеспечения, поэтому мне нужен недорогой способ получения результатов.Написание модульных тестов — это написание теста, который передает неверные данные в процедуру, чтобы проверить, сможет ли процедура их обработать должным образом.Модульные тесты также подтверждают, что хорошие данные обрабатываются надлежащим образом.Мой текущий подход основан на записи проверки ввода в каждую процедуру внутри приложения и поднятии флага успеха при успешном завершении кода.Каждая вызывающая процедура проверяет флаг успеха перед использованием результата.Если возникает проблема, о ней сообщается в виде сообщения об ошибке.Каждая функция имеет флаг успеха, возвращаемое значение, сообщение об ошибке, комментарий и источник.Определенный пользователем тип (fr для возврата функции) содержит элементы данных.Любая данная функция может заполнять только некоторые элементы данных определенного пользователем типа.Когда функция запускается, она обычно возвращает успех = true и возвращаемое значение, а иногда и комментарий.Если функция завершается с ошибкой, она возвращает успех = 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 для передачи параметров функциям, поскольку это создает ограниченный набор известных вариантов, которые может принять функция.Перечисления также обеспечивают интеллектуальный подход при вводе параметров в функции.Я предполагаю, что они предоставляют элементарный интерфейс для функции.
'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.Тестирование постоянно записывается в процедуры, а не является модульным тестом времени разработки.На практике код по-прежнему выполняется очень быстро.Я очень тщательно оптимизирую функции нижнего уровня, которые можно вызывать десять тысяч раз в минуту.Более того, я могу использовать код в производстве по мере его разработки.Если возникает ошибка, это удобно для пользователя, а источник и причина ошибки обычно очевидны.Об ошибках сообщается из вызывающей формы, а не из какого-либо модуля бизнес-уровня, что является важным принципом проектирования приложений.Более того, у меня нет бремени поддержки кода модульного тестирования, что действительно важно, когда я разрабатываю дизайн, а не кодирую четко концептуальный проект.
Есть некоторые потенциальные проблемы.Тестирование не автоматизировано, и новый плохой код обнаруживается только при запуске приложения.Код не похож на стандартный код VBA (обычно он короче).Тем не менее, этот подход имеет некоторые преимущества.Гораздо лучше использовать обработчик ошибок только для регистрации ошибки, поскольку пользователи обычно связываются со мной и выдают значимое сообщение об ошибке.Он также может обрабатывать процедуры, работающие с внешними данными.JavaScript напоминает мне VBA, и мне интересно, почему JavaScript — это страна фреймворков, а VBA в MS-доступе — нет.
Через несколько дней после написания этого поста я нашел статья о CodeProject это близко к тому, что я написал выше.В статье сравниваются и противопоставляются обработка исключений и обработка ошибок.То, что я предложил выше, похоже на обработку исключений.
Я этого не пробовал, но вы можете попытаться опубликуйте свои формы доступа в виде веб-страниц доступа к данным на чем-то вроде SharePoint или так же, как веб-страницы а затем используйте такой инструмент, как селен для управления браузером с помощью набора тестов.
Конечно, это не так идеально, как прогон кода напрямую через модульные тесты, но это может помочь вам частично.удачи
Access — это COM-приложение.Используйте COM, а не Windows API.тестировать вещи в Access.
Лучшей тестовой средой для приложения Access является Access.Все ваши формы/отчеты/таблицы/коды/запросы доступны, существует язык сценариев, похожий на MS Test (хорошо, вы, вероятно, не помните MS Test), существует среда базы данных для хранения ваших тестовых сценариев и результатов тестов, и навыки, которые вы приобретете здесь, можно перенести в ваше приложение.
Здесь есть хорошие предложения, но я удивлен, что никто не упомянул централизованную обработку ошибок.Вы можете получить надстройки, которые позволяют быстро создавать функции/подшаблоны и добавлять номера строк (я использую MZ-tools).Затем отправьте все ошибки в одну функцию, где вы сможете их зарегистрировать.Вы также можете затем выполнить прерывание на всех ошибках, установив одну точку останова.
Страницы доступа к данным уже давно устарели в MS и никогда по-настоящему не работали (они зависели от установленных виджетов Office и работали только в IE, и то плохо).
Это правда, что элементы управления доступом, которые могут получить фокус, имеют дескриптор окна только тогда, когда у них есть фокус (а те, которые не могут получить фокус, например метки, вообще никогда не имеют дескриптора окна).Это делает Access совершенно неподходящим для режимов тестирования, управляемых дескрипторами окон.
Действительно, я задаюсь вопросом, почему вы хотите проводить такое тестирование в Access.Для меня это звучит как ваша основная догма экстремального программирования, и не все принципы и методы XP могут быть адаптированы для работы с приложениями Access - квадратный колышек, круглое отверстие.
Итак, сделайте шаг назад и спросите себя, чего вы пытаетесь достичь, и подумайте, что вам, возможно, придется использовать совершенно другие методы, чем те, которые основаны на подходах, которые просто не могут работать в Access.
Или вопрос о том, действителен ли вообще такой вид автоматического тестирования или даже полезен ли он для приложения Access.