2

I'm using Visual Studio 2013 Unit Testing. My code is using time() function to generate some object names and thus it is hard to ensure consistent behavior when testing.

If it was C#, I could use shims as shown in the article http://msdn.microsoft.com/en-us/library/hh549175.aspx "Getting started with shims" section.

Is there any technique how to redefine time() calls during my C++ unit test?

Roland Sarrazin
  • 1,203
  • 11
  • 29
JustAMartin
  • 13,165
  • 18
  • 99
  • 183

4 Answers4

3

What you want to do is called mocking (http://en.wikipedia.org/wiki/Mock_object).

Personally I like to use Hippomocks (https://github.com/dascandy/hippomocks) which allows in the current version to mock C and C++ functions (with the notable exception of non-virtual class methods, see for example Mocking non-virtual methods in C++ without editing production code?).

Community
  • 1
  • 1
Roland Sarrazin
  • 1,203
  • 11
  • 29
  • Thanks, Hippomocks almost worked for me. At first it failed to compile because Microsoft VC++ compiler did not like `new (this)`. Then I risked and just commented it out. Now the `time()` calls were mocked correctly in my unit test code, but unfortunately, in the code under test the default `time()` was called. Maybe it has something to do with the fact that my tested code is located in a separate static `.lib` or maybe I totally broke it when commented away `new (this)`, or maybe there are some problems with my header inclusion order. I'll inspect it further. – JustAMartin Jan 16 '14 at 16:05
  • I don't know the exact mechanism used by Hippomocks' MockRepository::ExpectCallFunc to "intercept" a function but I have a similar setup having the testee part of a library statically linked against my unit test project. So I'm pretty sure this shall work. – Roland Sarrazin Jan 16 '14 at 16:15
  • Regarding new (this): What is the compilation failure symptom? Usually it is said that placement new requires #include but in my setup the Hippomocks header compiles fine. Note that I'm using Visual Studio 2010 Professional. – Roland Sarrazin Jan 16 '14 at 16:17
  • It seems, I found a working solution. `time()` was defined as `static __inline time_t __CRTDECL time(time_t * _Time) { return _time64(_Time); }` in time.inl file, and for some reason Hippomocks could not mock it. But it works with `_time64`. About `new` - it was my fault, I have redefined it in some header for debugging purposes: `#define new new(_NORMAL_BLOCK,__FILE__, __LINE__)`. Now I rearranged my headers, uncommented `new (this)` back as it was and now `mocks.OnCallFunc(_time64).Return(0)` works as expected. – JustAMartin Jan 16 '14 at 16:59
  • Inlined methods cannot be mocked as their is no possible "fake" call indirection to the mock. That it worked when calling `time()` from the test itself indicated that the compiler didn't inline it there. I'm glad it worked for you by mocking `_time64()`. – Roland Sarrazin Jan 17 '14 at 12:47
3

You can't "redefine" these things per se, but you can provide an alternative implementation under a different name, and make the distinction almost transparent in your real code.

Typically, you mock the function.

CxxTest takes the approach of having you invoke T::time() instead of std::time() (which is close enough!); with macro/include tricks, that call will resolve either to std::time() or to whatever replacement implementation you provide.

Here's a much-simplified, incredibly naive example designed only to demonstrate the basic principle:

foo.h

#include "mocked-functions.h"

/**
 * Takes the current time as a UNIX timestamp, and divides it by 10.
 */
inline int foo()
{
   return T::time(NULL) / 10;
}

main.cpp

#include "foo.h"
#include <iostream>

int main()
{
    std::cout << foo() << std::endl;
}

test.cpp

#define USE_MOCKS
#include "foo.h"
#include <cassert>

int main()
{
   mock_time_result = 50;
   assert(foo() == 5);

   mock_time_result = 400;
   assert(foo() == 40);
}

mocked-functions.h

#include <ctime>

#ifdef USE_MOCKS
   #include "mock-time.h"
#endif

namespace T {
   time_t time(time_t* ptr)
   {
      #ifdef USE_MOCKS
         return Mock_time(ptr);
      #else
         return std::time(ptr);
      #endif
   }
}

mock-time.h

#include <ctime>

time_t mock_time_result = 0;

time_t Mock_time(time_t* ptr)
{
   if (ptr) *ptr = mock_time_result
   return mock_time_result;
}

To run

$ g++ main.cpp -I. -o main
$ g++ test.cpp -I. -o test
$ ./main   # output is the real time div by 10
$ ./test   # output is nothing; all assertions succeed
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
2

If you don't include ctime or time.h then you may write whatever definition you want for time()

Code Painters
  • 7,175
  • 2
  • 31
  • 47
deeiip
  • 3,319
  • 2
  • 22
  • 33
  • Unfortunately, the code to test must be compiled with `ctime` because there is no other reasons to redefine `time()` calls. It seems not good idea to redefine something in the original code just for unit testing needs. That's why I hoped that there is some trick to override C/C++ functions just in the scope of my unit testing code. – JustAMartin Jan 16 '14 at 14:48
  • @Martin: If a function is non-deterministic, you must mock it in your unit tests - which *is* redefining it in such a way that it *is* deterministic. – Zac Howland Jan 16 '14 at 15:21
  • 1
    @Martin Why are "unit tests" not worth changing how the code works? Untestable code is, in a sense, worse. Making it testable is better. Change calls to `time` to calls to `my_time`, then hook `my_time` up to `ctime` normally, and up to some deterministic system when doing unit tests? – Yakk - Adam Nevraumont Jan 16 '14 at 15:39
  • This means that I'll have to do something like `if isTestMode` inside of `my_time`, and thus `my_time` function will be test-aware, which doesn't seem right. But you are right that I should redesign my code to be deterministic in some way (passing some flag etc.) – JustAMartin Jan 16 '14 at 16:09
1

Unit testing requires deterministic code. std::time(), by definition, is non-deterministic. This leaves you with 2 options: 1) Change how you are generating names to be deterministic, or 2) mock std::time().

It seems your focus is on #2, so one of the easiest ways to do that is to wrap the call to std::time() behind your own function for name generation.

std::string generate_name(bool use_time = true)
{
    ...
    if (use_time)
    {
        // do something with time()
    }
    else
    {
        // return a value that is deterministic
    }
}

Your unit tests will pass in false for the use_time parameter.

You can avoid including <ctime> in your unit test code, and write your own version of time() to call for the unit tests (this is actually closer to the approach most TDD developers prefer).

Since you are not testing the functionality of time, but rather how you treat its output, an even better approach (the first option) would be to pass the output of time into the function you use to generate names. This way, your unit tests can control the input and test the output, completely.

std::string generate_name(unsigned long long id)
{
    // do something to generate the name for the id
}

...

// production code
std::string name = generate_name(static_cast<unsigned long long>(std::time(NULL)));

// your unit test code could look like this
std::string name = generate_name(1ULL);
Zac Howland
  • 15,777
  • 1
  • 26
  • 42