Click here to Skip to main content
Click here to Skip to main content

Testing C++ classes

By , 9 Jul 2011
 

Introduction

This is just an idea on how to write test classes for testing C++ classes. I might reinvent the wheel here (since I am not an expert on automated testing...) but an idea/code snippet may prove useful in certain simpler cases.

Background

Familiarity with pointer to C++ class functions will help. A good intro can be found here.

Support classes

The main idea is to accumulate pointers to members that modify an input value and apply them sequentially to an input instance to obtain the final value, and compare to an expected value.

The first class - called functor - keeps a pointer to a member function of class _F and has a name (the name is for display purposes only):

template<typename _F> struct functor {
    _F& (_F:: *_pf)();
    std::string _name;

    functor(_F& (_F::*pf)(), const std::string& name) 
        : _pf(pf)
        , _name(name) {
        }
    functor(const functor& src) 
        : _pf(src._pf)
        , _name(src._name) {
        }
    virtual ~functor() {
        }
    _F& operator()(_F& arg) { 
        return (arg.*_pf)(); }
    const std::string& name() const {
        return _name; }
};

The point of interest here is the operator(), which will call the member function _pf on the object arg and return the object itself. Multiple calls of member functions on the same object somewhat resembles composing functions, where:

fn(...f2(f1(x)) ...)

will be in our case:

x.f1().f2(). ...

The next class, workflow, will encapsulate a sequence of such functors over a class _F and will call them in a single step, as described above:

template<typename _F> 
struct workflow {
    std::vector<functor<_F>> _funcs;

    workflow() {
        }
    virtual ~workflow() {
        }
    workflow& operator+=(const functor<_F>& f) {
        _funcs.push_back(f);
        return *this; }
    _F& operator()(_F& arg) {
        std::vector<functor<_F>>::const_iterator itf;
        std::cout << "==> arg=" << arg << std::endl;
        for(itf = _funcs.begin(); itf != _funcs.end(); itf++) {
            std::cout << " --> " << itf->name().c_str() << " arg=" << arg << std::endl;
            (const_cast<functor<_F>&>(*itf))(arg);
            std::cout << " <-- " << itf->name().c_str() << " arg=" << arg << std::endl;
        }
        std::cout << "<== arg=" << arg << std::endl;
        return arg; }
};

Again, the same interesting function is the operator(), which will call the sequence of functors on the input object arg and will return the argument itself.

The final class is the tester_t template class, which accepts two arguments in its constructor, the input value and the expected value, has a variable-argument method test which encapsulates the entire testing, and offers strong/weak assertion support.

template<typename _C>
struct tester_t {
    _C& _i;
    const _C& _e;

    tester_t(_C& i, const _C& e) 
        : _i(i)
        , _e(e) {
        }
    virtual ~tester_t() {
        }

    bool __cdecl 
    test(
        const char* name, 
        ...
    ) {
        typedef _C& (_C::* _PFMC)();
        typedef std::pair<_PFMC, std::string> PNFMC;

        workflow<_C> wf;

        va_list ap;
        va_start(ap, name);
        do {
            PNFMC* pfnmf = static_cast<PNFMC*>(va_arg(ap, PNFMC*));
            if(!pfnmf) {
                break; }
            else {
                wf += functor<_C>(pfnmf->first, pfnmf->second);
            }
        } while(true);
        va_end(ap);

        std::cout << "Running test:" << name << std::endl;
        wf(_i);

        return _i == _e;
    }

