Matlab: Improper index matrix reference (or outsmarting matlab)

前端 未结 5 2254
梦谈多话
梦谈多话 2021-02-09 16:47

I want to be able to write jasmine-like tests in Matlab. So something like

expect(myfibonacci(0)).toBe(0);
expect(myfibonacci(5)).toBe(15);
expect(myfibonacci(10         


        
相关标签:
5条回答
  • 2021-02-09 16:50

    I think the important thing to recognize is that MATLAB is not JavaScript. The jasmine-like syntax utilizes the semantics of JavaScript for this API. I think when designing any API it is important and valuable to think about the language it is written in and subscribe not only to its technical limitations, but also to its technical strengths and its established idioms.

    As Sam alluded to this is the approach taken in the MATLAB Unit Test Framework. For example, the constraints approach does not attempt to invoke a function immediately after any other function call or indexing operation, but rather constructs the constraint directly and names the constraint so as to create a literate programming interface. One example strength of MATLAB in this case is that it doesn't require a "new" before construction like Java/C#/etc do. You can actually see similar tradeoffs being done with Hamcrest matchers and NUnit constraints, neither of which subscribe to the same approach to producing their literate verifications, preferring instead to design their approaches after the languages to which they are written in.

    Also, while it is indeed a unit test framework, it certainly can be used to write other types of tests like system/integration. Given you mentioned you are actually writing tests, I would highly recommend utilizing the solution that is already available rather than reinventing the wheel. There is quite a lot of investment in the constraints and the other surrounding features of the framework, and it is definitely production grade.

    0 讨论(0)
  • 2021-02-09 16:51

    Your class definition works fine if you create a function instead of script

    So instead of testscript.m containing

    expect(myfibonacci(0)).toBe(0);
    expect(myfibonacci(5)).toBe(15);
    expect(myfibonacci(10)).toBe(55);
    

    you need a function testfunc.m containing

    function testfunc
    expect(myfibonacci(0)).toBe(0);
    expect(myfibonacci(5)).toBe(15);
    expect(myfibonacci(10)).toBe(55);
    
    0 讨论(0)
  • 2021-02-09 17:06

    Here's an examplary implementation with an overloaded subsref method. It could also be done with only one class I guess, but that would make the subsref-overloading even uglier.

    classdef Tester < handle
        methods
            function obj = Tester()
            end
    
            function [varargout] = subsref(this,S)
    
                if S(1).type(1) =='('
                    tv = TestValue(S(1).subs{:});
                end
    
                if numel(S) > 1
                    try
                        [varargout{1:nargout}] = builtin('subsref', tv, S(2:end));
                    catch me
                        me.throwAsCaller();
                    end
                else
                    varargout{1} = tv;
                end
    
            end
        end
    end
    

    And

    classdef TestValue 
        properties (Hidden)
            value;
        end
        methods
            function this = TestValue(value)
                this.value = value;
            end
    
            function toBe(this, v)
                assert( isequal(this.value, v) );
            end
        end
    end
    

    Results in:

    >> expect(1).toBe(1)
    >> expect(1).toBe(2)
    Error using TestValue/toBe (line 13)
    Assertion failed.
    
    0 讨论(0)
  • 2021-02-09 17:08

    In the latest versions of MATLAB (13a/13b) there's a unit testing framework built in that looks very similar to what you're attempting. Instead of

    expect(myfibonacci(0)).toBe(0);
    

    you would write

    import matlab.unittest.constraints.IsEqualTo
    testCase.verifyThat(myfibonacci(0), IsEqualTo(0))
    

    (You could also/instead have assumeThat, assertThat, or fatalAssertThat).

    If for some reason you wish to implement your own framework, note the small difference in your syntaxes - you have a dot whereas MathWorks have a comma between myfibonacci(0) and the test condition.

    In MATLAB you can't index into the result of a subscripted expression like that (well, you could, but you would have to overload subsref, and that's a world of pain, trust me). So the way they've done it is to introduce the test comparison conditions as a separate package, and apply them as a separate input argument rather than as a method with the dot syntax.

    Take a look at the documentation for the new unit testing framework to find out more about either the framework itself, or (if you'd prefer to roll your own) the syntaxes they have designed as a comparison to yours.

    0 讨论(0)
  • 2021-02-09 17:09

    To add to @MohsenNosratinia's remark, if you use nested-functions/closure instead of OOP classes, you get the yet another inconsistency:

    function obj = expect(expr)
        obj = struct();
        obj.toBe = @toBe;
        function toBe(expected)
            assert(isequal(expr,expected))
        end
    end
    
    1. The syntax doesn't work from the command prompt:

      >> expect(1+1).toBe(2)
      Undefined variable "expect" or class "expect". 
      
    2. Doesn't work either from a script:

      testScript.m

      expect(1+1).toBe(2)
      expect(1*1).toBe(2)
      

      with the same error as before:

      >> testScript
      Undefined variable "expect" or class "expect".
      Error in testScript (line 1)
      expect(1+1).toBe(2)
      
    3. But for an M-file function:

      testFcn.m

      function testFcn
          expect(1+1).toBe(2)
          expect(1*1).toBe(2)
      end
      

      it is strangely accepted:

      >> testFcn
      Error using expect/toBe (line 5)
      Assertion failed.
      Error in testFcn (line 3)
          expect(1*1).toBe(2) 
      

      (the second assertion failed as expected, but no syntax errors!)


    I consider throwing a "syntax error" to be the correct outcome here, as you should not directly index into the result of a function call. If you do, I think of it as "undefined behavior" :) (it might work, but not in all cases!)

    Instead, you should first store the result in a temporary variable, then apply indexing into it:

    >> obj = expect(1+1);
    >> obj.toBe(2);
    

    or resort to ugly hacks like:

    >> feval(subsref(expect(1+1), substruct('.','toBe')), 2)
    

    or even undocumented functions:

    >> builtin('_paren', builtin('_dot', expect(1+1), 'toBe'), 2)
    
    0 讨论(0)
提交回复
热议问题