9

R2013a で導入された MATLABの新しい単体テスト フレームワーク、matlab.unittest を使用しています。次の両方のことが起こるという主張を書きたいと思います。

  1. 指定された識別子を持つ例外が発生します。
  2. その例外のメッセージは、いくつかの条件を満たしています。

メソッドは見つかりましたverifyErrorが、識別子またはエラー メタクラスを確認することしかできないようです。

もう1つのオプションverifyThatは、Throws制約があるようです。それはより有望に思えますが、のドキュメントはThrowsややまばらに見え、私が望むようにする方法がわかりません。

メッセージテキストをエラー識別子に追加できることはわかっていますが、実際にはそうしたくありません。メッセージ テキストは、mex ファイルを使用して呼び出されるネイティブ ライブラリから取得されます。また、テキストはスペースなどでフォーマットされています。テキストは非常に長くなる可能性があり、エラー識別子が混乱する可能性があります。

それで、私が望むものを達成することは可能ですか?

4

1 に答える 1

10

それを行うための準備機能はありません。これをハッキングする方法の 1 つを次に示します。

テストしている次の関数を考えてみましょう。数値以外の入力に対して特定のエラーをスローします。

増分.m

function out = increment(x)
    if ~isa(x,'numeric')
        error('increment:NonNumeric', 'Input must be numeric.');
    end
    out = x + 1;
end

ユニットテストコードは次のとおりです。

IncrementTest.m

classdef IncrementTest < matlab.unittest.TestCase
    methods (Test)
        function testOutput(t)
            t.verifyEqual(increment(1), 2);
        end
        function testClass(t)
            t.verifyClass(increment(1), class(2));
        end
        function testErrId(t)
            t.verifyError(@()increment('1'), 'increment:NonNumeric');
        end

        function testErrIdMsg(t)
            % expected exception
            expectedME = MException('increment:NonNumeric', ...
                'Input must be numeric.');

            noErr = false;
            try
                [~] = increment('1');
                noErr = true;
            catch actualME
                % verify correct exception was thrown
                t.verifyEqual(actualME.identifier, expectedME.identifier, ...
                    'The function threw an exception with the wrong identifier.');
                t.verifyEqual(actualME.message, expectedME.message, ...
                    'The function threw an exception with the wrong message.');
            end

            % verify an exception was thrown
            t.verifyFalse(noErr, 'The function did not throw any exception.');
        end
    end
end

try/catch ブロックの使用は、 Steve Eddins によるassertExceptionThrown古いxUnit テスト フレームワークの関数に触発されています。(更新: フレームワークは File Exchange から削除されたようです。代わりに新しい組み込みフレームワークを使用することをお勧めします。興味がある場合は、古い xUnit の人気のあるフォークを次に示します: psexton/matlab-xunit )。

エラー メッセージをテストする際にもう少し柔軟性を持たせたい場合は、verifyMatches正規表現を使用して文字列と照合する代わりに を使用します。


また、冒険したい場合は、matlab.unittest.constraints.Throwsクラスを学習して、エラー ID に加えてエラー メッセージをチェックする独自のバージョンを作成することもできます。

ME = MException('error:id', 'message');

import matlab.unittest.constraints.Throws
%t.verifyThat(@myfcn, Throws(ME));
t.verifyThat(@myfcn, ThrowsWithId(ME));

拡張バージョンはどこThrowsWithIdですか


編集:

わかりましたので、のコードを調べて、カスタムクラスmatlab.unittest.constraints.Throwsを実装しました。 Constraint

クラスは に似ていThrowsます。インスタンスを入力として受け取りMException、テスト対象の関数ハンドルが同様の例外をスローするかどうかをチェックします (エラー ID とメッセージの両方をチェックします)。どのアサーション メソッドでも使用できます。

  • testCase.assertThat(@fcn, ThrowsErr(ME))
  • testCase.assumeThat(@fcn, ThrowsErr(ME))
  • testCase.fatalAssertThat(@fcn, ThrowsErr(ME))
  • testCase.verifyThat(@fcn, ThrowsErr(ME))

抽象クラス からサブクラスを作成するmatlab.unittest.constraints.Constraintには、インターフェースの 2 つの関数を実装する必要があります:satisfiedBygetDiagnosticFor. FunctionHandleConstraintまた、関数ハンドルを操作するためのいくつかのヘルパー メソッドを提供するため、代わりに別の抽象クラスから継承することにも注意してください。

コンストラクターは、予想される例外 (MExceptionインスタンスとして) と、テスト対象の関数ハンドルを呼び出すための出力引数の数を指定するオプションの入力を受け取ります。

コード:

