I am wondering how I can make a unit test to automatically stop, once an error in the system under test (sut) occurs?
Let's assume, the unit test runs some 1,000 different input combinations, and is supposed to verify that the results are equal to the expectations. Now, let's further assume there is a simple syntax error in sut, which causes an error. In this case I would like the unit test to automatically stop and invoke the tear down method.
Is that possible?
Edit:
I am borrowing a MATLAB example from their help site (https://de.mathworks.com/help/matlab/matlab_prog/create-basic-parameterized-test.html) in order to show more clearly what I mean:
Here you can see the test class:
classdef TestCarpet < matlab.unittest.TestCase
properties (TestParameter)
type = {'single','double','uint16'};
level = struct('small', 2,'medium', 4, 'large', 6);
side = struct('small', 9, 'medium', 81,'large', 729);
end
methods (Test)
function testRemainPixels(testCase, level)
% expected number pixels equal to 1
expPixelCount = 8^level;
% actual number pixels equal to 1
actPixels = find(sierpinski(level));
testCase.verifyNumElements(actPixels,expPixelCount)
end
function testClass(testCase, type, level)
testCase.verifyClass(...
sierpinski(level,type), type)
end
function testDefaultL1Output(testCase)
exp = single([1 1 1; 1 0 1; 1 1 1]);
testCase.verifyEqual(sierpinski(1), exp)
end
end
methods (Test, ParameterCombination='sequential')
function testNumel(testCase, level, side)
import matlab.unittest.constraints.HasElementCount
testCase.verifyThat(sierpinski(level),...
HasElementCount(side^2))
end
end
end
Here's the system under test:
function carpet = sierpinski(nLevels,classname)
if nargin == 1
classname = 'single';
end
% original line: mSize = 3^nLevels;
mSize = "That's clearly wrong here";
carpet = ones(mSize,classname);
cutCarpet(1,1,mSize,nLevels) % begin recursion
function cutCarpet(x,y,s,cL)
if cL
ss = s/3; % define subsize
for lx = 0:2
for ly = 0:2
if lx == 1 && ly == 1
% remove center square
carpet(x+ss:x+2*ss-1,y+ss:y+2*ss-1) = 0;
else
% recurse
cutCarpet(x + lx*ss, y + ly*ss, ss, cL-1)
end
end
end
end
end
end
I changed the definition of mSize to a string to produce an error. Now, if I run the tests, all tests will result in an error. I am wondering if it is possible to stop the tests as soon as possible, i.e. after the occurence of the first error?
The problem that I see is that the test code won't even reach the last line of testRemainPixels (testCase.verifyNumElements(actPixels,expPixelCount)). A fatalAssert does not help at this point, right?
Assuming you are using the test framework built into MATLAB, take a look at the types of qualifications, in particular the bulleted list at the top of this page:
https://www.mathworks.com/help/matlab/matlab_prog/types-of-qualifications.html
If you want to stop the entire testing session you can use fatal assertions. If you want other tests to continue you can use assertions. If you want to skip all the test methods (and parameterizations) in a single file you can use an assertion in TestClassSetup or just add a basic "smoke" level exercise step of the code in TestClassSetup and if it errors it will behave like an assertion.
Hope that helps.
Related
Please refer to the documentation for the testCase.verifyEqual method here. The documentation says that only one of the diagnostic features can be used. My requirement is I need two diagnostics at the same time - strings and function handle. The following is simple example of what I'm trying to achieve,
classdef testArrays < matlab.unittest.TestCase
methods (Test)
function testArraysEquality(testCase)
a = 1:10;
b = 1:10;
incrementFunc = #(x)x+1;
failureCount;
for i=1:length(a)
testCase.verifyEqual(a(i),b(i),'AbsTol',10e-3,['Test failed array element# ' num2str(i) ' failure count ' num2str(incrementFunc(failureCount))]);
end
disp([num2str(failureCount) ' out of ' num2str(length(a)) ' test cases failed']);
end
end
end
The problem is Anonymous function don't store values. On the other hand with the 'assignin' feature shown below, the value can be incremented and stored, but cannot be returned for use inside disp(). Is there any work around for this?
incrementFunc1 = #(x) assignin('caller', inputname(1), x+1);
You can include more than one (as well as more than one type) of diagnostic in the MATLAB Unit Test Framework by simply providing a diagnostic array to verifyEqual. You can actually do this explicitly as follows:
import matlab.unittest.diagnostics.StringDiagnostic;
import matlab.unittest.diagnostics.FunctionHandleDiagnostic;
testCase.verifyEqual(a,e, [StringDiagnostic('some string'), FunctionHandleDiagnostic(#() someFunction)]);
However, the Diagnostic.join method is provided to make that easier:
import matlab.unittest.diagnostics.Diagnostic;
testCase.verifyEqual(a,e, Diagnostic.join('some string', #() someFunction));
In order to do the increment call you are probably going to want to add a failed listener to the testCase in order to increment properly. Note that people/plugins can actually add listeners and execute these diagnostics in passing cases in addition to failing cases. As such your diagnostic messages should not assume that every time they are invoked it is in a failure condition. This not only applies to your incrementing code but also to just the message you are providing. I would suggest that instead of saying:
Test failed array element# 3 failure count 2
you should say:
Tested array element# 3 failure count 2
The framework diagnostic will let you know whether it failed or not. Anyway, takeaway, don't rely on invoking the diagnostics to determine failure count. What then? Take a look at the Events section here. You should listen explicitly for verification failed events in order to add that information to your diagnostics.
For the first solution, I am not sure why you need to provide the failure count for every failure. It seems like that would be very verbose. If you don't need that then you can do something like this:
classdef testArrays < matlab.unittest.TestCase
methods (Test)
function testArraysEquality(testCase)
a = 1:10;
b = 1:10;
failureCount = 0;
testCase.addlistener('VerificationFailed', #incrementFailureCount);
function incrementFailureCount(varargin)
% This is a nested function & has the scope and can see/modify
% the failureCount variable. This could also be done with a
% property on the class ans a method that increments it
failureCount = failureCount + 1;
end
for i=1:length(a)
testCase.verifyEqual(a(i),b(i),'AbsTol',10e-3,['Tested array element # ' num2str(i)]);
end
% I suggest using log instead of disp. If you want it to show up most of the time you can
% log it at Terse (1) verbosity. However, if you don't want to see it you can turn it off.
testCase.log(1, sprintf('%d out of %d test cases failed', failureCount, length(a)));
end
end
end
Is that good enough? If you really want to show the failure count in the diagnostics for each failure you can its just a bit more complicated and requires another nested function (or property access).
classdef testArrays < matlab.unittest.TestCase
methods (Test)
function testArraysEquality(testCase)
import matlab.unittest.diagnostics.Diagnostic;
a = 1:10;
b = 1:10;
failureCount = 0;
testCase.addlistener('VerificationFailed', #incrementFailureCount);
function incrementFailureCount(varargin)
failureCount = failureCount + 1;
end
function displayFailureCount
fprintf(1, 'Failure Count: %d', failureCount);
end
for i=1:length(a)
testCase.verifyEqual(a(i),b(i),'AbsTol',10e-3, ...
Diagnostic.join(...
['Tested array element #' num2str(i)], ...
#displayFailureCount));
end
testCase.log(1, sprintf('%d out of %d test cases failed', failureCount, length(a)));
end
end
end
Does that help you accomplish what you are trying to do?
function [ muln, varargout ] = my_mul( varargin )
%MY_MUL This function is used to multiply numbers.
% My_mul function multiplies array of entered numbers, and outputs single
% solution.
% For example: my_mul(12, 2, 3, 5) gives ans = 360
if nargout >=1
disp('Error, wrong number of output arguments');
varargout{1} = 0;
return
end
if nargin <= 1
disp('Error, small number of input argumnets');
return
else
muln = 1;
for i = 1:nargin
muln = muln*varargin{i};
end
end
end
Hi, everyone, I'm just doing my assignment for uni and have a qiuck question.
How can I make this function to give an error if it is called with more than one output.(It meant to give only one) Thanks!
In your function definition, you have defined your function to allow for an unlimited number of outputs. The keyword varargout is a place-holder for a variable number of outputs.
As you have stated in your question, you only want one possible output which in your case looks to be muln. So if you simply remove varargout from your function definition, MATLAB should automatically throw an error if too many outputs are requested
function muln = my_mul(varargin)
If you ever do need to use varargout but want to place constraints on how many outputs are provided for any given scenario, you can check the number of output arguments that were requested using nargout and then throw an error with the error function.
if nargout > 4
error('my_mul:TooManyOutputs', 'Too many outputs requested');
end
My opinion is that if a return value is expected the function needs to throw. Otherwise the caller (function calling this function) will expect everything to be ok. Note that disp('Error') gives information to the developer, but it does not give the program any indication on what happens. More importantly, the information does not give any indication of where the error occurs. This can force the developer to do heavy debugging just to find the error, which is completely unnecessary.
The use of variable output arguments should only be used in case a different number of output arguments should be expected. An example is some customized plot function
function varargout = myplot(varargin)
filename = '';
idx = find(strcmp(varargin,'filename'));
if (~isempty(idx) && length(varargin)<idx+1 && ~ischar(varargin{idx+1}))
error('filename property must be followed by a directory');
elseif(~isempty(idx))
filename = varargin{idx+1};
varargin([idx,idx+1]) = [];
end
h = plot(varargin{:});
varagout{1} = h;
if (~isempty(idx))
save(filename, h);
end
varagout{2} = filename;
This function works as plot except it saves the figure to file in case a filename is specified. In case the developer needs the handle it will be returned and in case the developer wants the save directory it can be returned as well. None of these arguments are necessary though. The developer may want to use this function as a standard plot function and this means that the user may want to call myplot as myplot(x,y);which does not return a value. Further note that even if 'filename' is not specified, the function can still return 2 outputs. The second output may be an empty array of char, but two outputs for the caller will never cause a crash.
Also, note that no further error handling is required. The only unchecked crashes are in plot and save. How this is handled may be different for different users and this means that it only is reasonable to let the user catch the error and handle it (as he would have done if save or plot would have thrown).
Apart from this you may also want to have a check so that the number of output variables are within the correct range (in this case 0,1 or 2 outputs).
I want to iterate through all classes and packages in a special path.
After that, I want to get the MethodList.
In the command window I can use following and it’s working fine:
a = ?ClassName;
a.MethodList(X);
Now I separate this into a function:
function s = befehlsreferenz(path)
s = what(path); % list MATLAB files in folder
for idx = 1:numel(s.classes)
c = s.classes(idx);
b = ?c;
b.MethodList(0);
end
end
I get an error:
Too many outputs requested. Most likely cause is missing [] around left hand side that has a comma separated list
expansion. Error in (line 7) b.MethodList(0);
While debugging I can see:
c: 1x1 cell = ‘Chapter’
b: empty 0x0 meta.class
Why is b empty? How can I get the methodlist?
1 Edit:
Here is an example class, also not working with it.
classdef TestClass
%TESTCLASS Summary of this class goes here
% Detailed explanation goes here
properties
end
methods
function [c] = hallo(a)
c = 1;
end
end
end
When struggling with operators in Matlab, it's typically the best choice to use the underlying function instead, which is meta.class.fromname(c)
Relevant documentation: http://de.mathworks.com/help/matlab/ref/metaclass.html
Further it seems s.classes(idx); is a cell, use cell indexing: s.classes{idx} ;
I'm working with someone else's code and I am unfamiliar with try/catch so I made a small, similar example. On line 11, if I write error(''), it doesn't seem to catch the error and increase the index j. However, writing error(' ') or error('bad!') does.
So does having an error with an empty string ignore the error, or am I doing something wrong?
% Just a file to understand the Matlab command try/catch
M = 3;
j = 1;
k = [Inf, 5, 4];
while M>0
try
M = M-1
u = k(j)
if (isinf(u)||isnan(u)), error(''), end;
catch
j = j+1
end
end
Yes, error('') and error([]) and error(struct([])) all do not actually display an error message and abort running code. I personally consider the use of the single string argument version of error to be bad practice in any real code. You should use always use both a 'MSGID' and a 'ERRMSG' when writing errors for your functions, e.g.
error('FunctionName:SubFunctionName:ErrorMSGID','Error message to be printed.')
Alternatively, you can use MException objects in conjuction with throw, rethrow, and throwAsCaller, which allow you to reuse error information. More here.
It is odd, but it's in the documentation for error, for the error('msgString') syntax:
All string input arguments must be enclosed in single quotation marks. If msgString is an empty string, the error command has no effect.
Similarly, if using the error(msgStruct) syntax:
If msgStruct is an empty structure, no action is taken and error returns without exiting the function.
if you have a look to the try documentation you can have an example.
Else want you want for your code it :
M = 3;
j = 1;
k = [Inf, 5, 4];
while M>0
try
M = M-1
u = k(j)
if (isinf(u)||isnan(u)), error(''), end;
catch
disp('I catch an error!');
j = j+1
end
end
Because If you never get an error in your code, it will never go in the catch. So by including error('');, it just to say, go execute the statement in the catch.
But you can just modify your code by replacing the error() by the statements into your catch like this :
while M>0
M = M-1
u = k(j)
if (isinf(u)||isnan(u)), j = j+1, end;
end
EDIT
If you take a look in the documentation, you can found this :
% ERROR(MSGSTRUCT) reports the error using fields stored in the scalar
% structure MSGSTRUCT. This structure can contain these fields:
%
% message - Error message string
% identifier - See MESSAGE IDENTIFIERS, below
% stack - Struct similar to the output of the DBSTACK function
%
% If MSGSTRUCT is an empty structure, no action is taken and ERROR
% returns without exiting the program. If you do not specify the
% stack, the ERROR function determines it from the current file and line.
So no action is taken as you can read. And nothing, so catch don't get any informations.
Not sure why you need it, but here is how it works.
error function does not throw an error with empty string or empty vector ([]) as an argument.
If you don't specify argument at all the error function itself generates the error "Not enough arguments". So it will go to catch.
Another way is to specify an empty structure as an argument.
s = struct();
error(s)
In this case, the error will be generated, but the code will not stop and in general flow you will hear no beep. In your case it should go to catch.
I was trying to make a simple statement with Matlab as follows:
if TF==1
disp('One'), break
else continue
end
... ... ...
... ... ...
But even if TF is not 1, when I run the command, it doesn't CONTINUE to the rest of the script!! Any help would be appreciated-- Thanks
The continue statement has a very different meaning. Within a loop, like a for or while loop, continue instructs to skip the current round and continue with the next iteration in the loop. So if you remove continue, you will see the behavior that you are expecting. Here is an example:
for k = 1 : 10
if k == 4
% skip the calculation in the case where k is 4
continue
end
area = k * k;
disp(area);
end
When the loop iterates at k == 4, the block calculating the area of the corresponding square is skipped. This particular example is not very practical.
However, imagine you have a list of ten file names, and you want to process each file in this loop "for k = 1 : 10". You will have to try and open each file, but then if you find out the file does not exist, an appropriate way to handle it would be to print a little warning and then continue to the next file.