We have moved at community.getvera.com

Author Topic: openLuup: Unit/fonctional testing on openLuup  (Read 794 times)

Offline vosmont

  • Beta Testers
  • Hero Member
  • *****
  • Posts: 687
  • Karma: +60/-8
openLuup: Unit/fonctional testing on openLuup
« on: April 25, 2016, 05:50:45 am »
openLuup is great for develop and test your plugins. But as each action taken into account by the Luup engine, can take some time to be processed, you have to test it asynchronously.

Here is a mini-tutorial to achieve this.

Test campaign :

It works the same way as classic tests with LuaUnit, except that your test has to be prefixed with 'asynctest'.

You will find a tool to execute your asynchronous sequences : 'doAsyncTestSequence' and a modified version of LuaUnit.

Code: [Select]
local t = require "tests.luaunit"
local RulesEngine = require "L_RulesEngine1"

RulesEngine.setVerbosity(4)
RulesEngine.enable()

-- Log messages concerning these unit tests
local function log(msg)
RulesEngine.log("TEST - " .. msg, "TestRulesEngine")
end

-- Wrap temporary a function in the global context
-- Make the function reacheable by luup fonctions
-- Once the function is called, the pointer in the global context
-- is released for garbage collecting
function wrapAnonymousCallback(callback)
local callbackName = "anonymousCallback-" .. tostring(callback)
_G[callbackName] = function(...)
local args = {...}
callback(unpack(args))
_G[callbackName] = nil
end
return callbackName
end

