c++ gtest print additional information in the end of a test when and only when the test fails
Asked Answered
A

2

8

I want to do somethink like this:

TEST(MyTestFixture, printAfterExpectationFailure)
{
  const string request("bring me tea");

  const string&& response = sendRequestAndGetResponse(request);

  checkResponseWithExpectarions1(response);
  checkResponseWithExpectarions2(response);
  checkResponseWithExpectarions3(response);
  checkResponseWithExpectarions4(response);

  if (anyOfExpectsFailed())
      cout << "Request: " << request << "\nresponse: " << response << endl;
}

TEST(MyTestFixture, printAfterAssertionFailure)
{
  const string request("bring me tea");

  const string&& response = sendRequestAndGetResponse(request);

  doWhenFailure([&request, &response]()
  {
      cout << "Request: " << request << "\nresponse: " << response << endl;
  });

  checkResponseWithAssertion1(response);
  checkResponseWithAssertion2(response);
  checkResponseWithAssertion3(response);
  checkResponseWithAssertion4(response);
}

I want to print some additional information when and only when expectations/assertions failures.

I know that I can do something like this:

#define MY_ASSERT_EQ(lhr, rhs, message) if(lhs != rhs) ASSERT_EQ(lhs, rhs) << message

but this kind of solution is not comfortable because:

  1. I check twice
  2. I use preprocessor so it can take some time to find bug.
  3. The solution is hard to use when functions are really nested.
  4. When many EXPECTations failure it would print message many times.
  5. It is necessairy to redefine macro for all kind of checking
Arapaima answered 7/12, 2016 at 20:38 Comment(0)
S
6

the fact that doing what you want to do is difficult is actually a test code smell. In particular, these two tests (1) do too much and (2) are unreadable, in the sense that they do not describe what the unit under test does.

I recommend two readings: Unit Tests are FIRST and the book Modern C++ Programming with Test-Driven Development.

Instead of trying to call 4 functions, each checking something, and then wondering how to print an error message in case of failure, I suggest the following:

  • ask yourself: "what am I testing here?" When you have an answer, use the answer to give a name to the test. If you cannot find an answer, it means (what I suspect) that the test does too much. Try to follow the "one assert per test" guideline and split the test accordingly.
  • in the same spirit, look at each of the 4 functions and try to give each a name. If you cannot, each function is checking too much. Split these functions.
  • Ask yourself if you really need expectations (as opposed to assertions). Often the only reason to have an EXPECT instead than an ASSERT is because the single test is doing too much. Split it.

At the end of this process, you should find yourself in a situation where your goal of printing additional information on test failure can be reached simply with something like:

ASSERT_THAT(Response, EQ("something")) << "Request: " << request;

Note: also if better that the starting point, I don't consider the above example good enough. The test name should be so good, so descriptive, that you would gain zero information by printing the value of request.

I realize this is a sort of philosophical answer; on the other hand it comes directly from my experience in attempting to write good, maintainable tests. Writing good tests requires the same care than writing good code, and it will pay off many times :-)

Slacker answered 8/12, 2016 at 19:45 Comment(1)
Agreed. One test, one assertion. Otherwise there is a one to many mapping of test to result, which makes the test useless and ambiguous.Mcevoy
M
4

A non-ideological answer (based on information from all over the place):

QDebugTest.h

class QDebugTest : public ::testing::Test
{
public:
    void SetUp() override;
    void TearDown() override;
};

QDebugTest.cpp

static std::ostringstream qdebugString;

static void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) {
    switch (type) {
        case QtDebugMsg:    qdebugString << "qDebug";    break;
        case QtInfoMsg:     qdebugString << "qInfo";     break;
        case QtWarningMsg:  qdebugString << "qWarning";  break;
        case QtCriticalMsg: qdebugString << "qCritical"; break;
        case QtFatalMsg:    qdebugString << "qFatal";    break;
    }
    if (context.file) {
        qdebugString << " (" << context.file << ":" << context.line ;
    }
    if (context.function) {
        qdebugString << " " << context.function;
    }
    if(context.file || context.function) {
        qdebugString << ")";
    }
    qdebugString << ": ";
    qdebugString << msg.toLocal8Bit().constData();
    qdebugString << "\n";
}

void QDebugTest::SetUp()
{
    assert(qdebugString.str().empty());
    qInstallMessageHandler(myMessageOutput);
}

void QDebugTest::TearDown()
{
    qInstallMessageHandler(0);
    if(!qdebugString.str().empty()) {
        const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info();
        if (test_info->result()->Failed()) {
            std::cout << std::endl << qdebugString.str();
        }
        qdebugString.clear();
    }
}

Now derive your Fixture-class from QDebugTest instead of ::testing::Test.

Mcfadden answered 12/2, 2021 at 8:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.