|
For my unit test tool I wrote a bit of magic that allows you to do object oriented like things with nested functions. Fairly soon after I discovered nested functions I realized the dual between objects and NF's. An object is really just a set of nested functions over a common scope.
In matlab we can do this manually by assigning function handles of nested functions to struct fields and returning those from a function. However it would be nice to do this automatically. When I wrote MUnit I wanted to use the OO nested function technique and I wanted the structure populated automatically. For a full idea of the code download and refer to the original code in MUnit at http://xtargets.com/cms/Tutorials/Matlab-Programming/MUnit-Matlab-Unit-Testing.html munit_testcase is the constructor for tests and it is always called in the following way. function test = test_template % Subclass the testcase object test = munit_testcase; % Create a structure of constraint objects for assertions c = munit_constraint; function setup % This function will be called at the % start of *every* test_ function end function teardown % This function will be called at the % end of *every* test_ function end % These are examples tests. Create new tests by creating % new functions whose name starts with test_ % % The three tests below show the three different styles you % can use to create assertions. Refer to the documentation % for more details. function test_0 x = 10; y = 20; % Simple assert using logical expression test.assert( x == y ); end function test_1 x = 10; y = 20; % Assert using anonymous functions ( recommended ) test.assert( @() x == y ); end function test_2 x = 10; y = 20; % Assert using constraint object test.assert( c.eq( x , y ) ); end end
|
The interesting code in munit_testcase that automatically populates the return struct with function handles is <snip> % ----------------------------------------------- % Get named actions from parent % ----------------------------------------------- stk = dbstack('-completenames'); mname = stk(2).file; fcn_names = scan(mname, {'setup' ,'teardown', test_[A-Za-z0-9_]*'}); fcns = evalin('caller',['@(){' sprintf('@%s ', fcn_names{:}) '}']); fcns = fcns(); for i = 1:length(fcns);fcn = fcns{i}; test_case.(fcn_names{i}) = fcn; end </snip> The function scan returns all functions names in the calling file matching the regular expressions in the list. The evalin line is a real mess but essentially tries to create function handles in the calling workspace. If you wonder why I have wrapped the function handles in an anonyomous function evaluation then so do I. It just works. Without that extra layer it all fails miserably. Ask Mathworks!! The scan function is below. It is a bit fancy cause it tries to intelligently cache the information so as not to rescan the files every time. xtargets_hash is a hash_table I wrote for matlab. The source is in the unit test code and I won't post it here. You can download it. It has some mex C code. % SCAN % % Scans and extracts function names from an m file % using regular expression patterns. The function % maintains a cache of these scans to speed up % operations the second time around. % % Arguments % file - full path to an m file to scan % patterns - A cell array of regular expressions for function names % to extract % function fcns = scan(file, patterns) persistent class_cache; if isempty(class_cache) % Create a new cache class_cache = xtargets_hash;; else % Check the cache to see if the % file has allready been parsed. entry = class_cache.get(file); if ~isempty(entry) && iscurrent(entry) fcns = entry.fcns; return; end end % ISCURRENT % % Checks the entry from the cache table to see if it is % current or not. Checks the datestamps on the files function out = iscurrent(entry) jfh = java.io.File(entry.file); out = jfh.lastModified == entry.date; end % Scan the file for all functions matching the pattern fid = fopen(file); fcns = {}; while 1 tline = fgetl(fid); if ~ischar(tline), break end for i = 1:length(patterns); pat = patterns{i}; if regexp(tline, sprintf('^\\s*function\\s*%s', pat), 'once') tok = regexp(tline, sprintf('^\\s*function\\s*(%s)', pat), 'tokens'); fcns{end+1}=tok{1}{1}; end end end fclose(fid); % Store the new cache entry jfh = java.io.File(file); entry.date = jfh.lastModified; entry.fcns = fcns; entry.file = file; class_cache.put(file, entry ); end
As I said the above code is specific to MUnit but I have successfully used it in a modified form in other projects where I wanted to grab function handles all matching a certain pattern from the current workspace. Good luck Brad. |