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
orReturnThruPtr
- 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
-