    void assert_succeeded() {
        assert(_i == _e); }
    void assert_fail() {
        assert(_i != _e); }
    void assert_ex(const std::string& msg, bool strong) {
        if(_i != _e) {
            std::cout << "assertion failure: expected " << _e 
                      << " but is " << _i 
                      << " FAILURE" << std::endl; if(strong) {
                assert(_i == _e); }
        }
        else {
            std::cout << "assertion passed : expected " 
              << _e << " and is " << _i 
              << " SUCCESS" << std::endl; }
    }
};
  1. The constructor has the first argument non-const since the functors applied to the object modifies it. The functors are of "A& A::function()" form because calling functors on an object modifies the object itself. (It is possible to have them also in "A A::function()" form or even non-member functions, "A func(A)" - whatever makes you comfortable.) The second argument is const because the "expected" value is used only for comparison.
  2. The test method has some pitfalls. Because it uses variable arguments, all arguments passed to the test call are pointers (I don't know if you can pass a const reference to va_arg). Also, the last argument of the test call is 0 to signal the end of the function calls list. For sure there are more prettier ways to signal the end to va_ calls, but for the moment I used a NULL pointer to stop argument extraction. The function call arguments are std::pair<pointer to member function, std::string>. The string contains the function name and is used, again, only for display purposes.

The pairs (function, name) are accumulated in the wf workflow variable, and the call wf(i); will invoke the workflow operator() and call all the accumulated functors.

The class to be tested

The test class is named integer in this example, which simply encapsulates an int variable and perform some very basic operations (the uninspired name dmult comes from double multiply since obviously double cannot be used).

struct integer { 
    int _n;

    explicit integer(int n = 0) 
        : _n(n) {
        }
    virtual ~integer() {
        }

    bool operator==(int n) const { 
        return _n == n; }
    bool operator==(const integer& right) const {
        return _n == right._n; }
    bool operator!=(int n) const { 
        return _n != n; }
    bool operator!=(const integer& right) const { 
        return _n != right._n; }

    integer& nop() { 
        return *this; }
    integer& inc() { 
        ++_n; 
        return *this; }
    integer& dec() { 
        --_n; 
        return *this; }
    integer& dmult() { 
        _n *= 2; 
        return *this; }

    friend std::ostream& operator<<(std::ostream& o, const integer& i) {
        o << i._n; 
        return o; }
};

Nothing special here. Again, operator<< is added for display purposes.

Using the code

Finally, a simple test function will instantiate the input and the expected objects, create the tester test object by passing the previous variables, make the variable argument test call by passing the variadic sequence of pairs (function, name) to perform the workflow call, and finally perform basic assertions (as commented assert_succeeded) or, as in this example, a more relaxed assert_ex call which prints a message.

void 
test_1() {
    integer input(2);
    integer expected(5);
    
    tester_t<integer> tester(input, expected);
    tester.test(
          "integer test (((2+1)*2)-1) ==> 5"
        , &std::make_pair<integer& (integer::*)(), std::string>(&integer::inc  , "inc  ")
        , &std::make_pair<integer& (integer::*)(), std::string>(&integer::dmult, "dmult")
        , &std::make_pair<integer& (integer::*)(), std::string>(&integer::dec  , "dec  ")
        , 0
    );
    //tester.assert_succeeded();
    tester.assert_ex("input 2 expected 5", false);
    std::cout << std::endl;

    return;
}

The image displays a sample result image produced by executing two sequential tests:

History

  • Version 1.0 - 10 July 2011.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Cristian Amarie
Team Leader BitDefender
Romania Romania
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
Questionlove to see thos c++ stuffmemberPranay Rana13-Jul-11 20:13 
have 5

AnswerRe: love to see thos c++ stuffmemberCristian Amarie13-Jul-11 20:15 
I appreciate this, thank you.
 
Nuclear launch detected

QuestionGood article !memberFlaviu210-Jul-11 9:29 
Hi Cristian. Is a good article, especially for novices ! You got my vote !
AnswerRe: Good article !memberCristian Amarie10-Jul-11 9:43 
Appreciate your message. Thank you!
 
Nuclear launch detected

QuestionGTest and GMockmemberShaun Wilde9-Jul-11 23:06 
Have you looked at GTest[^] and GMock[^]?
I'll be more enthusiastic about encouraging thinking outside the box when there's evidence of any thinking going on inside it. - pTerry
sawilde @ GitHub

AnswerRe: GTest and GMockmemberCristian Amarie9-Jul-11 23:17 
Nope. As I said, this is not my main thing - I'm sure there are plenty of professional tools out there. This is just a simple way that worth a small article.
But thanks for the tip. I'll surely take a look.
 
Nuclear launch detected

AnswerRe: GTest and GMockmemberCristian Amarie9-Jul-11 23:24 
Doesn't seem to be the same thing as far as I read. Mainly function composition I didn't saw (but for sure 5 minutes are not enough, maybe I didn't noticed).
 
Nuclear launch detected

GeneralRe: GTest and GMockmemberShaun Wilde10-Jul-11 1:14 
Sorry that was more of a FYI, as you said you aren't familiar with test tools in C++.
I'll be more enthusiastic about encouraging thinking outside the box when there's evidence of any thinking going on inside it. - pTerry
sawilde @ GitHub

GeneralRe: GTest and GMockmemberCristian Amarie10-Jul-11 1:15 
Thanks for the tips, no problem. I was thinking the tools you recommend me to take a look on contains similar functionality.
 
Nuclear launch detected

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130617.1 | Last Updated 10 Jul 2011
Article Copyright 2011 by Cristian Amarie
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid