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?
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.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.
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.-
- 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.
- o projecto não foi actualizado desde 2013.
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.
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.
-
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. -
Rubberduck VBE Add-In ' S Unit Testing Framework
Sou tendenciosa, mas esta é de longe a minha preferida.
Disclaimer: i'm one of the co-devs.- Pouca ou nenhuma placa de caldeira.
Intellisense está disponível.
- o projecto está activo.
- mais documentação do que a maioria destes projectos. Funciona na maioria das aplicações de escritório, não apenas no acesso.
- 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çãoMyModel.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.
- Os bons testes só falham quando há um erro introduzido no sistema ou os requisitos mudaram.
- 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.
- 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.
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árioCTLAfterUpdateMyForm
é um procedimento específico executado cada vez que um controle é atualizado em MyForm
-
utilityFormEvents
onde terei o meu evento Genérico CTLAfterUpdate
-
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.
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.Embora essa seja uma resposta muito antiga:
ExisteAccUnit , um framework de testes de unidade especializado para o Microsoft Access.
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 chamadasEval
Existem algumas idiossincrasias associadas a - It só pode ser usado em funções que retornam um resultado que se encaixa em uma única linha
Eval
para ter conhecimento de
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
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.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.
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.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.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.
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.