Another C++ Unit Testing Idea

In a previous blog entry about unit-testing in C++ I wrote that instead of writing something like this...
expected = 42;
actual = expression();
assert_equals(expected, actual);
you could write something like this...
ARE_EQUAL(int)
{
    ...
    expected = 42;
    actual = 40+1
}
Admittedly this was not much of an improvement, if at all. However, on a flight to Oslo a few days ago I suddenly realized a way to improve it. The idea is to fold an expected == actual assertion down to this:
expected(42) == actual(40+1);
Where expected and actual can be two function templates that return objects wrapping their arguments. These objects can have an overloaded == operator that does the assertion for me. Instead of emphasizing the assertion I emphasize the roles instead. Here's some proof of concept code:
#include <cassert>
#include <iostream>

template<typename T>
struct asserted_role
{
   asserted_role(const char * name, const T * ptr)
       : name(name)
       , ptr(ptr)
   {
   }
   const char * name;
   const T * ptr;
};

template<typename T>
void failed_assertion(const asserted_role<T> & lhs,
               const char * op,
               const asserted_role<T> & rhs)
{ 
   std::cout << "FAILED ASSERTION" ...   
       << " " << lhs.name << "(" << *lhs.ptr << ") " 
       << op 
       << " " << rhs.name << "(" << *rhs.ptr << ")" 
       << endl;      
}

template<typename T>
void operator==(const asserted_role<T> & lhs,
         const asserted_role<T> & rhs)
{
   if (*lhs.ptr == *rhs.ptr)
       ;
   else
      failed_assertion(lhs, "==", rhs);
}

template<typename T>
asserted_role<T> expected(const T & t)
{
   return asserted_role<T>("expected", &t);
}

template<typename T>
asserted_role<T> actual(const T & t)
{
   return asserted_role<T>("actual", &t);
}

int main()
{
   expected(42) == actual(40+1);
}
There are a couple of things I like about this idea. The first is I no longer have to remember to get the expected and actual in the right order; if I want to I can write:
   actual(40+1) == expected(42);
The second is that the idea can be extended. For example, I can create similar function templates called lesser and greater and add a < operator:
   lesser(42) < greater(40+1);
I can create a new template function of any name I like to express the role I'm thinking of. For example, suppose I want to test that the value of one expression is equal to the value of another expression, but neither value is "expected" or "actual" they are just two values and I don't care what the values actually are, other than the two values are equal. I can create two function templates called lhs and rhs:
   lhs(2 + 2) == rhs(4);

5 comments:

  1. What's the value in the lesser(42) < greater(40+1) example? Aren't you expressing the same relationship twice? (that the first operand is meant to be less than the second)? What happens if you do lesser(42) > greater(40+1) instead? Which form of expression takes precedence?

    I'm very interested in your rationale because you had me up until that point. I've actually been working on some very similar concepts myself already, but the lesser/ greater bit has thrown me.

    ReplyDelete
  2. What is tries to express is the idea that the value of one expression is less than the value of another expression. I agree it's a bad example because the expressions are constants. Sorry about that. Does...
    lesser(q()) < greater(a())
    make it a bit clearer...

    ReplyDelete
  3. not really :-) it wasn't the numbers that threw me - they're the things under test - it's the fact that you're writing lesser and greater *and* using the < operator. Which one conveys the meaning of the test? And what's the purpose of the other?

    ReplyDelete
  4. Let's start with
    expected(dee) == actual(dum)
    again. The words expected and actual convey the intent. You cant dispense with them and just write
    dee == dum
    because then there would be no assertion. Similarly if I want to convey the idea that dee is less than dum I can't just write
    dee < dum
    because again there is no assertion. So, using the same idea I have to create words to express the roles of the relationship; I chose lesser and greater but I'm open to better names if you can think of any.

    ReplyDelete
  5. I think I see. It's not that the names have any significance - it's just that they need to be there?
    In which case, lhs and rhs may be a better generic fit - although I'm still not keen.

    Currently I'm dispensing with the second wrapper and making the first part of the assertion expression:

    e.g.

    assertThat( dee ) == dum;

    The only downside is that you can't capture the name "dum", only its value - but I don't think that's an issue most of the time, and drops some of the syntatic clutter. However I'm toying with the idea of bringing an optional rhs wrapper back - just still not sure of the name - so we're almost at the same point :-)

    ReplyDelete