... | ... | @@ -998,27 +998,27 @@ void test_app(void) { |
|
|
If parameters into a function are an explicit type (not `void*`), then they can be compared just like comparing standard C types such as `int`, `char` etc.
|
|
|
|
|
|
```c
|
|
|
// msg_writer.h
|
|
|
// message_writer.h
|
|
|
typedef struct {
|
|
|
int a, b, c;
|
|
|
} msg_s;
|
|
|
} message_s;
|
|
|
|
|
|
void write_msg(msg_s msg);
|
|
|
void message_writer__write(message_s message);
|
|
|
```
|
|
|
|
|
|
```c
|
|
|
// Code under test
|
|
|
void app(void) {
|
|
|
msg_s msg = {1, 2, 3};
|
|
|
write_msg(msg);
|
|
|
message_s message = {1, 2, 3};
|
|
|
message_writer__write(message);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
```c
|
|
|
void test_your_struct_writer(void) {
|
|
|
msg_s expected_msg = {1, 2, 3};
|
|
|
message_s expected_message = {1, 2, 3};
|
|
|
|
|
|
write_msg_Expect(expected_msg);
|
|
|
message_writer__write_Expect(expected_message);
|
|
|
app();
|
|
|
}
|
|
|
```
|
... | ... | @@ -1028,28 +1028,28 @@ void test_your_struct_writer(void) { |
|
|
What is cool is that the `Expect()` API also works great with pointers too. As long as your pointers are of a typed data structure (not `void*`), then the `Expect()` API can compare data structures by de-referencing the pointers. For instance, this would work equally well.
|
|
|
|
|
|
```c
|
|
|
// msg_writer.h
|
|
|
// message_writer.h
|
|
|
typedef struct {
|
|
|
int a, b, c;
|
|
|
} msg_s;
|
|
|
void write_msg_with_ptr(const msg_s * msg_ptr);
|
|
|
} message_s;
|
|
|
void message_writer__write_with_ptr(const message_s * message_ptr);
|
|
|
```
|
|
|
|
|
|
```c
|
|
|
// Code under test
|
|
|
void app(void) {
|
|
|
msg_s msg = {1, 2, 3};
|
|
|
write_msg_with_ptr(&msg);
|
|
|
message_s message = {1, 2, 3};
|
|
|
message_writer__write_with_ptr(&message);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
```c
|
|
|
void test_your_struct_writer(void) {
|
|
|
const msg_s expected_msg = {1, 2, 3};
|
|
|
const message_s expected_message = {1, 2, 3};
|
|
|
|
|
|
// The Expect() will compare our struct copy
|
|
|
// against the one passed in by app() function
|
|
|
write_msg_with_ptr_Expect(&expected_msg);
|
|
|
message_writer__write_with_ptr_Expect(&expected_message);
|
|
|
|
|
|
app();
|
|
|
}
|
... | ... | @@ -1063,34 +1063,34 @@ The `ReturnThruPointer` add convenience when you want a function to write memory |
|
|
|
|
|
```c
|
|
|
// an_api.h
|
|
|
int read_msg(msg_s *msg_ptr);
|
|
|
int read_message(message_s *message_ptr);
|
|
|
```
|
|
|
|
|
|
```c
|
|
|
// Code under test
|
|
|
void app(void) {
|
|
|
msg_s msg;
|
|
|
if (read_msg(&msg)) {
|
|
|
message_s message;
|
|
|
if (read_message(&message)) {
|
|
|
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
```c
|
|
|
// In Unit-Tests, we wish for the read_msg() to write some
|
|
|
// In Unit-Tests, we wish for the read_message() to write some
|
|
|
// data at the pointer passed into this function
|
|
|
void test_something(void) {
|
|
|
// Setup your message that you wish to write to the 'input_buffer'
|
|
|
// when this function is invoked: read_msg(msg_s *input_buffer);
|
|
|
msg_s msg = {};
|
|
|
// when this function is invoked: read_message(message_s *input_buffer);
|
|
|
message_s message = {};
|
|
|
|
|
|
// Setup Expect() and ignore the buffer passed in (for now)
|
|
|
read_msg_ExpectAndReturn(NULL);
|
|
|
read_msg_IgnoreArg_msg_ptr();
|
|
|
read_message_ExpectAndReturn(NULL);
|
|
|
read_message_IgnoreArg_message_ptr();
|
|
|
|
|
|
// Tell the read_msg() mock to return 'input_buffer' by
|
|
|
// copying the 'msg_s' from '&msg'
|
|
|
read_msg_ReturnThruPtr_msg_ptr(&msg);
|
|
|
// Tell the read_message() mock to return 'input_buffer' by
|
|
|
// copying the 'message_s' from '&message'
|
|
|
read_message_ReturnThruPtr_message_ptr(&message);
|
|
|
|
|
|
// Carry out the test
|
|
|
app();
|
... | ... | @@ -1103,13 +1103,13 @@ This Mock API is the same as `ReturnThruPointer` except that it allows you to wr |
|
|
|
|
|
```c
|
|
|
void test_something(void) {
|
|
|
msg_s msgs[2] = {};
|
|
|
message_s messages[2] = {};
|
|
|
|
|
|
read_msg_ExpectAndReturn(NULL);
|
|
|
read_msg_IgnoreArg_msg_ptr();
|
|
|
read_message_ExpectAndReturn(NULL);
|
|
|
read_message_IgnoreArg_message_ptr();
|
|
|
|
|
|
// Tell the read_msg() mock to write 2 messages
|
|
|
read_msg_ReturnThruPtr_msg_ptr(&msgs, 2);
|
|
|
// Tell the read_message() mock to write 2 messages
|
|
|
read_message_ReturnThruPtr_message_ptr(&messages, 2);
|
|
|
|
|
|
// Carry out the test
|
|
|
app();
|
... | ... | @@ -1122,13 +1122,13 @@ This Mock API is more generic than the other two flavors above. You should avoi |
|
|
|
|
|
```c
|
|
|
void test_something(void) {
|
|
|
msg_s msg = {};
|
|
|
message_s message = {};
|
|
|
|
|
|
read_msg_ExpectAndReturn(NULL);
|
|
|
read_msg_IgnoreArg_msg_ptr();
|
|
|
read_message_ExpectAndReturn(NULL);
|
|
|
read_message_IgnoreArg_message_ptr();
|
|
|
|
|
|
// Tell the read_msg() mock to write 2 messages
|
|
|
read_msg_ReturnMemThruPtr_msg_ptr(&msg, sizeof(msg));
|
|
|
// Tell the read_message() mock to write 2 messages
|
|
|
read_message_ReturnMemThruPtr_message_ptr(&message, sizeof(message));
|
|
|
|
|
|
// Carry out the test
|
|
|
app();
|
... | ... | @@ -1150,9 +1150,9 @@ The second solution is to consistently use capital `STATIC` which is defined to |
|
|
* Developers will need training on the guideline
|
|
|
* Even with `STATIC`, we still are dependent on an include order
|
|
|
|
|
|
The reason for this debate is that due to `#define static /* blank */`, if the [Include Order](https://gitlab.com/sibros_public/public/wikis/c/coding_standards#order-of-includes) is not followed, it can have un-intended consequencies in your unit-test build. For example, if `<stdio.h>` uses the `static` keyword, and we include `unit_test_facilitator.h` before we include `<stdio.h>` then the compilation may fail. Same is true if other code modules are included that should not inherit this `static` hack.
|
|
|
The reason for this debate is that due to `#define static /* blank */`, if the [Include Order](https://gitlab.com/sibros_public/public/wikis/c/coding_standards#order-of-includes) is not followed, it can have un-intended consequencies in your unit-test build. For example, if `<stdio.h>` uses the `static` keyword, and we include `sl_unit_test_facilitator.h` before we include `<stdio.h>` then the compilation may fail. Same is true if other code modules are included that should not inherit this `static` hack.
|
|
|
|
|
|
`unit_test_facilitator.h`
|
|
|
`sl_unit_test_facilitator.h`
|
|
|
```c
|
|
|
#ifdef UNIT_TESTING
|
|
|
#define static /* blank */
|
... | ... | @@ -1166,7 +1166,7 @@ It is expected that you may have code and you wish to test a `static` function d |
|
|
|
|
|
```c
|
|
|
// Including this file will override 'static' keyword to <blank>
|
|
|
#include "unit_test_facilitator.h"
|
|
|
#include "sl_unit_test_facilitator.h"
|
|
|
|
|
|
// your_code_under_test.c
|
|
|
static void app_private_function(void) {
|
... | ... | @@ -1182,7 +1182,7 @@ void app(void) { |
|
|
}
|
|
|
```
|
|
|
|
|
|
You can then test by accessing the `static` functions directly because when your application code included `unit_test_facilitator.h`, it re-defined `static` to `<blank>`, and thereby taking away the `private` meaning to the use case of `static` in a source file.
|
|
|
You can then test by accessing the `static` functions directly because when your application code included `sl_unit_test_facilitator.h`, it re-defined `static` to `<blank>`, and thereby taking away the `private` meaning to the use case of `static` in a source file.
|
|
|
|
|
|
Although the code under test now will not use the true `static` keyword, we will need to still find function declarations that we can use to test code. There are two solutions here:
|
|
|
|
... | ... | @@ -1216,19 +1216,19 @@ void module__foo(module_s* module, int x); |
|
|
|
|
|
In the case above, the code `module` itself does not contain any global variables, and they operate on an instance the user provides, and this helps the testability aspect too.
|
|
|
|
|
|
However, since we do not live in a perfect world, let us assume that there are some private variables you need to access in `your_module.c` for testability purpose. We can access them as long as we `#include "unit_test_facilitator.h"` at your source code.
|
|
|
However, since we do not live in a perfect world, let us assume that there are some private variables you need to access in `your_module.c` for testability purpose. We can access them as long as we `#include "sl_unit_test_facilitator.h"` at your source code.
|
|
|
|
|
|
```c
|
|
|
// your_module.c
|
|
|
|
|
|
// Including this file will override 'static' keyword to <blank>
|
|
|
#include "unit_test_facilitator.h"
|
|
|
#include "sl_unit_test_facilitator.h"
|
|
|
|
|
|
static int x = 0;
|
|
|
static module_s module = { };
|
|
|
```
|
|
|
|
|
|
If your module includes `unit_test_facilitator.h`, your unit-test code can then access the `static` data members. Note that that we are not altering the meaning of `static` in production code, and this "hack" is only for unit-tests.
|
|
|
If your module includes `sl_unit_test_facilitator.h`, your unit-test code can then access the `static` data members. Note that that we are not altering the meaning of `static` in production code, and this "hack" is only for unit-tests.
|
|
|
|
|
|
```c
|
|
|
// Define 'extern' to access those 'static' members of your_module.c
|
... | ... | @@ -1239,7 +1239,7 @@ extern module_s module; |
|
|
// at the beginning of each test_ method
|
|
|
void setUp(void) {
|
|
|
x = 0;
|
|
|
memset(&module, 0, sizeof(module));
|
|
|
sl_utils__memset_zero(&module, sizeof(module));
|
|
|
}
|
|
|
```
|
|
|
|
... | ... | @@ -1377,9 +1377,9 @@ Code can be refactored to improve testing. The suggestion here is to first writ |
|
|
The `NOOP` trick is meant to spot code coverage issues.
|
|
|
```c
|
|
|
#ifndef UNIT_TESTING
|
|
|
#define NOOP(msg) /* Nothing */
|
|
|
#define NOOP(message) /* Nothing */
|
|
|
#else
|
|
|
#define NOOP(msg) \
|
|
|
#define NOOP(message) \
|
|
|
do { \
|
|
|
volatile int _do_not_optimize = 0; \
|
|
|
(void) _do_not_optimize; \
|
... | ... | @@ -1402,7 +1402,7 @@ void foo(void) { |
|
|
|
|
|
Here are the consolidated and common header files which you may use in your code. Note that you should avoid defining these in a makefile or similar because your IDE or code indexer may not be able to pick up these code `#defines`. Further, hiding macros degrades code readability.
|
|
|
|
|
|
### `unit_test_facilitator.h`
|
|
|
### `sl_unit_test_facilitator.h`
|
|
|
Only include this file for `your_module_under_test.c` such as `app.c`. The purpose is to alter the definition for this file only, and not globally for everything. Case in point, if you put this in a header file, and `your_module.h` includes it, then any user that includes this header will inherit the new definition of `static`. This may sound okay, however, when the CMock framework comes in to Mock a header file, it will also inherit the new definition of `static` and cause compile time errors.
|
|
|
|
|
|
```c
|
... | ... | @@ -1458,13 +1458,13 @@ Only include this file for `your_module_under_test.c` such as `app.c`. The purp |
|
|
* @endcode
|
|
|
*/
|
|
|
#ifdef UNIT_TESTING
|
|
|
#define NOOP(msg) \
|
|
|
#define NOOP(message) \
|
|
|
do { \
|
|
|
volatile int _do_not_optimize = 0; \
|
|
|
(void) _do_not_optimize; \
|
|
|
} while (0)
|
|
|
#else
|
|
|
#define NOOP(msg)
|
|
|
#define NOOP(message)
|
|
|
#endif
|
|
|
```
|
|
|
|
... | ... | @@ -1625,12 +1625,12 @@ In this lab, the objectives are: |
|
|
|
|
|
typedef struct {
|
|
|
char data[8];
|
|
|
} msg_s;
|
|
|
} message_s;
|
|
|
|
|
|
bool msg_read(msg_s *msg_to_read);
|
|
|
bool message__read(message_s *message_to_read);
|
|
|
```
|
|
|
|
|
|
`message_processor.c`: This code module processes messages arriving from `msg_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.
|
|
|
`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 testability, you should refactor the `} else {` logic into a separate `static` function that you can hit with your unit-tests directly.
|
|
|
|
... | ... | @@ -1642,23 +1642,23 @@ To improve testability, you should refactor the `} else {` logic into a separate |
|
|
#include "message_processor.h"
|
|
|
#include "sl_common.h"
|
|
|
|
|
|
bool msg_processor(void) {
|
|
|
bool message_processor(void) {
|
|
|
bool symbol_found = false;
|
|
|
msg_s msg;
|
|
|
memset(&msg, 0, sizeof(msg));
|
|
|
message_s message;
|
|
|
memset(&message, 0, sizeof(message));
|
|
|
|
|
|
const size_t max_msgs_to_process = 3;
|
|
|
for (size_t msg_count = 0; msg_count < max_msgs_to_process; msg_count++) {
|
|
|
if (!msg_read(&msg)) {
|
|
|
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 (msg_count < 2) {
|
|
|
} else if (message_count < 2) {
|
|
|
NOOP("Wait for last message");
|
|
|
} else {
|
|
|
if (msg.data[0] == '$') {
|
|
|
msg.data[1] = '^';
|
|
|
if (message.data[0] == '$') {
|
|
|
message.data[1] = '^';
|
|
|
symbol_found = true;
|
|
|
} else if (msg.data[0] == '#') {
|
|
|
msg.data[1] = '%';
|
|
|
} else if (message.data[0] == '#') {
|
|
|
message.data[1] = '%';
|
|
|
symbol_found = true;
|
|
|
} else {
|
|
|
NOOP("Symbol not found");
|
... | ... | @@ -1670,7 +1670,7 @@ bool msg_processor(void) { |
|
|
}
|
|
|
```
|
|
|
|
|
|
`test_msg_processor.c`: Add more unit-tests to this file as needed.
|
|
|
`test_message_processor.c`: Add more unit-tests to this file as needed.
|
|
|
```c
|
|
|
#include "unity.h"
|
|
|
|
... | ... | @@ -1679,16 +1679,16 @@ bool msg_processor(void) { |
|
|
#include "message_processor.h"
|
|
|
|
|
|
void test_process_3_messages(void) {
|
|
|
msg_read_ExpectAndReturn(NULL, true);
|
|
|
msg_read_IgnoreArg_msg_to_read();
|
|
|
message__read_ExpectAndReturn(NULL, true);
|
|
|
message__read_IgnoreArg_message_to_read();
|
|
|
|
|
|
msg_read_ExpectAndReturn(NULL, true);
|
|
|
msg_read_IgnoreArg_msg_to_read();
|
|
|
message__read_ExpectAndReturn(NULL, true);
|
|
|
message__read_IgnoreArg_message_to_read();
|
|
|
|
|
|
msg_read_ExpectAndReturn(NULL, true);
|
|
|
msg_read_IgnoreArg_msg_to_read();
|
|
|
message__read_ExpectAndReturn(NULL, true);
|
|
|
message__read_IgnoreArg_message_to_read();
|
|
|
|
|
|
TEST_ASSERT_EQUAL(0, msg_processor());
|
|
|
TEST_ASSERT_EQUAL(0, message_processor());
|
|
|
}
|
|
|
```
|
|
|
|
... | ... | |