Лучший способ протестировать приложение MS Access? - PullRequest
41 голосов
/ 06 сентября 2008

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

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

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

Ответы [ 12 ]

19 голосов
/ 05 февраля 2015

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

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

Как ты это делаешь? Знакомство с шаблоном Model-View-Controller - хорошее начало.

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. AccUnit

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

  3. xlUnit 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.

17 голосов
/ 16 сентября 2008

Я оценил ответы Нокса и Дэвида. Мой ответ будет где-то между ними: просто сделайте форм, которые не нужно отлаживать !

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

5 голосов
/ 12 февраля 2014

Хотя это очень старый ответ:

Существует AccUnit , специализированная инфраструктура модульного тестирования для Microsoft Access.

5 голосов
/ 05 августа 2011

Я взял страницу из концепции doctest в 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, о которых следует знать
  • Он может использоваться только для функций, которые возвращают результат, который помещается в одну строку

Несмотря на свои ограничения, я все же думаю, что это дает немалую выгоду за ваш доллар.

Редактировать : Вот простая функция с «правилами doctest», которой должна удовлетворять функция.

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
5 голосов
/ 16 сентября 2008

Еще одним преимуществом Access, являющимся COM-приложением , является то, что вы можете создать .NET-приложение для запуска и тестирования приложения Access через Automation . Преимущество этого состоит в том, что тогда вы можете использовать более мощную среду тестирования, такую ​​как NUnit , для написания автоматических проверочных утверждений в приложении Access.

Поэтому, если вы хорошо владеете C # или VB.NET в сочетании с чем-то вроде NUnit, тогда вам будет проще создать более широкий тестовый охват для вашего приложения Access.

4 голосов
/ 06 сентября 2008

Я бы спроектировал приложение так, чтобы как можно больше работы выполнялось в запросах и в подпрограммах vba, чтобы ваше тестирование могло состоять из заполнения тестовых баз данных, запуска наборов производственных запросов и vba для этих баз данных, а затем просмотра вывод и сравнение, чтобы убедиться, что вывод хороший. Этот подход, очевидно, не тестирует графический интерфейс, поэтому вы можете дополнить тестирование серией тестовых сценариев (здесь я имею в виду словесный документ с открытой формой 1 и щелчком по элементу управления 1), которые выполняются вручную.

Это зависит от масштаба проекта как уровня автоматизации, необходимого для аспекта тестирования.

2 голосов
/ 25 сентября 2015

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

Я пишу собственные приложения в основном для себя, поэтому я могу позволить себе подождать, пока проблемы найдут меня, вместо того, чтобы иметь идеальный код. Если я пишу приложения для клиентов, как правило, клиент не полностью осознает, сколько стоит разработка программного обеспечения, поэтому мне нужен недорогой способ получения результатов. Написание модульных тестов - это все о написании теста, который помещает неверные данные в процедуру, чтобы посмотреть, сможет ли процедура обработать ее соответствующим образом. Модульные тесты также подтверждают, что хорошие данные обрабатываются надлежащим образом. Мой текущий подход основан на записи проверки ввода в каждую процедуру в приложении и поднятии флага успеха, когда код успешно завершен. Каждая вызывающая процедура проверяет наличие флага перед использованием результата. Если возникает проблема, она сообщается с помощью сообщения об ошибке. Каждая функция имеет флаг успеха, возвращаемое значение, сообщение об ошибке, комментарий и источник. Пользовательский тип (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 для передачи параметров в функции, так как это создает ограниченный набор известных вариантов, которые может принять функция. Перечисления также обеспечивают 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. Тестирование постоянно записывается в процедуры, а не в единичное тестирование времени разработки. На практике код все еще выполняется очень быстро. Я очень тщательно оптимизирую функции нижнего уровня, которые можно вызывать десять тысяч раз в минуту. Кроме того, я могу использовать код в процессе разработки. Если возникает ошибка, она удобна для пользователя, и источник и причина ошибки обычно очевидны. Об ошибках сообщается из формы вызова, а не из какого-либо модуля на бизнес-уровне, который является важным принципом проектирования приложений. Кроме того, у меня нет бремени поддержки кода модульного тестирования, что действительно важно, когда я занимаюсь разработкой дизайна, а не программированием четко концептуализированного проекта.

Есть некоторые потенциальные проблемы. Тестирование не автоматизировано, и новый неверный код обнаруживается только при запуске приложения. Код не похож на стандартный код VBA (обычно он короче). Тем не менее, у подхода есть некоторые преимущества. Гораздо лучше использовать обработчик ошибок только для регистрации ошибки, так как пользователи обычно связываются со мной и дают мне значимое сообщение об ошибке. Он также может обрабатывать процедуры, которые работают с внешними данными. JavaScript напоминает мне VBA, мне интересно, почему JavaScript - это страна фреймворков, а VBA в ms-access - нет.

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

2 голосов
/ 16 сентября 2008

Если вы заинтересованы в тестировании вашего приложения Access на более детальном уровне, в частности, в самом коде VBA, тогда VB Lite Unit является отличной платформой для модульного тестирования для этой цели.

1 голос
/ 19 июня 2009

Здесь есть хорошие предложения, но я удивлен, что никто не упомянул централизованную обработку ошибок. Вы можете получить надстройки, которые позволяют быстро создавать функции / суб-шаблоны и добавлять номера строк (я использую MZ-инструменты). Затем отправьте все ошибки в одну функцию, где вы можете регистрировать их. Затем вы также можете разбить все ошибки, установив одну точку останова.

1 голос
/ 16 сентября 2008

Access - это приложение COM. Используйте COM, а не Windows API. проверить вещи в Access.

Лучшая среда тестирования для приложения доступа - это Access. Все ваши Формы / Отчеты / Таблицы / Код / Запросы доступны, есть язык сценариев, похожий на MS Test (хорошо, вы, вероятно, не помните MS Test), есть среда базы данных для хранения ваших сценариев тестирования и результатов тестирования и навыки, которые вы приобретаете здесь, можно перенести в ваше приложение.

...