5

As a fan of regular types and value semantics, I'm keen on classes becoming more regular and being non-polymorphic. As a fan of non-throwing operations, I'm keen on operations being noexcept. I also appreciate confirmation of what special member functions are actually being generated by the compiler. And I'd like to track these things along with code these are associated with.

C++ offers the <type_traits> header file which offers classes for checking qualities like these and I've found myself enjoying making use of them in unit test code like the following:

TEST(MyClass, Traits)
{      
    EXPECT_TRUE(std::is_constructible< MyClass >::value);
    EXPECT_TRUE(std::is_nothrow_constructible< MyClass >::value);
    EXPECT_FALSE(std::is_trivially_constructible< MyClass >::value);

    EXPECT_TRUE(std::is_copy_constructible< MyClass >::value);
    EXPECT_TRUE(std::is_nothrow_copy_constructible< MyClass >::value);
    EXPECT_FALSE(std::is_trivially_copy_constructible< MyClass >::value);

    EXPECT_TRUE(std::is_move_constructible< MyClass >::value);
    EXPECT_TRUE(std::is_nothrow_move_constructible< MyClass >::value);
    EXPECT_FALSE(std::is_trivially_move_constructible< MyClass >::value);

    EXPECT_TRUE(std::is_copy_assignable< MyClass >::value);
    EXPECT_TRUE(std::is_nothrow_copy_assignable< MyClass >::value);
    EXPECT_FALSE(std::is_trivially_copy_assignable< MyClass >::value);

    EXPECT_TRUE(std::is_move_assignable< MyClass >::value);
    EXPECT_TRUE(std::is_nothrow_move_assignable< MyClass >::value);
    EXPECT_FALSE(std::is_trivially_move_assignable< MyClass >::value);

    EXPECT_TRUE(std::is_destructible< MyClass >::value);
    EXPECT_TRUE(std::is_nothrow_destructible< MyClass >::value);
    EXPECT_FALSE(std::is_trivially_destructible< MyClass >::value);

    EXPECT_TRUE(!std::is_polymorphic< MyClass >::value);
}

As unit test code that's also going into my source code revision system, this has the advantage of tracking these qualities along with the code being committed for MyClass. I also like getting a gamification kick of out seeing classes take on more of these qualities - which I do using code like this.

I wonder however: are there other ways to achieve this that perhaps have more benefits without as many pitfalls?

Here's some concerns that I have about this technique:

  1. Are these acceptable as unit tests? They are to me but are they to others? While the definition of unit testing which Wikipedia provides seems subjective enough for this to qualify, I'm concerned here how widely others would agree.
  2. Would it be better to track these qualities elsewhere like in the class's code itself by using static_assert for example? This is different from the last concern in now focusing on whether their's a better place (not whether other software developers would accept this as unit testing). Tracking these qualities in the class's code via static_assert instead of in unit test code like shown, seems like an elevation of these qualities importance. For more performance critical classes that direction makes some sense. I also recognize a perspective that these should instead be lowered in importance to perhaps just being warnings or messages (instead of test failures or build failures).
  3. Lack of scalability. Is it more boilerplate code than it needs to be? It's an easy copy and paste operation to create tests like this but it doesn't lend itself to global updates. I'd prefer to abstract this more if possible so there was a single place it could be updated from. I've considered converting this into a template function that combines these checks and just gets the type as its template parameter. How would that track the true and false values however? It could generate an output report but that seems cumbersome to parse back into EXPECT_* contexts with local line numbers. A macro function meanwhile would be evaluated in the unit test's context but a multi-statement macro function still shows up as all on one source code line: the line where the macro is called from. I've also considered combining the normal, nothrow, and trivially forms of these checks, into single line checks taking a parameter to indicate which of these should apply in the positive. I haven't seen anything more satisfying yet than the shown pattern however.
  • 2
    Would you consider it an error if one of these tests fails? It's one thing to do a check to ensure that there likely won't be problems, but I don't know if unit testing is the place for this. – Neil Dec 24 '18 at 7:18
  • 1
    Is it really a requirement that your class is NOT trivially constructible? – D Drmmr Dec 30 '18 at 16:36
  • @DDrmmr It's not a requirement, no. And as perhaps you're suggesting, it wouldn't make as much sense to me as a requirement as the reverse. It does however make the fact that a class is not trivially constructible trackable. The question being how else might trait values be tracked especially towards motivating things like more-triviality. – Louis Langholtz Jan 4 at 14:25
  • @DDrmmr by the way, I do like your question and have upvoted it since I do see it as a question useful to think about in the context of my post. – Louis Langholtz Jan 4 at 14:26
  • @Neil Similar to my response to D Drmmr's comment, I do consider there being a degree of error worthiness to these failing depending on what exactly particular check was testing. I.e. a class failing to be non-trivially constructible being more a good thing IMO and in this context less an error. A class whose doxygen "contract" said it had standard layout or was supposed to be default constructible being examples where failing seem more error worthy. Using unit testing for tracking traits is agreeably not entirely to my liking though. But neither is using static_assert. – Louis Langholtz Jan 4 at 14:43
