2018-07-11 01:26:26 +03:00
|
|
|
---
|
|
|
|
title: Automatic Unit Testing
|
|
|
|
layout: default
|
2018-07-15 21:36:35 +03:00
|
|
|
root: ../..
|
2019-08-14 16:45:42 +03:00
|
|
|
idx: 8.5
|
2018-07-11 01:26:26 +03:00
|
|
|
---
|
|
|
|
|
2019-05-31 20:32:40 +03:00
|
|
|
## Introduction <!-- omit in toc -->
|
2018-07-11 01:26:26 +03:00
|
|
|
|
2018-07-15 21:02:10 +03:00
|
|
|
Unit tests are an essential tool in proving and reassuring yourself that your code
|
2018-07-11 01:26:26 +03:00
|
|
|
is correct. This chapter will show you how to write tests for Minetest mods and
|
2018-10-27 05:10:37 +03:00
|
|
|
games using Busted. Writing unit tests for functions where you call Minetest
|
2019-05-31 20:32:40 +03:00
|
|
|
functions is quite difficult, but luckily [in the previous chapter](clean_arch.html),
|
|
|
|
we discussed how to structure your code avoid this.
|
|
|
|
|
|
|
|
- [Installing Busted](#installing-busted)
|
|
|
|
- [Your First Test](#your-first-test)
|
|
|
|
- [init.lua](#initlua)
|
|
|
|
- [api.lua](#apilua)
|
|
|
|
- [tests/api_spec.lua](#testsapispeclua)
|
|
|
|
- [Mocking: Using External Functions](#mocking-using-external-functions)
|
|
|
|
- [Checking Commits with Travis](#checking-commits-with-travis)
|
|
|
|
- [Conclusion](#conclusion)
|
2018-07-11 01:26:26 +03:00
|
|
|
|
|
|
|
## Installing Busted
|
|
|
|
|
2019-05-31 20:32:40 +03:00
|
|
|
First, you'll need to install LuaRocks.
|
2018-07-15 21:02:10 +03:00
|
|
|
|
|
|
|
* Windows: Follow the [installation instructions on LuaRock's wiki](https://github.com/luarocks/luarocks/wiki/Installation-instructions-for-Windows).
|
|
|
|
* Debian/Ubuntu Linux: `sudo apt install luarocks`
|
2018-07-11 01:26:26 +03:00
|
|
|
|
2019-05-31 20:32:40 +03:00
|
|
|
Next, you should install Busted globally:
|
2018-07-11 01:26:26 +03:00
|
|
|
|
2018-07-15 21:02:10 +03:00
|
|
|
sudo luarocks install busted
|
2018-07-11 01:26:26 +03:00
|
|
|
|
2018-07-15 21:02:10 +03:00
|
|
|
Finally, check that it is installed:
|
2018-07-11 01:26:26 +03:00
|
|
|
|
2018-07-15 21:02:10 +03:00
|
|
|
busted --version
|
2018-07-11 01:26:26 +03:00
|
|
|
|
|
|
|
|
2018-07-15 21:02:10 +03:00
|
|
|
## Your First Test
|
2018-07-11 01:26:26 +03:00
|
|
|
|
2018-07-15 21:02:10 +03:00
|
|
|
Busted is Lua's leading unit test framework. Busted looks for Lua files with
|
|
|
|
names ending in `_spec`, and then executes them in a standalone Lua environment.
|
2018-07-11 01:26:26 +03:00
|
|
|
|
2018-07-15 21:02:10 +03:00
|
|
|
mymod/
|
|
|
|
├── init.lua
|
|
|
|
├── api.lua
|
|
|
|
└── tests
|
|
|
|
└── api_spec.lua
|
|
|
|
|
|
|
|
|
|
|
|
### init.lua
|
|
|
|
|
2018-09-19 14:04:51 +03:00
|
|
|
```lua
|
2018-07-15 21:02:10 +03:00
|
|
|
mymod = {}
|
|
|
|
|
|
|
|
dofile(minetest.get_modpath("mymod") .. "/api.lua")
|
2018-09-19 14:04:51 +03:00
|
|
|
```
|
2018-07-15 21:02:10 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### api.lua
|
|
|
|
|
2018-09-19 14:04:51 +03:00
|
|
|
```lua
|
2018-07-15 21:02:10 +03:00
|
|
|
function mymod.add(x, y)
|
|
|
|
return x + y
|
|
|
|
end
|
2018-09-19 14:04:51 +03:00
|
|
|
```
|
2018-07-15 21:02:10 +03:00
|
|
|
|
|
|
|
### tests/api_spec.lua
|
|
|
|
|
2018-09-19 14:04:51 +03:00
|
|
|
```lua
|
2018-07-15 21:02:10 +03:00
|
|
|
-- Look for required things in
|
|
|
|
package.path = "../?.lua;" .. package.path
|
|
|
|
|
|
|
|
-- Set mymod global for API to write into
|
|
|
|
_G.mymod = {} --_
|
|
|
|
-- Run api.lua file
|
|
|
|
require("api")
|
|
|
|
|
|
|
|
-- Tests
|
|
|
|
describe("add", function()
|
|
|
|
it("adds", function()
|
|
|
|
assert.equals(2, mymod.add(1, 1))
|
|
|
|
end)
|
|
|
|
|
|
|
|
it("supports negatives", function()
|
2019-05-31 20:32:40 +03:00
|
|
|
assert.equals(0, mymod.add(-1, 1))
|
2018-07-15 21:02:10 +03:00
|
|
|
assert.equals(-2, mymod.add(-1, -1))
|
|
|
|
end)
|
|
|
|
end)
|
2018-09-19 14:04:51 +03:00
|
|
|
```
|
2018-07-15 21:02:10 +03:00
|
|
|
|
|
|
|
You can now run the tests by opening a terminal in the mod's directory and
|
|
|
|
running `busted .`
|
|
|
|
|
2018-07-16 01:04:55 +03:00
|
|
|
It's important that the API file doesn't create the table itself, as globals in
|
2018-07-15 21:02:10 +03:00
|
|
|
Busted work differently. Any variable which would be global in Minetest is instead
|
|
|
|
a file local in busted. This would have been a better way for Minetest to do things,
|
|
|
|
but it's too late for that now.
|
|
|
|
|
|
|
|
Another thing to note is that any files you're testing should avoid calls to any
|
|
|
|
functions not inside of it. You tend to only write tests for a single file at once.
|
|
|
|
|
|
|
|
|
|
|
|
## Mocking: Using External Functions
|
|
|
|
|
|
|
|
Mocking is the practice of replacing functions that the thing you're testing depends
|
2018-10-27 05:10:37 +03:00
|
|
|
on. This can have two purposes; one, the function may not be available in the
|
|
|
|
test environment, and two, you may want to capture calls to the function and any
|
2018-07-15 21:02:10 +03:00
|
|
|
passed arguments.
|
|
|
|
|
|
|
|
If you follow the advice in the [Clean Architectures](clean_arch.html) chapter,
|
|
|
|
you'll already have a pretty clean file to test. You will still have to mock
|
2019-05-31 20:32:40 +03:00
|
|
|
things not in your area, however - for example, you'll have to mock the view when
|
2018-07-15 21:02:10 +03:00
|
|
|
testing the controller/API. If you didn't follow the advice, then things are a
|
|
|
|
little harder as you may have to mock the Minetest API.
|
|
|
|
|
2018-09-19 14:04:51 +03:00
|
|
|
```lua
|
2018-07-15 21:02:10 +03:00
|
|
|
-- As above, make a table
|
|
|
|
_G.minetest = {}
|
|
|
|
|
|
|
|
-- Define the mock function
|
|
|
|
local chat_send_all_calls = {}
|
|
|
|
function minetest.chat_send_all(name, message)
|
|
|
|
table.insert(chat_send_all_calls, { name = name, message = message })
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Tests
|
|
|
|
describe("list_areas", function()
|
|
|
|
it("returns a line for each area", function()
|
|
|
|
chat_send_all_calls = {} -- reset table
|
|
|
|
|
|
|
|
mymod.list_areas_to_chat("singleplayer", "singleplayer")
|
|
|
|
|
|
|
|
assert.equals(2, #chat_send_all_calls)
|
|
|
|
end)
|
|
|
|
|
|
|
|
it("sends to right player", function()
|
|
|
|
chat_send_all_calls = {} -- reset table
|
|
|
|
|
|
|
|
mymod.list_areas_to_chat("singleplayer", "singleplayer")
|
|
|
|
|
|
|
|
for _, call in pairs(chat_send_all_calls) do --_
|
|
|
|
assert.equals("singleplayer", call.name)
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
|
2018-09-24 19:16:00 +03:00
|
|
|
-- The above two tests are actually pointless,
|
|
|
|
-- as this one tests both things
|
2018-07-15 21:02:10 +03:00
|
|
|
it("returns correct thing", function()
|
|
|
|
chat_send_all_calls = {} -- reset table
|
|
|
|
|
|
|
|
mymod.list_areas_to_chat("singleplayer", "singleplayer")
|
|
|
|
|
|
|
|
local expected = {
|
|
|
|
{ name = "singleplayer", message = "Town Hall (2,43,63)" },
|
|
|
|
{ name = "singleplayer", message = "Airport (43,45,63)" },
|
|
|
|
}
|
|
|
|
assert.same(expected, chat_send_all_calls)
|
|
|
|
end)
|
|
|
|
end)
|
2018-09-19 14:04:51 +03:00
|
|
|
```
|
2018-07-15 21:02:10 +03:00
|
|
|
|
|
|
|
|
|
|
|
## Checking Commits with Travis
|
|
|
|
|
2018-10-27 05:10:37 +03:00
|
|
|
The Travis script from the [Automatic Error Checking](luacheck.html)
|
2018-07-15 21:02:10 +03:00
|
|
|
chapter can be modified to also run Busted.
|
|
|
|
|
2018-09-19 14:04:51 +03:00
|
|
|
```yml
|
2018-07-15 21:02:10 +03:00
|
|
|
language: generic
|
|
|
|
sudo: false
|
|
|
|
addons:
|
|
|
|
apt:
|
|
|
|
packages:
|
|
|
|
- luarocks
|
|
|
|
before_install:
|
|
|
|
- luarocks install --local luacheck && luarocks install --local busted
|
|
|
|
script:
|
2019-02-22 17:37:16 +03:00
|
|
|
- $HOME/.luarocks/bin/luacheck .
|
2018-07-15 21:02:10 +03:00
|
|
|
- $HOME/.luarocks/bin/busted .
|
|
|
|
notifications:
|
|
|
|
email: false
|
2018-09-19 14:04:51 +03:00
|
|
|
```
|
2018-07-15 21:02:10 +03:00
|
|
|
|
|
|
|
|
|
|
|
## Conclusion
|
|
|
|
|
|
|
|
Unit tests will greatly increase the quality and reliability of your project if used
|
|
|
|
well, but they require you to structure your code in a different way than usual.
|
|
|
|
|
|
|
|
For an example of a mod with lots of unit tests, see
|
|
|
|
[crafting by rubenwardy](https://github.com/rubenwardy/crafting).
|