REM ======================================================================================================================= REM === The ScriptForge library and its associated libraries are part of the LibreOffice project. === REM === Full documentation is available on https://help.libreoffice.org/ === REM ======================================================================================================================= Option Compatible Option ClassModule Option Explicit ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ''' SF_UnitTest ''' =========== ''' Class providing a framework to execute and check sets of unit tests. ''' ''' The UnitTest unit testing framework was originally inspired by unittest.py in Python ''' and has a similar flavor as major unit testing frameworks in other languages. ''' ''' It supports test automation, sharing of setup and shutdown code for tests, ''' aggregation of tests into collections. ''' ''' Both the ''' - code describing the unit tests ''' - code to be tested ''' must be written exclusively in Basic (the code might call functions written in other languages). ''' Even if either code may be contained in the same module, a much better practice is to ''' store them in separate libraries. ''' Typically: ''' - in a same document when the code to be tested is contained in that document ''' - either in a "test" document or in a "My Macros" library when the code ''' to be tested is a shared library (My Macros or LibreOffice Macros). ''' The code to be tested may be released as an extension. It does not need to make ''' use of ScriptForge services in any way. ''' ''' The test reporting device is the Console. Read about the console in the ScriptForge.Exception service. ''' ''' Definitions: ''' - Test Case ''' A test case is the individual unit of testing. ''' It checks for a specific response to a particular set of inputs. ''' A test case in the UnitTest service is represented by a Basic Sub. ''' The name of the Sub starts conventionally with "Test_". ''' The test fails if one of the included AssertXXX methods returns False ''' - Test Suite ''' A test suite is a collection of test cases that should be executed together. ''' A test suite is represented by a Basic module. ''' A suite may include the tasks needed to prepare one or more tests, and any associated cleanup actions. ''' This may involve, for example, creating temporary files or directories, opening a document, loading libraries. ''' Conventionally those tasks are part pf the SetUp') and TearDown() methods. ''' - Unit test ''' A full unit test is a set of test suites (each suite in a separate Basic module), ''' each of them being a set of test cases (each case is located in a separate Basic Sub). ''' ''' Two modes: ''' Beside the normal mode ("full mode"), using test suites and test cases, a second mode exists, called "simple mode" ''' limited to the use exclusively of the Assert...() methods. ''' Their boolean returned value may support the execution of limited unit tests. ''' ''' Service invocation examples: ''' In full mode, the service creation is external to test cases ''' Dim myUnitTest As Variant ''' myUnitTest = CreateScriptService("UnitTest", ThisComponent, "Tests") ''' ' Test code is in the library "Tests" located in the current document ''' In simple mode, the service creation is internal to every test case ''' Dim myUnitTest As Variant ''' myUnitTest = CreateScriptService("UnitTest") ''' With myUnitTest ''' If Not .AssertTrue(...) Then ... ' Only calls to the Assert...() methods are allowed ''' ' ... ''' .Dispose() ''' End With ''' ''' Minimalist full mode example ''' Code to be tested (stored in library "Standard" of document "MyDoc.ods") : ''' Function ArraySize(arr As Variant) As Long ''' If IsArray(arr) Then ArraySize = UBound(arr) - LBound(arr) + 1 Else ArraySize = -1 ''' End Function ''' Test code (stored in module "AllTests" of library "Tests" of document "MyDoc.ods") : ''' Sub Main() ' Sub to trigger manually, f.i. from the Tools + Run Macro tabbed bar ''' GlobalScope.BasicLibraries.loadLibrary("ScriptForge") ''' Dim test : test = CreateScriptService("UnitTest", ThisComponent, "Tests") ''' test.RunTest("AllTests") ' AllTests is a module name ; test cases are named "Test_*" (default) ''' test.Dispose() ''' End Sub ''' REM ------------------------------------------------------------------------------ ''' Sub Setup(test) ' The unittest service is passed as argument ''' ' Optional Sub to initialize processing of the actual test suite ''' Dim exc : exc = CreateScriptService("Exception") ''' exc.Console(Modal := False) ' Watch test progress in the console ''' End Sub ''' REM ------------------------------------------------------------------------------ ''' Sub Test_ArraySize(test) ''' On Local Error GoTo CatchErr ''' test.AssertEqual(ArraySize(10), -1, "When not array") ''' test.AssertEqual(ArraySize(Array(1, 2, 3)), 3, "When simple array") ''' test.AssertEqual(ArraySize(DimArray(3)), 4, "When array with empty items") ''' Exit Sub ''' CatchErr: ''' test.ReportError("ArraySize() is corrupt") ''' End Sub ''' REM ------------------------------------------------------------------------------ ''' Sub TearDown(test) ''' ' Optional Sub to finalize processing of the actual test suite ''' End Sub ''' ''' Error handling ''' To support the debugging of the tested code, the UnitTest service, in cases of ''' - assertion failure ''' - Basic run-time error in the tested code ''' - Basic run-time error in the testing code (the unit tests) ''' will comment the error location and description in a message box and in the console log, ''' providing every test case (in either mode) implements an error handler containing at least: ''' Sub Test_Case1(test As Variant) ''' On Local Error GoTo Catch ''' ' ... (AssertXXX(), Fail(), ...) ''' Exit Sub ''' Catch: ''' test.ReportError() ''' End Sub ''' ''' Detailed user documentation: ''' https://help.libreoffice.org/latest/en-US/text/sbasic/shared/03/sf_unittest.html?DbPAR=BASIC ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' REM ================================================================== EXCEPTIONS Private Const UNITTESTMETHODERROR = "UNITTESTMETHODERROR" REM ============================================================= PRIVATE MEMBERS Private [Me] As Object Private [_Parent] As Object Private ObjectType As String ' Must be "UNITTEST" Private ServiceName As String ' Testing code Private LibrariesContainer As String ' Document or user Basic library containing the test library Private Scope As String ' Scope when running a Basic script with Session.ExecuteBasicScript() Private Libraries As Variant ' Set of libraries Private LibraryName As String ' Name of the library containing the test code Private LibraryIndex As Integer ' Index in Libraries Private Modules As Variant ' Set of modules Private ModuleNames As Variant ' Set of module names Private MethodNames As Variant ' Set of methods in a given module ' Internals Private _Verbose As Boolean ' When True, every assertion is reported,failing or not Private _LongMessage As Boolean ' When False, only the message provided by the tester is considered ' When True (default), that message is appended to the standard message Private _WhenAssertionFails As Integer ' Determines what to do when a test fails ' Test status Private _Status As Integer ' 0 = standby ' 1 = test suite started ' 2 = setup started ' 3 = test case started ' 4 = teardown started Private _ExecutionMode As Integer ' 1 = Test started with RunTest() ' 2 = Test started with CreateScriptService() Only Assert() methods allowed Private _Module As String ' Exact name of module currently running Private _TestCase As String ' Exact name of test case currently running Private _ReturnCode As Integer ' 0 = Normal end ' 1 = Assertion failed ' 2 = Skip request (in Setup() only) '-1 = abnormal end Private _FailedAssert As String ' Assert function that returned a failure ' Timers Private TestTimer As Object ' Started by CreateScriptService() Private SuiteTimer As Object ' Started by RunTest() Private CaseTimer As Object ' Started by new case ' Services Private Exception As Object ' SF_Exception Private Session As Object ' SF_Session REM ============================================================ MODULE CONSTANTS ' When assertion fails constants: error is reported + ... Global Const FAILIGNORE = 0 ' Ignore the failure Global Const FAILSTOPSUITE = 1 ' Module TearDown is executed, then next suite may be started (default in full mode) Global Const FAILIMMEDIATESTOP = 2 ' Stop immediately (default in simple mode) ' Unit tests status (internal use only => not Global) Const STATUSSTANDBY = 0 ' No test active Const STATUSSUITESTARTED = 1 ' RunTest() started Const STATUSSETUP = 2 ' A Setup() method is running Const STATUSTESTCASE = 3 ' A test case is running Const STATUSTEARDOWN = 4 ' A TearDown() method is running ' Return codes Global Const RCNORMALEND = 0 ' Normal end of test or test not started Global Const RCASSERTIONFAILED = 1 ' An assertion within a test case returned False Global Const RCSKIPTEST = 2 ' A SkipTest() was issued by a Setup() method Global Const RCABORTTEST = 3 ' Abnormal end of test ' Execution modes Global Const FULLMODE = 1 ' 1 = Test started with RunTest() Global Const SIMPLEMODE = 2 ' 2 = Test started with CreateScriptService() Only Assert() methods allowed Const INVALIDPROCEDURECALL = "5" ' Artificial error raised when an assertion fails REM ===================================================== CONSTRUCTOR/DESTRUCTOR REM ----------------------------------------------------------------------------- Private Sub Class_Initialize() Set [Me] = Nothing Set [_Parent] = Nothing ObjectType = "UNITTEST" ServiceName = "SFUnitTests.UnitTest" LibrariesContainer = "" Scope = "" Libraries = Array() LibraryName = "" LibraryIndex = -1 _Verbose = False _LongMessage = True _WhenAssertionFails = -1 _Status = STATUSSTANDBY _ExecutionMode = SIMPLEMODE _Module = "" _TestCase = "" _ReturnCode = RCNORMALEND _FailedAssert = "" Set TestTimer = Nothing Set SuiteTimer = Nothing Set CaseTimer = Nothing Set Exception = CreateScriptService("ScriptForge.Exception") Set Session = CreateScriptService("ScriptForge.Session") End Sub ' SFUnitTests.SF_UnitTest Constructor REM ----------------------------------------------------------------------------- Private Sub Class_Terminate() If Not IsNull(CaseTimer) Then CaseTimer = CaseTimer.Dispose() If Not IsNull(SuiteTimer) Then SuiteTimer = SuiteTimer.Dispose() If Not IsNull(TestTimer) Then TestTimer = TestTimer.Dispose() Call Class_Initialize() End Sub ' SFUnitTests.SF_UnitTest Destructor REM ----------------------------------------------------------------------------- Public Function Dispose() As Variant Call Class_Terminate() Set Dispose = Nothing End Function ' SFUnitTests.SF_UnitTest Explicit destructor REM ================================================================== PROPERTIES REM ----------------------------------------------------------------------------- Property Get LongMessage() As Variant ''' When False, only the message provided by the tester is considered ''' When True (default), that message is appended to the standard message LongMessage = _PropertyGet("LongMessage") End Property ' SFUnitTests.SF_UnitTest.LongMessage (get) REM ----------------------------------------------------------------------------- Property Let LongMessage(Optional ByVal pvLongMessage As Variant) ''' Set the updatable property LongMessage _PropertySet("LongMessage", pvLongMessage) End Property ' SFUnitTests.SF_UnitTest.LongMessage (let) REM ----------------------------------------------------------------------------- Property Get ReturnCode() As Integer ''' RCNORMALEND = 0 ' Normal end of test or test not started ''' RCASSERTIONFAILED = 1 ' An assertion within a test case returned False ''' RCSKIPTEST = 2 ' A SkipTest() was issued by a Setup() method ''' RCABORTTEST = 3 ' Abnormal end of test ReturnCode = _PropertyGet("ReturnCode") End Property ' SFUnitTests.SF_UnitTest.ReturnCode (get) REM ----------------------------------------------------------------------------- Property Get Verbose() As Variant ''' The Verbose property indicates if all assertions (True AND False) are reported Verbose = _PropertyGet("Verbose") End Property ' SFUnitTests.SF_UnitTest.Verbose (get) REM ----------------------------------------------------------------------------- Property Let Verbose(Optional ByVal pvVerbose As Variant) ''' Set the updatable property Verbose _PropertySet("Verbose", pvVerbose) End Property ' SFUnitTests.SF_UnitTest.Verbose (let) REM ----------------------------------------------------------------------------- Property Get WhenAssertionFails() As Variant ''' What when an AssertXXX() method returns False ''' FAILIGNORE = 0 ' Ignore the failure ''' FAILSTOPSUITE = 1 ' Module TearDown is executed, then next suite may be started (default in FULL mode) ''' FAILIMMEDIATESTOP = 2 ' Stop immediately (default in SIMPLE mode) ''' In simple mode, only FAILIGNORE and FAILIMMEDIATESTOP are allowed. ''' In both modes, when WhenAssertionFails has not the value FAILIGNORE, ''' each test case MUST have a run-time error handler calling the ReportError() method. ''' Example: ''' Sub Test_sometest(Optional test) ''' On Local Error GoTo CatchError ''' ' ... one or more assert verbs ''' Exit Sub ''' CatchError: ''' test.ReportError() ''' End Sub WhenAssertionFails = _PropertyGet("WhenAssertionFails") End Property ' SFUnitTests.SF_UnitTest.WhenAssertionFails (get) REM ----------------------------------------------------------------------------- Property Let WhenAssertionFails(Optional ByVal pvWhenAssertionFails As Variant) ''' Set the updatable property WhenAssertionFails _PropertySet("WhenAssertionFails", pvWhenAssertionFails) End Property ' SFUnitTests.SF_UnitTest.WhenAssertionFails (let) REM ===================================================================== METHODS REM ----------------------------------------------------------------------------- Public Function AssertAlmostEqual(Optional ByRef A As Variant _ , Optional ByRef B As Variant _ , Optional ByVal Tolerance As Variant _ , Optional ByVal Message As Variant _ ) As Boolean ''' Returns True when A and B are numerical values and are found close to each other. ''' It is typically used to compare very large or very small numbers. ''' Equality is confirmed when ''' - A and B can be converted to doubles ''' - The absolute difference between a and b, relative to the larger absolute value of a or b, ''' is lower or equal to the tolerance. The default tolerance is 1E-09, ''' Examples: 1E+12 and 1E+12 + 100 are almost equal ''' 1E-20 and 2E-20 are not almost equal ''' 100 and 95 are almost equal when Tolerance = 0.05 Dim bAssert As Boolean ' Return value Const cstTolerance = 1E-09 Const cstThisSub = "UnitTest.AssertAlmostEqual" Const cstSubArgs = "A, B, [Tolerance=1E-09], [Message=""""]" Check: If IsMissing(A) Then A = Empty If IsMissing(B) Then B = Empty If IsMissing(Tolerance) Then Tolerance = cstTolerance If IsMissing(Message) Then Message = "" ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) ' Unconditional ! If Not ScriptForge.SF_Utils._Validate(Tolerance, "Tolerance", ScriptForge.V_NUMERIC) Then GoTo Catch Try: bAssert = _Assert("AssertAlmostEqual", True, A, B, Message, Tolerance) Finally: AssertAlmostEqual = bAssert ScriptForge.SF_Utils._ExitFunction(cstThisSub) Exit Function Catch: bAssert = False GoTo Finally End Function ' SFUnitTests.SF_UnitTest.AssertAlmostEqual REM ----------------------------------------------------------------------------- Public Function AssertEqual(Optional ByRef A As Variant _ , Optional ByRef B As Variant _ , Optional ByVal Message As Variant _ ) As Boolean ''' Returns True when A and B are found equal. ''' Equality is confirmed when ''' If A and B are scalars: ''' They should have the same VarType or both be numeric ''' Booleans and numeric values are compared with the = operator ''' Strings are compared with the StrComp() builtin function. The comparison is case-sensitive ''' Dates and times are compared up to the second ''' Null, Empty and Nothing are not equal, but AssertEqual(Nothing, Nothing) returns True ''' UNO objects are compared with the EqualUnoObjects() method ''' Basic objects are NEVER equal ''' If A and B are arrays: ''' They should have the same number of dimensions (maximum 2) ''' The lower and upper bounds must be identical for each dimension ''' Two empty arrays are equal ''' Their items must be equal one by one Dim bAssert As Boolean ' Return value Const cstThisSub = "UnitTest.AssertEqual" Const cstSubArgs = "A, B, [Message=""""]" Check: If IsMissing(A) Then A = Empty If IsMissing(B) Then B = Empty If IsMissing(Message) Then Message = "" ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) ' Unconditional ! Try: bAssert = _Assert("AssertEqual", True, A, B, Message) Finally: AssertEqual = bAssert ScriptForge.SF_Utils._ExitFunction(cstThisSub) Exit Function End Function ' SFUnitTests.SF_UnitTest.AssertEqual REM ----------------------------------------------------------------------------- Public Function AssertFalse(Optional ByRef A As Variant _ , Optional ByVal Message As Variant _ ) As Boolean ''' Returns True when A is a Boolean and its value is False Dim bAssert As Boolean ' Return value Const cstThisSub = "UnitTest.AssertFalse" Const cstSubArgs = "A, [Message=""""]" Check: If IsMissing(A) Then A = Empty If IsMissing(Message) Then Message = "" ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) ' Unconditional ! Try: bAssert = _Assert("AssertFalse", True, A, Empty, Message) Finally: AssertFalse = bAssert ScriptForge.SF_Utils._ExitFunction(cstThisSub) Exit Function End Function ' SFUnitTests.SF_UnitTest.AssertFalse REM ----------------------------------------------------------------------------- Public Function AssertGreater(Optional ByRef A As Variant _ , Optional ByRef B As Variant _ , Optional ByVal Message As Variant _ ) As Boolean ''' Returns True when A is greater than B. ''' To compare A and B: ''' They should have the same VarType or both be numeric ''' Eligible datatypes are String, Date or numeric. ''' String comparisons are case-sensitive. Dim bAssert As Boolean ' Return value Const cstThisSub = "UnitTest.AssertGreater" Const cstSubArgs = "A, B, [Message=""""]" Check: If IsMissing(A) Then A = Empty If IsMissing(B) Then B = Empty If IsMissing(Message) Then Message = "" ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) ' Unconditional ! Try: bAssert = _Assert("AssertGreater", True, A, B, Message) Finally: AssertGreater = bAssert ScriptForge.SF_Utils._ExitFunction(cstThisSub) Exit Function End Function ' SFUnitTests.SF_UnitTest.AssertGreater REM ----------------------------------------------------------------------------- Public Function AssertGreaterEqual(Optional ByRef A As Variant _ , Optional ByRef B As Variant _ , Optional ByVal Message As Variant _ ) As Boolean ''' Returns True when A is greater than or equal to B. ''' To compare A and B: ''' They should have the same VarType or both be numeric ''' Eligible datatypes are String, Date or numeric. ''' String comparisons are case-sensitive. Dim bAssert As Boolean ' Return value Const cstThisSub = "UnitTest.AssertGreaterEqual" Const cstSubArgs = "A, B, [Message=""""]" Check: If IsMissing(A) Then A = Empty If IsMissing(B) Then B = Empty If IsMissing(Message) Then Message = "" ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) ' Unconditional ! Try: bAssert = _Assert("AssertGreaterEqual", True, A, B, Message) Finally: AssertGreaterEqual = bAssert ScriptForge.SF_Utils._ExitFunction(cstThisSub) Exit Function End Function ' SFUnitTests.SF_UnitTest.AssertGreaterEqual REM ----------------------------------------------------------------------------- Public Function AssertIn(Optional ByRef A As Variant _ , Optional ByRef B As Variant _ , Optional ByVal Message As Variant _ ) As Boolean ''' Returns True when A, a string, is found within B ''' B may be a 1D array, a ScriptForge dictionary or a string. ''' When B is an array, A may be a date or a numeric value. ''' String comparisons are case-sensitive. Dim bAssert As Boolean ' Return value Const cstThisSub = "UnitTest.AssertIn" Const cstSubArgs = "A, B, [Message=""""]" Check: If IsMissing(A) Then A = Empty If IsMissing(B) Then B = Empty If IsMissing(Message) Then Message = "" ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) ' Unconditional ! Try: bAssert = _Assert("AssertIn", True, A, B, Message) Finally: AssertIn = bAssert ScriptForge.SF_Utils._ExitFunction(cstThisSub) Exit Function End Function ' SFUnitTests.SF_UnitTest.AssertIn REM ----------------------------------------------------------------------------- Public Function AssertIsInstance(Optional ByRef A As Variant _ , Optional ByRef ObjectType As Variant _ , Optional ByVal Message As Variant _ ) As Boolean ''' Returns True when A is an object instance of the class ObjectType or a variable of type ObjectType. ''' A may be: ''' - a ScriptForge object ''' ObjectType is a string like "DICTIONARY", "calc", "Dialog", "exception", etc. ''' - a UNO object ''' ObjectType is a string identical with values returned by the SF_Session.UnoObjectType() ''' - any variable, providing it is neither an object nor an array ''' ObjectType is a string identifying a value returned by the TypeName() builtin function ''' - an array ''' ObjectType is expected to be "array" Dim bAssert As Boolean ' Return value Const cstThisSub = "UnitTest.AssertIsInstance" Const cstSubArgs = "A, ObjectType, [Message=""""]" Check: If IsMissing(A) Then A = Empty If IsMissing(ObjectType) Then ObjectType = Empty If IsMissing(Message) Then Message = "" ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) ' Unconditional ! If Not ScriptForge.SF_Utils._Validate(ObjectType, "ObjectType", V_STRING) Then GoTo Catch Try: bAssert = _Assert("AssertIsInstance", True, A, Empty, Message, ObjectType) Finally: AssertIsInstance = bAssert ScriptForge.SF_Utils._ExitFunction(cstThisSub) Exit Function Catch: bAssert = False GoTo Finally End Function ' SFUnitTests.SF_UnitTest.AssertIsInstance REM ----------------------------------------------------------------------------- Public Function AssertIsNothing(Optional ByRef A As Variant _ , Optional ByVal Message As Variant _ ) As Boolean ''' Returns True when A is an object that has the Nothing value Dim bAssert As Boolean ' Return value Const cstThisSub = "UnitTest.AssertIsNothing" Const cstSubArgs = "A, [Message=""""]" Check: If IsMissing(A) Then A = Empty If IsMissing(Message) Then Message = "" ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) ' Unconditional ! Try: bAssert = _Assert("AssertIsNothing", True, A, Empty, Message) Finally: AssertIsNothing = bAssert ScriptForge.SF_Utils._ExitFunction(cstThisSub) Exit Function End Function ' SFUnitTests.SF_UnitTest.AssertIsNothing REM ----------------------------------------------------------------------------- Public Function AssertIsNull(Optional ByRef A As Variant _ , Optional ByVal Message As Variant _ ) As Boolean ''' Returns True when A has the Null value Dim bAssert As Boolean ' Return value Const cstThisSub = "UnitTest.AssertIsNull" Const cstSubArgs = "A, [Message=""""]" Check: If IsMissing(A) Then A = Empty If IsMissing(Message) Then Message = "" ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) ' Unconditional ! Try: bAssert = _Assert("AssertIsNull", True, A, Empty, Message) Finally: AssertIsNull = bAssert ScriptForge.SF_Utils._ExitFunction(cstThisSub) Exit Function End Function ' SFUnitTests.SF_UnitTest.AssertIsNull REM ----------------------------------------------------------------------------- Public Function AssertLess(Optional ByRef A As Variant _ , Optional ByRef B As Variant _ , Optional ByVal Message As Variant _ ) As Boolean ''' Returns True when A is less than B. ''' To compare A and B: ''' They should have the same VarType or both be numeric ''' Eligible datatypes are String, Date or numeric. ''' String comparisons are case-sensitive. Dim bAssert As Boolean ' Return value Const cstThisSub = "UnitTest.AssertLess" Const cstSubArgs = "A, B, [Message=""""]" Check: If IsMissing(A) Then A = Empty If IsMissing(B) Then B = Empty If IsMissing(Message) Then Message = "" ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) ' Unconditional ! Try: bAssert = _Assert("AssertLess", False, A, B, Message) Finally: AssertLess = bAssert ScriptForge.SF_Utils._ExitFunction(cstThisSub) Exit Function End Function ' SFUnitTests.SF_UnitTest.AssertLess REM ----------------------------------------------------------------------------- Public Function AssertLessEqual(Optional ByRef A As Variant _ , Optional ByRef B As Variant _ , Optional ByVal Message As Variant _ ) As Boolean ''' Returns True when A is less than or equal to B. ''' To compare A and B: ''' They should have the same VarType or both be numeric ''' Eligible datatypes are String, Date or numeric. ''' String comparisons are case-sensitive. Dim bAssert As Boolean ' Return value Const cstThisSub = "UnitTest.AssertLessEqual" Const cstSubArgs = "A, B, [Message=""""]" Check: If IsMissing(A) Then A = Empty If IsMissing(B) Then B = Empty If IsMissing(Message) Then Message = "" ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) ' Unconditional ! Try: bAssert = _Assert("AssertLessEqual", False, A, B, Message) Finally: AssertLessEqual = bAssert ScriptForge.SF_Utils._ExitFunction(cstThisSub) Exit Function End Function ' SFUnitTests.SF_UnitTest.AssertLessEqual REM ----------------------------------------------------------------------------- Public Function AssertLike(Optional ByRef A As Variant _ , Optional ByRef Pattern As Variant _ , Optional ByVal Message As Variant _ ) As Boolean ''' Returns True if string A matches a given pattern containing wildcards ''' Admitted wildcard are: the "?" represents any single character ''' the "*" represents zero, one, or multiple characters ''' The comparison is case-sensitive. Dim bAssert As Boolean ' Return value Const cstThisSub = "UnitTest.AssertLike" Const cstSubArgs = "A, Pattern, [Message=""""]" Check: If IsMissing(A) Then A = Empty If IsMissing(Pattern) Then Pattern = "" If IsMissing(Message) Then Message = "" ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) ' Unconditional ! If Not ScriptForge.SF_Utils._Validate(Pattern, "Pattern", V_STRING) Then GoTo Catch Try: bAssert = _Assert("AssertLike", True, A, Empty, Message, Pattern) Finally: AssertLike = bAssert ScriptForge.SF_Utils._ExitFunction(cstThisSub) Exit Function Catch: bAssert = False GoTo Finally End Function ' SFUnitTests.SF_UnitTest.AssertLike REM ----------------------------------------------------------------------------- Public Function AssertNotAlmostEqual(Optional ByRef A As Variant _ , Optional ByRef B As Variant _ , Optional ByVal Tolerance As Variant _ , Optional ByVal Message As Variant _ ) As Boolean ''' Returns True when A and B are numerical values and are not found close to each other. ''' Read about almost equality in the comments linked to the AssertEqual() method. Dim bAssert As Boolean ' Return value Const cstTolerance = 1E-09 Const cstThisSub = "UnitTest.AssertNotAlmostEqual" Const cstSubArgs = "A, B, [Tolerance=1E-09], [Message=""""]" Check: If IsMissing(A) Then A = Empty If IsMissing(B) Then B = Empty If IsMissing(Tolerance) Then Tolerance = cstTolerance If IsMissing(Message) Then Message = "" ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) ' Unconditional ! If Not ScriptForge.SF_Utils._Validate(Tolerance, "Tolerance", ScriptForge.V_NUMERIC) Then GoTo Catch Try: bAssert = _Assert("AssertNotAlmostEqual", False, A, B, Message, Tolerance) Finally: AssertNotAlmostEqual = bAssert ScriptForge.SF_Utils._ExitFunction(cstThisSub) Exit Function Catch: bAssert = False GoTo Finally End Function ' SFUnitTests.SF_UnitTest.AssertNotAlmostEqual REM ----------------------------------------------------------------------------- Public Function AssertNotEqual(Optional ByRef A As Variant _ , Optional ByRef B As Variant _ , Optional ByVal Message As Variant _ ) As Boolean ''' Returns True when A and B are found unequal. ''' Read about equality in the comments linked to the AssertEqual() method. Dim bAssert As Boolean ' Return value Const cstThisSub = "UnitTest.AssertNotEqual" Const cstSubArgs = "A, B, [Message=""""]" Check: If IsMissing(A) Then A = Empty If IsMissing(B) Then B = Empty If IsMissing(Message) Then Message = "" ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) ' Unconditional ! Try: bAssert = _Assert("AssertNotEqual", False, A, B, Message) Finally: AssertNotEqual = bAssert ScriptForge.SF_Utils._ExitFunction(cstThisSub) Exit Function End Function ' SFUnitTests.SF_UnitTest.AssertNotEqual REM ----------------------------------------------------------------------------- Public Function AssertNotIn(Optional ByRef A As Variant _ , Optional ByRef B As Variant _ , Optional ByVal Message As Variant _ ) As Boolean ''' Returns True when A, a string, is not found within B ''' B may be a 1D array, a ScriptForge dictionary or a string. ''' When B is an array, A may be a date or a numeric value. ''' String comparisons are case-sensitive. Dim bAssert As Boolean ' Return value Const cstThisSub = "UnitTest.AssertNotIn" Const cstSubArgs = "A, B, [Message=""""]" Check: If IsMissing(A) Then A = Empty If IsMissing(B) Then B = Empty If IsMissing(Message) Then Message = "" ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) ' Unconditional ! Try: bAssert = _Assert("AssertNotIn", False, A, B, Message) Finally: AssertNotIn = bAssert ScriptForge.SF_Utils._ExitFunction(cstThisSub) Exit Function End Function ' SFUnitTests.SF_UnitTest.AssertNotIn REM ----------------------------------------------------------------------------- Public Function AssertNotInstance(Optional ByRef A As Variant _ , Optional ByRef ObjectType As Variant _ , Optional ByVal Message As Variant _ ) As Boolean ''' Returns True when A is an object instance of the class ObjectType or a variable of type ObjectType. ''' More details to be read under the AssertInstance() function. Dim bAssert As Boolean ' Return value Const cstThisSub = "UnitTest.AssertNotInstance" Const cstSubArgs = "A, ObjectType, [Message=""""]" Check: If IsMissing(A) Then A = Empty If IsMissing(ObjectType) Then ObjectType = Empty If IsMissing(Message) Then Message = "" ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) ' Unconditional ! If Not ScriptForge.SF_Utils._Validate(ObjectType, "ObjectType", V_STRING) Then GoTo Catch Try: bAssert = _Assert("AssertNotInstance", False, A, Empty, Message, ObjectType) Finally: AssertNotInstance = bAssert ScriptForge.SF_Utils._ExitFunction(cstThisSub) Exit Function Catch: bAssert = False GoTo Finally End Function ' SFUnitTests.SF_UnitTest.AssertNotInstance REM ----------------------------------------------------------------------------- Public Function AssertNotLike(Optional ByRef A As Variant _ , Optional ByRef Pattern As Variant _ , Optional ByVal Message As Variant _ ) As Boolean ''' Returns True if A is not a string or does not match a given pattern containing wildcards ''' Admitted wildcard are: the "?" represents any single character ''' the "*" represents zero, one, or multiple characters ''' The comparison is case-sensitive. Dim bAssert As Boolean ' Return value Const cstThisSub = "UnitTest.AssertNotLike" Const cstSubArgs = "A, Pattern, [Message=""""]" Check: If IsMissing(A) Then A = Empty If IsMissing(Pattern) Then Pattern = "" If IsMissing(Message) Then Message = "" ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) ' Unconditional ! If Not ScriptForge.SF_Utils._Validate(Pattern, "Pattern", V_STRING) Then GoTo Catch Try: bAssert = _Assert("AssertNotLike", False, A, Empty, Message, Pattern) Finally: AssertNotLike = bAssert ScriptForge.SF_Utils._ExitFunction(cstThisSub) Exit Function Catch: bAssert = False GoTo Finally End Function ' SFUnitTests.SF_UnitTest.AssertNotLike REM ----------------------------------------------------------------------------- Public Function AssertNotNothing(Optional ByRef A As Variant _ , Optional ByVal Message As Variant _ ) As Boolean ''' Returns True except when A is an object that has the Nothing value Dim bAssert As Boolean ' Return value Const cstThisSub = "UnitTest.AssertNotNothing" Const cstSubArgs = "A, [Message=""""]" Check: If IsMissing(A) Then A = Empty If IsMissing(Message) Then Message = "" ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) ' Unconditional ! Try: bAssert = _Assert("AssertNotNothing", False, A, Empty, Message) Finally: AssertNotNothing = bAssert ScriptForge.SF_Utils._ExitFunction(cstThisSub) Exit Function End Function ' SFUnitTests.SF_UnitTest.AssertNotNothing REM ----------------------------------------------------------------------------- Public Function AssertNotNull(Optional ByRef A As Variant _ , Optional ByVal Message As Variant _ ) As Boolean ''' Returns True except when A has the Null value Dim bAssert As Boolean ' Return value Const cstThisSub = "UnitTest.AssertNotNull" Const cstSubArgs = "A, [Message=""""]" Check: If IsMissing(A) Then A = Empty If IsMissing(Message) Then Message = "" ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) ' Unconditional ! Try: bAssert = _Assert("AssertNotNull", False, A, Empty, Message) Finally: AssertNotNull = bAssert ScriptForge.SF_Utils._ExitFunction(cstThisSub) Exit Function End Function ' SFUnitTests.SF_UnitTest.AssertNotNull REM ----------------------------------------------------------------------------- Public Function AssertNotRegex(Optional ByRef A As Variant _ , Optional ByRef Regex As Variant _ , Optional ByVal Message As Variant _ ) As Boolean ''' Returns True when A is not a string or does not match the given regular expression. ''' The comparison is case-sensitive. Dim bAssert As Boolean ' Return value Const cstThisSub = "UnitTest.AssertNotRegex" Const cstSubArgs = "A, Regex, [Message=""""]" Check: If IsMissing(A) Then A = Empty If IsMissing(Regex) Then Regex = "" If IsMissing(Message) Then Message = "" ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) ' Unconditional ! If Not ScriptForge.SF_Utils._Validate(Regex, "Regex", V_STRING) Then GoTo Catch Try: bAssert = _Assert("AssertNotRegex", False, A, Empty, Message, Regex) Finally: AssertNotRegex = bAssert ScriptForge.SF_Utils._ExitFunction(cstThisSub) Exit Function Catch: bAssert = False GoTo Finally End Function ' SFUnitTests.SF_UnitTest.AssertNotRegex REM ----------------------------------------------------------------------------- Public Function AssertRegex(Optional ByRef A As Variant _ , Optional ByRef Regex As Variant _ , Optional ByVal Message As Variant _ ) As Boolean ''' Returns True when string A matches the given regular expression. ''' The comparison is case-sensitive. Dim bAssert As Boolean ' Return value Const cstThisSub = "UnitTest.AssertRegex" Const cstSubArgs = "A, Regex, [Message=""""]" Check: If IsMissing(A) Then A = Empty If IsMissing(Regex) Then Regex = "" If IsMissing(Message) Then Message = "" ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) ' Unconditional ! If Not ScriptForge.SF_Utils._Validate(Regex, "Regex", V_STRING) Then GoTo Catch Try: bAssert = _Assert("AssertRegex", True, A, Empty, Message, Regex) Finally: AssertRegex = bAssert ScriptForge.SF_Utils._ExitFunction(cstThisSub) Exit Function Catch: bAssert = False GoTo Finally End Function ' SFUnitTests.SF_UnitTest.AssertRegex REM ----------------------------------------------------------------------------- Public Function AssertTrue(Optional ByRef A As Variant _ , Optional ByVal Message As Variant _ ) As Boolean ''' Returns True when A is a Boolean and its value is True Dim bAssert As Boolean ' Return value Const cstThisSub = "UnitTest.AssertTrue" Const cstSubArgs = "A, [Message=""""]" Check: If IsMissing(A) Then A = Empty If IsMissing(Message) Then Message = "" ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) ' Unconditional ! Try: bAssert = _Assert("AssertTrue", True, A, Empty, Message) Finally: AssertTrue = bAssert ScriptForge.SF_Utils._ExitFunction(cstThisSub) Exit Function End Function ' SFUnitTests.SF_UnitTest.AssertTrue REM ----------------------------------------------------------------------------- Public Sub Fail(Optional ByVal Message As Variant) ''' Forces a test failure Dim bAssert As Boolean ' Fictive return value Const cstThisSub = "UnitTest.Fail" Const cstSubArgs = "[Message=""""]" Check: If IsMissing(Message) Then Message = "" ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) ' Unconditional ! Try: bAssert = _Assert("Fail", False, Empty, Empty, Message) Finally: ScriptForge.SF_Utils._ExitFunction(cstThisSub) Exit Sub End Sub ' SFUnitTests.SF_UnitTest.Fail REM ----------------------------------------------------------------------------- Public Sub Log(Optional ByVal Message As Variant) ''' Records the given message in the test report (console) Dim bAssert As Boolean ' Fictive return value Dim bVerbose As Boolean : bVerbose = _Verbose Const cstThisSub = "UnitTest.Log" Const cstSubArgs = "[Message=""""]" Check: If IsMissing(Message) Then Message = "" ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) ' Unconditional ! Try: ' Force the display of the message in the console _Verbose = True bAssert = _Assert("Log", True, Empty, Empty, Message) _Verbose = bVerbose Finally: ScriptForge.SF_Utils._ExitFunction(cstThisSub) Exit Sub End Sub ' SFUnitTests.SF_UnitTest.Log REM ----------------------------------------------------------------------------- Public Function GetProperty(Optional ByVal PropertyName As Variant) As Variant ''' Return the actual value of the given property ''' Args: ''' PropertyName: the name of the property as a string ''' Returns: ''' The actual value of the property ''' Exceptions ''' ARGUMENTERROR The property does not exist ''' Examples: ''' myUnitTest.GetProperty("Duration") Const cstThisSub = "UnitTest.GetProperty" Const cstSubArgs = "PropertyName" If ScriptForge.SF_Utils._ErrorHandling() Then On Local Error GoTo Catch GetProperty = Null Check: If ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then If Not ScriptForge.SF_Utils._Validate(PropertyName, "PropertyName", V_STRING, Properties()) Then GoTo Catch End If Try: GetProperty = _PropertyGet(PropertyName) Finally: ScriptForge.SF_Utils._ExitFunction(cstThisSub) Exit Function Catch: GoTo Finally End Function ' SFUnitTests.SF_UnitTest.Properties REM ----------------------------------------------------------------------------- Public Function Methods() As Variant ''' Return the list or methods of the UnitTest class as an array Methods = Array( _ "AssertAlmostEqual" _ , "AssertEqual" _ , "AssertFalse" _ , "AssertGreater" _ , "AssertGreaterEqual" _ , "AssertIn" _ , "AssertIsInstance" _ , "AssertIsNothing" _ , "AssertLike" _ , "AssertNotRegex" _ , "AssertIsNull" _ , "AssertLess" _ , "AssertLessEqual" _ , "AssertNotAlmostEqual" _ , "AssertNotEqual" _ , "AssertNotIn" _ , "AssertNotInstance" _ , "AssertNotLike" _ , "AssertNotNothing" _ , "AssertNotNull" _ , "AssertRegex" _ , "AssertTrue" _ , "Fail" _ , "Log" _ , "RunTest" _ , "SkipTest" _ ) End Function ' SFUnitTests.SF_UnitTest.Methods REM ----------------------------------------------------------------------------- Public Function Properties() As Variant ''' Return the list or properties of the UnitTest class as an array Properties = Array( _ "LongMessage" _ , "ReturnCode" _ , "Verbose" _ , "WhenAssertionFails" _ ) End Function ' SFUnitTests.SF_UnitTest.Properties REM ----------------------------------------------------------------------------- Public Sub ReportError(Optional ByVal Message As Variant) ''' DIsplay a message box with the current property values of the "Exception" service. ''' Depending on the WhenAssertionFails property, a Raise() or RaiseWarning() ''' is issued. The Raise() method stops completely the Basic running process. ''' The ReportError() method is presumed present in a user script in an error ''' handling part of the actual testcase. ''' Args: ''' Message: a string to replace or to complete the standard message description ''' Example: ''' See the Test_ArraySize() sub in the module's heading example Dim sLine As String ' Line number where the error occurred Dim sError As String ' Exception description Dim sErrorCode As String ' Exception number Const cstThisSub = "UnitTest.ReportError" Const cstSubArgs = "[Message=""""]" Check: If IsMissing(Message) Or IsEmpty(Message) Then Message = "" ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) ' Unconditional ! If VarType(Message) <> V_STRING Then Message = "" Try: sLine = "ln " & CStr(Exception.Source) If _ExecutionMode = FULLMODE Then sLine = _Module & "." & _TestCase & " " & sLine If Len(Message) > 0 Then sError = Message Else If Exception.Number = INVALIDPROCEDURECALL Then sError = "Test case failure" sErrorCode = "ASSERTIONFAILED" Else sError = Exception.Description sErrorCode = CStr(Exception.Number) End If End If Select Case _WhenAssertionFails Case FAILIGNORE Case FAILSTOPSUITE Exception.RaiseWarning(sErrorCode, sLine, sError) Case FAILIMMEDIATESTOP Exception.Raise(sErrorCode, sLine, sError) End Select Finally: ScriptForge.SF_Utils._ExitFunction(cstThisSub) Exit Sub End Sub ' SFUnitTests.SF_UnitTest.ReportError REM ----------------------------------------------------------------------------- Public Function RunTest(Optional ByVal TestSuite As Variant _ , Optional ByVal TestCasePattern As Variant _ , Optional ByVal Message As Variant _ ) As Integer ''' Execute a test suite pointed out by a module name. ''' Each test case will be run independently from each other. ''' The names of the test cases to be run may be selected with a string pattern. ''' The test is "orchestrated" by this method: ''' 1. Execute the optional Setup() method present in the module ''' 2. Execute once each test case, in any order ''' 3, Execute the optional TearDown() method present in the module ''' Args: ''' TestSuite: the name of the module containing the set of test cases to run ''' TestCasePattern: the pattern that the test cases must match. The comparison is not case-sensitive. ''' Non-matching functions and subs are ignored. ''' Admitted wildcard are: the "?" represents any single character ''' the "*" represents zero, one, or multiple characters ''' The default pattern is "Test_*" ''' Message: the message to be displayed in the console when the test starts. ''' Returns: ''' One of the return codes of the execution (RCxxx constants) ''' Examples: ''' GlobalScope.BasicLibraries.loadLibrary("ScriptForge") ''' Dim test : test = CreateScriptService("UnitTest", ThisComponent, "Tests") ''' test.RunTest("AllTests") ' AllTests is a module name ; test cases are named "Test_*" (default) Dim iRun As Integer ' Return value Dim sRunMessage As String ' Reporting Dim iModule As Integer ' Index of module currently running Dim vMethods As Variant ' Set of methods Dim sMethod As String ' A single method Dim iMethod As Integer ' Index in MethodNames Dim m As Integer Const cstThisSub = "UnitTest.RunTest" Const cstSubArgs = "TestSuite, [TestCasePattern=""Test_*""], [Message=""""]" iRun = RCNORMALEND If ScriptForge.SF_Utils._ErrorHandling() Then On Local Error GoTo Catch Check: If IsMissing(TestCasePattern) Or IsEmpty(TestCasePattern) Then TestCasePattern = "Test_*" If IsMissing(Message) Or IsEmpty(Message) Then Message = "" ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) ' Unconditional ! If Not ScriptForge.SF_Utils._Validate(TestSuite, "TestSuite", V_STRING, ModuleNames) Then GoTo Catch If Not ScriptForge.SF_Utils._Validate(TestCasePattern, "TestCasePattern", V_STRING) Then GoTo Catch If Not ScriptForge.SF_Utils._Validate(Message, "Message", V_STRING) Then GoTo Catch ' A RunTest() is forbidden inside a test suite or when simple mode If _Status <> STATUSSTANDBY Or _ExecutionMode <> FULLMODE Then GoTo CatchMethod ' Ignore any call when an abnormal end has been encountered If _ReturnCode = RCABORTTEST Then GoTo Catch Try: iModule = ScriptForge.SF_Array.IndexOf(ModuleNames, TestSuite, CaseSensitive := False, SortOrder := "ASC") _Module = ModuleNames(iModule) ' Start timer If Not IsNull(SuiteTimer) Then SuiteTimer = SuiteTimer.Dispose() Set SuiteTimer = CreateScriptService("ScriptForge.Timer", True) ' Report the start of a new test suite sRunMessage = "RUNTEST ENTER testsuite='" & LibraryName & "." & _Module & "', pattern='" & TestCasePattern & "'" _ReportMessage(sRunMessage, Message) _Status = STATUSSUITESTARTED ' Collect all the methods of the module If Modules(iModule).hasChildNodes() Then vMethods = Modules(iModule).getChildNodes() MethodNames = Array() For m = 0 To UBound(vMethods) sMethod = vMethods(m).getName() MethodNames = ScriptForge.SF_Array.Append(MethodNames, sMethod) Next m End If ' Execute the Setup() method, if it exists iMethod = ScriptForge.SF_Array.IndexOf(MethodNames, "Setup", CaseSensitive := False, SortOrder := "ASC") If iMethod >= 0 Then _TestCase = MethodNames(iMethod) ' _TestCase is used in ReportError() If Not _ExecuteScript(_TestCase) Then GoTo Catch End If ' Execute the test cases that match the pattern For iMethod = 0 To UBound(MethodNames) If _ReturnCode = RCSKIPTEST Or _ReturnCode = RCASSERTIONFAILED Then Exit For sMethod = MethodNames(iMethod) If ScriptForge.SF_String.IsLike(sMethod, TestCasePattern, CaseSensitive := False) Then _TestCase = sMethod ' Start timer If Not IsNull(CaseTimer) Then CaseTimer = CaseTimer.Dispose() Set CaseTimer = CreateScriptService("ScriptForge.Timer", True) If Not _ExecuteScript(sMethod) Then GoTo Catch CaseTimer.Terminate() _TestCase = "" End If Next iMethod If _ReturnCode <> RCSKIPTEST Then ' Execute the TearDown() method, if it exists iMethod = ScriptForge.SF_Array.IndexOf(MethodNames, "TearDown", CaseSensitive := False, SortOrder := "ASC") If iMethod >= 0 Then _TestCase = MethodNames(iMethod) ' _TestCase is used in ReportError() If Not _ExecuteScript(_TestCase) Then GoTo Catch End If End If ' Report the end of the current test suite sRunMessage = "RUNTEST EXIT testsuite='" & LibraryName & "." & _Module & "' " & _Duration("Suite", True) _ReportMessage(sRunMessage, Message) ' Stop timer SuiteTimer.Terminate() ' Housekeeping MethodNames = Array() _Module = "" _Status = STATUSSTANDBY Finally: _ReturnCode = iRun RunTest = iRun ScriptForge.SF_Utils._ExitFunction(cstThisSub) Exit Function Catch: iRun = RCABORTTEST GoTo Finally CatchMethod: ScriptForge.SF_Exception.RaiseFatal(UNITTESTMETHODERROR, "RunTest") GoTo Catch End Function ' SFUnitTests.SF_UnitTest.RunTest REM ----------------------------------------------------------------------------- Public Function SetProperty(Optional ByVal PropertyName As Variant _ , Optional ByRef Value As Variant _ ) As Boolean ''' Set a new value to the given property ''' Args: ''' PropertyName: the name of the property as a string ''' Value: its new value ''' Exceptions ''' ARGUMENTERROR The property does not exist Const cstThisSub = "UnitTest.SetProperty" Const cstSubArgs = "PropertyName, Value" If ScriptForge.SF_Utils._ErrorHandling() Then On Local Error GoTo Catch SetProperty = False Check: If ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then If Not ScriptForge.SF_Utils._Validate(PropertyName, "PropertyName", V_STRING, Properties()) Then GoTo Catch End If Try: SetProperty = _PropertySet(PropertyName, Value) Finally: ScriptForge.SF_Utils._ExitFunction(cstThisSub) Exit Function Catch: GoTo Finally End Function ' SFUnitTests.SF_UnitTest.SetProperty REM ----------------------------------------------------------------------------- Public Function SkipTest(Optional ByVal Message As Variant) As Boolean ''' Interrupt the running test suite. The TearDown() method is NOT executed. ''' The SkipTest() method is normally meaningful only in a Setup() method when not all the ''' conditions to run the test are met. ''' It is up to the Setup() script to exit shortly after the SkipTest() call.. ''' The method may also be executed in a test case. Next test cases will not be executed. ''' Remember however that the test cases are executed is an arbitrary order. ''' Args: ''' Message: the message to be displayed in the console ''' Returns: ''' True when successful ''' Examples: ''' GlobalScope.BasicLibraries.loadLibrary("ScriptForge") ''' Dim test : test = CreateScriptService("UnitTest", ThisComponent, "Tests") ''' test.SkipTest("AllTests") ' AllTests is a module name ; test cases are named "Test_*" (default) Dim bSkip As Boolean ' Return value Dim sSkipMessage As String ' Reporting Const cstThisSub = "UnitTest.SkipTest" Const cstSubArgs = "[Message=""""]" bSkip = False If ScriptForge.SF_Utils._ErrorHandling() Then On Local Error GoTo Catch Check: If IsMissing(Message) Or IsEmpty(Message) Then Message = "" ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) ' Unconditional ! If Not ScriptForge.SF_Utils._Validate(Message, "Message", V_STRING) Then GoTo Catch ' A SkipTest() is forbidden when simple mode If _ExecutionMode <> FULLMODE Then GoTo CatchMethod ' Ignore any call when an abnormal end has been encountered If _ReturnCode = RCABORTTEST Then GoTo Catch Try: If _Status = STATUSSETUP Or _Status = STATUSTESTCASE Then _ReturnCode = RCSKIPTEST bSkip = True ' Exit message sSkipMessage = " SKIPTEST testsuite='" & LibraryName & "." & _Module & "' " & _Duration("Suite", True) _ReportMessage(sSkipMessage, Message) End If Finally: SkipTest = bSkip ScriptForge.SF_Utils._ExitFunction(cstThisSub) Exit Function Catch: _ReturnCode = RCABORTTEST GoTo Finally CatchMethod: ScriptForge.SF_Exception.RaiseFatal(UNITTESTMETHODERROR, "SkipTest") GoTo Catch End Function ' SFUnitTests.SF_UnitTest.SkipTest REM =========================================================== PRIVATE FUNCTIONS REM ----------------------------------------------------------------------------- Private Function _Assert(ByVal psAssert As String _ , ByVal pvReturn As Variant _ , ByRef A As Variant _ , ByRef B As Variant _ , Optional ByVal pvMessage As Variant _ , Optional ByVal pvArg As Variant _ ) As Boolean ''' Evaluation of the assertion and management of the success or the failure ''' Args: ''' psAssert: the assertion verb as a string ''' pvReturn: may be True, False or Empty ''' When True (resp. False), the assertion must be evaluated as True (resp. False) ''' e.g. AssertEqual() will call _Assert("AssertEqual", True, ...) ''' AssertNotEqual() will call _Assert("AssertNotEqual", False, ...) ''' Empty may be used for recursive calls of the function (for comparing arrays, ...) ''' A: always present ''' B: may be empty ''' pvMessage: the message to display on the console ''' pvArg: optional additional argument of the assert function ''' Returns: ''' True when success Dim bAssert As Boolean ' Return value Dim bEval As Boolean ' To be compared with pvReturn Dim iVarTypeA As Integer ' Alias of _VarTypeExt(A) Dim iVarTypeB As Integer ' Alias of _VarTypeExt(B) Dim oVarTypeObjA As Object ' SF_Utils.ObjectDescriptor Dim oVarTypeObjB As Object ' SF_Utils.ObjectDescriptor Dim oUtils As Object : Set oUtils = ScriptForge.SF_Utils Dim iDims As Integer ' Number of dimensions of array Dim oAliasB As Object ' Alias of B to bypass the "Object variable not set" issue Dim dblA As Double ' Alias of A Dim dblB As Double ' Alias of B Dim dblTolerance As Double ' Alias of pvArg Dim oString As Object : Set oString = ScriptForge.SF_String Dim sArgName As String ' Argument description Dim i As Long, j As Long Check: bAssert = False If IsMissing(pvMessage) Then pvMessage = "" If Not oUtils._Validate(pvMessage, "Message", V_STRING) Then GoTo Finally If IsMissing(pvArg) Then pvArg = "" Try: iVarTypeA = oUtils._VarTypeExt(A) iVarTypeB = oUtils._VarTypeExt(B) sArgName = "" Select Case UCase(psAssert) Case UCase("AssertAlmostEqual"), UCase("AssertNotAlmostEqual") bEval = ( iVarTypeA = iVarTypeB And iVarTypeA = ScriptForge.V_NUMERIC ) If bEval Then dblA = CDbl(A) dblB = CDbl(B) dblTolerance = Abs(CDbl(pvArg)) bEval = ( Abs(dblA - dblB) <= (dblTolerance * Iif(Abs(dblA) > Abs(DblB), Abs(dblA), Abs(dblB))) ) End If Case UCase("AssertEqual"), UCase("AssertNotEqual") If Not IsArray(A) Then bEval = ( iVarTypeA = iVarTypeB ) If bEval Then Select Case iVarTypeA Case V_EMPTY, V_NULL Case V_STRING bEval = ( StrComp(A, B, 1) = 0 ) Case ScriptForge.V_NUMERIC, ScriptForge.V_BOOLEAN bEval = ( A = B ) Case V_DATE bEval = ( Abs(DateDiff("s", A, B)) = 0 ) Case ScriptForge.V_OBJECT Set oVarTypeObjA = oUtils._VarTypeObj(A) Set oVarTypeObjB = oUtils._VarTypeObj(B) bEval = ( oVarTypeObjA.iVarType = oVarTypeObjB.iVarType ) If bEval Then Select Case oVarTypeObjA.iVarType Case ScriptForge.V_NOTHING Case ScriptForge.V_UNOOBJECT bEval = EqualUnoObjects(A, B) Case ScriptForge.V_SFOBJECT, ScriptForge.V_BASICOBJECT bEval = False End Select End If End Select End If Else ' Compare arrays bEval = IsArray(B) If bEval Then iDims = ScriptForge.SF_Array.CountDims(A) bEval = ( iDims = ScriptForge.SF_Array.CountDims(B) And iDims <= 2 ) If bEval Then Select Case iDims Case -1, 0 ' Scalars (not possible) or empty arrays Case 1 ' 1D array bEval = ( LBound(A) = LBound(B) And UBound(A) = UBound(B) ) If bEval Then For i = LBound(A) To UBound(A) bEval = _Assert(psAssert, Empty, A(i), B(i)) If Not bEval Then Exit For Next i End If Case 2 ' 2D array bEval = ( LBound(A, 1) = LBound(B, 1) And UBound(A, 1) = UBound(B, 1) _ And LBound(A, 2) = LBound(B, 2) And UBound(A, 2) = UBound(B, 2) ) If bEval Then For i = LBound(A, 1) To UBound(A, 1) For j = LBound(A, 2) To UBound(A, 2) bEval = _Assert(psAssert, Empty, A(i, j), B(i, j)) If Not bEval Then Exit For Next j If Not bEval Then Exit For Next i End If End Select End If End If End If Case UCase("AssertFalse") If iVarTypeA = ScriptForge.V_BOOLEAN Then bEval = Not A Else bEval = False Case UCase("AssertGreater"), UCase("AssertLessEqual") bEval = ( iVarTypeA = iVarTypeB _ And (iVarTypeA = ScriptForge.V_NUMERIC Or iVarTypeA = V_STRING Or iVarTypeA = V_DATE) ) If bEval Then bEval = ( A > B ) Case UCase("AssertGreaterEqual"), UCase("AssertLess") bEval = ( iVarTypeA = iVarTypeB _ And (iVarTypeA = ScriptForge.V_NUMERIC Or iVarTypeA = V_STRING Or iVarTypeA = V_DATE) ) If bEval Then bEval = ( A >= B ) Case UCase("AssertIn"), UCase("AssertNotIn") Set oVarTypeObjB = oUtils._VarTypeObj(B) Select Case True Case iVarTypeA = V_STRING And iVarTypeB = V_STRING bEval = ( Len(A) > 0 And Len(B) > 0 ) If bEval Then bEval = ( InStr(1, B, A, 0) > 0 ) Case (iVarTypeA = V_DATE Or iVarTypeA = V_STRING Or iVarTypeA = ScriptForge.V_NUMERIC) _ And iVarTypeB >= ScriptForge.V_ARRAY bEval = ( ScriptForge.SF_Array.CountDims(B) = 1 ) If bEval Then bEval = ScriptForge.SF_Array.Contains(B, A, CaseSensitive := True) Case oVarTypeObjB.iVarType = ScriptForge.V_SFOBJECT And oVarTypeObjB.sObjectType = "DICTIONARY" bEval = ( Len(A) > 0 ) If bEval Then Set oAliasB = B bEval = ScriptForge.SF_Array.Contains(oAliasB.Keys(), A, CaseSensitive := True) End If Case Else bEval = False End Select Case UCase("AssertIsInstance"), UCase("AssertNotInstance") Set oVarTypeObjA = oUtils._VarTypeObj(A) sArgName = "ObjectType" With oVarTypeObjA Select Case .iVarType Case ScriptForge.V_UNOOBJECT bEval = ( pvArg = .sObjectType ) Case ScriptForge.V_SFOBJECT bEval = ( UCase(pvArg) = UCase(.sObjectType) Or UCase(pvArg) = "SF_" & UCase(.sObjectType) _ Or UCase(pvArg) = UCase(.sServiceName) ) Case ScriptForge.V_NOTHING, ScriptForge.V_BASICOBJECT bEval = False Case >= ScriptForge.V_ARRAY bEval = ( UCase(pvArg) = "ARRAY" ) Case Else bEval = ( UCase(TypeName(A)) = UCase(pvArg) ) End Select End With Case UCase("AssertIsNothing"), UCase("AssertNotNothing") bEval = ( iVarTypeA = ScriptForge.V_OBJECT ) If bEval Then bEval = ( A Is Nothing ) Case UCase("AssertIsNull"), UCase("AssertNotNull") bEval = ( iVarTypeA = V_NULL ) Case UCase("AssertLike"), UCase("AssertNotLike") sArgName = "Pattern" bEval = ( iVarTypeA = V_STRING And Len(pvArg) > 0 ) If bEval Then bEval = oString.IsLike(A, pvArg, CaseSensitive := True) Case UCase("AssertRegex"), UCase("AssertNotRegex") sArgName = "Regex" bEval = ( iVarTypeA = V_STRING And Len(pvArg) > 0 ) If bEval Then bEval = oString.IsRegex(A, pvArg, CaseSensitive := True) Case UCase("AssertTrue") If iVarTypeA = ScriptForge.V_BOOLEAN Then bEval = A Else bEval = False Case UCase("FAIL"), UCase("Log") bEval = True Case Else End Select ' Check the result of the assertion vs. what it should be If IsEmpty(pvReturn) Then bAssert = bEval ' Recursive call => Reporting and failure management are done by calling _Assert() procedure Else ' pvReturn is Boolean => Call from user script bAssert = Iif(pvReturn, bEval, Not bEval) ' Report the assertion evaluation If _Verbose Or Not bAssert Then _ReportMessage(" " & psAssert _ & Iif(IsEmpty(A), "", " = " & bAssert & ", A = " & oUtils._Repr(A)) _ & Iif(IsEmpty(B), "", ", B = " & oUtils._Repr(B)) _ & Iif(Len(sArgName) = 0, "", ", " & sArgName & " = " & pvArg) _ , pvMessage) End If ' Manage assertion failure If Not bAssert Then _FailedAssert = psAssert Select Case _WhenAssertionFails Case FAILIGNORE ' Do nothing Case Else _ReturnCode = RCASSERTIONFAILED ' Cause artificially a run-time error Dim STRINGBADUSE As String '++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '+ To avoid a run-time error on next executable statement, + '+ insert an error handler in the code of your test case: + '+ Like in next code: + '+ On Local Error GoTo Catch + '+ ... + '+ Catch: + '+ myTest.ReportError() + '+ Exit Sub + '++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ STRINGBADUSE = Right("", -1) ' Raises "#5 - Invalid procedure call" error End Select End If End If Finally: _Assert = bAssert Exit Function End Function ' SFUnitTests.SF_UnitTest._Assert REM ----------------------------------------------------------------------------- Private Function _Duration(ByVal psTimer As String _ , Optional ByVal pvBrackets As Variant _ ) As String ''' Return the Duration property of the given timer ''' or the empty string if the timer is undefined or not started ''' Args: ''' psTimer: "Test", "Suite" or "TestCase" ''' pbBrackets: surround with brackets when True. Default = False Dim sDuration As String ' Return value Dim oTimer As Object ' Alias of psTimer Check: If IsMissing(pvBrackets) Or IsEmpty(pvBrackets) Then pvBrackets = False Try: Select Case psTimer Case "Test" : Set oTimer = TestTimer Case "Suite" : Set oTimer = SuiteTimer Case "TestCase", "Case" : Set oTimer = CaseTimer End Select If Not IsNull(oTimer) Then sDuration = CStr(oTimer.Duration) & " " If pvBrackets Then sDuration = "(" & Trim(sDuration) & " sec)" Else sDuration = "" End If Finally: _Duration = sDuration End Function ' SFUnitTests.SF_UnitTest._Duration REM ----------------------------------------------------------------------------- Private Function _ExecuteScript(psMethod As String) As Boolean ''' Run the given method and report start and stop ''' The targeted method is presumed not to return anything (Sub) ''' Args: ''' psMethod: the scope, the library and the module are predefined in the instance internals ''' Returns: ''' True when successful Dim bExecute As Boolean ' Return value Dim sRun As String ' SETUP, TEARDOWN or TESTCASE On Local Error GoTo Catch bExecute = True Try: ' Set status before the effective execution sRun = UCase(psMethod) Select Case UCase(psMethod) Case "SETUP" : _Status = STATUSSETUP Case "TEARDOWN" : _Status = STATUSTEARDOWN Case Else : _Status = STATUSTESTCASE sRun = "TESTCASE" End Select ' Report and execute _ReportMessage(" " & sRun & " " & LibraryName & "." & _Module & "." & psMethod & "() ENTER") Session.ExecuteBasicScript(Scope, LibraryName & "." & _Module & "." & psMethod, [Me]) _ReportMessage(" " & sRun & " " & LibraryName & "." & _Module & "." & psMethod & "() EXIT" _ & Iif(_STATUS = STATUSTESTCASE, " " & _Duration("Case", True), "")) ' Reset status _Status = STATUSSUITESTARTED Finally: _ExecuteScript = bExecute Exit Function Catch: bExecute = False _ReturnCode = RCABORTTEST GoTo Finally End Function ' SFUnitTests.SF_UnitTest._ExecuteScript REM ----------------------------------------------------------------------------- Private Function _PropertyGet(Optional ByVal psProperty As String) ''' Return the named property ''' Args: ''' psProperty: the name of the property Dim cstThisSub As String Dim cstSubArgs As String cstThisSub = "UnitTest.get" & psProperty cstSubArgs = "" SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Select Case UCase(psProperty) Case UCase("LongMessage") _PropertyGet = _LongMessage Case UCase("ReturnCode") _PropertyGet = _ReturnCode Case UCase("Verbose") _PropertyGet = _Verbose Case UCase("WhenAssertionFails") _PropertyGet = _WhenAssertionFails Case Else _PropertyGet = Null End Select Finally: SF_Utils._ExitFunction(cstThisSub) Exit Function End Function ' SFUnitTests.SF_UnitTest._PropertyGet REM ----------------------------------------------------------------------------- Private Function _PropertySet(Optional ByVal psProperty As String _ , Optional ByVal pvValue As Variant _ ) As Boolean ''' Set the new value of the named property ''' Args: ''' psProperty: the name of the property ''' pvValue: the new value of the given property ''' Returns: ''' True if successful Dim bSet As Boolean ' Return value Dim vWhenFailure As Variant ' WhenAssertionFails allowed values Dim cstThisSub As String Const cstSubArgs = "Value" If ScriptForge.SF_Utils._ErrorHandling() Then On Local Error GoTo Catch bSet = False cstThisSub = "SFUnitTests.UnitTest.set" & psProperty ScriptForge.SF_Utils._EnterFunction(cstThisSub, cstSubArgs) bSet = True Select Case UCase(psProperty) Case UCase("LongMessage") If Not ScriptForge.SF_Utils._Validate(pvValue, "LongMessage", ScriptForge.V_BOOLEAN) Then GoTo Finally _LongMessage = pvValue Case UCase("Verbose") If Not ScriptForge.SF_Utils._Validate(pvValue, "Verbose", ScriptForge.V_BOOLEAN) Then GoTo Finally _Verbose = pvValue Case UCase("WhenAssertionFails") If _ExecutionMode = SIMPLEMODE Then vWhenFailure = Array(0, 3) Else vWhenFailure = Array(0, 1, 2, 3) If Not ScriptForge.SF_Utils._Validate(pvValue, "WhenAssertionFails", ScriptForge.V_NUMERIC, vWhenFailure) Then GoTo Finally _WhenAssertionFails = pvValue Case Else bSet = False End Select Finally: _PropertySet = bSet ScriptForge.SF_Utils._ExitFunction(cstThisSub) Exit Function Catch: GoTo Finally End Function ' SFUnitTests.SF_UnitTest._PropertySet REM ----------------------------------------------------------------------------- Private Function _ReportMessage(ByVal psSysMessage As String _ , Optional ByVal pvMessage As Variant _ ) As Boolean ''' Report in the console: ''' - either the standard message ''' - either the user message when not blank ''' - or both ''' Args: ''' psSysMessage: the standard message as built by the calling routine ''' psMessage: the message provided by the user script ''' Returns: ''' True when successful Dim bReport As Boolean ' Return value Dim sIndent As String ' Indentation spaces bReport = False On Local Error GoTo Catch If IsMissing(pvMessage) Or IsEmpty(pvMessage) Then pvMessage = "" Try: Select Case True Case Len(pvMessage) = 0 Exception.DebugPrint(psSysMessage) Case _LongMessage Exception.DebugPrint(psSysMessage, pvMessage) Case Else Select Case _Status Case STATUSSTANDBY, STATUSSUITESTARTED : sIndent = "" Case STATUSSUITESTARTED : sIndent = Space(2) Case Else : sIndent = Space(4) End Select Exception.DebugPrint(sIndent & pvMessage) End Select Finally: _ReportMessage = bReport Exit Function Catch: bReport = False GoTo Finally End Function ' SFUnitTests.SF_UnitTest._ReportMessage REM ----------------------------------------------------------------------------- Private Function _Repr() As String ''' Convert the UnitTest instance to a readable string, typically for debugging purposes (DebugPrint ...) ''' Args: ''' Return: ''' "[UnitTest] Const cstUnitTest = "[UnitTest]" Const cstMaxLength = 50 ' Maximum length for items _Repr = cstUnitTest End Function ' SFUnitTests.SF_UnitTest._Repr REM ============================================== END OF SFUNITTESTS.SF_UNITTEST