Tiny Redscript framework to run unit tests with helper functions.
- Install requirements:
- Codeware v1.8.0+ (in
scripts/Codewarefor autocompletion)
- Clone repository in your scripts directory:
git submodule add https://github.com/rayshader/cp2077-red-test.git scripts/RedTest/- Create a Test directory:
mkdir -p scripts/Test/- Add a test:
File: scripts/Test/MathTest.reds
- Rename
MathTestto your convenience. - Change
<ModName>with the name of your mod.
public static func Add(a: Int32, b: Int32) -> Int32 {
return a + b;
}
public class MathTest extends BaseTest {
public func Create() {
this.m_modName = "<ModName>";
this.m_name = "Math";
}
public func Setup() {
// e.g. get references before running tests.
}
private cb func Test_Add() {
let actual = Add(5, 5);
let expect = 10;
this.ExpectInt32("5 + 5 == 10", actual, expect);
}
/*
| Start with prefix "Test_" to
| automatically discover your test.
v
private cb func Test_() {
^
| Callback is required!
}
*/
}- Create entry-point and register your test:
File: scripts/Test/Main.reds
public class MainTest extends ScriptableSystem {
private let m_runner: ref<RedTest>;
/// Lifecycle ///
private func OnAttach() {
this.m_runner = new RedTest();
this.m_runner.Setup("MathTest", [
new MathTest()
]);
}
private func OnDetach() {
this.m_runner.TearDown();
}
}- Install scripts in game directory.
- Run the game.
- Open CET and show Game Log popup.
- You should see tests result.
DON'T include tests in the release of your mod!
ExpectBool(name: String, actual: Bool, expect: Bool) -> Bool
ExpectInt8(name: String, actual: Int8, expect: Int8)
ExpectInt16(name: String, actual: Int16, expect: Int16)
ExpectInt32(name: String, actual: Int32, expect: Int32)
ExpectInt64(name: String, actual: Int64, expect: Int64)ExpectUint8(name: String, actual: Uint8, expect: Uint8)
ExpectUint16(name: String, actual: Uint16, expect: Uint16)
ExpectUint32(name: String, actual: Uint32, expect: Uint32)
ExpectUint64(name: String, actual: Uint64, expect: Uint64)ExpectFloat(name: String, actual: Float, expect: Float)
ExpectDouble(name: String, actual: Double, expect: Double)ExpectString(name: String, actual: String, expect: String) -> Bool
ExpectUnicodeString(name: String, actual: String, expect: String) -> BoolExpectCName(name: String, actual: CName, expect: CName) -> Bool
ExpectResRef(name: String, actual: ResRef, expect: ResRef) -> Bool
ExpectTweakDBID(name: String, actual: TweakDBID, expect: TweakDBID) -> Bool
You can use ExpectString to test for enums like this:
enum Animal {
Dog = 0,
Bird = 1
}
let actual = Animal.Bird;
ExpectString("Is a bird", s"\(actual)", "Bird");You can write asynchronous tests (e.g when using callbacks). You need to tell the framework test class is asynchronous:
import Codeware.*
// Custom callback, implementation is not defined for brevity.
public class CustomCallback {
// ...
public static func Create(target: ref<IScriptable>, fn: CName, opt args: array<Variant>) -> ref<CustomCallback> {
// ...
}
public func Call() {
// ...
}
}
// Suppose this function will run in background and execute our callback
// when its job is finished.
public static func CallAsyncFunction(callback: ref<CustomCallback>) -> Void;
public class SpawnTest extends BaseTest {
public func Create() {
// ...
this.m_isAsync = true; // REQUIRED
}
/*
| callback to execute when test is finished.
v
*/
private cb func Test_Spawn(done: ref<CallbackTest>) {
let callback = CustomCallback.Create(this, n"Async_Spawn", [done]);
// Start a fake background job.
CallAsyncFunction(callback);
}
// Called when background job is finished.
private cb func Async_Spawn(done: ref<CallbackTest>) {
this.ExpectBool("Assert", true, true);
// Tell framework this unit test is finished.
done.Call();
}
// Synchronous test can also be declared in an asynchronous context.
private cb func Test_Sync(done: ref<CallbackTest>) {
// Do synchronous stuff here.
this.ExpectBool("Assert", true, true);
// You still need to tell the framework this unit test is finished.
done.Call();
}
}