Writing tests early and testing your code helps you catch bugs and ensure your code does what you think it does. MiniScript offers a way to do that. The QA module.
In this short blog I want to show you how I use MiniScript's QA module (https://miniscript.org/wiki/QA) to test my MiniScript projects. I will do so by going through a very small toy example which covers the basics of my TDD workflow.
Step 1: Importing the QA module
Whenever I want to to test code I write I import the QA module at the top of my file. QA is a system module so there isn't anything you need to install extra, it should already be provided by your MiniScript distribution. To do this just make sure that your first line reads:
import "qa"
This gives me access to useful and important functions such as assert
and assertEqual
, which form the basis of my testing.
Step 2: Write a test
Yes, before I write a line of code I write a test. I will not argue for or against this strategy here, I just will say that often when working I have great success writing my tests first. In my toy example I will implement a simple function that adds 2 numbers. To organise my test I put them in a function that I call runUnitTests
, in bigger projects I will then call my test functions in this function but for now we can write all the tests inside this function.
runUnitTests = function
print "Starting tests..."
qa.assert add(1,1) == 2, "Test add 1,1 results in 2"
end function
runUnitTests
Step 3: Run the test
Running the file will now result in an error: Runtime Error: Undefined Identifier: 'add' is unknown in this context [line 5]
. The error message clearly states that we did not implement add yet. This is expected as indeed we did not implement the function yet but we confirmed that our test is running as expected.
Step 4: Fix the error
In the next step I will fix the error. While in reality I can of course go a little bit faster, for this explanation I will just fix the error that add
does not exist in the current context by creating an empty function with the arguments I intend to pass like so:
add = function(a,b)
end function
When we run the script again we now see the test executed and failing:
Starting tests...
Assert failed: Test function add results in 2 for input a=1 and b=1
Call stack:
0. (current program) line 9
1. (current program) line 12
(To clear this display, enter: qa.clear)
This now tells me that my assertion failed and that add 1,1 did not return something equal to 2. I can easily fix this by having add return 2. This is a temporary step. I want to show you that using TDD it is a viable strategy to go for the simplest solution first and discover a full solution by adding more tests.
add = function(a,b)
return 2
end function
Running the script now will work. Amazing our code is tested!
Step 5: Write more tests
The attentive reader will have spotted, that the solution right now is indeed still rubbish. With the very limited toy example it is very clear to spot what the problem is but you would be surprised how often you can encounter cases that are basically the same as what I produced right now in the wild. Right now we have code that works and is even 100% tested but of course actually it only works for 1 test case. Let's fix this by adding another test case:
runUnitTests = function
print "Starting tests..."
qa.assert add(1,1) == 2, "Test function add results in 2 for input a=1 and b=1"
qa.assert add(2,3) == 5, "Test function add results in 5 for input a=2 and b=3"
end function
When we run our tests now it results in the following output:
Starting tests...
Assert failed: Test function add results in 5 for input a=2 and b=3
Call stack:
0. (current program) line 10
1. (current program) line 13
(To clear this display, enter: qa.clear)
Now let us fix this by actually implementing the function. I will change the add
function to read as follows:
add = function(a,b)
return a + b
end function
Running the script now everything works.
Put the testing inside if locals == globals
Very often the approach illustrated here is very useful when developing modules we want other people to be able to use in their projects. To allow them to use our module but also run our tests it is customary to put the unit tests inside an if locals == globals
statement. Joe Strout already explained this pattern in an excellent blog here:https://dev.to/joestrout/if-locals-globals-38bh
So to do this in our code we simply put the function call to runUnitTests
into the statement. This ensures that the tests are only ran when the module is executed directly and not when it is imported. Here the full code of our newly developed and tested add module:
import "qa"
add = function(a,b)
return a + b
end function
runUnitTests = function
print "Starting tests..."
qa.assert add(1,1) == 2, "Test function add results in 2 for input a=1 and b=1"
qa.assert add(2,3) == 5, "Test function add results in 5 for input a=2 and b=3"
end function
if locals == globals then runUnitTests
This of course is a very small toy example but if you practice the techniques outlined here it can allow you to write MiniScript using a TDD approach and help you deliver tested and less buggy modules. I encourage you to give this workflow a try in your next project!
Top comments (0)