UnitTest
The unit test frame work is used to build test cases and process them. The result will be logs sent to the System.Logger.Default.
Start with an example:
require "PLoop"
require "PLoop.System.UnitTest"
PLoop.System.Logger.Default:AddHandler(print)
UnitTest "Test.Example" (function(_ENV)
__Test__() function test1()
local a = 1
Assert.Equal(a, 1)
end
__Test__() function test2()
local a = 1
Assert.True( a > 1)
end
end)
UnitTest "Test.Example2" (function(_ENV)
__Test__() function test1()
local a = 1
Assert.Nil(a)
end
end)
-- [03/12/19 13:00:49][Info][UnitTest]Test.Example.test1 PASS
-- [03/12/19 13:00:49][Warn][UnitTest]Test.Example.test2 Failed - Expected true condition@xxxx.lua:14
-- [03/12/19 13:00:49][Warn][UnitTest]Test.Example2.test1 Failed - Expected nil value@xxxx.lua:21
UnitTest("Test"):Run()Table of Contents
System.UnitTest
The UnitTest system is a standalone lib in the PLoop, so we need to require it before using it, The System.UnitTest is a global namespace.
Before we use it, we must make sure the default logger has log handlers so we can get the test case results directly.
The System.UnitTest is a class inherit the System.Module, so it works just like the module system, we can have sub unit test and when the root unit test's Run method is called, the sub unit test's test cases will also be processed.
In an unit test module, we use __Test__ attribute to decalre a function as test case, each test case should be processed with orders. And the result will be saved in the test case, also will be send as logs as we can see in the previous example.
Within each test case, the Assert can be used, it contains several condition test method, and would raise the System.UnitTest.TestFailureException exception so the system know whether is a syntax error or a conditional check error.
| Event | Argument | Description |
|---|---|---|
| OnInit | Fired before process all test cases in the unit test | |
| OnFinal | Fired when all test cases in the unit test are processed | |
| BeforeCase | testCase | Fired before processing a test case |
| AfterCase | testCase | Fired when a test case is processed |
We'll see details about the test case in a later.
| Method | Description |
|---|---|
| Run | Process the test cases in the unit test and process all its sub unit tests |
| Property | Description |
|---|---|
| TestCases | Gets the test cases as a List |
| HasSubUnitTests | Gets whether the unit test has sub unit tests |
| SubUnitTests | Gets the sub unit tests as a List |
Here is an example:
require "PLoop"
require "PLoop.System.UnitTest"
UnitTest "Test.Example" (function(_ENV)
function OnInit(self)
print("Init before all test")
end
function OnFinal(self)
print("Finished all test")
end
function BeforeCase(self, case)
print("start " .. case.name)
end
function AfterCase(self, case)
print("finish " .. case.name)
end
__Test__() function test1() end
__Test__() function test2() Assert.Nil(1) end
end)
-- Init before all test
-- start test1
-- finish test1
-- start test2
-- finish test2
-- Finished all test
UnitTest("Test"):Run()
-- test1 Succeed
-- test2 Failed
for _, unittest in UnitTest("Test").SubUnitTests:GetIterator() do
for _, case in unittest.TestCases:GetIterator() do
print(case.name, UnitTest.TestState(case.state))
end
end
With the events, we can init objects, dispose objects, open/close resources. Through the test case objects, we can know every details of the test case and their test result.
System.UnitTest.TestCase
The TestCase is a member struct used to store the test function, owner, description, test result and error message.
Here is its definition:
__Sealed__() enum "TestState" {
Succeed = 1,
Failed = 2,
Error = 3,
}
__Sealed__() struct "TestCase" {
{ name = "owner", type = UnitTest, require = true },
{ name = "name", type = String, require = true },
{ name = "func", type = Function, require = true },
{ name = "desc", type = String },
{ name = "state", type = UnitTest.TestState, default = UnitTest.TestState.Succeed },
{ name = "message", type = String },
}For a more detail example:
require "PLoop"
require "PLoop.System.UnitTest"
UnitTest "Test.Example" (function(_ENV)
__Test__"Should always succeed"
function test1() end
__Test__"Should always fail"
function test2() Assert.Nil(1) end
end)
UnitTest("Test"):Run()
-- ---------
-- [NAME] test1
-- [DESC] Should always succeed
-- [RSLT] Succeed
-- [MESG] nil
-- ---------
-- [NAME] test2
-- [DESC] Should always fail
-- [RSLT] Failed
-- [MESG] Expected nil value@xxxx.lua:9
for _, case in UnitTest("Test.Example").TestCases:GetIterator() do
print("---------")
print("[NAME]", case.name)
print("[DESC]", case.desc)
print("[RSLT]", UnitTest.TestState(case.state))
print("[MESG]", case.message)
endSo the test result should saved in those test cases.
System.UnitTest.Assert
The Assert provide methods to check conditions, a test case is only success if all asserts in it are passed.
Equal
Checks whether the two values are equal(only primitive)
-
Params:
- expected -- The expected value
- actual -- The actual value
-
Usage:
Assert.Equal(10, func())False
Checks that the condition is false
-
Params:
- condition -- the condition
-
Usage:
Assert.False(1 > 3)NotNil
Checks that the value isn't nil
-
Params:
- val -- the value
-
Usage:
Assert.NotNil(func())Nil
Checks that the value is nil
-
Params:
- val -- the value
-
Usage:
Assert.Nil(func())True
Checks that the condition is true
-
Params:
- condition -- the condition
-
Usage:
Assert.True(4 > 3)Same
Checks that the two values are the same(like tables contains the same key-value pairs)
-
Params:
- expected -- The expected value
- actual -- The actual value
-
Usage:
Assert.Same({ 1 }, func())Include
Checks that the actual value(table) contains all elements of the expected
-
Params:
- expected -- The expected value
- actual -- The actual value
-
Usage:
Assert.Include({ 1 }, { 1, 2 })Error
Checks that the function should raise an error, the error message will be returned
-
Params:
- func -- The function to be called
- ... -- The arguments passed to the func Return:
- message -- The error message
-
Usage:
Assert.Error(function(v) return 1/v end)Match
Checks that the message should match the pattern
-
Params:
- pattern -- the pattern
- message -- the message
-
Usage:
Assert.Match("the value must be %s, got %s",
Assert.Error(
function()
local v = Number(true)
end
)
)Find
Checks that the message should contains the pattern as plain text
-
Params:
- pattern -- the pattern
- message -- the message
-
Usage:
Assert.Find("xxxx.lua:xx: the value must be number, got boolean",
Assert.Error(
function()
local v = Number(true)
end
)
)Fail
Fail the test with message
-
Params:
- message -- the fail message
-
Usage:
Assert.Fail("Something wrong")Step
This is a special test method combined with three method Step, GetSteps and ResetSteps, the Step method is used to record special value as step token, the GetSteps method will return those tokens in a table and the ResetSteps will be used to clear those tokens, normally the ResetSteps is called by the UnitTest system.
__Test__() function multiver()
class "MA" (function(_ENV)
function Test(self)
Assert.Step("Old")
end
end)
local obj = MA()
class "MA" (function(_ENV)
function Test(self)
Assert.Step("New")
end
end)
obj:Test()
Assert.Same({ "Old" }, Assert.GetSteps())
endDefine Custom Assert Method
Take the definition of the Equal :
__Static__() function Equal(expected, actual)
if expected ~= actual then throw(TestFailureException("Expected " .. tostring(expected) .. ", got " .. tostring(actual))) end
endTo define an assert method, we only need to throw a TestFailureException exception if check failed.
Here is an example:
require "PLoop"
require "PLoop.System.UnitTest"
PLoop(function(_ENV)
function Assert.CheckRange(min, max, val)
if val < min or val > max then
throw(TestFailureException("Expected value in [" .. min .. ", " .. max .. "]"))
end
end
UnitTest "Test.Example" (function(_ENV)
__Test__()
function test()
Assert.CheckRange(0, 1, 1.5)
end
end)
UnitTest("Test"):Run()
-- ---------
-- [NAME] test
-- [RSLT] Failed
-- [MESG] Expected value in [0, 1]@xxxx.lua:15
for _, case in UnitTest("Test.Example").TestCases:GetIterator() do
print("---------")
print("[NAME]", case.name)
print("[RSLT]", UnitTest.TestState(case.state))
print("[MESG]", case.message)
end
end)