How to write a composite contraint or comparator to be usable in unittest framework

1 次查看(过去 30 天)
I'm struggeling with a supposedly simple task:
For a given example class
classdef SimpleClass
properties
some_string (1,1) string = "some_string"
some_vague_number (1,1) double = 0
some_precise_number (1,1) double = pi()
end
end
I want to write a constraint that checks "similarity" of two instances of the class. The strings should be equal and I want to parametrize the tolerances for each property separately. Clearly i could verify the conditions one after another but I would have to repeat that for each qualification, which is not good, when the class changes.
In short, I would like to have a class that combines a sequence of Comparators (pluggable into IsEqualTo(...'Using'...) ) or a constraint that uses the tolerances for checking and the diagnostic reporting thereof.
I have already made some attempts that don't work:
  • Derive a class SimpleClassComparator from IsEqualTo and pass an array of PublicPropertyComparator, each just looking at one field, to the base class constructor. But the the tests always pass.
  • Derive a class from contraint and implement the abstract methods as a sequence of my nested constraints for each field. That had some other problems and cannot be used for containers.
I can provide code for my approaches but firstly I would like to know:
What is the right way to implement a custom Comparator for the unittest framework?
  6 个评论
Jan Tusch
Jan Tusch 2019-12-17
编辑:Jan Tusch 2019-12-17
Thanks, for making that clear and thanks for your efforts.
I totally agree, that understanding the workflow of test execution is a nightmare. To make things worse, the interfaces are undocumented, hidden or even sealed with a lot of side-effects and magic in the details.
By now I don't think that there is a straightforward way of doing what I want by just assembling framework components and glue them togethher in a customized comparator.
Maybe the "framework" is not as mature as it appears at first sight.
David Hruska
David Hruska 2019-12-18
Hi Jan and others,
I was going to suggest writing a custom comparator subclass, but it seems you've already tried this and run into issues. Could you expand on what those issues were? Also, could you clarify how you'd like your constraint to work with containers? Are you saying that you'd like to compare two SimpleClass instances stored inside cell arrays, for instance?
As you've found, comparing values can be trickier than you might expect. For this reason, the Comparator interface isn't documented for extension (it may change in future releases as the framework evolves). We'd definitely want to address these challenges in writing a custom comparator before documenting the interface.

请先登录,再进行评论。

采纳的回答

Lawrence
Lawrence 2021-3-16
编辑:Lawrence 2021-3-17
A bit of a late reply, but here's a solution for anyone still needing one.
Probably implementing a custom constraint is simpler than implementing a custom comparator. You would follow the steps here: https://www.mathworks.com/help/matlab/matlab_prog/create-custom-constraint.html. I'm not sure what you mean by "That had some other problems and cannot be used for containers", but the code below works with the SimpleClass you provided.
In short, the constructor of the constraint would take an expected value and two additional tolerance inputs, all of which are stored to member variables. Then you would have a helper function to do the comparisons.
classdef similarTo < matlab.unittest.constraints.Constraint
properties (SetAccess = immutable)
expected
large_tolerance
small_tolerance
end
methods
function constraint = similarTo(expected,large_tolerance,small_tolerance)
constraint.expected = expected;
constraint.large_tolerance = large_tolerance;
constraint.small_tolerance = small_tolerance;
end
function bool = satisfiedBy(constraint,actual)
bool = constraint.customSimilarTo(actual);
end
function diag = getDiagnosticFor(constraint,actual)
import matlab.unittest.diagnostics.StringDiagnostic
if constraint.customSimilarTo(actual)
diag = StringDiagnostic('similarTo has passed.');
else
diag = StringDiagnostic(sprintf(['similarTo has failed.\n'...
'Actual string: %s, Expected string: %s\n'...
'Actual vagueNum: %d, Expected vagueNum: %d, Precision: %d\n'...
'Actual preciseNum: %d, Expected preciseNum: %d, Precision: %d'],...
actual.some_string, constraint.expected.some_string,...
actual.some_vague_number, constraint.expected.some_vague_number, constraint.large_tolerance...
actual.some_precise_number, constraint.expected.some_precise_number, constraint.small_tolerance));
end
end
end
methods (Access = private)
function bool = customSimilarTo(constraint, actual)
bool = strcmp(actual.some_string, expected.some_string)...
&& abs(actual.some_vague_number - constraint.expected.some_vague_number) < constraint.large_tolerance...
&& abs(actual.some_precise_number - constraint.expected.some_precise_number) < constraint.small_tolerance;
end
end
end
You then use this in your unit test:
verifyThat(testCase, actual, similarTo(expected, large_tolerance, small_tolerance))
Some customizations you could do would be baking the tolerances into the constraint class, either within similarTo or by sub-classing from it, and splitting the conditions in customSimilarTo into separate helper functions, so you can have a more granular diagnostic in getDiagnosticFor.
If by "cannot be used for containers" you are talking about arrays and cell arrays, you can combine the custom similarTo constraint with matlab's EveryElementOf and EveryCellOf without any additional modification.
  3 个评论
Lawrence
Lawrence 2021-3-16
编辑:Lawrence 2021-3-16
Here's a shorter class from the one in my answer above which makes use of MATLAB's built-in constraint primitives:
classdef similarTo < matlab.unittest.constraints.AndConstraint
methods
function constraint = similarTo(expected,large_tolerance,small_tolerance)
import matlab.unittest.constraints.IsEqualTo
import matlab.unittest.constraints.AbsoluteTolerance
import matlab.unittest.constraints.AndConstraint
import matlab.unittest.constraints.PublicPropertyComparator
import matlab.unittest.constraints.StringComparator
import matlab.unittest.constraints.NumericComparator
props = properties(expected);
constraint@matlab.unittest.constraints.AndConstraint(...
IsEqualTo(expected,...
'Using',PublicPropertyComparator(StringComparator,'IgnoringProperties',props(~strcmp(props,'some_string')))...
),...
AndConstraint(IsEqualTo(expected,...
'Within',AbsoluteTolerance(large_tolerance),...
'Using',PublicPropertyComparator(NumericComparator,'IgnoringProperties',props(~strcmp(props,'some_vague_number')))...
),...
IsEqualTo(expected,...
'Within',AbsoluteTolerance(small_tolerance),...
'Using',PublicPropertyComparator(NumericComparator,'IgnoringProperties',props(~strcmp(props,'some_precise_number')))...
)))
end
end
end
I'll admit it's a bit ugly at first glance, but it allows you to avoid having to define satisfiedBy or getDiagnosticFor or modify them if you modify the class. The only shortcoming I can see is that NumericComparator and StringComparator don't have a class-insensitive option, so 'asdf' compared to "asdf" will fail, as will double(3) compared to uint8(3). The hand-rolled constraint above allows you to have these compare equal.
Jan Tusch
Jan Tusch 2021-3-17
That looks like reasonable effort for adapting constraints for a class and keep tolerances configurable. Thanks alot for your code sample and the good explanation.

请先登录,再进行评论。

更多回答(0 个)

类别

Help CenterFile Exchange 中查找有关 Argument Definitions 的更多信息

产品


版本

R2019b

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!

Translated by