classdef ThrowsErr < matlab.unittest.internal.constraints.FunctionHandleConstraint
    %THROWSERR  Constraint specifying a function handle that throws an MException
    %
    % See also: matlab.unittest.constraints.Throws

    properties (SetAccess = private)
        ExpectedException;
        FcnNargout;
    end

    properties (Access = private)
        ActualException = MException.empty;
    end

    methods
        function constraint = ThrowsErr(exception, numargout)
            narginchk(1,2);
            if nargin < 2, numargout = 0; end
            validateattributes(exception, {'MException'}, {'scalar'}, '', 'exception');
            validateattributes(numargout, {'numeric'}, {'scalar', '>=',0, 'nonnegative', 'integer'}, '', 'numargout');
            constraint.ExpectedException = exception;
            constraint.FcnNargout = numargout;
        end
    end

    %% overriden methods for Constraint class
    methods
        function tf = satisfiedBy(constraint, actual)
            tf = false;
            % check that we have a function handle
            if ~constraint.isFunction(actual)
                return
            end
            % execute function (remembering that its been called)
            constraint.invoke(actual);
            % check if it never threw an exception
            if ~constraint.HasThrownAnException()
                return
            end
            % check if it threw the wrong exception
            if ~constraint.HasThrownExpectedException()
                return
            end
            % if we made it here then we passed
            tf = true;
        end

        function diag = getDiagnosticFor(constraint, actual)
            % check that we have a function handle
            if ~constraint.isFunction(actual)
                diag = constraint.getDiagnosticFor@matlab.unittest.internal.constraints.FunctionHandleConstraint(actual);
                return
            end
            % check if we need to execute function
            if constraint.shouldInvoke(actual)
                constraint.invoke(actual);
            end
            % check if it never threw an exception
            if ~constraint.HasThrownAnException()
                diag = constraint.FailingDiagnostic_NoException();
                return
            end
            % check if it threw the wrong exception
            if ~constraint.HasThrownExpectedException()
                diag = constraint.FailingDiagnostic_WrongException();
                return
            end
            % if we made it here then we passed
            diag = PassingDiagnostic(constraint);
        end
    end

    %% overriden methods for FunctionHandleConstraint class
    methods (Hidden, Access = protected)
        function invoke(constraint, fcn)
            outputs = cell(1,constraint.FcnNargout);
            try
                [outputs{:}] = constraint.invoke@matlab.unittest.internal.constraints.FunctionHandleConstraint(fcn);
                constraint.ActualException = MException.empty;
            catch ex
                constraint.ActualException =  ex;
            end
        end
    end

    %% private helper functions
    methods (Access = private)
        function tf = HasThrownAnException(constraint)
            tf = ~isempty(constraint.ActualException);
        end

        function tf = HasThrownExpectedException(constraint)
            tf = metaclass(constraint.ActualException) <= metaclass(constraint.ExpectedException) && ...
                strcmp(constraint.ActualException.identifier, constraint.ExpectedException.identifier) && ...
                strcmp(constraint.ActualException.message, constraint.ExpectedException.message);
        end

        function diag = FailingDiagnostic_NoException(constraint)
            import matlab.unittest.internal.diagnostics.ConstraintDiagnosticFactory;
            import matlab.unittest.internal.diagnostics.DiagnosticSense;
            subDiag = ConstraintDiagnosticFactory.generateFailingDiagnostic(...
                constraint, DiagnosticSense.Positive);
            subDiag.DisplayDescription = true;
            subDiag.Description = 'The function did not throw any exception.';
            subDiag.DisplayExpVal = true;
            subDiag.ExpValHeader = 'Expected exception:';
            subDiag.ExpVal = sprintf('id  = ''%s''\nmsg = ''%s''', ...
                constraint.ExpectedException.identifier, ...
                constraint.ExpectedException.message);
            diag = constraint.generateFailingFcnDiagnostic(DiagnosticSense.Positive);
            diag.addCondition(subDiag);
        end

        function diag = FailingDiagnostic_WrongException(constraint)
            import matlab.unittest.internal.diagnostics.ConstraintDiagnosticFactory;
            import matlab.unittest.internal.diagnostics.DiagnosticSense;
            if strcmp(constraint.ActualException.identifier, constraint.ExpectedException.identifier)
                field = 'message';
            else
                field = 'identifier';
            end
            subDiag =  ConstraintDiagnosticFactory.generateFailingDiagnostic(...
                constraint, DiagnosticSense.Positive, ...
                sprintf('''%s''',constraint.ActualException.(field)), ...
                sprintf('''%s''',constraint.ExpectedException.(field)));
            subDiag.DisplayDescription = true;
            subDiag.Description = sprintf('The function threw an exception with the wrong %s.',field);
            subDiag.DisplayActVal = true;
            subDiag.DisplayExpVal = true;
            subDiag.ActValHeader = sprintf('Actual %s:',field);
            subDiag.ExpValHeader = sprintf('Expected %s:',field);
            diag = constraint.generateFailingFcnDiagnostic(DiagnosticSense.Positive);
            diag.addCondition(subDiag);
        end

        function diag = PassingDiagnostic(constraint)
            import matlab.unittest.internal.diagnostics.ConstraintDiagnosticFactory;
            import matlab.unittest.internal.diagnostics.DiagnosticSense;
            subDiag = ConstraintDiagnosticFactory.generatePassingDiagnostic(...
                constraint, DiagnosticSense.Positive);
            subDiag.DisplayExpVal = true;
            subDiag.ExpValHeader = 'Expected exception:';
            subDiag.ExpVal = sprintf('id  = ''%s''\nmsg = ''%s''', ...
                constraint.ExpectedException.identifier, ...
                constraint.ExpectedException.message);
            diag = constraint.generatePassingFcnDiagnostic(DiagnosticSense.Positive);
            diag.addCondition(subDiag);
        end
    end

end

使用例を次に示します (以前と同じ関数を使用):

t = matlab.unittest.TestCase.forInteractiveUse;

ME = MException('increment:NonNumeric', 'Input must be numeric.');
t.verifyThat(@()increment('5'), ThrowsErr(ME))

ME = MException('MATLAB:TooManyOutputs', 'Too many output arguments.');
t.verifyThat(@()increment(5), ThrowsErr(ME,2))

アップデート:

この回答を投稿してから、一部のクラスの内部が少し変更されました。最新の MATLAB R2016a で動作するように上記のコードを更新しました。古いバージョンが必要な場合は、改訂履歴を参照してください。

于 2013-07-02T14:52:14.507 に答える