Вопрос

Поскольку код, формы и данные находятся в одной базе данных, мне интересно, как лучше всего разработать набор тестов для приложения Microsoft Access (скажем, для Access 2007).

Одна из основных проблем с формами тестирования заключается в том, что лишь некоторые элементы управления имеют hwnd дескриптор и другие элементы управления получают только тот, у которого есть фокус, что делает автоматизацию довольно непрозрачной, поскольку вы не можете получить список элементов управления в форме, чтобы действовать над ними.

Есть опыт, которым можно поделиться?

Это было полезно?

Решение

1.Напишите тестируемый код

Во-первых, перестаньте писать бизнес-логику в коде вашей формы.Это не место для этого.Там его невозможно протестировать должным образом.На самом деле вам вообще не нужно тестировать саму форму.Это должно быть совершенно тупое простое представление, которое реагирует на взаимодействие с пользователем, а затем делегирует ответственность за реагирование на эти действия другому классу, который является проверяемый.

Как ты это делаешь?Знакомство с Шаблон Модель-Представление-Контроллер это хорошее начало.

Model View Controller diagram

Это невозможно сделать в совершенстве в VBA из-за того, что мы получаем либо события, либо интерфейсы, но не то и другое, но вы можете подобраться довольно близко.Рассмотрим эту простую форму с текстовым полем и кнопкой.

simple form with text box and button

В коде формы мы обернем значение 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, большого количества шаблонов, странного синтаксиса, написания тестов в виде комментариев и т. д.Вот почему я ввязался в строю один сам, поэтому эта часть моего ответа не является беспристрастной, но я постараюсь дать объективное описание того, что доступно.

  1. АккЮнит

    • Работает только в Access.
    • Требует от вас написания тестов в виде странного гибрида комментариев и кода.(нет смысла для части комментариев.
    • Там является графический интерфейс, который поможет вам писать эти странно выглядящие тесты.
    • Проект не видел обновлений с 2013 года.
  2. Модуль VB LiteНе могу сказать, что лично использовал.Он существует, но не обновлялся с 2005 года.

  3. кслюнитxlUnit не так уж и плох, но и не хорош.Это неуклюже, и в нем много шаблонного кода.Это лучшее из худшего, но оно не работает в Access.Итак, это закончилось.

  4. Создайте свою собственную структуру

    у меня есть был там и сделал это.Вероятно, это больше, чем хотелось бы большинству людей, но вполне возможно создать среду модульного тестирования в собственном коде VBA.

  5. Платформа модульного тестирования надстройки 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

Рекомендации по написанию хороших тестов

  1. Тестируйте только одну вещь за раз.
  2. Хорошие тесты терпят неудачу только тогда, когда в системе появилась ошибка или изменились требования.
  3. Не включайте внешние зависимости, такие как базы данных и файловые системы.Эти внешние зависимости могут привести к сбою тестов по причинам, не зависящим от вас.Во-вторых, они замедляют ваши тесты.Если ваши тесты медленные, вы не будете их запускать.
  4. Используйте названия тестов, которые описывают, что тестирует тест.Не волнуйтесь, если это затянется.Самое главное, чтобы оно было описательным.

Я знаю, что ответ был немного длинным и запоздалым, но, надеюсь, он поможет некоторым людям начать писать модульные тесты для своего кода 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.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top