HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

An attempt on unit testing in C++

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
unittestingattempt

Problem

I have no much experience with C++, so it is about time to start actually doing something about it. All in all, I coded this tiny unit test library:

assert.h:

#ifndef ASSERT_H
#define ASSERT_H

#include 

#define ASSERT(CONDITION) assert(CONDITION, #CONDITION, __FILE__, __LINE__);
#define REPORT            assert.report();
#define TOTAL_ASSERTIONS  assert.get_total_number_of_assertions()
#define FAILED_ASSERTIONS assert.get_number_of_failed_assertions()

class Assert {
public:
    bool operator()(const bool condition, 
                    const char *const condition_text, 
                    const char *const file_name, 
                    const int line_number);

    size_t get_total_number_of_assertions() const;
    size_t get_number_of_failed_assertions() const;
    void report() const;

private:
    size_t m_total_assertions;
    size_t m_failed_assertions;
};

// Can't think of anything better than a global.
extern Assert assert;

#endif  // ASSERT_H


assert.cpp:

```
#include "assert.h"
#include

bool Assert::operator()(const bool condition,
const char *const condition_text,
const char *const file_name,
const int line_number)
{
if (!condition)
{
m_failed_assertions++;
std::cerr << "'" << condition_text << "' is not true in file "
<< "'" << file_name << "' at line " << line_number << "."
<< std::endl;
}

m_total_assertions++;
return condition;
}

size_t Assert::get_number_of_failed_assertions() const
{
return m_failed_assertions;
}

size_t Assert::get_total_number_of_assertions() const
{
return m_total_assertions;
}

void Assert::report() const
{
std::cout << "[TOTAL ASSERTIONS: "
<< m_total_assertions
<< ", FAILED ASSERTIONS: "
<< m_failed_assertions
<< ", PASS RATIO: ";

if (m_total_assertions == 0)
{
std::cout << "N

Solution

I don't see anything wrong with your code as it is: formatting is consistent and tidy, functions do what they need to do and no more, no apparent bugs.

One nit: your header file includes ` but doesn't need it - you should remove that.

Having said that, "polluting" the global namespace with an object named
assert isn't a good idea: assert is defined as a macro in , you'll get all sorts of problems if your users include that, directly or indirectly. (Having a file called assert.h isn't a great idea either, that's the name of the C standard library assert header.)

Also your
ASSERT macro doesn't do what the standard assert macro does (calls std::abort if the condition doesn't check), so you're likely to confuse people.

While the
ASSERT macro itself is necessary to pull in the context information (AFAIK there's no other way to do that), the other three are a bit gratuitous and would benefit from being plain functions. Don't use macros when they provide no real benefit.

Here's a sketch about how you could clean that up a bit and avoid these issues (I'm not very good with names, so YMMV):

  • In a header, declare four plain functions inside a namespace:



  • bool check(bool, char const, char const, int): same as your Assert::operator() function



  • void report() (or possibly void report(std::ostream&) for a bit more flexibility)



  • int total_checks() and int failed_checks().



  • Also in that header define the CHECK function-like macro as you did your ASSERT, but using Testing::check` instead.



  • In an implementation file, inside the namespace:



  • Define two static ints to store the total and failed counts



  • Define all the functions you declared.



This avoids the name clash, and "hides" the global state (the state is still global, so you still need to worry about thread-safety and such, it is just not as visible anymore).

Note: I don't think there's anything fundamentally wrong with using a class rather than plain functions in a namespace, and a class approach would be better if you ever intend to have more than one (e.g. one "test counter" per thread). But as it is, just plain old functions and static data seems enough.

Sample incomplete implementation:

testing.h

#pragma once

#define CHECK(x) Testing::check((x), #x, __FILE__, __LINE__)

namespace Testing
{
bool check(bool, char const *, char const *, int);
int total();
int failed();
}


testing.cpp

#include "testing.h"

#include 

namespace Testing
{
static int total_checks;
static int failed_checks;

bool check(bool ok, char const *what, char const *file, int line)
{
  total_checks++;
  if (!ok) {
    failed_checks++;
    std::cerr << "Failure of " << what << " at " << file << ":" << line
              << std::endl;
  }
  return ok;
}

int total() { return total_checks; }
int failed() { return failed_checks; }
}


Use case:

#include "testing.h"

#include 

int main()
{
  CHECK(1==1);
  CHECK(1==0);
  std::cout << Testing::failed() << " / " << Testing::total() << std::endl;
}

Code Snippets

#pragma once

#define CHECK(x) Testing::check((x), #x, __FILE__, __LINE__)

namespace Testing
{
bool check(bool, char const *, char const *, int);
int total();
int failed();
}
#include "testing.h"

#include <iostream>

namespace Testing
{
static int total_checks;
static int failed_checks;

bool check(bool ok, char const *what, char const *file, int line)
{
  total_checks++;
  if (!ok) {
    failed_checks++;
    std::cerr << "Failure of " << what << " at " << file << ":" << line
              << std::endl;
  }
  return ok;
}

int total() { return total_checks; }
int failed() { return failed_checks; }
}
#include "testing.h"

#include <iostream>

int main()
{
  CHECK(1==1);
  CHECK(1==0);
  std::cout << Testing::failed() << " / " << Testing::total() << std::endl;
}

Context

StackExchange Code Review Q#102488, answer score: 5

Revisions (0)

No revisions yet.