How (and why!) to Unit Test Your URScript
by Gaurav Shetty, on Apr 14, 2020 9:21:18 AM
Are you interested in testing your URScript code?
If you use URScript language to write programs for your Universal Robot, you should be testing your code! During any problem solving exercise, testing solutions before implementation is a staple to success. Logical code is no exception, and it requires its author to consider each scenario it may encounter.
Check out the UR unit testing framework we have developed at Hirebotics.
What’s a Unit Test and Why Should I Care?
Unit testing refers to testing the smallest “units” of your application in isolation. It often refers to testing a single function. A failing unit test can reveal bugs which are easier to identify and fix. Having unit tests allows programmers to refactor code with confidence making sure the core functionality is still as expected.
How does Hirebotics use Unit Testing?
At Hirebotics, we use Universal Robots and our programs are primarily written in URScript with some occasional Polyscope. Like any other programming language, the URScript has variables, conditional statements, functions, etc. In addition, URScript allows us to leverage a whole array of software development tools as version control to help our team manage changes in the source code.
Another key benefit of using a scripting language is the ability to have unit tests, to find software bugs and errors before deploying the program to the robot. Finding bugs at an early stage helps reduce the cost of fixing them later on.
URScript Test Framework
The testing framework provides a rich set of tools to construct and validate unit tests. Some of the tools we use within the test framework are
- Ability to mock functions
- Executing logic before and after each individual test
- Asserting the results with expected values
Here we demonstrate a small subset of the tools to write a unit test.
Our goal is to test a function simpleValue() which returns a numerical value based on the argument we pass into it. If we pass in True, the value returned is '10', if we pass in False, the value returned is '0'. The unit test validates that the result matches the expected value.
We see two test cases to represent the two boolean inputs possible to the function simpleValue(). The assertEqual() compares the output of the function with the expected value. If the values do not compare equal, the test will fail.
When the unit tests complete its execution, we see a table representation of the name of the test which was performed and a passed keyword to indicate a successful test.
A failing unit test reveals the difference between the current result and the expected result. Hence, if a function was modified somewhere down the line which unknowingly affected the core functionality, this snapshot gives us information on what the expected value is.
Let’s take a look at a more complex test case of a function which uses in-built URScript functions to control the movement of the robot and to display information. The purpose of this unit test is to validate the performance of the function and not the individual methods called within.
For example, we want to test if functions such as movel() and stopj() are invoked during the flow of execution. Since it's an in-built function, we can trust that a movel() does what it's supposed to in the real world and hence, for the purpose of our unit test, we mock it out. The mock function helps us verify that the required function is being called without executing it in the real world.
Another useful tool is the concept of performing logic to run before each test case is executed. We do this in the beforeEach() function.
In the example, beforeEach() is used to initialize variables invoked in the mock functions which we use to assert if mock functions have been called. Similarly, you can add logic to the afterEach() function which is performed after every test case is executed.
The mock_movel(), mock_stopj() and mock_popup() are used to mock out the in-built URScript functions. We increment a variable inside the mock functions to track multiple invocations of the function we are mocking.
If you were to make a change to testExample(), such as remove or comment out the stopj(12) command and re-run the test without accounting for it in testExample.test.script , the unit test would fail because the test is expecting mock_stopj() to be called once but it doesn't because testExample() no longer invokes it.
The above examples show a quick way of making sure that our code compiles and the function returns the value we expect. Unit tests can vary in complexity depending on the application being tested. This allows bugs to be exposed and removed early in development, resulting in fewer errors occurring before a code is pushed to the robot remotely.