Using unittest with own matlab toolbox - matlab

I inherited a code base in matlab, which I like to put under unittest with the matlab.unittest framework.
To make the code base more robust against arbitrary addpath of my users, I have put most of the code into +folders like a toolbox. So the general layout is:
+folder1/file1.m
+folder1/runtestsuite.m
+folder1/unittest_data/file1_testdata.mat
+folder1/+folder2/file2.m
+folder1/+folder2/unittest_data/file2_testdata.mat
...
and updated all internal references with the correct import statements.
Now, I like to add a unittest for file1.m. However if I put a file in +folder1/file1_test.m file1.m seems not to be visible.
Here is my example code of file1_test.m
classdef file1_test < matlab.unittest.TestCase
properties
path
end
methods(TestMethodSetup)
function setunittestdatapath(testCase)
p = mfilename('fullpath');
[directory,~,~]=fileparts(p);
testCase.path = fullfile(directory,'unittest_data');
end
end
methods (Test)
function file1_input(testCase)
%import folder1.file1
testdata = load(fullfile(testCase.path),'file1_testdata.mat');
result = file1(testdata.input);
testCase.verifyEqual(result, testdata.output);
end
end
end
If I uncomment the import statement the unittest works fine. So currently I have to add all import statements to each individual test, which I like to avoid. Is there a more elegant way for doing something like this?
I tried importing it at the beginning of the file, although matlab complains "Parse error at CLASSDEF: usage might be invalid MATLAB syntax." this also works. So what is the correct and most pragmatically way for doing something like this?

import statements only apply to the local scope of where they are used so if you want a function to be able to not use the full-qualified name, then you'll have to add the import statement to each function separately.
The import list scope is defined as follows:
Script invoked from the MATLAB® command prompt — Scope is the base MATLAB workspace.
Function, including nested and local function — Scope is the function and the function does not share the import list of the parent function. If the import list is needed in a MATLAB function or script and in any local functions, you must call the import function for each function.
For unit tests though, I would argue that it is probably best to use the fully-qualified function name every time (rather than relying on import) so that it's clear to the user what you're testing.
result = folder1.file1(testdata.input)

Currently import statements in MATLAB have function scope as mentioned in the answer by Suever.
However, I often use local functions as a workaround to mimic a file level import:
classdef file1_test < matlab.unittest.TestCase
properties
path
end
methods(TestMethodSetup)
function setunittestdatapath(testCase)
p = mfilename('fullpath');
[directory,~,~]=fileparts(p);
testCase.path = fullfile(directory,'unittest_data');
end
end
methods (Test)
function file1_input(testCase)
%import folder1.file1
testdata = load(fullfile(testCase.path),'file1_testdata.mat');
result = file1(testdata.input);
testCase.verifyThat(result, IsEqualTo(testdata.output));
end
end
end
% Include file level "import" functions below
function f = file1(varargin)
f = folder1.file1(varargin{:});
end
function c = IsEqualTo(varargin)
c = matlab.unittest.constraints.IsEqualTo(varargin{:});
end
Note in this example I "imported" both your source code as well as some of the test framework source code in order to use the literate form of verifyEqual using verifyThat. Note this is the same functional behavior, but in general there exists more functionality with the constraints than with the qualification methods so this may be helpful to you at some point.

Related

Why can you import a package *after* using its content in a function?

