Giter Club home page Giter Club logo

matlab-xunit's Introduction

matlab-xunit

xUnit for MATLAB with JUnit-compatible XML output

README

Testing is wonderful! Let's make it easier and more rewarding!

The most popular testing platform for MATLAB functions and classes is/was Steve Eddins' excellent MATLAB xUnit package.

The previous maintainer, Thomas Smith, made two additions to that package: the ability to give output in a JUnit-compatible XML format, and the ability to run DocTests, similar to the doctest module in Python or vignettes in R.

I've made one additional change: renaming runtests to runxunit so that it's compatible with MATLAB R2013a and newer. (runtests is now a built-in function.)

Other contributors have added compatibility with R2016b and newer, bug fixes, and internal refactorings.

Installation and Usage

To install matlab-xunit, clone or download this from GitHub, and put the matlab-xunit/src and matlab-xunit/matlab-xunit directories on your MATLAB path (using addpath).

Once you've written some unit tests (see xUnit's help), you can then run:

runxunit path/to/test/dir

If everything goes well, you'll see some output like this:

>> runxunit ./tests
Test suite: tests
Test suite location: ./tests
17-Jul-2014 20:49:47

Starting test run with 153 test cases.
....................
....................
....................
....................
....................
....................
....................
.............
PASSED in 2.922 seconds.

If any of the tests failed, they'll be marked with a F instead of a . and more info about the failure will be printed at the end. You can also get more verbose info on all tests, both passes and failures, by using the -verbose flag.

XML Output

Why would you want to do that? Well, because other tools understand it. In particular, I'm using the Jenkins continuous integration system (http://jenkins-ci.org/) to automatically run unit tests when I check in code. Jenkins understands JUnit's XML report format, and can display it in very nice ways. By creating a test report file in the same format, we can leverage all of that.

For example, here's a screenshot of the table Jenkins generates from a single build's report:

Jenkins test results

And here's a graph of the test trend:

Jenkins trend graph

The implementation is based on xml_io_tools by Jaroslaw Tuszynski, which is a nice way to generate XML in MATLAB. It uses about 1/3 the lines of code as MATLAB's built-in xmlwrite.

Usage

Once you've written some unit tests (see xUnit's help), you can then run:

runxunit path/to/test/dir -xmlfile testreport.xml

Unsurprisingly, this will run your unit tests and put the results into testreport.xml in the current directory.

Usage with Jenkins

OK, this is really cool, but involves some setup. First, you're going to have to either install Jenkins on the machine that has MATLAB, or give Jenkins remote access to that machine (there may be MATLAB licensing issues to this, I have no idea). As a note, installing Jenkins is incredibly easy: you download one file and run one command.

Now, you need to create a job that checks out your code from Subversion or whatever, and then runs your tests. I'm not going to run you through the whole thing, but here are the two important points:

First, you need a build step that will run the tests. Mine looks something like this:

/path/to/matlab -nodisplay -r "try; \
    addpath /path/to/xunit-matlab-doctest/xunit; \
    runxunit -xmlfile testreport.xml the_tests/; \
  catch Ex; fprintf(2, Ex.getReport()); quit(1); end; \
  quit(0);"

And second, you need to check the Jenkins box that says "Publish JUnit test result report." I tell it to look at **/testreport.xml.

Now save the configuration, tell the project to Build Now, and you should have a lovely display of what tests were run, and which failed!

DocTests

As of version 4.0.0, DocTests is no longer part of the "core" matlab-xunit and lives in its own repository at matlab-xunit-doctests.

Versioning

This codebase is over 5 years old now, and has had several maintainers, so past versioning is kind of a mess. Starting with version 4.0.0, it follows Semantic Versioning for versions, and Semanticly Versioned Names for tags and releases.

