Skip to content
Permalink
master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time

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)
end

So 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())
end

Define 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
end

To 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)