2

I'm working on a project that does things depend on how much time has elapsed. Specifically for testing purposes, I need to call a method twice a second. The basic idea of the test is something like this:

Class c;

// Call the do_something() method 2x/second for 5 seconds
for( int num_second = 0; num_second < 5; num_second++ ){
  c.do_something();
  std::this_thread::sleep_for(std::chrono::milliseconds(500));
  c.do_something();
  std::this_thread::sleep_for(std::chrono::milliseconds(500));
}

In the do_something() method, we then do some operations based on time, similar to the following pseudo-code:

void class::do_something(){
  now = create_time_from(std::chrono::system_clock::now());
  if( now.second() - last_time.second() >= 1 ){
    // The second has rolled over - do some special handling code
  }
  // Do something important here
  last_time = now;
}

Unfortunately this means that the unit tests can be unreliable, as depending on how loaded the system is the do_something() method may not be called at the correct time, resulting in the special handling code not running. Specifically, since it should be called 2x/second, sometimes it gets called only 1x/second due to delays caused by the sleep.

I would like to mock the clock to make this more reliable, but I'm not quite sure how to do it. There are two options that I see:

  1. Add in a method to the public API of the class that lets you set the clock. I don't want to do this, since this is required only for tests and adding methods just for tests is generally bad.
  2. I could extend the class just for testing, providing a mocked clock for testing somehow. This would potentially involve modifying the public API slightly(for example adding a virtual protected method to get the clock to use).

Is there a better way to mock the clock in this case?

rm5248
  • 323
  • 1
  • 5
  • 1
    Dont take _adding methods just for tests is generally bad_ too seriously. A whole religion (called test-driven development) first writes tests and then shapes the implementation such that it matches the test. In TDD, you'd probably mock the clock or the sleep function. – pschill Jan 29 '22 at 10:50

3 Answers3

6

Sleeping in a test is always a horrible idea. Whenever possible, all your unit tests should execute in just a few seconds, even if there's thousands of them.

Just create an abstraction for getting the current time and make your class use that.

This abstraction can then have two implementations: once that actually takes the current time from the operating system and is therefore not well testable. It's an implementation artifact, should contain almost no logic at all, and can be minimally tested against crashing, as it does not wait for anything, taking the time itself is pretty quick.

Then create a second abstraction that fakes getting the time. Do both calls to your class immediately after each other, without any delay, and make your mocked time getting abstraction return whatever time seems appropriate. This time should also be fixed so the success of your unit test does not depend on anexternality like the actual time of day.

Always separate your logic from code with external dependencies. Then unit test your logic. The code with the external dependencies is often very hard to test, so keep it free of any avoidable logic. If it does need to do actual logic, seperate that out into its own function / class and unit test that as well.

Unit tests are for logic and code with external dependencies should be as logic-free as possible. That's how you create a testable system

yeoman
  • 373
  • 2
  • 6
  • I know that the abstraction is what I need to do, but the question is really _how_ to do it. If I add a method to the public API, that would let clients modify the behavior(which would always be incorrect in this case). So is there a way to make it testable without giving the ability for clients to shoot themselves in the foot? – rm5248 Jan 29 '22 at 16:27
  • The mentioned abstractions do not need to be part of a public API. The public API, e.g. a public and visible constructor / factory function for the class, should just inject the actual get-time-from-OS variant. The test code uses e.g. a non-public ctor via a friend, or a factpry function not visible in the public API, which allows arbitrary implementations of "get the time". – yeoman Jan 30 '22 at 11:15
1

Mocking the clock is difficult. The problem is, there are many system functions that check the time, and mocking them all may be impossible - with the result that different bits of code get inconsistent time information which can lead to misbehaviour that isn’t there in the real, unmocked code. And tests that only fail while testing but not in real life are pointless.

gnasher729
  • 42,090
  • 4
  • 59
  • 119
0

there's a concise example using static dependency injection in the constructor, here: [mocking std::this_thread::sleep] 1