3
  1. Are these acceptable as unit tests?

No. These are not acceptable unit tests. Unit tests should check behaviour rather than implementation. The unit test you have is not checking any behaviour of MyClass.

Suppose I am another developer with doesn't really care about your regular types in your software suite, and I for some reason want to remove the copy constructor. My code compiles but all of a sudden this unit test fails. What do I do now? Probably remove the unit test and go on. And if I write a new class I wouldn't even consider for a second to add these tests.

  1. Would it be better to track these qualities elsewhere like in the class's code itself by using static_assert for example?

For sure, the static_assert is a better solution than a unit test (if that's even possible to implement). But I still think it is useless. Partially for the same reason as the unit test. Would another developer in your team add them? You'll probably end up copying all your asserts every time, or creating an ugly macro generating them.

  1. Lack of scalability. Is it more boilerplate code than it needs to be?

You have identified the most serious problem. And suppose you want to change anything in your code standards, then you need to update not only all your classes, but also all tests or assert that you copied. Wait, did I mention code standards? Yes, I think this should be part of your code standards. A team typically defines code standards on how to code. This is not only code style (which you obviously need to agree about), but also things like obeying the rule of zero, or not using protected member variables, or have your classes behave as regular types. Next, you have a review system where other developers check the compliance with these code standards. Yes, one may slip through, but with the testing and asserts without review you don't have any guarantee either.

  • Agreed lack of scalability is most serious problem. Also agreed unit testing is more useful when used to test noticeable behavior. While I recognize a perspective that these aren't unit test worthy, I'll disagree with a black & white position on this. Whether class is default constructible or copy assignment is noexcept are indeed noticeable behaviors. I don't want to split hairs though over what unit tests should test as much as get ideas for better solution. I'm leaning towards a static code analysis tool that generates reports & tracks changes. Opinions/recommendations on that? – Louis Langholtz Jan 4 at 15:08
  • A static analysis tool is something that can assist you in a code review. I've used it in teams to check on code style (more for the developer before offering it for review than for the reviewer). This seems a good solution to remain consistent. – Bernhard Jan 5 at 0:58
2

If these traits are part of the type's interface, they should ideally be asserted close to the declaration of that type. A static_assert is the ideal way to do that, since compilation will fail (rather than just your tests).

In general, tests are useful for checking certain properties of your program by example. They cannot typically show that a program is bug-free, but may be useful to find bugs if any are present. In contrast, type systems provide an automated proof of correctness properties. Because the type system is a proof (rather than just an example scenario), it is usually vastly preferable to use type-system level checks where possible. Here the type traits are type-system level properties, not runtime properties. Checking them as part of a unit test suite is not wrong, but I find it confusing.

  • +1 for answer. Agreed for when these traits are part of the type's design. Reminds me of motto: save run time checks for what can't be done at compile time. Doesn't seem as appropriate from perspective of motivating regularity over libraries of types or confirming what compiler's indeed auto-generating however. Here operations not being noexcept for example isn't deal breaker; just less desirable perhaps. Ideas for that? How about a doxygen comment generator program that checked types & generated documentation re. current traits? – Louis Langholtz Dec 24 '18 at 19:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.