Reputation: 24127
I have the following very simple utility function:
function vfprintf(verbose, varargin)
% VFPRINTF Display output optionally depending on the level of verbosity.
%
% VFPRINTF(TF, ARGS) passes the arguments ARGS to the built-in MATLAB
% command |fprintf| if TF is logical true. If TF is logical false, VFPRINTF
% does nothing.
assert(islogical(verbose),...
'utils:InvalidVerbose',...
'VERBOSE must be logical true or false');
if verbose
fprintf(varargin{:});
end
It turns out that even though the function is super simple, it had a issue that caused me a problem (the assert
condition should have been islogical(verbose) && isscalar(verbose)
, not just islogical(verbose)
), so I'd like to implement some unit tests around it.
Note that I don't want to test fprintf
- I'm assuming that's OK. So is there a way that I can test something like:
verbose
is logical scalar true, then a call is made to fprintf
"verbose
is logical scalar false, then no call is made to fprintf
"verbose
is logical nonscalar, then no call is made to fprintf
"verbose
is not logical, then no call is made to fprintf
"I can't find a way to verify that a call is made out to a particular function. Any ideas? The only thing I can think of is to mock out fprintf
with my own function that shadows the real one on the MATLAB path, which somehow raises an fprintfCalled
event that is listened to by the testing code to tell when it's called. Is that the only approach? Seems like overkill.
Or perhaps I'm approaching this the wrong way - maybe I should forget about testing the calls that are made, and instead be testing the command-line and/or file output of vfprintf
directly. But that feels like I'm then testing fprintf
rather than vfprintf
.
Maybe I'm overthinking things, but I'd like to improve my testing practices, so would appreciate some advice. Thanks!
Upvotes: 3
Views: 619
Reputation: 2187
I think at this point you really have 4 options. I like the 4th, but I'll run through them all:
VerboseArgumentsHolder.m
classdef VerboseArgumentsHolder < handle
properties
Arguments = {};
end
end
VerbosePrinterSpy.m
classdef VerbosePrinterSpy
properties(Constant)
ArgumentsHolder = VerboseArgumentsHolder;
end
end
* (test folder)/overloads/fprintf/fprintf.m*
function fprintf(varargin)
argHolder = VerbosePrinterSpy.ArgumentsHolder;
argHolder.Arguments = varargin;
end
vfprintfTest.m
classdef vfprintfTest < matlab.unittest.TestCase
methods(Test)
function testWhenScalarTrue(testCase)
import matlab.unittest.fixtures.PathFixture;
testCase.applyFixture(PathFixture(...
fullfile((test folder),'overloads','fprintf')));
argHolder = VerbosePrinterSpy.ArgumentsHolder;
argHolder.Arguments = {}; % reset values since this is global and stateful.
vfprintf(true,'dummy input');
testCase.verifyEqual(argHolder.Arguments, 'dummy input');
end
function testWhenScalarFalse(testCase)
testCase.applyFixture(PathFixture(...
fullfile((test folder),'overloads','fprintf')));
argHolder = VerbosePrinterSpy.ArgumentsHolder;
argHolder.Arguments = {}; % reset values
vfprintf(false,'dummy input');
testCase.verifyEmpty(argHolder.Arguments);
end
end
end
Restructure your production code to have an interface for printing, then you can add a test specific spy as that interface. This is a nice approach, but has an implication on your software structure that may not be all that easy to adjust to , especially if your codebase is already heavily reliant on this utility.
Since you are just passing on the varargin to fprintf directly, then you can create a test double with an fprintf method to specifically test this out. Then, the fprintf call would dispatch to your testing specific class which can simply spy on the inputs. It may look something like this:
VerbosePrinterSpy.m
classdef VerbosePrinterSpy < handle
properties
WasInvoked = false;
ArgumentsUsedInPrintCall = {'Not invoked'};
end
methods
function fprintf(spy, varargin)
spy.WasInvoked = false;
spy.ArgumentsUsedInPrintCall = varargin;
end
end
end
vfprintfTest.m
classdef vfprintfTest < matlab.unittest.TestCase
methods(Test)
function testWhenScalarTrue(testCase)
spy = VerbosePrinterSpy;
vfprintf(true, spy, 'dummy input');
testCase.verifyTrue(spy.WasInvoked);
testCase.verifyEqual(spy.ArgumentsUsedInPrintCall, 'dummy input');
end
function testWhenScalarFalse(testCase)
spy = VerbosePrinterSpy;
vfprintf(false, spy, 'dummy input');
testCase.verifyFalse(spy.WasInvoked);
end
end
end
Hope that helps!
Upvotes: 2