templatesvisual-c++microsoft-cpp-unit-test

CppUnitTestingFramework with templated TEST_CLASSes


Firstly, there is a question with a similar goal described here: C++ unit test testing, using template test class.

This question is regarding my attempt to solve the same problem.

Using the Microsoft CppUnitTestFramework, we can create unit tests using something like the following:

using namespace Microsoft::VisualStudio::CppUnitTestFramework;

namespace MyUnitTests {
    TEST_CLASS(NameOfMyTestClass) {
    public:
        TEST_METHOD(MyMethod1) {
            Assert::IsTrue(false);
        }
    };
}

I'd like to test a collection of similar tests (without using a For loop to put all of the Asserts in a single TEST_METHOD), so I looked at the TEST_CLASS macro:

#define TEST_CLASS(className) \
ONLY_USED_AT_NAMESPACE_SCOPE class className : public ::Microsoft::VisualStudio::CppUnitTestFramework::TestClass<className>

This can't be used with a template directly - as far as I can see there is no way to specify a className value that would include template parameters with the correct syntax to compile.

As a result, I attempted the following:

namespace MyUnitTests {

    ONLY_USED_AT_NAMESPACE_SCOPE
    template<MyEnumClass MeasurementType, char ExpectedShift>
    class templatedScaleTestClass : public TestClass<templatedScaleTestClass<MeasurementType,ExpectedShift>>
    {
    public:
        TEST_METHOD(Test_ExpectedShift) {
            Assert::AreEqual(ExpectedShift, Calculations::getShiftAmount(MeasurementType));
        }
    };

    ONLY_USED_AT_NAMESPACE_SCOPE template class templatedScaleTestClass<MyEnumClass::FIRST,3>;
    ONLY_USED_AT_NAMESPACE_SCOPE template class templatedScaleTestClass<MyEnumClass::THIRD,1>;
}

This compiles, and looks to me like it should allow me to define a collection of TEST_METHODs in the template class, then just instantiate the necessary collection of Enums and constant values to set them up (perhaps using some sort of constructor for other parameters in the future, although looking at CppUnitTest.h makes me wonder if that might be another problem...)

However, the class never appears in the test explorer, and trying to right click on the test (in the template code) and clicking "Run Test(s)" produces the following output:

[datetime Informational] Executing test method 'MyUnitTests.templatedScaleTestClass<MeasurementType, ExpectedShift>.Test_ExpectedShift'
[datetime Informational] No tests found to run.

Edit: Not sure how relevant the last part ("No tests found to run") is - doing the same with a normal test (no user-side templates) produces the same output. Clicking away from a specific test runs all tests in the .cpp file. Perhaps I'm using the right-click menu wrongly.


Solution

  • Despite having tried several attempts at getting this to display, and checking the output of a function like the following:

    template<typename T>
    void debugMethod(TestClass<T> *tc) {
        const TestClassInfo* classInfo = tc->__GetTestClassInfo();
        std::stringstream msg;
        msg << "Tag: " << classInfo->metadata->tag << std::endl;
        msg << "helpMethodName: " << classInfo->metadata->helpMethodName << std::endl;
        msg << "helpMethodDecoratedName: " << classInfo->metadata->helpMethodDecoratedName << std::endl;
        msg << "New method address: " << &(classInfo->pNewMethod) << std::endl;
        const MemberMethodInfo* methodInfo = T::__GetTestMethodInfo_Debug();
        msg << "methodInfo - Tag: " << methodInfo->metadata->tag << std::endl;
        msg << "methodInfo - methodName: " << methodInfo->metadata->methodName << std::endl;
        msg << "methodInfo - helpMethodName: " << methodInfo->metadata->helpMethodName << std::endl;
        msg << "methodInfo - helpMethodDecoratedName: " << methodInfo->metadata->helpMethodDecoratedName << std::endl;
        msg << "methodInfo - lineNo: " << methodInfo->metadata->lineNo << std::endl;
        Logger::WriteMessage(msg.str().c_str());
    }
    
    ... (namespace, test class etc)
    
    TEST_METHOD(Debug) { debugMethod(this); }
    

    and observing similar results in both a standard TEST_CLASS and my templated class, I was unable to get templated classes to display in the Test Explorer.

    It is possible to template a class then call the test functions from a non-templated class:

    template <MyEnum val>
    class myClass : public TestClass<myClass<val>>
    {
    public:
        TEST_METHOD(MyTest) {
            Assert::AreEqual(val, MyEnum::exampleValue);
        }
    }
    
    TEST_CLASS(DummyTests) {
        TEST_METHOD(Test_across) {
                auto a = myClass<MyEnum::MyEnumValue>();
                a.MyTest();
            }
    }
    

    but this still provides less than ideal feedback in the Test Explorer.

    A further alternative (ugly as it is...) is to define a macro function that takes the parameter you want to template on, and then define your entire class inside the macro:

    using namespace Microsoft::VisualStudio::CppUnitTestFramework;
    
    #define SCALING_TEST(TYPE_TO_TEST, EXPECTED_SHIFT)\
        TEST_CLASS(CATNAME(ScalingTest_, TYPE_TO_TEST)) {\
            private:\
            MyEnum type = MyEnum::TYPE_TO_TEST;\
            public:\
            TEST_METHOD(HasExpectedShift) {\
                Assert::AreEqual((char)EXPECTED_SHIFT, Calculations::getShiftAmount(type));\
            }\
        }
    
    namespace ScalingTests {
        SCALING_TEST(SPEED, 3);
    }