classdef TestCase < handle
% TESTCASE Superclass for user test case classes
%
% This class is part of the mlunit_2008a testing framework.
%
% To create tests, you create a test class by subclassing TestCase and
% adding test methods. Test methods are any methods with names
% beginning with "test". The testing framework automatically finds and
% runs these methods for you.
%
% Within your test functions you can run whatever arbitrary code you
% wish, and validate results using one of the inherited assert methods
% described below.
%
% assert(obj, expr, [fail_message])
% Succeeds if expr is true, otherwise records a failure.
%
% assertEquals(obj, expected, actual, [fail_message])
% Succeeds if actual equals expected, otherwise records a failure.
% Equality is determined using the MATLAB isequal() function, and
% works on pretty much any MATLAB types.
%
% assertNotEquals(obj, a, b, [fail_message])
% Suceeds if ~isequal(a,b), and otherwise records a failure.
%
% The optional fail_message parameter in each of these methods should
% contain a string which will be added to the test report if the assert
% fails.
%
% You can also run code that throws a catchable exception; the test
% framework will catch exceptions and record them as errors. You can
% also perform your own custom validations, and record errors using the
% fail() method:
%
% fail(obj, message)
% Always records a failure.
%
% Sometimes it may be desirable to initialize some object (typically
% stored in a class property) with a known starting state, for instance
% opening a file or constructing a matrix.
%
% Rather than performing this operation yourself in each test method,
% you can override the inherited setUp method to do it for you. The
% setUp method is automatically run before every invocation of a test
% method. Similarly, tearDown is called after every test method,
% and may be used to clean up as necessary.
%
% The method signatures to override in your class:
% setUp(obj)
% tearDown(obj)
properties
stopRequested = false;
end
methods
% you can/should override these methods
function self = setUp(self), end
function self = tearDown(self), end
end
% you can, but probably don't want to override these
methods
function s = getDescription(self)
s = class(self);
end
function self = assert(self, expr, msg, internal_call)
if nargin < 3, msg = 'Assert failure'; end
if nargin < 4, internal_call = 1; end
if ((isempty(expr)) || (~expr))
self.fail(msg, internal_call + 1);
end;
end
function self = assertEquals(self, expected, actual, msg, internal_call)
if nargin < 5, internal_call = 1; end
if nargin < 4
s1 = self.toString(expected);
s2 = self.toString(actual);
msg = sprintf('Assert equals failure: \n expected:\n %s\n\n actual:\n %s', s1, s2);
end
self.assert(isequal(expected, actual), msg, internal_call + 1);
end
function self = assertNotEquals(self, a, b, msg, internal_call)
if nargin < 5, internal_call = 1; end
if nargin < 4
s1 = self.toString(a);
msg = sprintf('Assert not equals failure:\n both values ==\n %s\n', s1);
end
self.assert(~isequal(a, b), msg, internal_call + 1);
end
function fail(self, msg, internal_call)
if nargin < 2, msg = 'Unspecified failure'; end
if nargin < 3, internal_call = 1; end
error(['MLUNIT:TESTFAIL:frame' num2str(internal_call)], msg);
end
end
% don't fool around with these
methods
% use reflection to obtain test method names
function names = getTestNames(self)
c = class(self);
fns = methods(c);
% exclude incidentally matching constructors - users *should*
% avoid this, but...
exclude = strcmp(c, fns);
fns = fns(~exclude,1);
tests = strmatch('test', fns);
names = fns(tests,1);
end
% instantiate a test result object on ourselves
function tr = getNewTestResult(self, parent)
if nargin < 2, parent = []; end
tr = TestCaseResult(self, parent);
end
% the big kahuna
function self = runTests(self, result)
names = self.getTestNames();
for i = 1:length(names)
drawnow
if self.stopRequested
return;
end
name = names{i};
try
self.setUp();
feval(name, self);
self.tearDown();
result.recordSuccess(name);
catch ME
msg = ME.message;
stack = ME.stack;
msg_id = ME.identifier;
start_frame = 1;
if strncmp('MLUNIT:TESTFAIL', msg_id, 15)
start_frame = start_frame + str2num(msg_id(22:end));
end
result.recordFailure(name, msg, stack(start_frame:end));
end
end
end
function stopTests(self)
self.stopRequested = true;
end
function clearStop(self)
self.stopRequested = false;
end
% for printing out expected and actual objects in the assertEquals
% method. This will attempt to convert various types into a
% reasonable string representation, and will truncate strings when
% they get too long.
function s = toString(self, x)
if ischar(x)
s = x;
elseif isnumeric(x)
s = mat2str(x);
else
[m,n] = size(x);
c = class(x);
if m > 1 || n > 1
s = ['A ' num2str(m) 'x' num2str(n) ' ' c ' array'];
elseif any(strmatch(c(1), ['a';'e';'i';'o';'u';'A';'E';'I';'O';'U']))
s = ['An ' c];
else
s = ['A ' c];
end
end
if length(s) > 75
s = [s(1:75) '...'];
end
end
end
end