LAB: Unit testing with mocks

This article is based on unit-testing article and code labs from:

 For a conceptual overview, see Unit-Test Basics and Mocks.



Part 1

Let us practice unit-testing, with a little bit of TDD thrown into the mix.

steering.h: This is just a header file and we will Mock out this file and therefore you do not need to write this file's implementation.

#pragma once

void steer_left(void);
void steer_right(void);

steer_processor.h: You will write the implementation of this file yourself at steer_processor.c

#pragma once

#include <stdint.h>

#include "steering.h"

/**
 * Assume that a threshold value is 50cm
 * Objective is to invoke steer function if a sensor value is less than the threshold
 *
 * Example: If left sensor is 49cm, and right is 70cm, then we should call steer_right()
 */
void steer_processor(uint32_t left_sensor_cm, uint32_t right_sensor_cm);

test_steer_processor.c You will write the test code, before you write the implementation of steer_processor() function.

#include "unity.h"

#include "steer_processor.h"

#include "Mocksteering.h"

void test_steer_processor__move_left(void) { }

void test_steer_processor__move_right(void) { }

void test_steer_processor__both_sensors_less_than_threshold(void) { }

// Hint: If you do not setup an Expect()
// then this test will only pass none of the steer functions is called
void test_steer_processor__both_sensors_more_than_threshold(void) {

}

// Do not modify this test case
// Modify your implementation of steer_processor() to make it pass
// This tests corner case of both sensors below the threshold
void test_steer_processor(void) {
  steer_right_Expect();
  steer_processor(10, 20);

  steer_left_Expect();
  steer_processor(20, 10);
}

Do the following:

  • Put the steering.h in your source code
  • Put the steer_processor.h in your source code
  • Put the test_steer_processor.c in your test code folder
  • Write the implementation of test_steer_processor.c and run the tests to confirm failing tests
  • Write the implementation of steer_processor.c

  



Part 2

In this part, the objectives are:

  • Practice StubWithCallback or ReturnThruPtr
  • Ignore particular arguments

message.h: This is just an interface, and we will Mock this out meaning that we will not write the code for message_read() API:

#pragma once

#include <stdbool.h>

typedef struct {
  char data[8];
} message_s;

bool message__read(message_s *message_to_read);

message_processor.c: This code module processes messages arriving from message__read() function call. There is a lot of nested logic that is testing if the third message contains $ or # at the first byte. To get to this level of the code, it is difficult because you would have to setup your test code to return two dummy messages, and a third message with particular bytes.

To improve test-ability, you should refactor the } else { logic into a separate static function that you can hit with your unit-tests directly. Please ask your instructor to demonstrate how to refactor code for improved ability to test.

#include <stdbool.h>
#include <stddef.h>
#include <string.h>

#include "message_processor.h"

/**
 * This processes messages by calling message__read() until:
 *   - There are no messages to process -- which happens when message__read() returns false
 *   - At most 3 messages have been read
 */
bool message_processor(void) {
  bool symbol_found = false;
  message_s message;
  memset(&message, 0, sizeof(message));

  const static size_t max_messages_to_process = 3;
  for (size_t message_count = 0; message_count < max_messages_to_process; message_count++) {
    if (!message__read(&message)) {
      break;
    } else {
      if (message.data[0] == '$') {
        symbol_found = true;
      } else {
        // Symbol not found
      }
    }
  }

  return symbol_found;
}

test_message_processor.c: Add more unit-tests to this file as needed.

#include "unity.h"

#include "Mockmessage.h"

#include "message_processor.h"

// This only tests if we process at most 3 messages
void test_process_3_messages(void) {
  message__read_ExpectAndReturn(NULL, true);
  message__read_IgnoreArg_message_to_read();

  message__read_ExpectAndReturn(NULL, true);
  message__read_IgnoreArg_message_to_read();

  // Third time when message_read() is called, we will break the loop since it is meant to process 3 msgs only
  message__read_ExpectAndReturn(NULL, true);
  message__read_IgnoreArg_message_to_read();

  // Since we did not return a message that starts with '$' this should return false
  TEST_ASSERT_FALSE(message_processor());
}

void test_process_message_with_dollar_sign(void) {
}

void test_process_messages_without_any_dollar_sign(void) {
}

// Add more tests if necessary

 

Hint (sample code):

#include "unity.h"

#include "Mockmessage.h"

#include "message_processor.h"

static bool message__read_stub(message_s *message_to_read, int call_count) {
  bool message_was_read = false;
  
  if (call_count >= 2) {
    message_was_read = false;
  } else {
    message_was_read = true;
  }
 
  if (call_count == 0) {
    message_to_read->data[0] = 'x';
  }
  if (call_count == 1) {
    message_to_read->data[1] = '$';
  }

  return message_was_read;
}

// This only tests if we process at most 3 messages
void test_process_messages_with_stubWithCallback(void) {
  // message_processor() makes a call to:
  // bool message__read(message_s *message_to_read);
  
  // Whenever message__read() occurs, it will go to your custom "stub" function
  // Once we stub, then each function call to message__read() will go to message__read_stub()
  message__read_StubWithCallback(message__read_stub);

  // Function under test
  message_procesor();
}

// Add more tests if necessary


Requirements
  • Test thoroughly
    • Do not hack internals of a module.
    • This means that only operate using the APIs, and do not modify the data structure
  • Each test should start with a known initial state, you should not rely on previous test to run before the current test
    • setUp() method may be used to re-initialize code modules

 

Back to top