I'm on MATLAB R2014b and have a question that I will illustrate with the following example.
MWE can be made as follows or download it as a .zip file here.
Create a package folder +test on your path with four function files in it:
+test
a.m
b.m
c.m
d.m
Content of a.m:
function a
disp 'Hello World!'
Content of b.m:
function b
a
If you run b from the command line, you will have to import the test package first (import test.*) or run test.b.
Running b will result in an error, since the scope of function b doesn't contain function a. We must import it before it can be used. For this I've created c.m:
function c
import test.*
a
Now running c works fine.
Now my question. If I change c.m to (saved in d.m):
function d
a
import test.*
I.e. the import command is issued after the call to package function a. Running d still works just fine, as if the position of the import command in d.m does not matter. The import appears to have occurred before the call to function a, which in d.m happens on the line before the import.
Why does this happen. Is this the intended behaviour and what are its uses? How and in what order does MATLAB read a .m file and process it? And more off-topic, but in general: how is importing packages handled in different languages compared to MATLAB, does the order of commands matter?
My preemptive conclusion based on the comments: It is probably best practice to only use the import function at or near the beginning of MATLAB code. This makes clearly visible the imported content is available throughout the entire element (e.g. function). It also prevents the incorrect assumption that before the import, the content is not yet available or refers to a different thing with the same name.
MATLAB performs static code analysis prior to evaluating a function in order to determine the variables/functions used by that function. Evaluation of the import statements is part of this static code analysis. This is by design because if you import a package and then use it's functions, MATLAB needs to know this during the static code analysis. As a result, regardless of where you put the import statement within your function, it will have the same effect as if it were at the beginning of the function.
You can easily test this by looking at the output of import which will list all of the current imported packages.
+test/a.m
function a(x)
disp(import)
import test.*
end
test.a()
% test.*
This is why the documentation states to not put an import statement within a conditional.
Do not use import in conditional statements inside a function. MATLAB preprocesses the import statement before evaluating the variables in the conditional statements.
function a(x)
disp(import)
if x
import test.*
else
import othertest.*
end
end
test.a()
% test.*
% othertest.*
The only way to avoid this behavior is to allow the static code analyzer to determine (without a doubt) that an import statement won't be executed. We can do this by having our conditional statement be simply a logical value.
function a()
disp(import)
if true
import test.*
else
import othertest.*
end
end
test.a()
% test.*
As far as importing compared to other languages, it really depends on the language. In Python for example, you must place the import before accessing the module contents. In my experience, this is the typical case but I'm sure there are many exceptions. Every language is going to be different.

Import functions in Matlab for every local function

I have an m-file with a couple of tests defined as local functions. They are called from the main function:
function tests = main_function_test()
tests = functiontests(localfunctions);
end
I am doing assertions with some tolerance, so I need to import in each local function:
import matlab.unittest.constraints.IsEqualTo;
import matlab.unittest.constraints.AbsoluteTolerance;
in order to make assertions of the form:
verifyThat(testCase, actual, IsEqualTo(expected, ...
'Within', AbsoluteTolerance(0.00001)));
Is it possible to import those functions just once so they can be reused in each local function?
This is not possible per the documentation:
Scope is the function and the function does not share the import list of the parent function. If the import list is needed in a MATLAB function or script and in any local functions, you must call the import function for each function.
That being said, you could use eval with the output from import (cell array of strings) but it's extremely poor coding practice and I highly recommend against doing it.
function trialcode
import matlab.unittest.constraints.IsEqualTo;
import matlab.unittest.constraints.AbsoluteTolerance;
importlist = import;
sub1(importlist)
end
function sub1(L)
for ii = 1:length(L)
estr = sprintf('import %s', L{ii});
eval(estr);
end
disp(import)
end
Again, this is technically possible but please don't do it this way. You have little control over the imports (and controlling logic would likely be longer than implicitly importing them in the first place), it's difficult to debug, impossible for MATLAB's compiler to optimize, and makes the code tremendously unclear.
There are two things you can do here.
Use the verifyEqual function (doc) to get most of the functionality you have with verifyThat. Note that there exists the 'RelTol' and 'AbsTol' name value pairs with that function.
Define special local functions to use like import statements. These will have precedence within the file just like you would expect from a file level import.
This looks like so:
function tests = main_function_test()
tests = functiontests(localfunctions);
end
function firstTest(testCase)
testCase.verifyThat(actual, IsEqualTo(expected, ...
'Within', AbsoluteTolerance(0.00001)));
end
function testASecondThing(testCase)
testCase.verifyThat(actual, IsEqualTo(expected, ...
'Within', RelativeTolerance(0.0005)));
end
% "import" functions
function c = IsEqualTo(varargin)
c = matlab.unittest.constraints.IsEqualTo(varargin{:});
end
function t = AbsoluteTolerance(varargin)
t = matlab.unittest.constraints.AbsoluteTolerance(varargin{:});
end
function t = RelativeTolerance(varargin)
t = matlab.unittest.constraints.RelativeTolerance(varargin{:});
end
Hope that helps!