Contributing

  1. Fork it ( https://github.com/psexton/matlab-xunit/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

Suggestion: Please try to keep contributions to one {bug fix, feature, etc} per pull request. If you've got 3 unrelated bug fixes, submit 3 pull requests. If I can read through your changes in < 5 minutes, I'll probably merge it immediately. If it's a huge patch that touches dozens of files for multiple reasons, I'll probably put off merging it until I can do a more thorough code review. And then it will drop off my mental radar. (But not because I don't like you!)

License

BSD 3-Clause

matlab-xunit's People

Contributors

bauglir avatar jamesmyatt avatar psexton avatar tgs avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

matlab-xunit's Issues

Exceptions are printed out too verbosely

The stack trace doesn't have anything to do with the actual test failure:

===== Test Case Failure =====
Location: /home/tgs/multi-lib/data-analysis/cevent/cevent_relative_intervals.m
Name:     cevent_relative_intervals

/home/tgs/src/xunit-git/xunit/DocTest.m at line 203
/home/tgs/src/xunit-git/xunit/DocTest.m at line 100
/home/tgs/src/xunit-git/xunit/TestSuite.m at line 85
/home/tgs/src/xunit-git/xunit/TestSuite.m at line 85
/home/tgs/src/xunit-git/xunit/TestSuite.m at line 85
/home/tgs/src/xunit-git/xunit/runtests.m at line 126

DocTest #4 from cevent_relative_intervals failed; expected first, got second
Inputs are not equal.

First input:
ans = 1 10 1 21 21 2 81 85 3

Second input:
??? Error using ==> cevent_relative_intervals at 76 The WHENCE argument must be either 'start' or 'end'.

`localfunctions` function was only added in R2013b

I think that the new subfunction test suites are incompatible with versions of MATLAB before R2013b, since that's when the localfunctions function was added in MATLAB.

I think this is a particular problem since people who know that they're only using R2013b or newer are likely to be using matlab.unittest instead.

dependency on width of output

If you run your examples in Matlab that's like 130 columns wide, but the tests are run in an environment that's only 80 columns wide, there will be differences in how Matlab paginates the output, so the tests will fail. This only happens if you e.g. print a 30x30 matrix.

Partial paths are mishandled

In TestSuite.fromName, isdir() is used to determine if the string argument "name" is a subdirectory (TestSuite.m:199). If isdir(name) returns true, xUnit calls cd(name) in TestSuiteInDir.gatherTestCases() (TestSuiteInDir.m:45). The problem is that Matlab knows three types of paths (absolute/full paths, relative paths, partial paths [1]) and isdir() accepts all three of them although this is not explicitly said in the documentation. This may cause an exception when cd() is called in gatherTestCases() because cd() handles only absolute and relative paths (the documentation is clear here). This causes XUnit to throw an exception whenever the name argument to TestSuite.fromName() happens to be a partial path.

Demo:

  • Create an empty directory
  • Change into the empty directory
  • Start Matlab in this directory
  • Execute isdir('elmat')
  • Execute cd('elmat')

isdir('elmat') will return logical one because MATLABROOT/toolbox/matlab/elmat is in the search path hence "elmat" is a partial path. cd('elmat') will throw an exception because "elmat" is neither a relative nor an absolute path.

Moreover isdir() is called before ispackage() in TestSuite.fromName() so if a package name happens to be a partial path, then an exception will be thrown as well.

[1] http://www.mathworks.com/help/matlab/matlab_env/specify-file-names.html

when traversing a directory that's not on the path, there's no warning

the testDocTestsHere function needs to give a warning or error if this is the case

also, when constructing the DocTestSuite, it needs to use the full path rather than just the name, because names may exist in multiple places

Hard to know how to run the correct copy of the function if there's more than one, though.

Broken in R2016b

I realize this package is targeted at older MATLAB releases before the introduction of the matlab.unittest framework, but I wanted to report that matlab-xunit doesn't work anymore in the latest R2016b.

The problem is with function-based unit-tests and the initTestSuite.m script. Unfortunately this hack of running a script from another function won't work to get access to its local functions.

Let me give a simplified example to illustrate the issue.
Consider the following file containing function-based tests implemented as sub-functions:

my_tests.m

function funcs = my_tests
    my_init;

function test_1
    assert(true);

function test_2
    assert(true);

Then consider this simplified version of the init script:

my_init.m

funcs = cell(2,1);
funcs{1} = str2func('test_1');
funcs{2} = str2func('test_2');

Running the file in R2016b we get:

>> f = my_tests
f =
  2ร—1 cell array
    @test_1
    @test_2
>> functions(f{1})
ans =
  struct with fields:

    function: 'test_1'
        type: 'simple'
        file: ''
>> feval(f{1})
Undefined function or variable 'test_1'. 

So you can see that the function handles returned by str2func are not actually bound to the subfunctions, thus calling them will fail.

To add one more problem, if you call nargout from inside the script (similar to initTestSuite.m), it will actually throw an error now in the latest version.

The only solution I can think of is to use localfunctions introduced in R2013b, which is what matlab.unittest framework relies on:

function funcs = my_tests
    if verLessThan('matlab','9.1')
        my_init;
    else
        funcs = localfunctions;
    end

Now I get this:

>> functions(f{1})
ans = 
  struct with fields:

     function: 'test_1'
         type: 'scopedfunction'
         file: 'C:\path\to\my_tests.m'
    parentage: {'test_1'  'my_tests'}

Of course for matlab-xunit, you'll have to use create a helper function to iterate through the cell array of function handles returned by localfunctions and create a test suite out of them (similar to the related functiontests function but specifically for this package tests), i.e something like this:

function test_suite = test_something
    if verLessThan('matlab','9.1')
        initTestSuite;
    else
        test_suite = initTestSuiteFromFcnHandles(localfunctions);
    end

where initTestSuiteFromFcnHandles will basically filter function handles (extract setup, teardown, and test functions based on their names), then create a TestSuite from those composed of FunctionHandleTestCase tests.

I'm not sure if this issue is restricted to R2016b, so it might be a good idea to test on R2016a and R2015b too, which is around the time MATLAB switched to the new LXE execution engine, which might explain why it broke matlab-xunit..

standardize line endings

There's some that's just \n and some that are \r\n

Git seems to really want them to be \n in the repository, but since so many Matlab people use it on Windows, I'm not sure if that's best.

Tests in `tests/src/*` are not run by default

The command in the Readme.md file (runxunit ./tests) only runs the tests that are directly in the tests directory and not those in the sub-directories, especially ./tests/src/ etc.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.