Debug.Assert()
更好的东西,因此我开始构建此框架。当前缺少大量功能,但是由于我是单元测试和接口的新手,所以我不想在意识到自己犯了巨大错误之前就不做深入的研究。代码很简单,但是效果很好。我希望能够将输出运行到文件或直接窗口中,因此我创建了一个简单的
IOutput
接口,其中包含一个子例程。 IOutput.cls
Public Sub PrintLine(Optional ByVal object As Variant)
End Sub
以及一个实现它的
Console
类。 Console
使用VBPredeclaredId = True
创建默认实例。 Logger类暂时仍未实现。 Console.cls
Implements IOutput
Public Sub PrintLine(Optional ByVal object As Variant)
If IsMissing(object) Then
'newline
Debug.Print vbNullString
Else
Debug.Print object
End If
End Sub
Private Sub IOutput_PrintLine(Optional ByVal object As Variant)
PrintLine object
End Sub
然后,
UnitTest
类将一个IOutput
对象放入并将其存储为属性。我需要输出流可用于本地项目,但我不想将其公开给引用它的外部项目,因此我在Friend
范围内对其进行了声明(稍后会详细介绍)。UnitTest.cls
Private Type TUnitTest
Name As String
OutStream As IOutput
Assert As Assert
End Type
Private this As TUnitTest
Public Property Get Name() As String
Name = this.Name
End Property
Friend Property Get OutStream() As IOutput
Set OutStream = this.OutStream
End Property
Public Property Get Assert() As Assert
Set Assert = this.Assert
End Property
Friend Sub Initialize(Name As String, out As IOutput)
this.Name = Name
Set this.OutStream = out
Set this.Assert = New Assert
Set this.Assert.Parent = Me
End Sub
UnitTest
创建自己的Assert
对象实例。我在这里真的很担心。我不喜欢我必须传递测试名称以及正在测试的实际条件。 Assert.cls
Private Const PASS As String = "Pass"
Private Const FAIL As String = "Fail"
Private Type TAssert
Parent As UnitTest
End Type
Private this As TAssert
Public Static Property Get Parent() As UnitTest
Set Parent = this.Parent
End Property
Public Static Property Set Parent(ByVal Value As UnitTest)
Set this.Parent = Value
End Property
Public Sub IsTrue(testName As String, condition As Boolean, Optional message As String)
Dim output As String
output = IIf(condition, PASS, FAIL)
Report testName, output, message
End Sub
Public Sub IsFalse(testName As String, condition As Boolean, Optional message As String)
Dim output As String
output = IIf(condition, FAIL, PASS)
Report testName, output, message
End Sub
Private Sub Report(testName As String, output As String, message As String)
output = this.Parent.Name & "." & testName & ": " & output
If message <> vbNullString Then
output = output & ": " & message
End If
this.Parent.OutStream.PrintLine output
End Sub
最后,我不想将所有这些类导入我正在研究的每个项目中。当我对VBAUnit项目进行更改时,保持它们全部同步将是一场噩梦。因此,我将其实例更改为“ PublicNotCreatable”。
如果instancing属性为PublicNotCreatable,则该类在同一项目中使用时行为正常,但是可以在其他项目中声明该类类型的变量。另一个项目不能创建该类的新实例,但是可以具有该类类型的变量。为了允许另一个项目使用该类的新实例,包含该类的项目必须提供一个全局作用域函数,该函数创建一个类的新实例并将其返回给调用者。例如,假设Project1包含一个名为Class1的类,其Instancing属性为PublicNotCreatable。还假设Project2引用了Project1。
Cpearson.com
所以我有一个名为
Provider
的常规* .bas模块,其中包含此单个函数。Provider。 bas
Public Function New_UnitTest(Name As String, out As IOutput) As UnitTest
Set New_UnitTest = New UnitTest
New_UnitTest.Initialize Name, out
End Function
然后,从另一个项目中,我将VBAUnit添加到引用中。 (如果您没有打开它,则必须单击浏览并导航到实际文件。)我就是这样做的,并编写了一些本质上可以自我测试的测试。
这就是
Friend
示波器发挥作用的地方。 VBAUnit
可以访问Initialize
子例程和OutputStream
属性,但是它们对任何外部项目都不可见。AssertConditionTest
此代码是样板代码。我创建的每个新测试都需要这些行。另外,一旦实现了文件记录器,就需要在此处确定将结果输出到何处。我不喜欢样板,但是我想不出办法解决它。我愿意就此提出建议。
Private test As VBAUnit.UnitTest
Private Sub Class_Initialize()
Set test = VBAUnit.New_UnitTest(TypeName(Me), VBAUnit.Console)
End Sub
Private Sub Class_Terminate()
Set test = Nothing
End Sub
随后进行实际测试。
Public Sub RunAllTests()
IsTrueShouldPass
IsTrueShouldFail
IsFalseShouldPass
IsFalseShouldFail
End Sub
Public Sub IsTrueShouldPass()
test.Assert.IsTrue "IsTrueShouldPass", True
End Sub
Public Sub IsTrueShouldFail()
test.Assert.IsTrue "IsTrueShouldFail", False
End Sub
Public Sub IsFalseShouldPass()
test.Assert.IsFalse "IsFalseShouldPass", False, "with a message."
End Sub
Public Sub IsFalseShouldFail()
test.Assert.IsFalse "IsFalseShouldFail", True, "with a message."
End Sub
最后,在这个项目中,我们有一个常规的* bas。这只是我们用于运行我们感兴趣的测试的废弃代码。
Public Sub TestTheTests()
Dim test As New AssertConditionTest
test.RunAllTests
test.IsFalseShouldPass
End Sub
总结:
我以一种智能的方式使用界面吗?
无论如何,是否有必要放弃
AssertConditionTest
中的样板代码?如何避免在每个Assert语句中传递“测试名称”并仍然得到这样的结果?我的方法充其量像是一个肮脏的技巧。
AssertConditionTest.IsTrueShouldPass: Pass
AssertConditionTest.IsTrueShouldFail: Fail
AssertConditionTest.IsFalseShouldPass: Pass: with a message.
AssertConditionTest.IsFalseShouldFail: Fail: with a message.
这是一个愚蠢的决定,使
Assert
成为自己的班级并保留一个父母UnitTest属性?#1 楼
IOutput类模块(接口)
查看接口的使用方式:
this.Parent.OutStream.PrintLine output
output
显然是String
,这很有意义。但是接口的签名并不能反映这一点,并且令人困惑:Public Sub PrintLine(Optional ByVal object As Variant)
为什么参数是可选的?为什么是变体? ...为什么称为对象?我本来希望这样的:
Public Sub PrintLine(ByVal output As String)
这会引导我去实现:
控制台类模块
如果参数是
String
,并且不是可选参数,则PrintLine
的实现会变得更简单一些:Option Explicit 'always. even if you're not **yet** declaring anything.
Implements IOutput
Public Sub PrintLine(ByVal output As String)
Debug.Print output
End Sub
Private Sub IOutput_PrintLine(ByVal output As String)
PrintLine output
End Sub
它看来您的
Console
类打算像.net System.Console
一样使用,是静态类。在
IOutput
接口实现的上下文中,该类为静态是没有意义的,其PrintLine
方法也没有可选参数。但是,如果将测试结果封装在其自己的类中... Option Explicit
Public Enum TestOutcome
Inconclusive
Failed
Succeeded
End Enum
Private Type TTestResult
outcome As TestOutcome
output As String
End Type
Private this As TTestResult
Public Property Get TestOutcome() As TestOutcome
TestOutcome = this.outcome
End Property
Friend Property Let TestOutcome(ByVal value As TestOutcome)
this.outcome = value
End Property
Public Property Get TestOutput() As String
TestOutput = this.output
End Property
Friend Property Let TestOutput(ByVal value As String)
this.output = value
End Property
Public Function Create(ByVal outcome As TestOutcome, ByVal output As String)
Dim result As New TestResult
result.TestOutcome = outcome
result.TestOutput = output
Set Create = result
End Function
...然后我将
IOutput
重命名为ITestOutput
,并像这样更改签名:Public Sub WriteResult(ByVal result As TestResult)
End Sub
和
Console
看起来不一样:Option Explicit
Implements ITestOutput
Public Sub WriteResult(ByVal result As TestResult)
Debug.Print result.TestOutput
End Sub
Private Sub ITestOutput_WriteResult(ByVal result As TestResult)
WriteResult result
End Sub
这使
WriteResult
的意图比PrintLine
,并不会阻止您实现WriteLine(String)
方法并将Console
保留为静态实用工具类,并且,您有一个关于测试结果的概念,这个结论可能是不确定的,失败的或成功的。UnitTest类模块
真是太客气了,这是我第一次看到在VBA中有必要使用
Friend
关键字。这非常聪明,它启用了VBA否则无法实现的一些功能:工厂:现在可以使用参数值创建和初始化类,就像用构造函数创建。
不变性:类只能公开getter,并且从客户端代码的角度来看是不变的。
令人印象深刻。我希望10年前我已经意识到VBAProjects可以互相引用!
提供程序代码模块
我不知道像这样。我本可以将其设置为“静态”类模块(具有默认实例),并命名为
UnitTestFactory
。我也不喜欢方法名称-再次,标识符中的下划线在VBA中令人困惑。如果该代码在名为
UnitTestFactory
的类中,则该方法的名称可能只是Create
。我不喜欢您要分配结果,然后在该引用上调用一个方法-它看起来非常尴尬,使用
result
变量会更清楚,我将使IOutput
实现/引用成为工厂类的属性,将其从方法的签名中删除:Option Explicit
Private Type TUnitTestFactory
TestOutput As IOutput
End Type
Private this As TUnitTestFactory
Public Property Get TestOutput() As IOutput
Set TestOutput = this.TestOutput
End Property
Public Property Set TestOutput(ByVal value As IOutput)
Set this.TestOutput = value
End Property
Public Function Create(ByVal testName As String) As UnitTest
Dim result As New UnitTest
Set result.Initialize testName, TestOutput
Set Create = result
End Function
声明类模块
我从未见过像这样使用
Static
关键字(无论如何在VBA中是静态属性?)和Parent
财产使我认为课堂做的比应做的更多。我相信我上面建议的TestOutcome
枚举和TestResult
类在这里会有所帮助...但是我不认为Assert
的工作是报告测试结果-通过将责任保持在测试级别,您可以删除需要将测试名称传递给Assert
方法。问题是,该怎么做?
我想我会公开一个事件:
Public Event AssertCompleted(ByVal result As TestResult)
这将使像
IsTrue
这样的方法看起来像这样:这-请注意,我想调用变量UnitTest
,所以我将类重命名为assert
,并且放弃了类型的默认实例/静态性:Public Sub IsTrue(ByVal condition As Boolean, Optional message As String)
Dim outcome As TestOutcome
outcome = IIf(condition, TestOutcome.Succeeded, TestOutcome.Failed)
result = TestResult.Create(outcome, message)
RaiseEvent AssertCompleted(result)
End Sub
...宾果游戏!
该代码是样板代码。我创建的每个新测试都需要这些行。
不用担心样板代码。这是必需的,因为这是客户端代码为输出接口指定实现的唯一逻辑位置-测试可能想要输出到文本文件,另一个可能想要输出到即时窗格,另一个可能想要发送输出到模式窗体上的列表框...指定它确实是客户端代码的工作。
评论
\ $ \ begingroup \ $
使用带有默认实例的静态类是genius。我本来应该想到的,但是我猜我在抽MSDN的文档。使用事件报告断言已完成也很出色。很好的建议。您将断言和报告的关注点分离也完全正确。静态属性隐藏在其中,因为我正在使用“ Insert”工具.....从那里学到的教训。从现在开始,我将其键入。
\ $ \ endgroup \ $
–RubberDuck
2014年9月14日下午1:20
\ $ \ begingroup \ $
另外,IOutput具有可选的变体,因为如果我不带参数调用Console.PrintLine或Logger.PrintLine,则希望它打印空白行。由于对象将被强制转换为字符串并进行打印,因此需要多种形式。我一定是在Ruby中找到的。不知道我会改变那一部分。
\ $ \ endgroup \ $
–RubberDuck
2014年9月14日下午13:58
\ $ \ begingroup \ $
@RubberDuck再次思考,...是什么阻止了Assert.IsTrue作为函数并返回TestResult,而不是引发事件?
\ $ \ endgroup \ $
– Mathieu Guindon♦
2014-09-14 19:33
\ $ \ begingroup \ $
据我所知,这是行为。它不问这是否正确,而是断言。它需要采取行动。
\ $ \ endgroup \ $
–RubberDuck
2014年9月14日在21:12