[Module] UnitTest - Unit Testing for your applications

Share your advanced PureBasic knowledge/code with the community.
Env
Enthusiast
Enthusiast
Posts: 151
Joined: Tue Apr 27, 2010 3:20 pm
Location: Wales, United Kingdom

[Module] UnitTest - Unit Testing for your applications

Post by Env »

Hi,

I had a requirement to include unit testing in a project I'm working on, so created this UnitTest module which allows you to create a unit test file within your application and create tests (procedures) which consist of assertion tests, providing a nice readable output (as well as providing an exit code of 0 on success, or -1 on test(s) failing for automation purposes)

Assertion Methods:

Code: Select all

UnitTest::Assert(Expression, Description$) ;- Generic expression assertion test (i.e. #True = #True)
UnitTest::AssertTrue(Expression, Description$) ;- Test the expression is true.
UnitTest::AssertFalse(Expression, Description$) ;- Test the expression is false.
UnitTest::AssertEqual(LH, RH, Description$) ;- Test that the Left Hand variable equals Right Hand variable.
UnitTest::AssertNotEqual(LH, RH, Description$) ;- Test that the Left Hand variable does not equal Right Hand variable.
UnitTest::AssertGreater(LH, RH, Description$) ;- Test that the Left Hand variable is greater than the Right Hand variable.
UnitTest::AssertLess(LH, RH, Description$) ;- Test that the Left Hand variable is less than the Right Hand variable.
UnitTest::AssertEmpty(String, Description$) ;- Test that the passed String is empty (Len=0)
UnitTest::AssertNotEmpty(String, Description$) ;- Test that the passed String is not empty (Len>0)
Creating Tests:

Code: Select all

;- Create a Test
Procedure MyTest()
 
  ; Perform Assertions

Endprocedure : UnitTest::Register(MyTest)
When you call UnitTest::Register, pass in the Procedure (without brackets) and it gets added to the unit test queue.

Executing:

Code: Select all

;- Will run the unit tests in the queue and quit the application. 
UnitTest::RunAll(#True) ; Pass True if you want the console to pause (input()) on exit.

;- If you want a quick way to only run if the file is the main file;
UnitTest::RunIfMain(#True)
Console Preview:
Image

Code (with Sample)

Code: Select all

CompilerIf Defined(PreComp, #PB_Module) = #False
  DeclareModule PreComp
    Macro _DQuote
      "
    EndMacro
    Macro _Str(Expression)
      PreComp::_DQuote#Expression#PreComp::_DQuote
    EndMacro
  EndDeclareModule
  Module PreComp : EndModule
CompilerEndIf

DeclareModule UnitTest
  
  Prototype pTestHandler()
  
  Declare _registerHandler(TestName.s, TestFile.s, *TestHandler.pTestHandler)
  Declare _handleAssert(AssertType.s, Description.s, Result, Test.s, FailReason.s = "")
  Declare RunAll(PauseOnExit = #False)
  
  Macro RunIfMain(PauseOnExit = #False)
    CompilerIf #PB_Compiler_IsMainFile
      UnitTest::RunAll(PauseOnExit)
    CompilerEndIf
  EndMacro
  
  Macro Assert(Expression, Description)
    If Expression
      UnitTest::_handleAssert("Assert", Description, #True, #PB_Compiler_Procedure)
    Else
      UnitTest::_handleAssert("Assert", Description, #False, #PB_Compiler_Procedure)
    EndIf
  EndMacro
  
  Macro AssertTrue(Expression, Description)
    If (Expression) = #True
      UnitTest::_handleAssert("AssertTrue", Description, #True, #PB_Compiler_Procedure)
    Else
      UnitTest::_handleAssert("AssertTrue", Description, #False, #PB_Compiler_Procedure)
    EndIf
  EndMacro
  
  Macro AssertFalse(Expression, Description)
    If (Expression) = #False
      UnitTest::_handleAssert("AssertFalse", Description, #True, #PB_Compiler_Procedure)
    Else
      UnitTest::_handleAssert("AssertFalse", Description, #False, #PB_Compiler_Procedure)
    EndIf
  EndMacro
  
  Macro AssertEqual(LH, RH, Description)
    CompilerIf TypeOf(LH) <> #PB_String
      If LH = RH
        UnitTest::_handleAssert("AssertEqual", Description, #True, #PB_Compiler_Procedure)
      Else
        UnitTest::_handleAssert("AssertEqual", Description, #False, #PB_Compiler_Procedure, Str(LH) + " != " + Str(RH))
      EndIf
    CompilerElse
      If LH = RH
        UnitTest::_handleAssert("AssertEqual", Description, #True, #PB_Compiler_Procedure)
      Else
        UnitTest::_handleAssert("AssertEqual", Description, #False, #PB_Compiler_Procedure, "Strings not Equal")
      EndIf
    CompilerEndIf
  EndMacro

  Macro AssertNotEqual(LH, RH, Description)
    CompilerIf TypeOf(LH) <> #PB_String
      If LH <> RH
        UnitTest::_handleAssert("AssertNotEqual", Description, #True, #PB_Compiler_Procedure)
      Else
        UnitTest::_handleAssert("AssertNotEqual", Description, #False, #PB_Compiler_Procedure, Str(LH) + " = " + Str(RH))
      EndIf
    CompilerElse
      If LH <> RH
        UnitTest::_handleAssert("AssertNotEqual", Description, #True, #PB_Compiler_Procedure)
      Else
        UnitTest::_handleAssert("AssertNotEqual", Description, #False, #PB_Compiler_Procedure, "Strings Equal")
      EndIf
    CompilerEndIf
  EndMacro  
    
  Macro AssertGreater(LH, RH, Description)
    CompilerIf TypeOf(LH) <> #PB_String
      If Bool(LH > RH)
        UnitTest::_handleAssert("AssertGreater", Description, #True, #PB_Compiler_Procedure)
      Else
        UnitTest::_handleAssert("AssertGreater", Description, #False, #PB_Compiler_Procedure, Str(LH) + " not greater than " + Str(RH))
      EndIf
    CompilerElse
      UnitTest::_handleAssert("AssertGreater", Description, #False, #PB_Compiler_Procedure, "Cannot compare string")
    CompilerEndIf
  EndMacro
  
  Macro AssertLess(LH, RH, Description)
    CompilerIf TypeOf(LH) <> #PB_String
      If Bool(LH < RH)
        UnitTest::_handleAssert("AssertLess", Description, #True, #PB_Compiler_Procedure)
      Else
        UnitTest::_handleAssert("AssertLess", Description, #False, #PB_Compiler_Procedure, Str(LH) + " not less than " + Str(RH))
      EndIf
    CompilerElse
      UnitTest::_handleAssert("AssertLess", Description, #False, #PB_Compiler_Procedure, "Cannot compare string")
    CompilerEndIf
  EndMacro
    
  Macro AssertNotEmpty(String, Description)
    CompilerIf TypeOf(String) <> #PB_String
      UnitTest::_handleAssert("AssertNotEmpty", Description, #False, #PB_Compiler_Procedure, "Not a string")
    CompilerElse
      UnitTest::_handleAssert("AssertNotEmpty", Description, Bool(Len(String) > 0), #PB_Compiler_Procedure, "Length is zero")
    CompilerEndIf
  EndMacro
  
  Macro AssertEmpty(String, Description)
    CompilerIf TypeOf(String) <> #PB_String
      UnitTest::_handleAssert("AssertEmpty", Description, #False, #PB_Compiler_Procedure, "Not a string")
    CompilerElse
      UnitTest::_handleAssert("AssertEmpty", Description, Bool(Len(String) = 0), #PB_Compiler_Procedure, "Length not zero")
    CompilerEndIf
  EndMacro
  
  Macro Register(Method)
    CompilerIf Defined(Method, #PB_Procedure)
      UnitTest::_registerHandler(PreComp::_Str(Method), #PB_Compiler_FilePath + #PB_Compiler_Filename, @Method#())
    CompilerElse
      CompilerError "Test handler '" + PreComp::_Str(Method) + "' is not declared."
    CompilerEndIf    
  EndMacro
  
EndDeclareModule

Module UnitTest
  
  Structure sTest
    name.s
    file.s
    *handler.pTestHandler
  EndStructure
  
  Structure sTests
    List test.sTest()
    testFail.w
    testPass.w
    groupFail.a
    failed.a
  EndStructure
  
  Global gTests.sTests
  
  Procedure _registerHandler(TestName.s, TestFile.s, *TestHandler.pTestHandler)
    If TestName <> "" And *TestHandler <> #Null
      AddElement(gTests\test())
      With gTests\test()
        \name = TestName
        \file = TestFile
        \handler = *TestHandler
      EndWith
    EndIf
  EndProcedure
  
  Procedure _handleAssert(AssertType.s, Description.s, Result, Test.s, FailReason.s = "")
    ConsoleColor(7, 0)
    Print(" - " + LSet("(" + AssertType + ")", 18) + " " + Test + " - " + Description + ": ")
    If Result
      ConsoleColor(10, 0)
      PrintN("Passed")
    Else
      ConsoleColor(12, 0)
      If FailReason = ""
        PrintN("Failed")
      Else
        Print("Failed")
        ConsoleColor(3, 0)
        PrintN(" (" + FailReason + ")")
      EndIf
      gTests\testFail + 1
      gTests\failed = #True
    EndIf
  EndProcedure
  
  Procedure RunAll(PauseOnExit = #False)
    Protected.s currentFile
    OpenConsole("Unit Tests")
    EnableGraphicalConsole(#True)
    SortStructuredList(gTests\test(), #PB_Sort_Ascending | #PB_Sort_NoCase, OffsetOf(sTest\file), #PB_String)
    ConsoleColor(7, 0)
    PrintN("Running " + ListSize(gTests\test()) + " unit tests...")
    gTests\groupFail = #False
    ForEach gTests\test()
      With gTests\test()
        ConsoleColor(7, 0)
        If currentFile <> \file
          PrintN("")
          PrintN("Running tests for: " + \file)
          PrintN("")
          currentFile = \file
        EndIf          
        gTests\failed = #False
        \handler()
        ConsoleColor(7, 0)
        PrintN("")
        Print(\name + " tests: ")
        If gTests\failed = #False
          ConsoleColor(10, 0)
          PrintN("Passed")
          gTests\testPass + 1
        Else
          ConsoleColor(12, 0)
          PrintN("Failed")
        EndIf
        PrintN("")
      EndWith
    Next      
    ConsoleColor(7, 0)
    If gTests\testFail > 0
      Print(Str(gTests\testFail) + " tests ")
      ConsoleColor(12, 0)
      PrintN("Failed")
    Else
      Print("All tests ")
      ConsoleColor(10, 0)
      PrintN("Passed")
    EndIf    
    If PauseOnExit
      ConsoleColor(7, 0)
      PrintN("")
      PrintN("Hit return to exit.")
      Input()
    EndIf
    If gTests\testFail > 0
      End - 1
    EndIf
    End 0
  EndProcedure
EndModule

; ==============================================
; Sample Start
; ==============================================

CompilerIf #PB_Compiler_IsMainFile

Procedure UnitTest_BasicTests()
  
  UnitTest::Assert(#True = #True, "Assert Test")
  UnitTest::AssertTrue(#True = #True, "AssertTrue Test")
  UnitTest::AssertFalse(#True = #False, "AssertFalse Test")
  
EndProcedure : UnitTest::Register(UnitTest_BasicTests)

Procedure UnitTest_ComparisonTests()
  Protected value1.i, value2.i
  
  value1 = 100
  value2 = 200
  
  UnitTest::AssertEqual(value1, value1, "AssertEqual Test")
  UnitTest::AssertNotEqual(value1, value2, "AssertNotEqual Test")
  
  UnitTest::AssertLess(value1, value2, "AssertLess Test")
  UnitTest::AssertGreater(value2, value1, "AssertGreater Test")
    
EndProcedure : UnitTest::Register(UnitTest_ComparisonTests)

Procedure UnitTest_StringTests()
  Protected string1.s, string2.s, string3.s
  
  string1 = "Hello"
  string2 = "World"
  string3 = ""
  
  UnitTest::AssertNotEqual(string1, string2, "AssertNotEqual String Test")
  UnitTest::AssertNotEmpty(string1, "AssertNotEmpty String Test")
  UnitTest::AssertEmpty(string3, "AssertEmpty String Test")

EndProcedure : UnitTest::Register(UnitTest_StringTests)

Procedure UnitTest_FailTests()
  Protected value1.i, value2.i
  Protected string1.s, string2.s, string3.s
  
  value1 = 100
  value2 = 200
  
  string1 = ""
  string2 = "Hello"
  string3 = "Not Empty"
  
  UnitTest::AssertEqual(value1, value2, "AssertEqual Test (Should Fail)")
  UnitTest::AssertNotEqual(value1, value1, "AssertNotEqual Test (Should Fail)")
  
  UnitTest::AssertLess(value2, value1, "AssertLess Test (Should Fail)")
  UnitTest::AssertGreater(value1, value2, "AssertGreater Test (Should Fail)")
  
  UnitTest::AssertNotEqual(string2, string2, "AssertNotEqual String Test (Should Fail)")
  UnitTest::AssertNotEmpty(string1, "AssertNotEmpty String Test (Should Fail)")
  UnitTest::AssertEmpty(string3, "AssertEmpty String Test (Should Fail)")
  
EndProcedure : UnitTest::Register(UnitTest_FailTests)

UnitTest::RunIfMain(#True)

CompilerEndIf
Thanks!
Cyllceaux
Enthusiast
Enthusiast
Posts: 510
Joined: Mon Jun 23, 2014 1:18 pm

Re: [Module] UnitTest - Unit Testing for your applications

Post by Cyllceaux »

Really nice :D

But for me, I used a simpler way

Code: Select all

DeclareModule Assert
	
	Macro assertEquals(expect,actual)
		CompilerIf #PB_Compiler_Debugger
			If expect<>actual
				DebuggerWarning("Ungleicher Wert")
				Debug "Erwartet",1
				Debug expect,1
				
				Debug "Aktuell",1
				Debug actual,1
			EndIf
			
		CompilerEndIf
	EndMacro
	
	Macro assertNotEquals(expect,actual)
		CompilerIf #PB_Compiler_Debugger
			If expect=actual
				DebuggerWarning("Gleicher Wert")
				Debug "Wert 1",1
				Debug expect,1
				
				Debug "Wert 2",1
				Debug actual,1
			EndIf
			
		CompilerEndIf
	EndMacro
	
	Macro assertFalse(actual)
		CompilerIf #PB_Compiler_Debugger
			If actual
				DebuggerWarning("Wert gesetzt")
			EndIf
		CompilerEndIf
	EndMacro
	
	Macro assertTrue(actual)
		CompilerIf #PB_Compiler_Debugger
			If Not actual
				DebuggerWarning("Wert nicht gesetzt")
			EndIf
		CompilerEndIf
	EndMacro
	
EndDeclareModule

Module Assert:EndModule

CompilerIf #PB_Compiler_IsMainFile
	; OK
	Assert::assertEquals(1,1)
	Assert::assertEquals("MM","MM")
	Assert::assertNotEquals(1,2)
	Assert::assertNotEquals("MM","LL")
	Assert::assertTrue(#True)
	Assert::assertTrue(Bool("MM"="MM"))
	Assert::assertFalse(#False)
	Assert::assertFalse(Bool("MM"="LL"))
	
	; Warnings
	Assert::assertEquals(1,2)
	Assert::assertEquals("MM","LL")
	Assert::assertNotEquals(1,1)
	Assert::assertNotEquals("MM","MM")
	Assert::assertTrue(#False)
	Assert::assertTrue(Bool("MM"="NN"))
	Assert::assertFalse(#True)
	Assert::assertFalse(Bool("MM"="MM"))
	
	
CompilerEndIf
Its not really Unit-Testing Like your version, but It shows me the Warnings in the code with the Message
Post Reply