6

我希望能够在 Matlab中编写类似 jasmine的测试。所以像

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

我尝试使用两种策略来实现这一点:

(1)第一种策略使用结构体

expect = @(actual_value) struct('toBe', @(expected_value) assert(actual_value == expected_value));

(真正的实现不会只是调用assert)

但是,这不起作用:

expect(1).toBe(1); % this triggers a syntax error
??? Improper index matrix reference.

% this will work:
x = expect(1);
x.toBe(1);

(2)我尝试的第二种策略是使用一个类:

classdef expect
properties (Hidden)
    actual_value
end

methods
    function obj = expect(actual_value)
        obj.actual_value = actual_value;
    end

    function obj = toBe(obj, expected_value)
        assert(obj.actual_value == expected_value);
    end
end
end

乍一看,这看起来不错:您可以在控制台中运行

expect(1).toBe(1);

但是,不是在控制台中而是在脚本中运行它会给出

??? Static method or constructor invocations cannot be indexed.
Do not follow the call to the static method or constructor with
any additional indexing or dot references.

Error in ==> test at 1
expect(1).toBe(1);

这里有什么方法可以让这个想法在 matlab 中发挥作用吗?

4

5 回答 5

7

在最新版本的 MATLAB (13a/13b) 中,内置了一个单元测试框架,看起来与您正在尝试的非常相似。代替

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

你会写

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

(您也可以/代替拥有assumeThatassertThatfatalAssertThat)。

如果出于某种原因您希望实现自己的框架,请注意语法上的细微差别——您有一个点,而 MathWorksmyfibonacci(0)和测试条件之间有一个逗号。

在 MATLAB 中,你不能索引到这样的下标表达式的结果(好吧,你可以,但你必须重载subsref,这是一个痛苦的世界,相信我)。所以他们这样做的方式是将测试比较条件作为一个单独的包引入,并将它们作为单独的输入参数应用,而不是作为具有点语法的方法。

查看新单元测试框架的文档以了解更多关于框架本身的信息,或者(如果您更愿意自己动手​​)他们设计的语法来与您的比较。

于 2013-09-09T10:10:00.400 回答
4

如果您创建一个函数而不是脚本,您的类定义工作正常

所以而不是testscript.m包含

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

你需要一个testfunc.m包含

function testfunc
expect(myfibonacci(0)).toBe(0);
expect(myfibonacci(5)).toBe(15);
expect(myfibonacci(10)).toBe(55);
于 2013-09-09T14:29:40.480 回答
3

要添加到@MohsenNosratinia的评论中,如果您使用嵌套函数/闭包而不是 OOP 类,则会出现另一个不一致之处:

function obj = expect(expr)
    obj = struct();
    obj.toBe = @toBe;
    function toBe(expected)
        assert(isequal(expr,expected))
    end
end
  1. 该语法在命令提示符下不起作用:

    >> expect(1+1).toBe(2)
    Undefined variable "expect" or class "expect". 
    
  2. 从脚本中也不起作用:

    测试脚本.m

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

    与以前相同的错误:

    >> testScript
    Undefined variable "expect" or class "expect".
    Error in testScript (line 1)
    expect(1+1).toBe(2)
    
  3. 但对于 M 文件功能:

    测试Fcn.m

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

    它被奇怪地接受了:

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

    (第二个断言按预期失败,但没有语法错误!)


我认为在这里抛出“语法错误”是正确的结果,因为您不应该直接索引到函数调用的结果。如果你这样做,我认为它是“未定义的行为” :)(它可能有效,但并非在所有情况下都有效!)

相反,您应该首先将结果存储在一个临时变量中,然后对其应用索引:

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

诉诸丑陋的黑客,例如:

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

甚至是未记录的功能:

>> builtin('_paren', builtin('_dot', expect(1+1), 'toBe'), 2)
于 2013-09-10T02:45:00.897 回答
2

这是一个具有重载 subsref 方法的示例实现。我猜它也可以只用一个类来完成,但这会使 subsref 重载更加丑陋。

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

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

结果是:

>> expect(1).toBe(1)
>> expect(1).toBe(2)
Error using TestValue/toBe (line 13)
Assertion failed.
于 2013-09-09T10:42:27.183 回答
2

我认为重要的是要认识到 MATLAB 不是 JavaScript。类似 jasmine 的语法为此 API 使用了 JavaScript 的语义。我认为在设计任何 API 时,重要且有价值的是考虑编写它的语言,不仅要承认它的技术限制,还要承认它的技术优势和既定的惯用语。

正如 Sam 所提到的,这是 MATLAB 单元测试框架中采用的方法。例如,约束方法不会尝试在任何其他函数调用或索引操作之后立即调用函数,而是直接构造约束并命名约束以创建文字编程接口。在这种情况下,MATLAB 的一个示例优势是它不需要像 Java/C#/etc 那样在构造之前使用“新”。您实际上可以看到使用Hamcrest 匹配器NUnit 约束进行了类似的权衡,它们都没有订阅相同的方法来生成他们的识字验证,而是更喜欢在编写它们的语言之后设计他们的方法。

此外,虽然它确实是一个单元测试框架,但它当然可以用于编写其他类型的测试,如系统/集成。鉴于您提到您实际上是在编写测​​试,我强烈建议您使用已经可用的解决方案,而不是重新发明轮子。在框架的约束和其他周边特性上投入了很多,而且绝对是生产级的。

于 2013-09-10T20:52:26.530 回答