-
Notifications
You must be signed in to change notification settings - Fork 125
Automated Testing & Code Coverage
Note that this guide does not cover more advanced test cases beyond simple DM tests. Please ask on Discord for assistance with advanced testing.
To automate testing of the compiler and runtime, OpenDream (and the underlying Robust Toolbox engine) make use of the NUnit testing framework. If needed, you can find further details and documentation of NUnit on their website. These tests are ran automatically on all pull requests by GitHub Actions.
In most cases, an OpenDream test is simply a self-contained DM code file that attempts to check one of the following pre-defined outcomes:
- Code successfully compiles and runs without error.
- Code fails to compile (for testing that we actually compile error on bad code or that a pragma actually emits).
- Runtime error occurs (for testing that we actually runtime on bad code).
- Proc returns
TRUE. -
ASSERT()that a value is what we expect it to be.
Robust Toolbox also has its own suite of tests, though running these is typically not recommended as it can take quite a while and generally should not be pertinent to OpenDream development.
Code coverage measures what percentage of OpenDream's source code is actually executed by automated testing and can be used to guide test development. If a piece of code is not covered, it needs tests written for it.
You can peruse OpenDream's code coverage by file here.
All of OpenDream's tests are either unit tests or integration tests.
This guide will not get too detailed about the differences between unit tests and integration tests, but there are some important things to know:
- Use essentially just a compiler & interpreter, not a full-fledged server.
- Contained within
Content.Tests/DMProject/Tests - Does not allow creating/using subtypes of
/atom. Only/datum. - Does not have a map to test on.
- Does not support a variety of other things beyond simply compiling and executing a subset of DM code.
- Code execution begins in a user-declared
RunTest()proc (/proc/RunTest()). - One test per DM file.
- Full-fledged server.
- Contained within
Content.IntegrationTests/DMProject/Tests. - Allows creation of
atomtypes/subtypes. - Has a map to test on.
- Code execution begins in a user-declared
RunTest()override (e.g./datum/unit_test/your_new_test/RunTest()). Ignore that the typepath saysunit_testinstead ofintegration_test. - One test per DM file.
Generally speaking, you want to be writing unit tests unless you depend on features that necessitate an integration test. Unit tests have less overhead and are faster to execute.
Your IDE or text editor (e.g. Rider, Visual Studio, or VSCode) has the ability to run all or specific tests through a GUI. Refer to your application's documentation for how to do that. Unit tests are in Content.Tests while integration tests are in Content.IntegrationTests.
Note that DM tests are detected automatically by the testing framework, therefore you may not initially see all of the individual tests listed until after the first run.
- Running
dotnet testwill run all tests including Robust Toolbox's (not advised). - Running
dotnet test Content.Testsanddotnet test Content.IntegrationTestswill run OpenDream's unit tests and integration tests, respectively.
First determine if you're writing a unit test or an integration test based on the aforementioned differences. Try to create a unit test unless you need features that are restricted to integration tests.
Don't stress about which subdirectory under Tests to put your test file in; it is purely organizational and mostly unimportant.
The entry point for a test is the user-declared RunTest() proc. The testing framework will automatically call RunTest() when your test executes.
- Create a new DM file in
Content.Tests/DMProject/Tests. - Declare the
RunTest()proc:/proc/RunTest(). - Put the code you want to execute within that
RunTest()proc.
By default, a test is expected to compile and run without any compile-time or runtime errors. Most testing in this case is done using ASSERT() to check that a value is what you expect it to be. For testing other scenarios, you can use test flags.
The first line of a unit test can be a DM code comment with a test flag that impacts test behavior.
For example, the first line of your file could be:
// RETURN TRUE
This will cause the test to fail unless RunTest() returns TRUE.
Here are all of the test flags:
-
// IGNORE- This DM file will not be executed as a DM test. Useful for writing helper code that you want to share between multiple tests (just don't forget to#includeit in your tests). -
// RETURN TRUE-RunTest()must returnTRUEfor the test to pass. -
// NO RETURN-RunTest()must not return a value at all for the test to pass. -
// RUNTIME ERROR- The test must create a runtime error to pass. Currently, you cannot control which runtime error you're looking for. -
// COMPILE ERROR OD####- The test will only pass if the specified compiler error occurs, whereOD####is theWarningCodefor that error (e.g. to test thePointlessParentCallpragma you would specify// COMPILE ERROR OD2205then include#pragma PointlessParentCall errorin your code).
- Create a new DM file in
Content.IntegrationTests/DMProject/Tests. - Declare the
RunTest()override:/datum/unit_test/your_new_test/RunTest()whereyour_new_testis the name of the test. Ignore the fact that the typepath saysunit_testeven though this is an integration test. - Put the code you want to execute within that
RunTest()override.
Note that integration tests do not have access to unit tests' test flags (e.g. // RUNTIME ERROR or // IGNORE).
There are a variety of tests that have already been written that should work in OpenDream but currently don't. As a result, they are currently ignored. These can be found in Content.Tests/DMProject/BrokenTests.
These broken tests are a good TODO list of problems in OpenDream that need to be fixed. Simply move them from BrokenTests to Tests and fix whatever bug with OpenDream is causing them to fail.
Note that many of these tests are quite old and should be executed in BYOND first to confirm that they are still accurate and correct behavior.