How to nest anonymous functions in Matlab?

I have a file funcs.m that stores anonymous functions. They must be usable by the files in the directory where it is. Currently, I use the anonymous functions so that I execute the file funcs.m in different files but I think this is a a wrong way of doing things. The other functions such as main.m and its nested function nest.m need to use the anonymous functions from funcs.m. I think paths won't solve this problem because the files are in the same folder. Basically I could solve this problem by copy-pasting the anonymous functions to every file but code-smell so:
Is there some way of reusing the funcs.m having the anon functions in Matlab?
Example
main.m
function main
funcs; % loads the anonymous functions
nest(par1,...,parN)
end
nest.m
function nest(par1,...,parN)
funcs; %ERRR: This fires err, why? Look: this was sourced already in main.m!
function neededOnlyHere(par100)
bq(q,A) %This needs the functions of the funcs
end
neededOnlyHere(somePar) %ERR to use the anon funcs from funcs
end
Functions main.m and nest.m use this function funcs.m having the anonymous funcs
bq=#(q,A) q*A; %Bolded q
I=#(ii,jj,A) find(A(ii,:)==1 & A(jj,:)==0);
AiNotj=zeros(1,Ncut);
...
ERROR
Attempt to add "bq" to a static workspace.
See MATLAB Programming, Restrictions on
Assigning to Variables for details.
Error in funcs (line 10)
bq=#(q,A) q*A;
%Bolded q
Why it's breaking
You get the error when calling it in nest.m because having a nested function makes its enclosing function's workspace a "static workspace"; that is, variable names cannot be added via eval(), assignin(), or other "dynamic" techniques; only variables that are explicitly assigned in that function's text are allowed. Evaluating a script to define local variables - which is what you're doing when calling funcs.m - is "dynamic", so prohibited in functions with nested functions. It works in main.m because main has no nested functions and is thus a "dynamic" workspace.
There are a couple ways you could change it to work with static workspaces and nested functions. The first thing to ask is whether you really need to make them anonymous functions?
Using package functions instead
If you don't need them to be anonymous functions per se, just break them out and put each one as a regular function in its own .m file; e.g. bg.m, I.m, AiNotj.m, and so on. Then they're all available to all other functions in that directory.
If that turns in to a mess of files, or if you want to scope them and maybe make them available only to the selected functions that really need them (that is, the functions currently calling funcs()), then you can stick them in a package. Create a subdirectory called +myfuncs and move all the little function files in there; e.g. +myfuncs/bq.m, +myfuncs/I.m, +myfuncs/AiNotj.m. (The + prefix tells Matlab the directory is a package.) Then you can pull all of them in to your function scope by doing import myfuncs.* as a direct replacement for where you're currently calling funcs().
function nest(par1,...,parN)
import myfuncs.*;
function neededOnlyHere(par100)
bq(q,A) % This will work, resolving to myfuncs.bq
end
You can do the import myfuncs.* from the command line to make them available interactively, too.
This is probably how Matlab itself wants you to organize clusters of related functions like this, and would be my first approach. It's the least "smelly" IMHO. If you really wanted to be able to edit them all in a single file like funcs.m for convenience, you could write a little code munger in Perl or whatever that parsed funcs.m and output them all as equivalent individual functions as a preprocessing step. (I think it's a bit of a bummer that you can't define multiple top-level functions in an M-file like this, but oh well.)
If you really need to work with anonymous functions, there are some workarounds.
Passing functions in a struct
You can change your funcs() function to actually return a struct of all those anonymous functions, using field names instead of local variable names.
function out = funcs
out.bq=#(q,A) q*A; %Bolded q
out.I=#(ii,jj,A) find(A(ii,:)==1 & A(jj,:)==0);
out.AiNotj=zeros(1,Ncut);
For this, you'd have to prefix all the function references with the struct name you're holding them in. Don't know how big a deal this is for you.
function nest(par1,...,parN)
fs = funcs;
function neededOnlyHere(par100)
fs.bq(q,A) %This needs the functions of the funcs
end
Preallocating variables
To get funcs() to work as-is, you can statically pre-allocate variables with all the function names you're going to use, so the Matlab parser recognizes them as statically assigned variables. Then when you call funcs(), it will re-assign the values of the existing variables, which is permissible in dynamic workspaces.
function nest(par1,...,parN)
[bq, I, AiNotj] = deal(); % Preallocate all names from funcs
funcs;
function neededOnlyHere(par100)
bq(q,A) %This needs the functions of the funcs
end
This would be a bit of a pain, because you'd have to re-edit every file that uses funcs whenever a new function name is added. You could at least write a little perl script to auto-generate that line of code by parsing funcs.m and outputting a "[bg, I, AiNotj,...] = deal();" with all the functions it finds, and you can just copy that in to your code.
Another way to do this would be to have funcs actually return all the functions in its output list. This would have the benefit of continuing to work even as you add new functions to funcs.m, as long as you don't remove or change the order of your existing anonymous functions.
function [bg,I,AiNotj] = funcs()
bg = ...
I = ...
% And then in the calling functions:
[bg,I,AiNotj] = funcs(); % which you can copy and paste from funcs.m's header
There are many ways of passing anonymous functions:
1) Pass the function itself:
function main
f = #(t) t^2 - 3;
param = randn(12,1);
g = test22(param,f);
disp (g)
end
function g = test22(param,f)
g = f(param(2));
disp(param(2))
end
2) Use globals (which usually should be avoided in complex code)
function main
global f
f = #(t) t^2 - 3;
param = randn(12,1);
g = test22(param);
disp (g)
end
function g = test22(param)
global f
g = f(param(2));
disp(param(2))
end

