10

When I test a method using

BOOST_CHECK_NO_THROW( method_to_test() );

and an exception is thrown, it displays that an exception was thrown, but never the exception's message like this:

test.cpp(14): error in "test": incorrect exception my_exception is caught

Is it possible to print the exception message as well, i.e. the string returned by my_exception.what()? my_exception is derived from std::exception and overloads what().

jogojapan
  • 68,383
  • 11
  • 101
  • 131
550
  • 1,070
  • 1
  • 13
  • 28
  • Write to where? Boost Tests are for checking if the code is broken not for debugging the code. Is it broken? It is boolean value: true or false. – Öö Tiib Feb 28 '13 at 11:29
  • (1) Writing to standard output (console or xml file, depending of command line arguments of test runner) (2) If exception is thrown, yes it is broken. But if one can see the message of exception (output of what()) it's faster to find out where the bug comes from. – 550 Feb 28 '13 at 13:17
  • Yes but why? Test did show that code is broken: it throws exceptions where it should not throw. Next step is to take and to fix either the code or the test and not to create detailed XML files of fill console with garbage. – Öö Tiib Feb 28 '13 at 13:23
  • Hm, okay. I thought it would be nice to find faster the bug. My test uses a db (in real, no a mock object) and a test may fails because of different reasons (not only as a result of wrong class or test code). I believe there might be a solution due to the 'high customizable' boost test framework, but than i may end up without using this nice `BOOST_AUTO_*()`. But thank you Tiib! – 550 Feb 28 '13 at 14:19
  • 4
    Why, @ÖöTiib? Would you rather have your doctor tell you "I've identified a problem with you." or "I've identified a problem with your knee."? The unit test framework would have printed the exception message if the statement hadn't been wrapped in a `BOOST__NO_THROW`. Using the assertions shouldn't give you _less_ information than you'd get without them. – Trebor Rude Apr 25 '13 at 18:32

3 Answers3

8

I found myself annoyed by the same problem with BOOST_REQUIRE_NO_THROW. I solved it by simply removing the BOOST_REQUIRE_NO_THROW. This results in output like:

unknown location(0): fatal error in "TestName": std::runtime_error: Exception message

and aborts the test (but goes on with the next text), which is what I wanted. This doesn't help much if you wanted to use BOOST_CHECK_NO_THROW or BOOST_WARN_NO_THROW, though.

Trebor Rude
  • 1,904
  • 1
  • 21
  • 31
7

I read a bit in the boost headers and redefined BOOST_CHECK_NO_THROW_IMPL in my own header file I use in the project to redefine the boost behavior. Now it looks like this:

#ifndef _CATCH_BOOST_NO_THROW_H_
#define _CATCH_BOOST_NO_THROW_H_  

#include <boost/test/unit_test.hpp>
#include <sstream>
#include <string>

#define BOOST_CHECK_NO_THROW_IMPL( S, TL )                                                      \
    try {                                                                                       \
    S;                                                                                          \
    BOOST_CHECK_IMPL( true, "no exceptions thrown by " BOOST_STRINGIZE( S ), TL, CHECK_MSG ); } \
    catch( const std::exception & e ) {                                                         \
    std::stringstream ss;                                                                       \
    ss << std::endl                                                                             \
    << "-----------------------------------------------" << std::endl                           \
    << "test case: " << boost::unit_test::framework::current_test_case().p_name << std::endl    \
    << std::endl << "exception message: " << e.what() << std::endl;                             \
    BOOST_TEST_MESSAGE(ss.str());                                                               \
    BOOST_CHECK_IMPL( false, "exception thrown by " BOOST_STRINGIZE( S ), TL, CHECK_MSG );      \
    }                                                                                           \
    catch( ... ) {                                                                              \
    std::stringstream ss;                                                                       \
    ss << std::endl                                                                             \
    << "-----------------------------------------------" << std::endl                           \
    << "test case: " << boost::unit_test::framework::current_test_case().p_name << std::endl    \
    << std::endl << "exception message : <unknown exception>" << std::endl;                     \
    BOOST_TEST_MESSAGE(ss.str());                                                               \
    BOOST_CHECK_IMPL( false, "exception thrown by " BOOST_STRINGIZE( S ), TL, CHECK_MSG );      \
    }                                                                                           \
    /**/

#define BOOST_WARN_NO_THROW( S )            BOOST_CHECK_NO_THROW_IMPL( S, WARN )
#define BOOST_CHECK_NO_THROW( S )           BOOST_CHECK_NO_THROW_IMPL( S, CHECK )
#define BOOST_REQUIRE_NO_THROW( S )         BOOST_CHECK_NO_THROW_IMPL( S, REQUIRE )

#endif // _CATCH_BOOST_NO_THROW_H_

The disadvantages are: It works as long as there are no changes in BOOST_*_NO_THROW

and

the exception message will be printed before it is marked as error in the test output. That looks in the first place a bit pitty, that's why I group the output by writing "---" to the outstream to enhance the reading. But code after BOOST_CHECK_IMPL will never be reached.

The solution above work quite nice for me. Feel free to use to, if you got the same whish =)

(Using CDash for ctest output, don't forget to increase the test output limit, or simple disable the limit: http://web.archiveorange.com/archive/v/5y7PkVuHtkmVcf7jiWol )

550
  • 1,070
  • 1
  • 13
  • 28
0

Solution 1.

Use a wrapper method that catches the exception, then prints the error message and then re-throws so that BOOST can report it:

void method_to_test(int s)
{
    if(s==0)
        throw std::runtime_error("My error message");
}

void middle_man(int x)
{
    try
    {
        method_to_test(x);
    }catch(std::exception& e)
    {
        std::stringstream mes;
        mes << "Exception thrown: " << e.what();
        BOOST_TEST_MESSAGE(mes.str());// BOOST_ERROR(mes.str());
        throw;
    }
}

Then your test case would read:

BOOST_AUTO_TEST_CASE(case1)
{
    BOOST_CHECK_NO_THROW( middle_man(0) );
}

A drawback of this approach is that you will need to use a different middle_man function for every method_to_test.

Solution 2.

Use decorators, see this answer, to do just what a wrapper from the previous solution would do.

template <class> struct Decorator;

template<class R,class ... Args>
struct Decorator<R(Args ...)>
{
    std::function<R(Args ...)> f_;
    
    Decorator(std::function<R(Args ...)> f):
        f_{f} 
    {}
    
    R operator()(Args ... args)
    {
        try
        {
            f_(args...);
        }catch(std::exception& e)
        {
            std::stringstream mes;
            mes << "Exception thrown: " << e.what();
            BOOST_TEST_MESSAGE(mes.str());
            throw;
        }
    }
};

template<class R,class ... Args>
Decorator<R(Args...)> makeDecorator(R (*f)(Args ...))
{
    return Decorator<R(Args...)>(std::function<R(Args...)>(f));
}

then your test cases would look like this:

BOOST_AUTO_TEST_CASE(case2)
{
    BOOST_CHECK_NO_THROW( makeDecorator(method_to_test)(0) );
    BOOST_CHECK_NO_THROW( makeDecorator(another_method_to_test)() );
}