Browse Source
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.new
Anton Tarasenko
5 years ago
3 changed files with 264 additions and 0 deletions
@ -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