A melhor maneira de testar uma aplicação de acesso MS?

com o código, formulários e dados dentro da mesma base de dados, pergunto-me quais são as melhores práticas para conceber um conjunto de testes para uma aplicação Microsoft Access (digamos para Access 2007).

um dos principais problemas com os formulários de teste é que apenas alguns controlos têm um {[[0]} punho e outros controlos só têm um que têm foco, o que torna a automação bastante opaca, uma vez que você não pode obter uma lista de controlos em um formulário para agir.

qualquer experiência partilhar?

Author: Rino Raj, 2008-09-06

12 answers

1. Escrever O Código Testável

Primeiro, pára de escrever lógica empresarial no código do teu formulário. Esse não é o lugar para isso. Não pode ser devidamente testado lá. Na verdade, não devias ter de testar a tua própria forma. Deve ser uma visão simples e burra que responde à interação do Usuário e, em seguida, delega a responsabilidade de responder a essas ações para outra classe que é testável. Como é que fazes isso? Familiarizar-se com o modelo View-Controller padrão é um bom começo.

Model View Controller diagram

Não pode ser feito perfeitamente em VBA devido ao fato de que temos eventos ou interfaces, nunca ambos, mas você pode chegar muito perto. Considere este formulário simples que tem uma caixa de texto e um botão.

simple form with text box and button

No código do formulário por trás, vamos embrulhar o valor da caixa de texto numa Propriedade pública e re-elevar quaisquer eventos que nos interessem.
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
Agora precisamos de um modelo para trabalhar. Aqui criei um novo módulo de classe Chamado MyModel. Aqui está o código que vamos testar. Note que, naturalmente, compartilha uma estrutura semelhante à nossa visão.
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, o nosso controlador liga tudo. O controlador escuta eventos de forma e comunica mudanças no modelo e aciona rotinas do 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
Agora este código pode ser executado a partir de qualquer outro módulo. Para os propósitos deste exemplo, eu usei um módulo padrão. I altamente encorajá-lo a construir isto sozinho usando o código que eu forneci e vê-lo funcionar.
Private controller As FormController

Public Sub Run()
    Set controller = New FormController
    controller.Run
End Sub

Então, isso é óptimo e tudo. mas o que tem a ver com testes?! amigo, tem Tudo tem a ver com testes. O que fizemos foi tornar o nosso código testável. No exemplo que eu dei, não há nenhuma razão que-assim-sempre tentar testar o GUI. A única coisa que realmente precisamos de testar é o model. É onde todos os a verdadeira lógica é. Então, vamos ao segundo passo.

2. Escolha uma estrutura de testes unitários

Não há muitas opções aqui. A maioria dos frameworks requer a instalação de COM Add-ins, muitas placas de caldeira, sintaxe estranha, escrevendo testes como comentários, etc. Foi por isso que me envolvi na construção de um, por isso esta parte da minha resposta não é imparcial, mas vou tentar fazer um resumo justo do que está disponível.
  1. AccUnit

    • obras só no acesso.
    • requer que você escreva testes como um estranho híbrido de comentários e Código. (no intellisense for the comment part.
    • Pronto. é Uma interface gráfica para ajudá-lo a escrever esses estranhos testes.
  2. o projecto não foi actualizado desde 2013.
  3. VB Lite Unit Não posso dizer que o tenha usado pessoalmente. Está lá fora, mas não viu uma actualização desde então. 2005.

  4. XlUnit xlUnit não é horrível, mas também não é bom. É desajeitado e tem muita placa de caldeira. É o melhor dos piores, mas não funciona no acesso. Então, acabou-se.

  5. Constrói a tua própria estrutura

    Já passei por isso. É provavelmente mais do que a maioria das pessoas querem entrar, mas é completamente possível construir uma estrutura de teste de unidade em Vba nativa codigo.
  6. Rubberduck VBE Add-In ' S Unit Testing Framework
    Disclaimer: i'm one of the co-devs.

    Sou tendenciosa, mas esta é de longe a minha preferida.
      Pouca ou nenhuma placa de caldeira.
  7. Intellisense está disponível.
  8. o projecto está activo.
  9. mais documentação do que a maioria destes projectos.
  10. Funciona na maioria das aplicações de escritório, não apenas no acesso.
  11. It é, infelizmente, um Add-In COM, Assim que tem que ser instalado em sua máquina.

3. Iniciar os testes de escrita

Então, de volta ao nosso código da Secção 1. O único código que nós realmente precisávamos para testar era a função MyModel.Reversed(). Então, vamos dar uma olhada em como esse teste poderia ser. (Example given uses Rubberduck, but it's a simple test and could translate into the framework of your choice.)
'@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
Directrizes para escrever bem Testes
    Teste apenas uma coisa de cada vez.
  1. Os bons testes só falham quando há um erro introduzido no sistema ou os requisitos mudaram.
  2. Não inclua dependências externas como bases de dados e sistemas de ficheiros. Estas dependências externas podem fazer os testes falharem por razões fora do seu controle. Em segundo lugar, atrasam os testes. Se os teus testes forem lentos, não os vais executar.
  3. usar nomes de teste que descrevam o que o teste está a testar. Nao preocupa-te se demorar. É muito importante que seja descritivo.

Eu sei que a resposta foi um pouco longa e tardia, mas espero que ajude algumas pessoas a começar a escrever testes de unidade para o seu código VBA.
 23
Author: RubberDuck, 2017-04-13 12:40:33
Gostei das respostas do knox e do david. A minha resposta estará algures entre a deles: formulários que não precisam de ser depurados! Acho que os formulários devem ser usados exclusivamente como o que são basicamente, ou seja, interface gráfica. apenas, o que significa aqui que eles não têm que ser depurados! A tarefa de depuração é então limitada aos módulos e objetos VBA, o que é muito mais fácil de manusear. Há, claro, um tendência natural para adicionar o código VBA aos formulários e / ou controles, especialmente quando o Access lhe oferece esses grandes eventos" após atualização "e" sobre mudança", mas eu definitivamente aconselho você Não para colocar qualquer forma ou controle de código específico no módulo do formulário. Isso faz com que a manutenção e atualização sejam muito custosas, onde o seu código é dividido entre módulos VBA e módulos de formulários/controles.

Isto não significa que não possa usar mais este AfterUpdate evento! Basta colocar o código padrão no caso, como isto:

Private Sub myControl_AfterUpdate()  
    CTLAfterUpdate myControl
    On Error Resume Next
    Eval ("CTLAfterUpdate_MyForm()")
    On Error GoTo 0  
End sub

Em que:

  • CTLAfterUpdate é um procedimento padrão executado cada vez que um controle é atualizado em um formulário

  • CTLAfterUpdateMyForm é um procedimento específico executado cada vez que um controle é atualizado em MyForm

Tenho então dois módulos. O primeiro é
  • utilityFormEvents
    onde terei o meu evento Genérico CTLAfterUpdate
O segundo é
  • MyAppFormEvents
    que contenha o código específico de todos os formulários da aplicação MyApp e incluindo o procedimento CTL após a actualização da Declaração. Claro, depois da actualização da minha forma. pode não existir se não houver um código específico para executar. É por isso que viramos o "On error "para" resume next"...

Escolher uma solução tão genérica significa muito. Significa que você está alcançando um alto nível de normalização de código (o que significa manutenção indolor de código). E quando você diz que você não tem nenhum código específico do formulário, isso também significa que os módulos do formulário são totalmente padronizado, e sua produção pode ser automatizado: basta dizer que eventos você quer gerenciar no nível de forma / Controle, e definir a sua terminologia de procedimentos genéricos/específicos.
Escreva o seu código de automação, de uma vez por todas.
Leva alguns dias de trabalho, mas dá resultados emocionantes. Eu tenho usado esta solução nos últimos 2 anos e é claramente a correta: meus formulários são completa e automaticamente criados a partir do zero com uma "tabela de formulários", ligada a um "Controls Table".
Posso então passar o meu tempo a trabalhar nos procedimentos específicos do formulário, se houver.

A normalização do Código, mesmo com o MS Access, é um processo longo. Mas vale mesmo a pena a dor!
 17
Author: Philippe Grondier, 2014-05-07 12:50:14

Outra vantagem do acesso ser uma aplicação COMé que você pode criar uma aplicação .NET para executar e testar uma aplicação de acesso através de automação. A vantagem disso é que então você pode usar uma estrutura de testes mais poderosa, como NUnit para escrever testes de afirmação automatizados contra um aplicativo de acesso.

Por conseguinte, se for proficiente em C# ou VB.NET combinado com algo como NUnit então você pode mais facilmente criar uma maior cobertura de teste para o seu aplicativo de acesso.
 6
Author: Ray, 2017-05-23 12:32:32

Embora essa seja uma resposta muito antiga:

ExisteAccUnit , um framework de testes de unidade especializado para o Microsoft Access.

 5
Author: paulroho, 2014-02-12 15:20:12

Tirei uma página do conceito do doctest do Python e implementei um procedimento de DocTests no Access VBA. Obviamente, esta não é uma solução completa de teste de unidade. Ainda é relativamente jovem, por isso duvido que tenha resolvido todos os insectos, mas acho que é maduro o suficiente para se libertar na natureza.

Copie o seguinte código para um módulo de código padrão e carregue em F5 no interior do Sub para o ver em acção:

'>>> 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, colar e executar o código acima de um módulo chamado Module1 produz:

Module: 3 - 1 evaluates to:  2  Expected:  0 
Tests passed:  1  of  2

Algumas notas rápidas:

  • não tem dependências (quando usado de dentro do acesso)
  • ele faz uso de Eval que é uma função no acesso.Application object model; isto significa que você poderia usá-lo fora do Access, mas seria necessário criar um Access.Objecto da aplicação e qualificação completa das chamadas Eval
  • Existem algumas idiossincrasias associadas a Eval para ter conhecimento de
  • It só pode ser usado em funções que retornam um resultado que se encaixa em uma única linha
Apesar das suas limitações, ainda acho que dá um grande estrondo ao seu dinheiro.

Edit : aqui está uma função simples com "regras doctest" que a função deve satisfazer.

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
Author: mwolfe02, 2017-05-23 12:02:08

Eu desenharia o aplicativo para ter o máximo de trabalho possível feito em consultas e sub-rotinas vba de modo que seus testes pudessem ser feitos de bases de dados de testes populando, rodando conjuntos das consultas de produção e vba contra essas bases de dados e, em seguida, olhando para a saída e comparando para se certificar de que a saída é boa. Esta abordagem não testa a interface gráfica obviamente, por isso poderá aumentar o teste com uma série de scripts de teste (aqui quero dizer como um documento word que diz open form 1, e clique no controle 1) que são executados manualmente.

([1]}depende do âmbito do projecto como o nível de automatização necessário para o aspecto de ensaio.
 4
Author: Knox, 2008-09-06 12:13:42

Se o seu interesse em testar a sua aplicação de acesso a um nível mais granular especificamente o código VBA em si, então a unidade VB Lite é uma grande estrutura de testes de unidade para esse efeito.

 2
Author: Ray, 2008-09-16 16:54:29
Há boas sugestões aqui, mas surpreende-me que ninguém tenha mencionado o processamento centralizado de erros. Você pode obter addins que permitem uma rápida função / sub templação e para adicionar números de linha (eu uso MZ-tools). Em seguida, envie todos os erros para uma única função onde você pode registrá-los. Você também pode então quebrar em todos os erros, definindo um único ponto de ruptura.
 2
Author: Steve Mallory, 2009-06-18 20:28:43
Acho que há relativamente poucas oportunidades para testes unitários nas minhas aplicações. A maior parte do código que escrevo interage com dados da tabela ou com o sistema de arquivo, por isso é fundamentalmente difícil de testar por unidade. No início, eu tentei uma abordagem que pode ser semelhante à troça (spoofing), onde eu criei um código que tinha um parâmetro opcional. Se o parâmetro foi usado, então o procedimento usaria o parâmetro em vez de obter dados do banco de dados. É muito fácil configurar um utilizador tipo definido que tem os mesmos tipos de campo que uma linha de dados e para passar isso para uma função. Agora tenho uma maneira de colocar os dados do teste no procedimento que quero testar. Dentro de cada procedimento havia um código que trocava a verdadeira fonte de dados para a fonte de dados de teste. Isso me permitiu usar testes de unidade em uma maior variedade de funções, usando minhas próprias funções de teste de unidade. Escrever o teste de unidade é fácil, é apenas repetitivo e chato. No final, desisti de testes de unidade e comecei usando uma abordagem diferente. Escrevo candidaturas internas para mim, principalmente para poder esperar que os problemas me encontrem, em vez de ter de ter o código perfeito. Se eu escrever aplicações para os clientes, geralmente o cliente não está totalmente ciente de quanto os custos de desenvolvimento de software para que eu preciso de uma maneira de baixo custo de obter resultados. Escrever testes de unidade é tudo sobre escrever um teste que empurra dados ruins em um procedimento para ver se o procedimento pode manuseá-lo adequadamente. Testes unitários também confirmar que os dados são tratados adequadamente. Minha abordagem atual é baseada em escrever validação de entrada em cada procedimento dentro de uma aplicação e levantar uma bandeira de sucesso quando o código tiver concluído com sucesso. Cada procedimento de chamada verifica a bandeira de sucesso antes de usar o resultado. Se ocorrer um problema, é reportado através de uma mensagem de erro. Cada função tem uma bandeira de sucesso, um valor de retorno, uma mensagem de erro, um comentário e uma origem. Um tipo definido pelo utilizador (fr para a função return) contém os membros dos dados. Qualquer função dada muitos povoam apenas alguns dos membros de dados no tipo definido pelo Usuário. Quando uma função é executada, ela geralmente retorna sucesso = verdadeiro e um valor de retorno e às vezes um comentário. Se uma função falhar, ela retorna sucesso = false e uma mensagem de erro. Se uma cadeia de funções falhar, as mensagens de erro são daisy alteradas, mas o resultado é realmente muito mais legível que um traço de pilha normal. As origens também estão acorrentadas, por isso sei onde está o problema. registar. A aplicação raramente falha e relata com precisão quaisquer problemas. O resultado é muito melhor do que o tratamento padrão de erros.
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

Código explicado. eOutputFolder é um Enum definido pelo utilizador como abaixo

Public Enum eOutputFolder
    eDefaultDirectory = 1
    eAppPath = 2
    eCustomPath = 3
End Enum

Estou a usar o Enum para passar parâmetros para funções, pois isto cria um conjunto limitado de escolhas conhecidas que uma função pode aceitar. Os Enums também fornecem intellisense quando entram parâmetros em funções. Suponho que eles fornecem uma interface rudimentar para funcao.

'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

Um tipo definido pelo utilizador como um FunctRet também fornece a completação de código que ajuda. Dentro do procedimento, eu normalmente armazeno resultados internos para uma variável interna anônima (fr) antes de atribuir os resultados para a variável return (GetOutputFolder). Isso torna os procedimentos de renomeação muito fácil, uma vez que apenas o topo e o fundo foram alterados.

Em resumo, desenvolvi um quadro com ms-access que abrange todas as operações que envolvem VBA. O teste é permanentemente escrito nos procedimentos, em vez de um teste de unidade de tempo de desenvolvimento. Na prática, o código ainda funciona muito rápido. Tenho muito cuidado em otimizar funções de nível inferior que podem ser chamadas dez mil vezes por minuto. Além disso, posso utilizar o código em Produção Tal como está a ser desenvolvido. Se ocorrer um erro, é fácil de usar e a fonte e a razão para o erro são geralmente óbvias. Erros são relatados a partir do formulário de chamada, não de algum módulo na camada de negócio, que é um importante princípio da concepção da aplicação. Além disso, Não tenho o encargo de manter o código de teste de unidade, o que é realmente importante quando estou evoluindo um projeto em vez de codificar um projeto claramente conceitualizado. Existem alguns problemas potenciais. O teste não é automatizado e novo código ruim só é detectado quando a aplicação é executada. O código não se parece com o código VBA padrão (geralmente é mais curto). Ainda assim, a abordagem tem algumas vantagens. É muito melhor. que usando um manipulador de erros apenas para logar um erro como os usuários normalmente contatar-me-ão e dar-me-ão uma mensagem de erro significativa. Ele também pode lidar com procedimentos que funcionam com dados externos. JavaScript me lembra de VBA, eu me pergunto Por Que JavaScript é a terra dos frameworks e VBA no ms-access não é. Alguns dias depois de escrever este post, encontrei um artigo sobre o Codeprojeto que se aproxima do que escrevi acima. O artigo compara e contrasta o tratamento de excepções e tratamento de erros. O que sugeri acima é semelhante ao tratamento de excepções.
 2
Author: AndrewM, 2015-09-28 00:39:20

Eu não tentei isto, mas você poderia tentar publicar os seus formulários de acesso como páginas web de acesso a dados para algo como sharepoint ou assim como páginas web e então usar uma ferramenta como selénio para conduzir o navegador com um conjunto de testes.

Obviamente, isto não é tão ideal como conduzir o código directamente através de testes de unidade, mas pode ajudar-te a fazer parte do caminho. boa sorte.
 1
Author: Xian, 2008-09-06 12:21:02
O acesso é uma aplicação COM. Use COM,NÃO Windows API. para testar as coisas no acesso.

O melhor ambiente de teste para uma aplicação de acesso é o acesso. Todos os seus formulários/relatórios/tabelas/Código / consultas estão disponíveis, há uma linguagem de scripting semelhante ao MS Test (Ok, você provavelmente não se lembra do MS Test), há um ambiente de banco de dados para manter seus scripts de teste e resultados de teste, e as habilidades que você constrói aqui são transferíveis para a sua aplicação.

 1
Author: , 2008-09-16 02:52:29

As páginas de acesso aos dados foram desactualizadas pelo MS por algum tempo, e nunca realmente funcionou em primeiro lugar (eles eram dependentes dos Widgets do Escritório sendo instalados, e trabalhou apenas no IE, e só mal então).

É verdade que os controles de acesso que podem ficar focados só têm um manípulo da janela quando eles têm o foco (e aqueles que não podem ficar focados, como etiquetas, nunca têm um manípulo da janela em tudo). Isto torna o acesso singularmente inapropriado para o teste de manipulação de janelas regime.

Na verdade, pergunto-me porque quer fazer este tipo de testes no Access. Parece - me o seu dogma básico de programação extrema, e nem todos os princípios e práticas do XP podem ser adaptados para trabalhar com aplicações de acesso -- Peg quadrado, buraco redondo. Então, afaste - se e pergunte a si mesmo o que está a tentar fazer e considere que pode precisar de utilizar métodos completamente diferentes dos que são baseados nas abordagens que não podem funcionar. Acesso.

Ou se esse tipo de teste automatizado é válido ou mesmo útil com uma aplicação de acesso.

 -1
Author: David-W-Fenton, 2012-06-15 16:00:20