Add basic unit test support
Add class that can automatically perform defined tests on user request. Developers that wish to implemet unit tests for some functionality must extend that class (`TestCase`) and add it to the manifest, so that Acedia can read, register and later use it to perform tests.
This commit is contained in:
parent
427d6fea58
commit
a4a1c21cd7
@ -31,6 +31,9 @@ var private Acedia selfReference;
|
|||||||
// Array of predefined services that must be started along with Acedia mutator.
|
// Array of predefined services that must be started along with Acedia mutator.
|
||||||
var private array< class<Service> > systemServices;
|
var private array< class<Service> > systemServices;
|
||||||
|
|
||||||
|
// All unit tests loaded from all packages.
|
||||||
|
var private array< class<TestCase> > testCases;
|
||||||
|
|
||||||
static public final function Acedia GetInstance()
|
static public final function Acedia GetInstance()
|
||||||
{
|
{
|
||||||
return default.selfReference;
|
return default.selfReference;
|
||||||
@ -69,6 +72,12 @@ private final function LoadManifest(class<Manifest> manifestClass)
|
|||||||
manifestClass.default.features[i].static.EnableMe();
|
manifestClass.default.features[i].static.EnableMe();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Load unit tests
|
||||||
|
for (i = 0; i < manifestClass.default.testCases.length; i += 1)
|
||||||
|
{
|
||||||
|
if (manifestClass.default.testCases[i] == none) continue;
|
||||||
|
testCases[testCases.length] = manifestClass.default.testCases[i];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final function LaunchServices()
|
private final function LaunchServices()
|
||||||
|
@ -26,6 +26,9 @@
|
|||||||
// List of features in this manifest's package.
|
// List of features in this manifest's package.
|
||||||
var public const array< class<Feature> > features;
|
var public const array< class<Feature> > features;
|
||||||
|
|
||||||
|
// List of features in this manifest's package.
|
||||||
|
var public const array< class<TestCase> > testCases;
|
||||||
|
|
||||||
// Listeners listed here will be automatically activated.
|
// Listeners listed here will be automatically activated.
|
||||||
var public const array< class<Listener> > requiredListeners;
|
var public const array< class<Listener> > requiredListeners;
|
||||||
|
|
||||||
|
252
sources/TestCase.uc
Normal file
252
sources/TestCase.uc
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
/**
|
||||||
|
* Base class aimed to contain sets of unit tests for various components of
|
||||||
|
* Acedia and it's features.
|
||||||
|
* Currently provides bare-bones testing functions that check boolean
|
||||||
|
* variables for true/false and objects for whether they're `none` or not.
|
||||||
|
* Tests:
|
||||||
|
* ~ can be grouped by their "context",
|
||||||
|
* describing what they are testing;
|
||||||
|
* ~ test (or several tests) can be assigned an error message,
|
||||||
|
* describing what exactly went wrong.
|
||||||
|
* Copyright 2020 Anton Tarasenko
|
||||||
|
*------------------------------------------------------------------------------
|
||||||
|
* This file is part of Acedia.
|
||||||
|
*
|
||||||
|
* Acedia is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Acedia is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
class TestCase extends Actor
|
||||||
|
abstract;
|
||||||
|
|
||||||
|
// Name by which this set of unit tests can be referred to.
|
||||||
|
var protected const string caseName;
|
||||||
|
|
||||||
|
// Information about how well testing went for a particular context,
|
||||||
|
// i.e. subsets of tests for a particular functionality.
|
||||||
|
struct ContextSummary
|
||||||
|
{
|
||||||
|
// Text, human-readable description of the purpose of
|
||||||
|
// tests in this context.
|
||||||
|
var string description;
|
||||||
|
// `false` if at least one test failed, `true` otherwise.
|
||||||
|
var bool passed;
|
||||||
|
// How many test were performed.
|
||||||
|
var int testsPerformed;
|
||||||
|
// How many tests failed.
|
||||||
|
var int testsFailed;
|
||||||
|
// Error messages generated by failed tests.
|
||||||
|
var array<string> errors;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Collection of summaries for all contexts defined by the user so far.
|
||||||
|
struct Summary
|
||||||
|
{
|
||||||
|
var bool passed;
|
||||||
|
var array<ContextSummary> contextSummaries;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Has function for defining context (`Context()`) been called.
|
||||||
|
var private bool userDefinedContext;
|
||||||
|
// Were all tests performed?
|
||||||
|
var private bool finishedTests;
|
||||||
|
// Error message that will be generated if some test will fail now.
|
||||||
|
var private string currentErrorMessage;
|
||||||
|
|
||||||
|
// Store complete summary here.
|
||||||
|
var private Summary currentSummary;
|
||||||
|
// For quick access store current context's summary here and update it in
|
||||||
|
// `currentSummary` once done.
|
||||||
|
var private ContextSummary activeContextSummary;
|
||||||
|
|
||||||
|
// Call this function to define a context for subsequent test
|
||||||
|
// (until another call).
|
||||||
|
public final static function Context(string description)
|
||||||
|
{
|
||||||
|
if ( default.userDefinedContext
|
||||||
|
|| default.activeContextSummary.testsPerformed > 0)
|
||||||
|
{
|
||||||
|
UpdateContextSummary(default.activeContextSummary);
|
||||||
|
}
|
||||||
|
default.userDefinedContext = true;
|
||||||
|
default.activeContextSummary = GetContextSummary(description);
|
||||||
|
default.currentErrorMessage = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call this function to define an error message for tests that
|
||||||
|
// would fail after it.
|
||||||
|
// Message is reset by another call of `Issue()` or
|
||||||
|
// by changing the context via `Context()`.
|
||||||
|
public final static function Issue(string errorMessage)
|
||||||
|
{
|
||||||
|
default.currentErrorMessage = errorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// All tests to be performed can be placed in this function,
|
||||||
|
// along with appropriate calls to `Context()` and `Issue()`.
|
||||||
|
// For an example see class `TEST_JSON`.
|
||||||
|
protected static function TESTS(){}
|
||||||
|
|
||||||
|
// Following functions provide simple test primitives,
|
||||||
|
public final static function TEST_ExpectTrue(bool result)
|
||||||
|
{
|
||||||
|
RecordTestResult(result, default.currentErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final static function TEST_ExpectFalse(bool result)
|
||||||
|
{
|
||||||
|
RecordTestResult(!result, default.currentErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final static function TEST_ExpectNone(Object object)
|
||||||
|
{
|
||||||
|
RecordTestResult(object == none, default.currentErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final static function TEST_ExpectNotNone(Object object)
|
||||||
|
{
|
||||||
|
RecordTestResult(object != none, default.currentErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the summary of how testing went.
|
||||||
|
public final static function Summary GetSummary()
|
||||||
|
{
|
||||||
|
return default.currentSummary;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name by which this set of unit tests can be referred to.
|
||||||
|
public final static function string GetName()
|
||||||
|
{
|
||||||
|
return default.caseName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates brand new summary for context with a given description,
|
||||||
|
// marked as "passed" and zero tests done.
|
||||||
|
private final static function ContextSummary NewContextSummary
|
||||||
|
(
|
||||||
|
string description
|
||||||
|
)
|
||||||
|
{
|
||||||
|
local ContextSummary newSummary;
|
||||||
|
newSummary.passed = true;
|
||||||
|
newSummary.description = description;
|
||||||
|
newSummary.testsPerformed = 0;
|
||||||
|
newSummary.testsFailed = 0;
|
||||||
|
newSummary.errors.length = 0;
|
||||||
|
return newSummary;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns index of summary with given description
|
||||||
|
// in our records (`currentSummary`).
|
||||||
|
// Return `-1` if there is no context with such description.
|
||||||
|
private final static function int GetContextSummaryIndex(string description)
|
||||||
|
{
|
||||||
|
local int i;
|
||||||
|
for (i = 0; i < default.currentSummary.contextSummaries.length; i += 1)
|
||||||
|
{
|
||||||
|
if ( default.currentSummary.contextSummaries[i].description
|
||||||
|
~= description)
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
// Returns index summary with given description
|
||||||
|
// in our records.
|
||||||
|
// Return new context summary if there is no context with such description.
|
||||||
|
private final static function ContextSummary GetContextSummary
|
||||||
|
(
|
||||||
|
string description
|
||||||
|
)
|
||||||
|
{
|
||||||
|
local int index;
|
||||||
|
if (default.activeContextSummary.description ~= description)
|
||||||
|
{
|
||||||
|
return default.activeContextSummary;
|
||||||
|
}
|
||||||
|
index = GetContextSummaryIndex(description);
|
||||||
|
if (index < 0)
|
||||||
|
{
|
||||||
|
return NewContextSummary(description);
|
||||||
|
}
|
||||||
|
|
||||||
|
return default.currentSummary.contextSummaries[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rewrites summary (with the same name as a given summary)
|
||||||
|
// in `currentSummary` records.
|
||||||
|
// If there's no such record - adds a new one.
|
||||||
|
private final static function UpdateContextSummary
|
||||||
|
(
|
||||||
|
ContextSummary relevantSummary
|
||||||
|
)
|
||||||
|
{
|
||||||
|
local int index;
|
||||||
|
index = GetContextSummaryIndex(relevantSummary.description);
|
||||||
|
if (index < 0)
|
||||||
|
{
|
||||||
|
index = default.currentSummary.contextSummaries.length;
|
||||||
|
}
|
||||||
|
default.currentSummary.contextSummaries[index] = relevantSummary;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Records (in current context summary) that another test was performed and
|
||||||
|
// succeeded/failed, along with given error message.
|
||||||
|
private final static function RecordTestResult
|
||||||
|
(
|
||||||
|
bool isSuccessful,
|
||||||
|
string errorMessage
|
||||||
|
)
|
||||||
|
{
|
||||||
|
local int i;
|
||||||
|
local int errorsAmount;
|
||||||
|
if (default.finishedTests) return;
|
||||||
|
default.activeContextSummary.testsPerformed += 1;
|
||||||
|
if (isSuccessful) return;
|
||||||
|
|
||||||
|
default.currentSummary.passed = false;
|
||||||
|
default.activeContextSummary.passed = false;
|
||||||
|
default.activeContextSummary.testsFailed += 1;
|
||||||
|
errorsAmount = default.activeContextSummary.errors.length;
|
||||||
|
for (i = 0; i < errorsAmount; i += 1)
|
||||||
|
{
|
||||||
|
if (default.activeContextSummary.errors[i] ~= errorMessage)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default.activeContextSummary.errors[errorsAmount] = errorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calling this function will perform unit tests defined in `TESTS()`
|
||||||
|
// function of this test case and will prepare the summary,
|
||||||
|
// obtainable through `GetSummary()` function.
|
||||||
|
// Returns `true` if all tests have successfully passed
|
||||||
|
// and `false` otherwise.
|
||||||
|
public final static function bool PerformTests()
|
||||||
|
{
|
||||||
|
default.finishedTests = false;
|
||||||
|
default.userDefinedContext = false;
|
||||||
|
default.currentSummary.passed = true;
|
||||||
|
default.currentSummary.contextSummaries.length = 0;
|
||||||
|
default.activeContextSummary = NewContextSummary("");
|
||||||
|
TESTS();
|
||||||
|
UpdateContextSummary(default.activeContextSummary);
|
||||||
|
default.finishedTests = true;
|
||||||
|
return default.currentSummary.passed;
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultproperties
|
||||||
|
{
|
||||||
|
caseName = ""
|
||||||
|
}
|
Reference in New Issue
Block a user