Introduction
I want to start off strong by having you try something in Roblox Studio.
- Create a script
- Put .spec at the end of the script name
Now try typing “describe.” Notice how the function name autofills, despite it not being explicitly defined. Why?
This is because internally, Roblox uses .spec
files to test and debug code. This code is run with TestEZ, and is used as a BDD-style testing framework.
TestEZ is used for testing apps, in-game core scripts, built-in Roblox Studio plugins, and libraries such as Roact and Rodux. It provides an API that can run all tests with a single method call as well as a more granular API that exposes each step of the pipeline.
How do I use TestEZ?
Rather than break it down here, I recommend you read the official documentation on it!
https://roblox.github.io/testez/
How do I download TestEZ?
You could always visit the source code and port it to Roblox yourself. Don’t worry though, I have a Roblox model you can use.
TestEZ.rbxm (19.3 KB)
Download this module and put it in a spot where it’s easily accessible to your code.
Using TestEZ with .spec
files
Now for the part you want, the actual walkthrough!
For the sake of the following examples, I have both TestEZ and a test script located in ServerScriptService
.
In TestScript
, I want to make an example module, as well as a .spec
file for it. I’m going to name the module “DevforumExample,” and will name the spec file “DevforumExample.spec.”
First in the TestScript
, I will declare both TestEZ
and DevforumExample
. I will NOT declare DevforumExample.spec
. Attempting to run a spec file outside of the TestEZ environment will result in errors.
Now, for example’s sake, I will make some example code in the DevforumExample
module. It looks like this:
Now we can open the spec
file for this module. Let’s start by making it return a function instead of a dictionary.
We will use this spec
file to describe what actions are appropriate with each function. Let’s start by defining what behavior should be explicit with DevforumExample.foo()
Before this, we have to define the DevforumExample
module in the spec
file.
Now let’s use it
. This function creates a new ‘it’ block. These blocks correspond to the behaviors that should be expected of the thing you’re testing.
In this case, let’s expect DevforumExample.foo()
to return a string.
What you put in the first parameter doesn’t affect behavior, and is simply used for you to document what should happen.
However, we do still need the machine to understand that the function should a string. To do this, we use expect
. This creates a new Expectation
, used for testing the properties of the given value.
Expectations are intended to be read like English assertions. These are all true:
-- Equality
expect(1).to.equal(1)
expect(1).never.to.equal(2)
-- Approximate equality
expect(5).to.be.near(5 + 1e-8)
expect(5).to.be.near(5 - 1e-8)
expect(math.pi).never.to.be.near(3)
-- Optional limit parameter
expect(math.pi).to.be.near(3, 0.2)
-- Nil checking
expect(1).to.be.ok()
expect(false).to.be.ok()
expect(nil).never.to.be.ok()
-- Type checking
expect(1).to.be.a("number")
expect(newproxy(true)).to.be.a("userdata")
-- Function throwing
expect(function()
error("nope")
end).to.throw()
expect(function()
-- I don't throw!
end).never.to.throw()
expect(function()
error("nope")
end).to.throw("nope")
expect(function()
error("foo")
end).never.to.throw("bar")
Since we need to expect foo()
to return a string, let’s explicitly define that!
Let’s go back to the main TestScript
script. In there, we will use TestEZ to run this code.
We use run
to run tests in a containing bin. The first parameter is the bin of modules, and the second parameter is the callback function, which has a result
parameter. For testing sake, we will just print the result.
If we run the experience, we get this in the output:
Notice how there are no errors, and there is one success. This means the test we made in the spec
file runs as expected. Below is an implementation for the rest of the functions:
Now how do we know when something does not function as intended?
Let’s assume that we broke DevforumExample.add()
If we click run again, we get the following result:
If we expand the errors index, we can see very clearly what went wrong:
Pay attention to the top line of the stack, which reads:
"ServerScriptService.TestScript.DevforumExample.spec:16: Expected value "24" (number), got "0" (number) instead
You can use information like this to fix code that doesn’t function as intended!
Concluding Statements
I created this tutorial simply because I discovered that .spec
was used in places such as Roact, but I had no idea what they did and could not find anything on the internet about it. Big shoutout to @boatbomber for helping me find out what they do and how to run them.
I would love to hear what everybody thinks of Roblox’s system of internal testing. Are spec
files and TestEZ the way to test, or do you have something that is better?
Thanks for the read, have a good day!