From 9be101c2f9af11c43e76e8dd3d6cb1d2e960368c Mon Sep 17 00:00:00 2001 From: James Myatt Date: Tue, 10 Feb 2015 14:21:35 +0000 Subject: [PATCH 1/6] Allow tests to be created as subclasses of TestSuite This is the simplest way to support parametrized tests in matlab-xunit --- src/+xunit/+utils/isTestSuiteSubclass.m | 9 +++++++++ src/TestSuite.m | 5 ++++- 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 src/+xunit/+utils/isTestSuiteSubclass.m diff --git a/src/+xunit/+utils/isTestSuiteSubclass.m b/src/+xunit/+utils/isTestSuiteSubclass.m new file mode 100644 index 0000000..a3392d5 --- /dev/null +++ b/src/+xunit/+utils/isTestSuiteSubclass.m @@ -0,0 +1,9 @@ +function tf = isTestSuiteSubclass(name) + +class_meta = meta.class.fromName(name); +if isempty(class_meta) + % Not the name of a class + tf = false; +else + tf = class_meta < ?TestSuite; +end diff --git a/src/TestSuite.m b/src/TestSuite.m index 9d41f6d..60a8d2d 100644 --- a/src/TestSuite.m +++ b/src/TestSuite.m @@ -231,8 +231,11 @@ function keepMatchingTestCase(self, name) if xunit.utils.isTestCaseSubclass(name) suite = TestSuite.fromTestCaseClassName(name); + elseif xunit.utils.isTestSuiteSubclass(name) + suite = feval(name); + elseif ~isempty(meta.class.fromName(name)) - % Input is the name of a class that is not a TestCase subclass. + % Input is the name of a class that is not a TestCase or TestSuite subclass. % Return an empty test suite. suite = TestSuite(); suite.Name = name; From ce02d57ed528209de43ab6ee6ed1499401d3f603 Mon Sep 17 00:00:00 2001 From: James Myatt Date: Tue, 10 Feb 2015 14:28:05 +0000 Subject: [PATCH 2/6] Discover all TestCases and TestSuites in PWD including when using class or package directories --- src/TestSuite.m | 44 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/src/TestSuite.m b/src/TestSuite.m index 60a8d2d..1902bc5 100644 --- a/src/TestSuite.m +++ b/src/TestSuite.m @@ -305,15 +305,39 @@ function keepMatchingTestCase(self, name) test_suite.Name = pwd; test_suite.Location = pwd; - mfiles = dir(fullfile('.', '*.m')); - for k = 1:numel(mfiles) - [path, name] = fileparts(mfiles(k).name); - if xunit.utils.isTestCaseSubclass(name) - test_suite.add(TestSuite.fromTestCaseClassName(name)); - elseif xunit.utils.isTestString(name) - suite_k = TestSuite.fromName(name); - if ~isempty(suite_k.TestComponents) - test_suite.add(suite_k); + list = dir(); + for k = 1:numel(list) + if list(k).isdir + % Directories + if strncmpi(list(k).name, '@', 1) + % Class directory + name = list(k).name(2:end); + if xunit.utils.isTestCaseSubclass(name) + test_suite.add(TestSuite.fromTestCaseClassName(name)); + elseif xunit.utils.isTestSuiteSubclass(name) + test_suite.add(feval(name)); + end + elseif strncmpi(list(k).name, '+', 1) + % Package directory + name = list(k).name(2:end); + test_suite.add(TestSuite.fromPackageName(name)); + end + else + % Files + [~,~,e] = fileparts(list(k).name); + if strcmpi(e, '.m') + % M-files + [~, name] = fileparts(list(k).name); + if xunit.utils.isTestCaseSubclass(name) + test_suite.add(TestSuite.fromTestCaseClassName(name)); + elseif xunit.utils.isTestSuiteSubclass(name) + test_suite.add(feval(name)); + elseif xunit.utils.isTestString(name) + suite_k = TestSuite.fromName(name); + if ~isempty(suite_k.TestComponents) + test_suite.add(suite_k); + end + end end end end @@ -324,7 +348,7 @@ function keepMatchingTestCase(self, name) % test_suite = TestSuite.fromPackageName(name) constructs a % TestSuite object from all the test components found in the % specified package. - + package_info = meta.package.fromName(name); if isempty(package_info) error('xunit:fromPackageName:invalidName', ... From c88953eb49e2133c4cadc0f91392b183769441fa Mon Sep 17 00:00:00 2001 From: James Myatt Date: Tue, 10 Feb 2015 14:28:22 +0000 Subject: [PATCH 3/6] Discover TestSuites in test packages --- src/TestSuite.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/TestSuite.m b/src/TestSuite.m index 1902bc5..2164e4a 100644 --- a/src/TestSuite.m +++ b/src/TestSuite.m @@ -373,6 +373,8 @@ function keepMatchingTestCase(self, name) class_names{k} = class_name; if xunit.utils.isTestCaseSubclass(class_name) test_suite.add(TestSuite.fromTestCaseClassName(class_name)); + elseif xunit.utils.isTestSuiteSubclass(class_name) + test_suite.add(feval(class_name)); end end From 153323547054f8e34fc259b5005f06ebd78c526f Mon Sep 17 00:00:00 2001 From: James Myatt Date: Tue, 10 Feb 2015 15:33:03 +0000 Subject: [PATCH 4/6] Add tests on IsTestSuiteSubclass --- tests/testIsTestSuiteSubclass.m | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 tests/testIsTestSuiteSubclass.m diff --git a/tests/testIsTestSuiteSubclass.m b/tests/testIsTestSuiteSubclass.m new file mode 100644 index 0000000..02d25cb --- /dev/null +++ b/tests/testIsTestSuiteSubclass.m @@ -0,0 +1,21 @@ +classdef testIsTestSuiteSubclass < TestCase + %testIsTestSuiteSubclass Unit tests for isTestSuiteSubclass + + methods + function self = testIsTestSuiteSubclass(name) + self = self@TestCase(name); + end + + function testTestSuite(~) + assertEqual(false, xunit.utils.isTestSuiteSubclass('TestSuite')); + end + + function testSubclass(~) + assertEqual(true, xunit.utils.isTestSuiteSubclass('xunit.mocktests.C')); + end + + function testNotASubclass(~) + assertEqual(false, xunit.utils.isTestSuiteSubclass('atan2')); + end + end +end From 8aa132c8e0c2a9ac251202cf7618591008d583c1 Mon Sep 17 00:00:00 2001 From: James Myatt Date: Tue, 10 Feb 2015 15:33:16 +0000 Subject: [PATCH 5/6] Add tests for parametrized tests as subclass of TestSuite --- tests/TestParameterizedTests.m | 46 +++++++++++++++++++ tests/TestSuiteTest.m | 6 +-- tests/helper_classes/TestParameterizedSuite.m | 46 +++++++++++++++++++ 3 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 tests/TestParameterizedTests.m create mode 100644 tests/helper_classes/TestParameterizedSuite.m diff --git a/tests/TestParameterizedTests.m b/tests/TestParameterizedTests.m new file mode 100644 index 0000000..4f8ddb4 --- /dev/null +++ b/tests/TestParameterizedTests.m @@ -0,0 +1,46 @@ +%TestParameterizedTests Unit tests for sub-classing the TestSuite class. + +classdef TestParameterizedTests < TestCaseInDir + methods + function self = TestParameterizedTests(name) + self = self@TestCaseInDir(name, ... + fullfile(fileparts(which(mfilename)), 'helper_classes')); + end + + function testConstructor(~) + % Exercise the constructor. Verify that the Name and Location + % properties are set correctly. + ts = TestParameterizedSuite(); + assertEqual(ts.Name, 'TestParameterizedSuite'); + assertEqual(ts.Location, which('TestParameterizedSuite')); + assertEqual(numel(ts.TestComponents), 3); + end + + function testPassingTest(~) + logger = TestRunLogger(); + suite = TestParameterizedSuite(); + tc = findTestComponent(suite, 'test_sin_0deg'); + tc.run(logger); + assertEqual(logger.NumFailures, 0) + end + + function testFailingTest(~) + logger = TestRunLogger(); + suite = TestParameterizedSuite(); + tc = findTestComponent(suite, 'test_sin_90deg'); + tc.run(logger); + assertEqual(logger.NumFailures, 1) + end + end +end + +function theTestComponent = findTestComponent(suite, name) +% Find the TestComponent given a name of a function under test. + +componentNames = cellfun(@(x) x.Name, suite.TestComponents, 'UniformOutput', false); +index = find(strcmp(name, componentNames), 1); +assertTrue(~ isempty(index), ['Could not find test component for ' name]); + +theTestComponent = suite.TestComponents{index}; + +end diff --git a/tests/TestSuiteTest.m b/tests/TestSuiteTest.m index 6552e5b..9e69547 100644 --- a/tests/TestSuiteTest.m +++ b/tests/TestSuiteTest.m @@ -117,9 +117,7 @@ function test_fromPwd(self) % test components. suite = TestSuite.fromPwd(); assertTrue(isa(suite, 'TestSuite')); - assertTrue(numel(suite.TestComponents) == 16); + assertEqual(numel(suite.TestComponents), 17); end - end - -end \ No newline at end of file +end diff --git a/tests/helper_classes/TestParameterizedSuite.m b/tests/helper_classes/TestParameterizedSuite.m new file mode 100644 index 0000000..0fde399 --- /dev/null +++ b/tests/helper_classes/TestParameterizedSuite.m @@ -0,0 +1,46 @@ +classdef TestParameterizedSuite < TestSuite + methods + function this = TestParameterizedSuite() + this = this@TestSuite(); + this.Name = class(this); + this.Location = which(class(this)); + + values = {0, 0; 30 0.5; 90 0.8}; + + testCases = this.createTestCases(values); + this.add(testCases); + end + + function testCases = createTestCases(this, values) + testCases = cell(1, size(values, 1)); + for idx = 1:size(values, 1) + testCases{idx} = this.createTestCase(values{idx, 1}, values{idx, 2}); + end + end + + function testCase = createTestCase(this, x, y) + %CREATETESTCASE Creates a function handle test case for a given test case + + % Create TestCase object + testFcn = @() TestParameterizedSuite.checkResult(x, y); + setupFcn = []; + teardownFcn = []; + + testCase = FunctionHandleTestCase(testFcn, setupFcn, teardownFcn); + + % Set name and location of test from name of file + testCase.Name = this.makeTestName(x); + testCase.Location = this.Location; + end + function str = makeTestName(this, x) %#ok + str = sprintf('test_sin_%.0fdeg', x); + str = genvarname(str); + end + end + + methods (Static) + function checkResult(x, y) + assertElementsAlmostEqual(sind(x), y, 'absolute', 1e-9) + end + end +end From 931b894967d503365b1f09c5d262360841d0daa3 Mon Sep 17 00:00:00 2001 From: James Myatt Date: Tue, 10 Feb 2015 15:33:38 +0000 Subject: [PATCH 6/6] Add tests with TestSuite subclass in package --- tests/+xunit/+mocktests/C.m | 37 +++++++++++++++++++++++++++++++++++++ tests/test_packageName.m | 9 ++++++--- 2 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 tests/+xunit/+mocktests/C.m diff --git a/tests/+xunit/+mocktests/C.m b/tests/+xunit/+mocktests/C.m new file mode 100644 index 0000000..2635632 --- /dev/null +++ b/tests/+xunit/+mocktests/C.m @@ -0,0 +1,37 @@ +% Class C is a TestSuite subclass containing two test cases (test_a and test_b). +classdef C < TestSuite + methods + function self = C() + self = self@TestSuite(); + self.Name = class(self); + self.Location = fileparts(which(class(self))); + + names = {'test_a'; 'test_b'}; + + testCases = self.createTestCases(names); + self.add(testCases); + end + + function testCases = createTestCases(self, names) + testCases = cell(1, size(names, 1)); + for idx = 1:size(names, 1) + testCases{idx} = self.createTestCase(names{idx, 1}); + end + end + + function testCase = createTestCase(self, name) + %CREATETESTCASE Creates a function handle test case for a given test case + + % Create TestCase object + testFcn = @() []; + setupFcn = []; + teardownFcn = []; + + testCase = FunctionHandleTestCase(testFcn, setupFcn, teardownFcn); + + % Set name and location of test from name of file + testCase.Name = name; + testCase.Location = which(class(self)); + end + end +end diff --git a/tests/test_packageName.m b/tests/test_packageName.m index 4b5ac70..6b70577 100644 --- a/tests/test_packageName.m +++ b/tests/test_packageName.m @@ -5,7 +5,7 @@ function test_happyCase suite = TestSuite.fromPackageName('xunit.mocktests'); -assertEqual(numel(suite.TestComponents), 5); +assertEqual(numel(suite.TestComponents), 6); theTestComponent = findTestComponent(suite, 'xunit.mocktests.subpkg'); assertEqual(numel(theTestComponent.TestComponents), 1); @@ -13,6 +13,9 @@ theTestComponent = findTestComponent(suite, 'xunit.mocktests.A'); assertEqual(numel(theTestComponent.TestComponents), 2); +theTestComponent = findTestComponent(suite, 'xunit.mocktests.C'); +assertEqual(numel(theTestComponent.TestComponents), 2); + theTestComponent = findTestComponent(suite, 'xunit.mocktests.FooTest'); assertEqual(numel(theTestComponent.TestComponents), 1); @@ -33,8 +36,8 @@ % This is needed because meta.package.fromName() doesn't sort its list of % functions, so the ordering isn't always stable. -components = [suite.TestComponents{:}]; -index = find(strcmp(name, {components.Name}), 1); +componentNames = cellfun(@(x) x.Name, suite.TestComponents, 'UniformOutput', false); +index = find(strcmp(name, componentNames), 1); assertTrue(~ isempty(index), ['Could not find test component for ' name]); theTestComponent = suite.TestComponents{index};