MATLAB单元测试函数调用

时间:2016-11-10 13:54:19

标签: matlab unit-testing

我有以下非常简单的实用功能:

function vfprintf(verbose, varargin)
% VFPRINTF Display output optionally depending on the level of verbosity.
%
% VFPRINTF(TF, ARGS) passes the arguments ARGS to the built-in MATLAB
% command |fprintf| if TF is logical true. If TF is logical false, VFPRINTF
% does nothing.

assert(islogical(verbose),...
    'utils:InvalidVerbose',...
    'VERBOSE must be logical true or false');

if verbose
    fprintf(varargin{:});
end

事实证明,尽管该功能非常简单,但它有一个问题导致我出现问题(assert条件应该是islogical(verbose) && isscalar(verbose),而不仅仅是islogical(verbose)),所以我想围绕它实施一些单元测试。

请注意,我不想测试fprintf - 我假设没问题。那么我有没有办法测试类似的东西:

  • "如果verbose的逻辑标量为true,则调用fprintf"
  • "如果verbose的逻辑标量为false,则不会调用fprintf"
  • "如果verbose符合逻辑非标量,则不会致电fprintf"
  • "如果verbose不符合逻辑,则不会致电fprintf"

我无法找到验证是否针对特定功能进行呼叫的方法。有任何想法吗?我唯一能想到的就是用我自己的函数模拟fprintf,这个函数会影响MATLAB路径上的真实函数,它会以某种方式引发一个fprintfCalled事件,该事件由测试代码监听什么时候打电话。这是唯一的方法吗?看起来像是矫枉过正。

或者我可能以错误的方式接近这个 - 也许我应该忘记测试所做的调用,而是直接测试vfprintf的命令行和/或文件输出。但感觉就像我在测试fprintf而不是vfprintf

也许我会过度思考,但我想改进我的测试实践,所以会很感激一些建议。谢谢!

1 个答案:

答案 0 :(得分:2)

我认为在这一点上你真的有4种选择。我喜欢第四节,但我会仔细研究它们:

  1. 在evalc中执行对vfprintf的调用,以验证打印到命令窗口的内容或创建文件并打印到该文件。缺点1:一种测试fprintf(尽管它更具学术性,因为fprintf很可能会发生重大改变或不履行合同)。缺点2:两种情况都与全局状态交互 - 全局命令窗口输出(其他东西可以打印到)或文件系统。不是世界末日,但如果可以的话,可以避免。对你的测试来说,完全避免触及外面的世界会更好。
  2. 影子fprintf功能。您可以将它放在路径中的文件夹中,然后在该文件夹中添加自己的fprintf函数,然后在测试中使用PathFixture将其添加到路径顶部。缺点:仍然依赖于改变全局状态(路径),它可能会更慢,因为从语言执行的角度来看,路径操作是昂贵的。不是一个大粉丝,但这是如何发挥作用。如果你能建议我在下面的#4下面:
  3. <强> VerboseArgumentsHolder.m

        classdef VerboseArgumentsHolder < handle
            properties
                Arguments = {};
            end
        end
    

    <强> VerbosePrinterSpy.m

        classdef VerbosePrinterSpy
            properties(Constant)
                ArgumentsHolder = VerboseArgumentsHolder;
            end
        end
    

    *(测试文件夹)/overloads/fprintf/fprintf.m *

       function fprintf(varargin)
       argHolder = VerbosePrinterSpy.ArgumentsHolder;
       argHolder.Arguments = varargin;
       end
    

    <强> vfprintfTest.m

        classdef vfprintfTest < matlab.unittest.TestCase
            methods(Test)
                function testWhenScalarTrue(testCase)
                    import matlab.unittest.fixtures.PathFixture;
    
                    testCase.applyFixture(PathFixture(...
                        fullfile((test folder),'overloads','fprintf')));
    
                    argHolder = VerbosePrinterSpy.ArgumentsHolder;
                    argHolder.Arguments = {}; % reset values since this is global and stateful.
                    vfprintf(true,'dummy input');
                    testCase.verifyEqual(argHolder.Arguments, 'dummy input');
                end
                function testWhenScalarFalse(testCase)
    
                    testCase.applyFixture(PathFixture(...
                        fullfile((test folder),'overloads','fprintf')));
    
                    argHolder = VerbosePrinterSpy.ArgumentsHolder;
                    argHolder.Arguments = {}; % reset values
    
                    vfprintf(false,'dummy input');
                    testCase.verifyEmpty(argHolder.Arguments);
                end
            end
        end
    
    1. 重构您的生产代码以具有打印界面,然后您可以添加特定于测试的间谍作为该界面。这是一种很好的方法,但对您的软件结构有一定的影响,可能并不容易调整,特别是如果您的代码库已经严重依赖于此实用程序。

    2. 由于您只是直接将varargin传递给fprintf,因此您可以使用fprintf方法创建一个test double来专门测试它。然后,fprintf调用将调度到您的测试特定类,它可以简单地监视输入。它可能看起来像这样:

    3. <强> VerbosePrinterSpy.m

          classdef VerbosePrinterSpy < handle
              properties
                  WasInvoked = false;
                  ArgumentsUsedInPrintCall = {'Not invoked'};
              end
      
              methods
                  function fprintf(spy, varargin)
                      spy.WasInvoked = false;
                      spy.ArgumentsUsedInPrintCall = varargin;
                  end
              end
          end
      

      <强> vfprintfTest.m

          classdef vfprintfTest < matlab.unittest.TestCase
              methods(Test)
                  function testWhenScalarTrue(testCase)
                      spy = VerbosePrinterSpy;
                      vfprintf(true, spy, 'dummy input');
                      testCase.verifyTrue(spy.WasInvoked);
                      testCase.verifyEqual(spy.ArgumentsUsedInPrintCall, 'dummy input');
                  end
                  function testWhenScalarFalse(testCase)
                      spy = VerbosePrinterSpy;
                      vfprintf(false, spy, 'dummy input');
                      testCase.verifyFalse(spy.WasInvoked);
                  end
              end
          end
      

      希望有所帮助!