MATLAB: modify import list of other scopes dynamically?

So, is there a way in MATLAB to modify the import list of a scope different from the current one? That is, I would like to be able to do this:
function import_mypackage()
evalin('caller', 'import mypackage.*');
end
This doesn't work. No error is produced when calling import_mypackage, but the namespace contained in mypackage is not imported, i.e:
function foo()
import_mypackage;
g(); % Wanted mypackage.g() but got: Undefined function or variable 'g()'
end
I know that you can modify dynamically the import list of the current scope either using eval or by passing a variable to import(). However, I cannot find any way to modify the import list of other scopes. Is there any way?
EDIT: Rody Oldenhuis found in his answer a strange behavior of function import. He suggested that evalin actually modifies the import list of the caller but that such list is cleared once you return from import_mypackage. However, I think that what is happening is that import expressions always evaluate in the current worspace. See:
function import_mypackage()
evalin('caller', 'L = import(''mypackage.foo'');');
foo; % It works! So the import happened in this workspace
end
Modified from Rody's response:
function foo()
import mypackage.f;
import_mypackage;
L
import
end
will print:
L =
'mypackage.foo'
ans =
'mypackage.f'
indicating the L was set to the import list of import_mypackage scope but that it never really cleared the import list of foo().
EDIT: #RodyOldenhuis
The reason why I want to mess around with the import list of the caller scope is that I want to define an "aliased" version of import(). Such alias_import() would allow the user to define package aliases so that:
alias_import my_toolbox
may be equivalent to:
import my_toolbox_v1.*
or to:
import my_toolbox_v2.*
that is, I want to be able to maintain several versions of a toolbox and control dynamically the version that is being imported. This is useful for comparing results between different my_toolbox versions or whenever you want to ensure that certain my_toolbox version will be used. All without having to go to the code and manually change import directives in hundreds of functions whenever I am upgrading to a new version of my_toolbox. Of course there is the alternative of making this in an indirect way, like:
import(alias_import('my_toolbox'))
so that alias_import will not perform the actual importing but simply will produce the input to the built-in import. This is perfectly fine but a bit more verbose and that is why I would have liked alias_import to modify the caller's import list. But after seeing the weird behavior of evalin() I think I rather leave things like they are now.
It seems you have stumbled upon some odd behavior (I'm not using the workd "bug" until I'm sure this is not what Mathworks intended :).
It seems that
function import_mypackage()
evalin('caller', 'import mypackage.*');
end
function foo()
import_mypackage;
g();
end
does not work, but
function import_mypackage()
evalin('caller', 'L = import mypackage.*');
end
function foo()
import_mypackage;
L
import
end
shows
L =
'mypackage.*'
ans =
Empty cell array: 0-by-1
which implies that the import list in the caller (foo) is cleared when the function import_mypackage goes out of scope.
I'd say this is at least unwanted behaviour, and I'd say this is a case for a bug report.
As a work-around, use something like
function L = import_mypackage()
L = import('mypackage.*');
end
function foo()
L = import_mypackage;
import(L{:});
% do stuff with pacakge contents
...
end
which I think is advisable over evalin anyway.