-- Execute an asynchronous test sequence,
-- params are functions to execute by LuaUnit and optionnal waiting times
local _sequenceItemIdx
function _doAsyncTestSequence(...)
local args = {...}
local firstArg = table.remove(args, 1)
if (type(firstArg) == "number") then
log("Wait " .. firstArg .. " second(s) before doing sequence part #" .. _sequenceItemIdx)
luup.call_delay(
wrapAnonymousCallback(function()
_doAsyncTestSequence(unpack(args))
end),
firstArg, ""
)
elseif (type(firstArg) == "function") then
-- Execute by LuaUnit (to catch error)
log("Execute sequence part #" .. _sequenceItemIdx)
local ok, errMsg = t.protectedCall(firstArg)
if ok then
-- If there's no error, resume the sequence
if (#args > 0) then
_sequenceItemIdx = _sequenceItemIdx + 1
_doAsyncTestSequence(unpack(args))
end
else
-- Otherwise, the test sequence is finished
log("There's an error : stop test sequence")
t.done()
return
end
else
-- error
print("arg of type " .. type(firstArg) .. " is not handled")
end
if (#args == 0) then
-- It's finished
t.done()
end
end
function doAsyncTestSequence(...)
_sequenceItemIdx = 1
_doAsyncTestSequence(...)
end


-- **************************************************
-- RulesEngine TestCases
-- **************************************************

TestRulesEngine = {}

function TestRulesEngine:setUp(testName)
log("********************************************************\n-------> Begin of TestCase '" .. tostring(testName) .. "'")
end

function TestRulesEngine:tearDown(testName)
log("********************************************************\n<------- End of TestCase '" .. tostring(testName) .. "'")
end

function TestRulesEngine:asynctest_condition_value_EQ()
doAsyncTestSequence(
function()
log("(Value==) Under")
luup.variable_set("urn:upnp-org:serviceId:TemperatureSensor1", "CurrentTemperature", 9.2, RulesEngine.getDeviceIdByName("Temperature 1"))
end,
1,
function()
t.assertFalse(RulesEngine.Rule.isActive("Rule_Condition_Value"))
log("(Value==) Equals")
luup.variable_set("urn:upnp-org:serviceId:TemperatureSensor1", "CurrentTemperature", 10.0, RulesEngine.getDeviceIdByName("Temperature 1"))
end,
1,
function()
t.assertTrue(RulesEngine.Rule.isActive("Rule_Condition_Value"))
log("(Value==) Above")
luup.variable_set("urn:upnp-org:serviceId:TemperatureSensor1", "CurrentTemperature", "11", RulesEngine.getDeviceIdByName("Temperature 1"))
end,
1,
function()
t.assertFalse(RulesEngine.Rule.isActive("Rule_Condition_Value"))
end
)
end

-- run all tests
t.LuaUnit.run "-v"

Startup :

Here is a startup that initialize your openLuup system (run only on a copy of a working openLuup environment, because it will be reinitialized/erased).
There are 2 tricks :
- Startup LUA code that will start the tests some seconds after the reload of the Luup engine.
- Preload of the library/module of the plugin to test, and put it in loaded modules (Luup engine does not do that)
  package.loaded["L_RulesEngine1"] = require "L_RulesEngine1"
  When your plugin requires this module in its implementation file, it will reuse the preloaded one.
  It makes you able to manipulate internal methods of your plugin, because you will share the same library.

Code: [Select]
-- Create rooms
do
local function room(n)
luup.inet.wget ("127.0.0.1:3480/data_request?id=room&action=create&name=" .. n)
end 
room "Room 1"
room "Room 2"
end

luup.attr_set ("StartupCode", [[
luup.log "Load RulesEngine library"
package.loaded["L_RulesEngine1"] = require "L_RulesEngine1"

function startUnitTests()
luup.log "Start RulesEngine unit tests"
assert(loadfile("./tests/test_RulesEngine.lua"))()
end

luup.call_delay("startUnitTests", 2)
]])

do -- ALTUI
luup.create_device ("", "ALTUI", "ALTUI", "D_ALTUI.xml")
end

do -- RulesEngine
local deviceId = luup.create_device ("", "RulesEngine", "RulesEngine", "D_RulesEngine1.xml")
luup.variable_set("urn:upnp-org:serviceId:RulesEngine1", "Debug", "4", deviceId)
end

do -- Temperature sensors
for i = 1, 5 do
luup.create_device ("", "Temperature" .. i, "Temperature " .. i, "D_TemperatureSensor1.xml")
end
end
do -- Motion sensors
for i = 1, 5 do
luup.create_device ("", "Motion" .. i, "Motion " .. i, "D_MotionSensor1.xml")
end
end
do -- Switches
local deviceId, roomId
for i = 1, 10 do
deviceId = luup.create_device ("", "Switch" .. i, "Switch " .. i, "D_BinaryLight1.xml")
if (i < 6) then
roomId = 1
else
roomId = 2
end
luup.attr_set ("room", roomId, deviceId)
end
end

-- Force writing "user_data.json"
luup.reload()

Enjoy !
« Last Edit: April 27, 2016, 09:50:10 am by vosmont »

Offline akbooer

  • Moderator
  • Master Member
  • *****
  • Posts: 6387
  • Karma: +292/-70
  • "Less is more"
Re: openLuup: Unit/fonctional testing on openLuup
« Reply #1 on: April 25, 2016, 08:38:03 am »
@vosmont.

This is a tour de force - will take a little while to assimilate and comment!

The tip about require is interesting... perhaps I should make this behaviour the default?  Although I think this is not so for Vera plugins.
3x Vera Lite-UI5/Edge-UI7, 25x Fibaro, 23x TKB, 9x MiniMote, 2x NorthQ Power, 2x Netatmo, 1x Foscam FI9831P, 9x Philips Hue,
Razberry, MySensors Arduino, HomeWave, AltUI, AltHue, DataYours, Grafana, openLuup, ZWay, ZeroBrane Studio.

Offline vosmont

  • Beta Testers
  • Hero Member
  • *****
  • Posts: 687
  • Karma: +60/-8
Re: openLuup: Unit/fonctional testing on openLuup
« Reply #2 on: April 27, 2016, 09:54:55 am »
I've updated the code of the function doAsyncTestSequence.

Since the first post, I've done a lot of functionnal tests with this tutorial... and it works very well  :)
(I've even found bugs in my plugin).

Just after having updated your code, the test campaign is automaticly launched at the reload of the Luup engine.
So you can detect very quickly a regression.