Is self-reference possible in MATLAB?

As noted here, functions in packages, as well as static methods in classes, still need to use a packagename.functionname syntax or import packagename.* for each function (since the imports are part of the function workspace and not global). This means that changing the package/class name later on can become a tedious nuisance.
Is there any way to do something like import this.*, i.e. a package/class name agnostic method to access all functions/static methods in the same package/class?
So... doesn't this require importthis to also be imported? Or is importthis a function you always have in your path?
It seems hardly more complex to just paste an "import this" block with this at the top of each function, and then you don't have to worry about importthis being in your path. I tend to feel that reliance on path is dangerous.
"Import This" block
%% Import own package
[~, pkgdir] = fileparts(fileparts(mfilename('fullpath')));
import([pkgdir(2:end) '.*']);
You can even put it in a try/catch block to make sure it's in a package directory, and decide what to do if it's not.
%% Import own package
try
[~, pkgdir] = fileparts(fileparts(mfilename('fullpath')));
import([pkgdir(2:end)'.*']);
catch err
if ~strcmp(err.identifier,'MATLAB:UndefinedFunction'), rethrow(err); end
end
I recently ran into a similar problem and found the following solution for packages. However it is VERY hacky.
You create a function called import this with an optional argument.
function to_eval = importthis(exclude_list)
if nargin == 0
exclude_list = [];
end
var_name = genvarname('A', exclude_list); %avoid shadowing
to_eval = ['[~,'...
, var_name...
, ']=fileparts(fileparts(mfilename(''fullpath'')));'... %get containing dir
, 'eval([''import '','...
, var_name...
, '(2:end),''.*'']);'... %remove '+'
, 'clear '... %clean up
, var_name
];
end
This function returns a string which can then be evaled that imports the "this" package. So in your package functions you would put the following near the top:
function B = myfunc(A)
eval(importthis);
%function body
end
You can also pass who to importhis, leaving your function's namespace clean.
function B = myfunc(A)
eval(importthis(who));
%function body
end
I can't decide whether I should feel proud or discusted by what I did.
This probably is not a bounty worthy answer but as you do not have any answers I thought I would post it anyway! You can invoke static methods via an instance of the class which you would only need to define once. You can invoke functions via a function handle but this would require one handle per function.
Using these techniques you could define all your static method and function references in one place. Then you would use these references throughout your package. Then if you decided to change the package name at a later point you would only need to update these references which are all stored in one place.
See:
Calling Static Methods
You can also invoke static methods using an instance of the class,
like any method:
obj = MyClass;
value = obj.pi(.001);
function_handle (#)
The following example creates a function handle for the humps function
and assigns it to the variable fhandle.
fhandle = #humps;