[go: up one dir, main page]

100% found this document useful (1 vote)
2K views99 pages

Field Manual For Ceedling

Uploaded by

bokic88
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
100% found this document useful (1 vote)
2K views99 pages

Field Manual For Ceedling

Uploaded by

bokic88
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 99

A Field Manual For Ceedling

How to Unit Test Embedded Software (in C)

Matt Chernosky
Copyright © 2017 Matt Chernosky.
All rights reserved.

Thank you for purchasing this book, I hope you find it useful. Feel free to share it within your
development teams, but please don't share it outside your teams. If you want to recommend
it to others, please suggest that they purchase a copy of their own. This helps me keep
creating useful stuff for you and for everyone else.
--Matt

ElectronVector
Colorado, USA
http://www.electronvector.com

Revision 2
Table of Contents
1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1  

1.1. This is Ceedling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1  

1.2. The Approach of This Book. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1  

1.3. Your IDE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2  

1.4. Versions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
 

1.5. Definitions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2  

2. Installing Ceedling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5  

2.1. Install the Tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5  

2.1.1. Linux. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
 

2.1.2. Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6  

2.2. Install Ceedling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9  

2.3. Prepare Your Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11  

2.4. Add Ceedling to Your Project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13  

2.5. Test That Ceedling Works . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15  

2.6. Check Back Into Source Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16  

2.7. Getting Around . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17  

3. Creating Modules and Running Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20  

3.1. Conventions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20  

3.2. Creating New Modules. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20  

3.3. Adding New Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24  

3.4. Running Tests. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25  

3.5. Cleaning Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25  

3.6. Creating Nested Modules. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26  

3.7. Removing Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27  

3.8. printf from Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28  

4. Writing Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
 

4.1. Creating Your Own Test Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30  

4.2. Simple, Functional Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31  

4.3. Dealing with Collaborators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33  

5. Testing with Unity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36  

5.1. Using Unity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36


 

5.2. Test Assertions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36  


5.3. Multiple Assertions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39  

5.4. Comparing Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39  

5.5. Integers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
 

5.6. Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
 

5.7. Floats . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
 

5.7.1. Within a Range. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42  

5.7.2. Equality . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
 

5.7.3. Special Cases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43  

5.7.4. Double Precision . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43  

5.8. Pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
 

5.9. Memory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
 

5.10. Structs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
 

5.10.1. Contents. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45  

5.10.2. Instances . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47  

5.10.3. The Trouble with Comparing Structures as Memory . . . . . . . . . . . . . . . . . . . . . . . . . . 47  

5.11. Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
 

5.12. Ignoring Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51  

5.13. Failing Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52  

5.14. Extra Messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52  

6. Mocking with CMock . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54  

6.1. What’s a Mock? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54  

6.2. Creating a Mock. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55  

6.3. Using a Mock . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56  

6.4. Multiple Expectations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58  

6.5. Mocking Return Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59  

6.6. Expecting Arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59  

6.7. Ignoring Arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61  

6.8. Arguments Passed by Reference . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62  

6.9. Expecting a Pointer Value . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64  

6.10. Arguments Returned by Reference . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64  

6.11. Strings as Arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67  

6.12. Ignoring Function Calls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68  

6.13. Arrays as Arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69  

6.14. Using Callbacks to Take Control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70  

7. How Ceedling Knows What to Test. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72  


7.1. Dealing with Dependencies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74  

7.2. Integration Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76


 

8. Changing the Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80  

8.1. The Configuration File (project.yml) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80  

8.2. Changing Source File Paths. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81  

8.2.1. Recursively Including All Paths . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82  

8.2.2. Adding Paths. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82


 

8.2.3. Excluding Paths . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82


 

8.3. Including/Excluding Individual Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83  

8.4. Changing the Build Output Paths . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83  

8.5. Changing Test File Names . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84  

8.6. Setting Preprocessor Definitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84  

8.7. Enabling Plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85


 

8.8. Unity Options . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86


 

8.9. CMock Options . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86


 

8.9.1. Enabling CMock Plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86  

8.9.2. Change the Naming Convention of Mock Files. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87  

8.9.3. Mocking extern Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87  

8.9.4. Including Additional Header Files in Mocks. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88  

8.9.5. Pointer Handling Options . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88  

8.9.6. Disable Call Order Checking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89  

8.10. Customizing the Complier and Linker . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90  

8.10.1. Checking the Current Settings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90  

8.10.2. Changing Compiler Settings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91  

8.10.3. Changing Linker Settings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93  


Chapter 1. Introduction
1.1. This is Ceedling
Ceedling is a tool to create and run unit tests for C applications. It’s built around two main
components: the Unity unit test framework and the CMock mocking framework. Both Unity
and CMock can be used on their own, but the simplest way to use them is with Ceedling.

You don’t need to install Unity or CMock separately, they come included with
 Ceedling.

Ceedling makes writing and running unit tests easier by automatically discovering and
running tests — and generating mocks so that you can test modules in isolation. Ceedling
minimizes the amount of boilerplate code you need to write to create a unit test suite.

Sure… unit tests are just code, and you could probably roll your own build system to run your
unit tests. You could even figure out how to use Unity and CMock (or other frameworks) by
themselves.

Do yourself a favor though, and use Ceedling instead. Don’t get hung up on perfecting your
build system. Use the off-the-shelf unit test build system provided by Ceedling — and start
writing unit tests as quickly as possible.

1.2. The Approach of This Book


This book will explain how to use Ceedling to create and run unit tests, as well as how to use
mocks to test your modules in isolation. All of this testing is designed to be done on your host
computer — not your target board.

By running tests on the host, we make them easier and faster to run. This makes it more
likely that we’re actually going to run the tests (and keep them up to date). We also won’t be
dependent on the hardware for testing — which can be an advantage if it isn’t available.

To make testing on the host easier, you’ll want to design your software so that any hardware
dependencies are isolated in modules that can be mocked. A common way to do this is to use
a hardware abstraction layer (HAL). If you don’t take this into consideration then unit testing
on the host is still possible — just more difficult.

Chapter 1. Introduction 1
1.3. Your IDE
The simplest approach to using Ceedling is to run it alongside your IDE. So use your IDE to
build your application binary but jump out into a command line to run rake for your Ceedling
tests.

The simplest IDE integration would be to configure a shortcut key to run rake test:all so
that you can run Ceedling without leaving the IDE.

If you really want to get fancy, if there is a way you can access the name of the file you are
currently editing you could configure a shortcut key to run rake test:<current_file>. This
would only run the tests for the module you are working on — which will run a lot faster
once you have a lot of tests. You might need to manipulate the name of the file to remove the
extension or any test_ prefix (if it’s the test file you have open).

1.4. Versions
The examples in this book were developed and tested with Ceedling 0.25.0 and Ruby 2.3.1.

1.5. Definitions
binary
A software application that has been compiled and linked from source code. A binary can
be executed — either on the host or the target — depending on how it was was compiled.

Ceedling
A build and test system for C language applications. It provides automatic test execution
and mock generation.

CMock
The mocking framework used by Ceedling. It can be used to create mocks for functions to
be called during a test.

collaborator
A software module used by another to do its work (i.e. a dependency).

cross compilation
Compiling on system with the intention of running on another. This is what you do when
you use your computer to compile for your target. You use a cross-complier (like gcc-arm-
none-eabi) which can be run on the host, but produces binaries that can only be run the

2 Chapter 1. Introduction
target.

dependency
When one software unit requires another one to do some operation. When module_a calls
functions in module_b, we say that module_a has a dependency on module_b.

dependency chain
The module-to-module links created when a function calls a function in another module,
and then that function calls a function in another module, etc.

function prototype
The declaration of a function’s signature. For "public" functions, this is usually defined in a
header file which other modules can include to use the function.

"functional" test
A test that doesn’t require mocking any functions to execute.

function under test


The function (or functions) that we call in a unit test to verify a module’s behavior.

GCC
GNU Compiler Collection - A collection of free software compilers from the GNU
project — only the C compiler is relevant here. This is the C compiler Ceedling uses by
default.

HAL
hardware abstraction layer

host
Your computer. This is where you develop and compile your code. You can also run your
tests here as explained in this book.

integration test
Generally, this is a test of more than one component at a time. In this document an
integration test is a software integration test, which is a test of multiple software units.

mock
A "stand-in" for another module, that can be used to isolate a software module from its
dependencies for unit testing. It can be used to simulate behavior and test interactions.

module

Chapter 1. Introduction 3
A software component consting of source file (.c) and corresponding header file (.h). The
header file defines the "public interface" to the module by declaring the functions that can
be called. The source file contains the implementation. Often used interchangeably with
unit.

module under test


The module that a particular unit test is testing. Functions from this module are called and
the behavior observed is tested to verify it works correctly.

native compilation
Compiling on the same system you’re going to run the application on. This is what you’re
doing when you use gcc to compile your tests. These tests can be run on the host — not the
target.

stub
An empty function declaration used in place of a real one. When unit testing, stubs are
used to independently compile modules with dependencies on other modules.

target
Your embedded system. This is were you load your application and run it.

test assertion
A statement in a unit test that validates some expected outcome. With Ceedling we use the
TEST_ASSERT_ style macros provided by Unity.

TDD
test driven development - A development approach where unit tests are created at the
same time as the code they’re testing.

unit
The smallest independently testable component of your software application. This is
typically a single a c source file (with corresponding header file).

unit test
A single function that tests some behavior of a software module. There are usually many
of these per software module.

Unity
The unit test framework used by Ceedling. It provides all of the TEST_ASSERT macros
needed to create unit tests.

4 Chapter 1. Introduction
Chapter 2. Installing Ceedling
In this section we’ll 1) set your system up to run Ceedling and 2) install it into an existing
project.

This will involve:

1. Installing the tools required by Ceedling.

2. Installing Ceedling itself on to your system.

3. Some minor reorganization of your code.

4. Installing Ceedling into your project.

If you don’t have an existing project yet, you’ll probably want to start with an example
project for your processor or board. This project should be able to build your application
from your source code, load it to the target and run it. We won’t need any of this to run host-
based unit tests with Ceedling - but will be useful if you actually want to use the modules
you’ll be unit testing.

2.1. Install the Tools


Ceedling requires a few other tools to be installed on your system to work its magic. It’s
written in (and runs under) Ruby, and uses GCC for compiling our tests. So, you’ll need both
Ruby and GCC installed.

We need to install a native GCC for our host PC, not any kind of cross-
 compiler for a target processor. Our goal in this guide is to set up to run unit
tests on the host PC.

Ceedling will be run from the command line, and so you’ll also need to make sure that your
path is set up correctly to point to both Ruby and GCC.

2.1.1. Linux

Installing the tools for Linux should be relatively easy. The simplest way to install everything
in Linux is with a package manager like apt-get.

Install Ruby with

> sudo apt-get install ruby

Chapter 2. Installing Ceedling 5


GCC is probably already installed by default. If necessary you can also install it via your
package manager.

2.1.2. Windows

Setting things up on Windows requires a bit more effort (but not too much!).

Ruby

Ruby can be installed for Windows from rubyinstaller.org. You may want to check out the
information on the downloads page about which version of Ruby to install. The examples in
this book are tested with version 2.3.1.

Download the correct installer for your platform — either the 32-bit or the 64-bit (x64)
version — and run the installer.

During installation, I recommend using the default installation folder and selecting to have
Ruby added to the path. This will make it easier to install and use Ceedling from the
command line.

Don’t install Ruby into a location with spaces in the path like C:\Program
 Files.

 Be sure to select the option for Add Ruby executables to your PATH.

6 Chapter 2. Installing Ceedling


When the installer has finished, you can confirm that the installation was successful by
opening up a command prompt and running ruby --version:

> ruby --version


ruby 2.3.1p112 (2016-04-26 revision 54768) [x64-mingw32]

This shows that we’re running with our expected Ruby version.

If you already have a command prompt open — you’ll need to make sure


 you open a new command prompt after you’ve finished installing Ruby. If
not then your PATH variable won’t be set correctly yet.

GCC

GCC is our C compiler. There are two primary ways to install GCC on Windows: via MinGW or
Cygwin. I recommend MinGW for use with Ceedling.

Chapter 2. Installing Ceedling 7


You can get MinGW from www.mingw.org via the MinGW installer.

Download and run the installer. The default options should work fine.

Don’t install MinGW into a location with spaces in the path like C:\Program
 Files.

Eventually you’ll come to the package selection screen. Click on the box next to mingw32-
base and select Mark for Installation:

The provides a basic installation including GCC:

8 Chapter 2. Installing Ceedling


Click on Installation → Apply Changes:

Then click the Apply button and everything will be installed. When it’s done, you can close
the installer.

If you need any more help installing MingGW, see the Getting Started guide at mingw.org.

2.2. Install Ceedling


You can install Ceedling though the Ruby package manager gem. The gem command should be

Chapter 2. Installing Ceedling 9


available once Ruby is installed and available in your path. Just run:

> gem install ceedling

That’s it. On Linux, you’ll probably need to use sudo.

Ceedling Installation Error: “certificate verify failed”

I’ve recently had an issue trying to use the gem command to install Ceedling. It’s a
problem with the RubyGems server (where Ceedling and other gems are hosted). If you
get an error like this:

ERROR: Could not find a valid gem 'ceedling' (>= 0), here is why:
  Unable to download data from https://rubygems.org/ - SSL_connect
returned=1 errno=0 state=SSLv3 read server certificate B: certificate
verify failed (https://api.rubygems.org/specs.4.8.gz)

The quick fix is to install a new certificate into Ruby. Download this certificate file:

https://raw.githubusercontent.com/rubygems/rubygems/master/lib/rubygems/ssl_certs/
index.rubygems.org/GlobalSignRootCA.pem

and place it into C:\Ruby23\lib\ruby\2.3.0\rubygems\ssl_certs. Note that your path


may be different if you installed to another folder or installed a different version.

You can check your installed version of Ceedling with:

> gem list ceedling


ceedling (0.25.0)

And confirm that Ceedling is installed by running it:

10 Chapter 2. Installing Ceedling


> ceedling
Welcome to Ceedling!
Commands:
  ceedling example PROJ_NAME [DEST] # new specified example project (in DEST...
  ceedling examples # list available example projects
  ceedling help [COMMAND] # Describe available commands or one spe...
  ceedling new PROJECT_NAME # create a new ceedling project

2.3. Prepare Your Code


Now that we have Ceedling installed on our system, we’re almost ready to install it into our
project. First though, we’ll make things easier by making sure our code is organized in the
way that Ceedling would like it.

Before we make any changes to the code, your code should be in source
 control.

Source Control Rant

Keeping your code in source control is good practice in general. It allows you to freely
take risks and experiment with the code (like installing Ceedling!) without the fear of
ruining anything or losing any work. You can throw away any changes at any time and
return to a known good state.

That being said, maybe you’re going to ignore my advice. In that case please — at
least — make a backup copy of your project before making any changes. I don’t want
you to blame me for ruining your project.

Ceedling works best when all of the source code is in a single folder called src at the top level
of the project. This is one of the most basic Ceedling conventions and how Ceedling knows
where to find your source files to test.

So, you’ll want to create a folder at the top level of your project and put all of your project
source in it. It’ll look something like this:

Chapter 2. Installing Ceedling 11


Listing 1. Top level "src" folder contains all the source files.

my_project/
├── my_project.config
└── src
    ├── display.c
  ├── display.h
  ├── led_driver.c
  ├── led_driver.h
  └── main.c

It’s perfectly fine to have other project configuration files (like my_project.config) at the top
level, but just make sure all your source code is under src.

Third Party Libraries

If you’re using any third party libraries, Ceedling needs to know about them if you’re
going to use them in your unit tests. I wouldn’t necessarily recommend trying to unit
test these libraries, but you may want to create mocks based on their header files.
Despite what some hardware vendors do by default, you shouldn’t put any of this code
elsewhere on your computer — it should be inside of your project folder (and stored in
source control with your project). You can do this by putting any library folders directly
in the src folder, however you might want to put them somewhere else for
organizational purposes (good candidates include creating a new lib folder or using the
vendor folder where Ceedling lives). If you’re not using the src folder, you’ll need to
add your library folder to the source paths in the project.yml configuration file (see
the section on Changing Source File Paths in Chapter 8: Changing the Configuration).

It’s also fine to have whatever complicated source tree you want within the src folder. For
example, your project could look something like this:

12 Chapter 2. Installing Ceedling


Listing 2. The "src" folder can have whatever complicated structure you want.

my_project/
├── my_project.config
└── src
    ├── display
    │   ├── display.c
    │   └── display.h
    ├── drivers
    │   └── led
    │   ├── led_driver.c
    │   └── led_driver.h
    └── main.c

In this case we have a more complicated source tree, but everything is still in the src folder.

Ceedling isn’t great about handling source files with the same names but in
different folders (especially when it comes to mocking). Try to avoid this
situation if you can. In the above example, the led_driver files are in an led
 folder so it seems like it might be redundant to include the prefix led_ in
their names. This is done intentionally here to avoid conflicts with other
files named driver that might be in other folder paths.

Once you have everything rearranged and can build your application though your normal
build tool/IDE, check it all back into source control and you’re ready to install Ceedling into
the project.

2.4. Add Ceedling to Your Project


In order to use Ceedling on your project, you need to install it into your project folder. Do this
by running ceedling new on the top level folder of your project. For example, if your project is
in a folder named my_project you’d go into the folder containing the my_project folder (i.e.
the folder above it) and run:

Chapter 2. Installing Ceedling 13


Listing 3. Installing Ceedling into an existing project.

> dir ①
my_project

> ceedling new my_project ②


Welcome to Ceedling!
  create my_project/vendor/ceedling/docs/CeedlingPacket.pdf
  create my_project/vendor/ceedling/docs/CExceptionSummary.pdf
  create my_project/vendor/ceedling/docs/CMock Summary.pdf
  ...
  create my_project/vendor/ceedling/vendor/unity/src/unity_internals.h
  create my_project/project.yml
  create my_project/rakefile.rb

Project 'my_project/' created!


- Tool documentation is located in vendor/ceedling/docs
- Execute 'rake -T' to view available test & build tasks

① We’re in the parent folder of my_project.

② Run with the name of your project folder.

This adds all a whole bunch of files to your project. Let’s take a look at the new folder now,
after adding Ceedling to the project:

Listing 4. The Ceedling project structure.

my_project/
├── build ①
├── my_project.config
├── project.yml ②
├── rakefile.rb ③
├── src ④
├── test ⑤
│   └── support
└── vendor ⑥
  └── ceedling
  ├── docs
  ├── lib
  ├── plugins
  └── vendor

① The build directory is where Ceedling puts everything that it generates when it runs.

14 Chapter 2. Installing Ceedling


② project.yml is the project configuration file. Initially it contains the default settings. If you
need to change anything, this is where to do it.

③ Since Ceedling is based on Rake, rakefile.rb is what "installs" all the Ceedling tasks we’ll
use to run tests.

④ src is where your source files go.

⑤ The test directory is where your tests go.

⑥ The vendor directory contains Ceedling itself. Inside vendor/ceedling/docs you’ll find
documentation for Ceedling, as well for Unity, CMock and CException — three other
components used by Ceedling.

Note that we have Ceedling installed on our system, but we just installed that copy of Ceedling
into our project. That means that this particular version of Ceedling will stay with the project
(if we check it into source control — which we should do) no matter where it goes or what
system it gets installed on. There isn’t any dependency on each developer’s local
environment. This is particularly useful when we want to keep close track of the versions of
the tools we’re using, like when developing a product for a regulated industry.

2.5. Test That Ceedling Works


You can test that Ceedling is working by running rake test:all from inside your project
folder. This will run all of the unit tests in your project, but since we haven’t written any yet
none are going to run.

> rake test:all

--------------------
OVERALL TEST SUMMARY
--------------------

No tests executed.

You can use rake from anywhere within the project tree — you don’t
 necessarily have to be at the top level project folder.

Chapter 2. Installing Ceedling 15


Using ceedling vs rake

With newer versions of Ceedling, Ceedling’s authors recommended to use ceedling to


run your tests (instead of rake). This enables some features that aren’t available when
simply using rake. However for the examples in this book using rake should be
fine — and that’s what I use in any examples. Just be aware.

Since we install all of Ceedling into the project folder, using rake lets you run your tests
on a system that doesn’t have Ceedling installed. All you need is Ruby and Rake. To use
ceedling — even if you have installed Ceedling into your project — you would need to
install Ceedling on your system. Keeping track of Ceedling versions could be an issue
here if you have multiple projects. Also if you’re typing it out, rake is a few characters
shorter.

2.6. Check Back Into Source Control


Before you share your changes with your team via source control, you’ll
probably want to make sure your release build — the one that builds the
 binary you load on your hardware target, possibly using your IDE — is
working. You don’t want to people thinking your unit tests broke the build.

With Ceedling installed in the project, it’s time to check it back into source control. Every
thing in your project should go into source control except for the contents of the build folder.
Here are the details:

build
This folder should go in source control but you should ignore all of it’s contents. The
folder needs to exist to run Ceedling, but all of its contents are generated and thus
shouldn’t be under source control.

project.yml
This contains your project’s Ceedling configuration and should go in source control.

rakefile.rb
You need this to run Ceedling — it should go in source control.

src
This is your application! Obviously everything in this directory should go in source
control.

16 Chapter 2. Installing Ceedling


test
This is were your tests will go. You want everything in here (including the support folder)
to go in source control.

vendor
The vendor directory should go in source control as well. This allows you to run your unit
tests after a clean checkout on a new machine without having to install Ceedling again.

2.7. Getting Around


The way to see what Ceedling can do is the -T option.

Chapter 2. Installing Ceedling 17


> rake -T
rake clean # Delete all build artifacts and temporary
products
rake clobber # Delete all generated files (and build
artifacts)
rake environment # List all configured environment variables
rake files:header # List all collected header files
rake files:source # List all collected source files
rake files:test # List all collected test files
rake logging # Enable logging
rake module:create[module_path] # Generate module (source, header and test
files)
rake module:destroy[module_path] # Destroy module (source, header and test
files)
rake paths:source # List all collected source paths
rake paths:support # List all collected support paths
rake paths:test # List all collected test paths
rake summary # Execute plugin result summaries (no build
triggering)
rake test:* # Run single test ([*] real test or source file
name, no path)
rake test:all # Run all unit tests
rake test:delta # Run tests for changed files
rake test:path[dir] # Run tests whose test path contains [dir] or
[dir] substring
rake test:pattern[regex] # Run tests by matching regular expression
pattern
rake verbosity[level] # Set verbose output (silent:[0] -
obnoxious:[4])
rake version # Display build environment version info

These are all of the commands you can run from within your project now. Try running a few
to see what they do. For example, take a look at what Ceedling thinks are your source files:

> rake files:source


source files:
 - src/display/display.c
 - src/drivers/led/led_driver.c
 - src/main.c
file count: 3

18 Chapter 2. Installing Ceedling


And the paths it’s configured to look in:

> rake paths:source


source paths:
 - src
 - src/display
 - src/drivers
 - src/drivers/led

and the versions of all its components:

> rake version


  Ceedling:: 0.25.0
CException:: 1.3.1.18
  CMock:: 2.4.3.215
  Unity:: 2.4.0.120

Chapter 2. Installing Ceedling 19


Chapter 3. Creating Modules and Running
Tests
3.1. Conventions
It’s a lot easier to test your code (especially with Ceedling) if you follow some conventions
when you’re writing it. C is the wild west — there are few if any conventions imposed by the
language or its community. When you try to test existing (legacy) code, this will likely be a
problem.

For now though, let’s look at some reasonable conventions suggested by Ceedling. I think
these are good practice to follow.

• All source code goes in the src folder (for third party libraries located elsewhere, see
[Third Party Libaries] in Chapter 2: Installing Ceedling and Changing Source File Paths in
Chapter 8: Changing the Configuration).

• Every source module is implemented in a .c file with a corresponding .h file. The .h file
defines the public interface of the module — the interface that we will be exercising in our
tests.

• Each source module has a corresponding unit test file (a .c file) that goes in the test
folder. This file is named test_module.c, where the module is the name of the module
we’re testing.

Ceedling actually provides an easy way for us to create modules that follow these
conventions, the module generator.

3.2. Creating New Modules


For this example, I’ve created a project called simple_tests. We can use the module generator
to generate the source files required by our conventions, with the module:create task:

> rake module:create[simple_examples]


File src/simple_examples.c created
File src/simple_examples.h created
File test/test_simple_examples.c created
Generate Complete

This adds three files to our project:

20 Chapter 3. Creating Modules and Running Tests


simple_tests
├── build
├── project.yml
├── rakefile.rb
├── src
│   ├── simple_examples.c ①
│   └── simple_examples.h ②
├── test
│   ├── support
│   └── test_simple_examples.c ③
└── vendor

① The source file.

② The header file.

③ The unit test file.

The simple_examples.c and simple_examples.h files generated are sparse, but they have
everything we need to get started:

Listing 5. simple_examples.h

#ifndef _SIMPLE_EXAMPLES_H
#define _SIMPLE_EXAMPLES_H

#endif // _SIMPLE_EXAMPLES_H

Listing 6. simple_examples.c

#include "simple_examples.h"

The test file is a little bit handier because it creates the little bit of boilerplate that we need to
get started writing unit tests. :

Chapter 3. Creating Modules and Running Tests 21


Listing 7. test_simple_example.c

#include "unity.h" ①
#include "simple_examples.h" ②

void setUp(void) ③
{
}

void tearDown(void) ④
{
}

void test_simple_examples_NeedToImplement(void) ⑤
{
  TEST_IGNORE_MESSAGE("Need to Implement simple_examples"); ⑥
}

① Include Unity, to get our TEST_ASSERT_ macros.

② Include the header file for the module under test.

③ setUp is called before each test function.

④ tearDown is called after each test function.

⑤ This is a template for our first unit test.

⑥ The TEST_IGNORE_MESSAGE macro tells Unity to ignore the test (not counted as passing or
failing) and print the given message.

Run all tests in the project with rake test:all and you can see what we’ve created:

22 Chapter 3. Creating Modules and Running Tests


Listing 8. Run all tests in the project with rake test:all.

> rake test:all

Test 'test_simple_examples.c'
-----------------------------
Generating runner for test_simple_examples.c... ①
Compiling test_simple_examples_runner.c...
Compiling test_simple_examples.c...
Compiling unity.c...
Compiling simple_examples.c...
Compiling cmock.c...
Linking test_simple_examples.out...
Running test_simple_examples.out...

-----------
TEST OUTPUT
-----------
[test_simple_examples.c]
  - ""

--------------------
IGNORED TEST SUMMARY
--------------------
[test_simple_examples.c]
  Test: test_simple_examples_NeedToImplement
  At line (14): "Need to Implement simple_examples" ②

--------------------
OVERALL TEST SUMMARY
--------------------
TESTED: 1
PASSED: 0
FAILED: 0
IGNORED: 1

① This is Ceedling telling us what it’s actually doing. This can come in handy when things
start to get more complicated.

② Here is our ignored test and message.

Chapter 3. Creating Modules and Running Tests 23


3.3. Adding New Tests
We add new tests to our test file simply by adding functions with names that start with test_.
So in our test_simple_example.c file the module generator starts us off with the
test_simple_examples_NeedToImplement() test (which we’ll change to implement a real test),
but we can add new tests like test_that_will_pass():

Listing 9. test_simple_example.c

void test_simple_examples_NeedToImplement(void) ①
{
  TEST_IGNORE_MESSAGE("Need to Implement simple_examples");
}

void test_that_will_pass(void) ②
{
  TEST_ASSERT_EQUAL(1,1);
}

① The original test from the module generator.

② Our first new test.

Note the use of the TEST_ASSERT_EQUAL(1,1) in the new test. Every test needs to actually test
something with a test assertion. If the assertion fails, then the test fails. In this case we test
that 1 is equal to 1 — which will always be true — so the test will pass. The test assertions
used in Ceedling come from the Unity unit test framework (and there are quite a variety of
them). You can see a lot more of them in Chapter 5: Testing with Unity.

24 Chapter 3. Creating Modules and Running Tests


Test Naming

When the module generator creates our first test function from its template
(test_simple_examples_NeedToImplement), it suggests a naming convention where our
tests should start with test_<module_name>_. Every test function does need to start with
test_, but there really isn’t a good reason to include the module name as well.

Typically in a C application we prefix all of our function names with our unique
module name so we don’t run into collisions with similar functions from other
modules.

That’s not necessary with Ceedling though since each test file is compiled into its own
binary. So don’t put the name of the module under test in your test name, it’s not
necessary and will just add clutter.

3.4. Running Tests


The test command is used to run the tests in your project. There are a few different options
you can use by providing them to the test command after a colon, like this: rake
test:<option>.

rake test:all
This runs all of the tests in your project.

rake test:<module_name>
This runs the tests from only a single module — the tests in the file
test_<module_name>.c. For example, run our simple_examples tests with rake
test:simple_examples. This comes in handy when your project has a lot tests.

rake test:delta
Run the tests for only for modules that have been changed since the last time the tests
were run. Tests will be re-run if the test file or the module .c file have been changed. The is
the default when you simply run rake test.

3.5. Cleaning Tests


To reset Ceedling and delete all build files so you can get a clean test run, use rake clobber.

Chapter 3. Creating Modules and Running Tests 25


Listing 10. Using the clobber task to delete all build files.

> rake clobber

Clobbering all generated files...


(For large projects, this task may take a long time to complete)

3.6. Creating Nested Modules


We can also use the module generator to create modules nested deeply in our source tree
hierarchy:

Listing 11. Creating deeply nested modules with the module generator.

> rake module:create[path/to/another/module]


File src/path/to/another/module.c created
File src/path/to/another/module.h created
File test/path/to/another/test_module.c created
Generate Complete

Note that this creates a tree within the test folder that matches the source file tree:

26 Chapter 3. Creating Modules and Running Tests


Listing 12. The source and test file trees resulting from the creation of a deeply nested module.

simple_tests
├── build
├── project.yml
├── rakefile.rb
├── src
│   ├── path
│   │   └── to
│   │   └── another
│   │   ├── module.c ①
│   │   └── module.h
│   ├── simple_examples.c
│   └── simple_examples.h
├── test
│   ├── path
│   │   └── to
│   │   └── another
│   │   └── test_module.c ②
│   ├── support
│   └── test_simple_examples.c
└── vendor

① The deeply nested module.

② The test folder contains a tree matching the one under src.

3.7. Removing Modules


You can use the module generator to remove modules too. Sometimes this is useful if you
named something incorrectly when creating it. The destroy operation deletes the same files
generated by create.

Chapter 3. Creating Modules and Running Tests 27


Listing 13. Creating and removing modules with the module generator.

> rake module:create[temp]


File src/temp.c created
File src/temp.h created
File test/test_temp.c created
Generate Complete

> rake module:destroy[temp]


File src/temp.c deleted
File src/temp.h deleted
File test/test_temp.c deleted
Destroy Complete

3.8. printf from Tests


Sometimes it’s useful to print out messages when running your tests. You can do this easily
with printf from stdio.h. Whatever you print will show up in your test output.

Add a printf call:

Listing 14. A unit test function that contains a printf call.

void test_simple_examples_NeedToImplement(void)
{
  printf("Something to help debug this test");
  TEST_IGNORE_MESSAGE("Need to Implement simple_examples");
}

And the message will show up in the test output:

28 Chapter 3. Creating Modules and Running Tests


Listing 15. Running a test that makes a printf call.

$ rake test:all

Test 'test_simple_examples.c'
-----------------------------
Running test_simple_examples.out...

-----------
TEST OUTPUT
-----------
[test_simple_examples.c]
  - "Something to help debug this test" ①
  - ""

--------------------
IGNORED TEST SUMMARY
--------------------
[test_simple_examples.c]
  Test: test_simple_examples_NeedToImplement
  At line (15): "Need to Implement simple_examples"

--------------------
OVERALL TEST SUMMARY
--------------------
TESTED: 1
PASSED: 0
FAILED: 0
IGNORED: 1

① Here’s the message in the output.

One thing to note though is that there isn’t any way to tell which specific unit test is
producing the output (the only indication is which test file it came from). If you have multiple
tests with lots of output, it’s going to be hard to tell what is going on. So use output in your
tests sparingly. I’ll usually only use it temporarily to debug a problem with a complicated
test — and then remove it.

Chapter 3. Creating Modules and Running Tests 29


Chapter 4. Writing Tests
4.1. Creating Your Own Test Files
In the previous chapter, we used Ceedling’s module generator to create our test files (and
module source files). This is just a convenience though — we can create our own test files
manually. There isn’t any "magic" going on behind the scenes when we run the module
generator — it just creates a bunch of files based on some built-in templates.

To make Ceedling build and run a test file, just put it in the test folder and give it a name that
starts with test_. By default any test file created like this will be executed by Ceedling (also
remember that test function names need to start with test_ to be executed).

To test a state machine implementation in state_machine.c, we’d create a test file named
test_state_machine.c in the test folder.

Although it’s a handy convention — and the one used by Ceedling’s module


generator — a test file doesn’t have to be named to match the source module
 it’s testing. So we don’t have to use test_state_machine.c to test our
state_machine module. The module under test is determined only by what
header files are included in the test file.

In the test file, we need to include unity.h as well as the header of the module under test. To
test the logic in state_machine.c we’d include it’s corresponding header file
state_machine.h (the one that exposes its "public" interface).

The other thing the test file must have are setUp and tearDown functions. Without these,
Ceedling will fail with lots of errors. You don’t actually need to use them, but you at least need
to have empty implementations. Altogether, the minimum requirements for a test file look
like this:

30 Chapter 4. Writing Tests


Listing 16. Minimum requirements of a test file.

#include "unity.h" ①
#include "state_machine.h" ②

void setUp(void) ③
{
}

void tearDown(void) ④
{
}

① Unity must be included.

② Include the header file of the module under test.

③ There must be a setUp…

④ … and a tearDown function.

Any code you put in the setUp function is executed before each test function is run. If you
have three test functions in your test file, setUp gets called three times. This can be useful for
simplifying your tests (by reducing code duplication) if you have common set up code that
you need to run before each of your tests. The tearDown works similarly but after each test
function is run.

4.2. Simple, Functional Tests


Let’s add a test for the state machine. This will be a relatively simple test that I’ll call a
"functional" test. The function we’ll call during our test doesn’t call any other functions (or
have any other side effects). The correct behavior of the function is determined only by its
input parameters and return value. In this case, the function we’ll use to test is
state_get_next:

Listing 17. A function signature to test (from state_machine.h).

typedef enum {
  RESET,
  READY,
} state_t; ①

state_t state_get_next(state_t current); ②

Chapter 4. Writing Tests 31


① Define a type for the possible states.

② This function determines the next state based on the current state.

The behavior of the state machine can be tested by using the state_get_next function.
Suppose that after RESET, the next state should be READY. Here’s what the test file would look
like:

Listing 18. Add a simple test for the state machine (test_state_machine.c)

#include "unity.h" ①
#include "state_machine.h" ②

void setUp(void) ③
{
}

void tearDown(void)
{
}

void test_reset_goes_to_ready(void) ④
{
  state_t current = RESET;
  state_t next = state_get_next(current); ⑤

  TEST_ASSERT_EQUAL(READY, next); ⑥
}

① Include Unity so we can use it in the tests.

② Include the header file for the module under test. This instructs Ceedling to include the
state_machine module logic (implemented in state_machine.c) in our test.

③ Provide setUp and tearDown implementations or Ceedling will fail.

④ Here’s our test. We are testing that we go from the RESET state to the READY state.

⑤ Calling state_get_next(current) is how we get the state machine to give us the next state
based on the current state.

⑥ Use a Unity test assertion to verify that we got the correct next state.

In this test we set up some initial conditions (the current state), exercised the module under
test by calling one of its functions, and used Unity to verify that we got the correct results.

32 Chapter 4. Writing Tests


Note again that I’m calling this a "functional" test. The state_get_next() function does not call
any other functions to do it’s work — all of its logic is self-contained. We don’t need to call or
mock any other functions to test it.

 It’s a lot easier to test functions that don’t call other functions.

Here’s the simplest implementation of state_get_next that passes the test:

Listing 19. Simple implementation of state_get_next that passes the test (state_machine.c).

state_t state_get_next(state_t current)


{
  return READY;
}

4.3. Dealing with Collaborators


Suppose however, that our system has button on it. If that button is pressed we don’t want to
go to the READY state — instead we want to stay in the RESET state. Our new implementation
might look like this:

Listing 20. A more complicated implementation of state_get_next that calls another function
(state_machine.c)

#include "button.h" ①

state_t state_get_next(state_t current)


{
  if (button_is_pressed()) ②
  {
  return RESET;
  }
  else
  {
  return READY;
  }
}

① Include the header file for another collaborator. The button module is now a dependency.

② Call a function in the button module to determine the button status (and thus the next
state).

Chapter 4. Writing Tests 33


For testing the state machine, we can’t just rely on Unity any more. We don’t actually want to
call the button_is_pressed function during our test. Instead we need to mock the
button_is_pressed function so that we can control how it behaves during our tests. This is
were CMock comes in.

Presuming that button_is_pressed() is defined in button.h, we include mock_button.h to


instruct Ceedling to use CMock to mock it. Then we set up an expectation of what we want to
happen when button_is_pressed is called.

Here’s a new test file to test the state machine that collaborates with a the button module:

Listing 21. A new test file that mocks the button module interface
(test_state_machine_with_collaborator.c).

#include "unity.h"
#include "state_machine_with_collaborator.h"
#include "mock_button.h" ①

void setUp(void)
{
}

void tearDown(void)
{
}

void test_reset_stays_at_reset_when_button_pressed(void)
{
  button_is_pressed_ExpectAndReturn(true); ②

  state_t current = RESET;


  state_t next = state_get_next(current); ③

  TEST_ASSERT_EQUAL(RESET, next); ④
}

① Tell Ceedling to use CMock to mock all of the functions it can find in button.h. These get
built into the test instead of anything in a button.c.

② Tell CMock what should happen when state_get_next calls button_is_pressed. In this case,
we want it to return true to simulate that the button is actually pressed.

③ Then we call our function under test.

④ Finally we verify that we got the correct result.

34 Chapter 4. Writing Tests


This is the way we test modules that have collaborators (i.e. they call functions in other
modules) — we use CMock to mock these interfaces. CMock gives us a bunch of new function
calls based on the mocked functions. That’s where the funny-looking function named
button_is_pressed_ExpectAndReturn came from. This function directs CMock to expect to have
this function called and return a value (true in this case).

This is just a taste of the way CMock works — for more details see the
 Chapter 6: Mocking with CMock.

We could write a similar test for the other case — when the button is not pressed. In this case
we instruct CMock to return false for the call to button_is_pressed:

Listing 22. Test that we do go from the RESET to the READY state when the button is not pressed

void test_reset_goes_to_ready_when_button_not_pressed(void)
{
  button_is_pressed_ExpectAndReturn(false); ①

  state_t current = RESET;


  state_t next = state_get_next(current);

  TEST_ASSERT_EQUAL(READY, next); ②
}

① Simulate the button not being pressed.

② In this case, we should go to the ready state.

Since we mocked button.h in this test, we don’t even need a button


implementation in *button.c* to run these tests. This can be handy if we
 know we need a collaborating module, but don’t want to implement it yet.
We can design its interface and exercise it through a mock — and then
implement it later.

Chapter 4. Writing Tests 35


Chapter 5. Testing with Unity
Unity is the unit test framework used by Ceedling. It’s the tool that provides all of the
TEST_ASSERT_ macros used to write your tests. It’s installed into your project when you install
Ceedling.

In this section we’ll cover most of your options with Unity, but it’s not necessarily exhaustive.

The documentation for Unity can be found right in your own project folder (after you’ve
installed Ceedling into it) in vendor/ceedling/docs. Depending on which version of Ceedling
you have, there will be one or more documents about Unity (the most recent versions have
more documents with better information). These are really good guides to all the different
flavors of TEST_ASSERT_ macros (there are a lot of them!), and you should really take a look at
some point.

Most of the example tests in this chapter are trivial — they don’t do anything!
These are just simple examples to show you the features of Unity and what
 types of tests you can create. In your real tests, you’ll be calling functions
and testing their return values.

5.1. Using Unity


To use Unity in your tests, just include the Unity header in your test file.

Listing 23. Use Unity by including its header file in the test file.

#include "unity.h"

This is done automatically if you use the module generator to create your
 test file.

5.2. Test Assertions


A test assertion is a test we use to make sure that our code is working correctly. A test
assertion is what makes a unit test a unit test. When we run Ceedling, the output shows
which tests passed and which tests failed based on the test assertions we use.

The simplest test assertion is TEST_ASSERT(condition). This test passes if the boolean condition
evaluates to true and fails if it evaluates to false. For example, this test passes:

36 Chapter 5. Testing with Unity


Listing 24. A simple passing test.

void test_simple_pass(void)
{
  TEST_ASSERT(1);
}

And this test fails:

Listing 25. A simple failing test.

void test_simple_fail(void)
{
  TEST_ASSERT(2 == 1);
}

If we put these tests in a file named test_basics.c and run them, we get output that looks like
this:

Chapter 5. Testing with Unity 37


Listing 26. The output from running a simple passing and failing test.

> rake test:all

Test 'test_basics.c'
--------------------
Generating runner for test_basics.c...
Compiling test_basics_runner.c...
Compiling test_basics.c...
Linking test_basics.out...
Running test_basics.out...

-----------
TEST OUTPUT
-----------
[test_basics.c]
  - ""

-------------------
FAILED TEST SUMMARY
-------------------
[test_basics.c]
  Test: test_simple_fail
  At line (19): "Expression Evaluated To FALSE"

--------------------
OVERALL TEST SUMMARY
--------------------
TESTED: 2
PASSED: 1
FAILED: 1
IGNORED: 0

This TEST_ASSERT() macro is the simplest way to create a test. Actually, we could use
TEST_ASSERT() for all of our tests, but we don’t for two reasons.

The first reason is that we don’t get much information when the test fails. In the above
example, the test failure told us Expression Evaluated to FALSE but didn’t tell us about what
values we were actually comparing.

The second reason is that some of the comparison logic — like when we’re testing all the
values in an array — would be a pain to create or reuse for each different type of test.

38 Chapter 5. Testing with Unity


Instead, Unity provides us with a variety of test assertions for testing different types, e.g.
integers or strings. This makes it easier to test each particular data type and gives us more
information about any test failures.

5.3. Multiple Assertions


You can have multiple assertions in the same test, like this:

Listing 27. A test with multiple assertions.

void test_multiple_things(void)
{
  int x = 0;
  int y = 0;

  get_two_values(&x, &y);

  TEST_ASSERT(x == 1);
  TEST_ASSERT(y == 2);
}

When you have multiple assertions, the first one to fail will cause your test fail and stop
executing. This means that no code after the first failing test will be run.

Also, this still counts as a single test in the report generated by Ceedling when it’s finished. So
if this test passes (both assertions are true), we would get something that looks like:

Listing 28. A test report for single passing test with multiple assertions.

--------------------
OVERALL TEST SUMMARY
--------------------
TESTED: 1
PASSED: 1
FAILED: 0
IGNORED: 0

5.4. Comparing Values


We’ve already seen TEST_ASSERT() which tests a single, boolean condition. When we actually
want to compare two values — and get more information from a failure — we can use the

Chapter 5. Testing with Unity 39


Unity test assertions that take expected and actual values for comparing. These take the form:

TEST_ASSERT_EQUAL_type(expected, actual);

where type is the type of thing that we’re comparing. For example, you can compare two
integers like this:

Listing 29. A failing test comparing two integer values

void test_integers(void)
{
  TEST_ASSERT_EQUAL_INT(2, 1);
}

and get this failure message:

Listing 30. A failure message when comparing two integer values

Test: test_integers
At line (24): "Expected 2 Was 1" ①

① The expected value of 2 did not equal the actual value of 1.

5.5. Integers
There are actually lots of ways to compare integers, based on the width of the integer,
whether it is signed or unsigned, and if you want the results in decimal or hex.

For signed values, use: TEST_ASSERT_EQUAL_INT(expected, actual)

For unsigned values, use: TEST_ASSERT_EQUAL_UINT(expected, actual)

There are tests for all of the unsigned and signed C99 standard types:

• TEST_ASSERT_EQUAL_UINT8(expected, actual)
• TEST_ASSERT_EQUAL_UINT16(expected, actual)
• TEST_ASSERT_EQUAL_UINT32(expected, actual)
• TEST_ASSERT_EQUAL_UINT64(expected, actual)
• TEST_ASSERT_EQUAL_INT8(expected, actual)
• TEST_ASSERT_EQUAL_INT16(expected, actual)

40 Chapter 5. Testing with Unity


• TEST_ASSERT_EQUAL_INT32(expected, actual)
• TEST_ASSERT_EQUAL_INT64(expected, actual)

Make sure to use correctly sized types for your target. If you’re testing a
uint8_t, make sure to use TEST_ASSERT_EQUAL_UINT8. If you don’t, you may get
 unexpected errors from comparing the wrong number of bits — depending
on what’s next to your values in memory.

Standard Integer Types

In the C99 version of the C language standard, the standard integer types in stdint.h
were introduced. Use them! Even though C99 has been around for a while, I still see a
lot of embedded code that doesn’t use standard types. Instead, each project rolls its own
version of Byte and Word, etc. I really recommend using these standard types like
uint8_t or int32_t whenever possible. It makes it easier to understand your code. Oh
and use stdbool.h too.

There are also tests that will print your results in hex:

• TEST_ASSERT_EQUAL_HEX(expected, actual)
• TEST_ASSERT_EQUAL_HEX8(expected, actual)
• TEST_ASSERT_EQUAL_HEX16(expected, actual)
• TEST_ASSERT_EQUAL_HEX32(expected, actual)
• TEST_ASSERT_EQUAL_HEX64(expected, actual)

The numbers (8, 16, 32, 64) in these names are the widths of the values (in bits) like the stdint
types. Note that these are all compared as unsigned values. As advertised, this test:

Listing 31. A test using a hex test assertion to report errors with hex values.

void test_hex(void)
{
  TEST_ASSERT_EQUAL_HEX8(0x20, 0x10);
}

generates this error:

Chapter 5. Testing with Unity 41


Listing 32. The results when a hex test assertion fails.

Test: test_hex
At line (29): "Expected 0x20 Was 0x10" ①

① The results are reported in hex.

5.6. Strings
To compare two null-terminated character arrays (ahem, "strings") use:

TEST_ASSERT_EQUAL_STRING(expected, actual)

Listing 33. Some example tests for strings

void test_strings_that_dont_match(void)
{
  TEST_ASSERT_EQUAL_STRING("yes", "no"); ①
}

void test_strings_that_match(void)
{
  char actual[] = "yes";
  TEST_ASSERT_EQUAL_STRING("yes", actual); ②
}

① This test will fail.

② This test will pass.

5.7. Floats
5.7.1. Within a Range

As you probably know, because of the way floating point numbers are represented you
usually don’t want to test them directly for equality. Instead, you usually want to test that
they are within some tolerance of each other. This is done in Unity with
TEST_ASSERT_FLOAT_WITHIN(delta, expected, actual). This tests that the actual value is within
the range of the expected value +/- delta.

42 Chapter 5. Testing with Unity


Listing 34. A test that two floating point values are within a range of each other.

void test_floats_close_enough(void)
{
  TEST_ASSERT_FLOAT_WITHIN(0.1f, 2.2f, 2.23f); ①
}

① This test passes because the actual value of 2.23 is within the range of 2.1 to 2.3 (2.2 +/- 0.1).

5.7.2. Equality

Despite the typical problems comparing floats for equality, Unity does have a way to do it:
TEST_ASSERT_EQUAL_FLOAT(expected, actual). It works because it’s a bit more sophisticated
than directly testing for equality. It tests that the values are "very close" to each other. How
close they have to be is recalculated for each test based on the expected value. Pretty magical
huh?

Listing 35. Test that two floats are "equal".

void test_floats_equal(void)
{
  TEST_ASSERT_EQUAL_FLOAT(1.0f, 1.0f);
}

5.7.3. Special Cases

There are also a variety of ways to test if a single float value is not a number (NaN), infinity, or
negative infinity:

• TEST_ASSERT_FLOAT_IS_INF (value)
• TEST_ASSERT_FLOAT_IS_NEG_INF (value)
• TEST_ASSERT_FLOAT_IS_NAN (value)

There are quite a few other variants too. See the Unity documentation in
vendor/ceedling/docs for all the options.

5.7.4. Double Precision

For each FLOAT test, there is a corresponding DOUBLE test that works on double values. For
example:

• TEST_ASSERT_DOUBLE_WITHIN(delta, expected, actual)

Chapter 5. Testing with Unity 43


• TEST_ASSERT_EQUAL_DOUBLE(delta, expected, actual)

 Double precision is disabled by default.

To enable double precision tests in your project, you need to define UNITY_INCLUDE_DOUBLE.
This can be done in the project.yml configuration file by adding these lines:

Listing 36. The setting in project.yml to enable DOUBLE test assertions

:unity:
  :defines:
  - UNITY_INCLUDE_DOUBLE

See the section on Unity Options in Chapter 8: Changing the Configuration for more
information.

5.8. Pointers
Test pointers with TEST_ASSERT_EQUAL_PTR(expected, actual):

Listing 37. A test comparing two pointer values

void test_pointers(void)
{
  int x; ①
  int* x_ptr = &x; ②
  TEST_ASSERT_EQUAL_PTR(&x, x_ptr); ③
}

① Declare an integer.

② Save the pointer to x.

③ Compare the saved pointer to the address of x.

This compares the two pointer values. The data they point to is not tested.

5.9. Memory
Compare arbitrary blocks of memory with TEST_ASSERT_EQUAL_MEMORY(expected_ptr,
actual_ptr, size):

44 Chapter 5. Testing with Unity


Listing 38. A test comparing two blocks of memory

void test_memory(void)
{
  uint8_t ones[] = {1, 1, 1, 1, 1}; ①
  uint8_t buffer[5];

  memset(buffer, 1, 5); ②
  TEST_ASSERT_EQUAL_MEMORY(ones, buffer, 5); ③
}

① Create an array of 5 bytes in memory.

② Create another array in memory that matches the first.

③ Compare 5 bytes of memory from both locations.

This is a powerful test that can be used in a variety of ways, e.g. to test structs or arrays. On a
failure, you’ll get a message telling you which bytes don’t match, e.g. Memory Mismatch. Byte 4
Expected 0x02 Was 0x01.

5.10. Structs
5.10.1. Contents

When testing the contents of a struct, you can compare all of it’s members individually:

Listing 39. A test for all the members of a struct

typedef struct ①
{
  int x;
  int y;
} point_t;

void test_matching_members(void)
{
  point_t actual;
  set_to_origin(&actual); ②

  TEST_ASSERT_EQUAL_INT(0, actual.x); ③
  TEST_ASSERT_EQUAL_INT(0, actual.y);
}

Chapter 5. Testing with Unity 45


① point_t is a custom struct type — with x and y values — representing a 2D coordinate.

② The function we’re testing should set the actual point to the origin.

③ Test each struct member individually.

Or, you can directly compare the memory between two structures with
TEST_ASSERT_EQUAL_MEMORY(expected, actual, size). This just compares the raw memory.

Listing 40. Testing two structures by comparing their raw memory.

typedef struct
{
  int x;
  int y;
} point_t;

void test_matching_memory(void)
{
  point_t expected = { ①
  .x = 0,
  .y = 0,
  };

  point_t actual;
  set_to_origin(&actual);

  TEST_ASSERT_EQUAL_MEMORY(&expected, &actual, sizeof(point_t)); ②


}

① Create a struct with the expected values to use for comparison.

② Compare the actual to the expected data.

While testing structures as memory can be convenient, it is possible that this


could result in false failures if your compiler leaves uninitialized "holes" in
 your structures due to bit or word alignment. See The Trouble with
Comparing Structures as Memory below for details.

If this test fails, you’re just going to get a simple error message like Memory
 Mismatch. Byte 0 Expected 0x00 Was 0x01. To get a better indication of what
went wrong, test each structure member individually instead.

46 Chapter 5. Testing with Unity


5.10.2. Instances

To test that two structures are the same instance in memory, you want to compare pointers
with TEST_ASSERT_EQUAL_PTR(expected, actual).

Listing 41. Testing for a particular instance of a struct

typedef struct
{
  int x;
  int y;
} point_t;

void test_same_struct_instance(void)
{
  point_t a = { .x = 1, .y = 1 };
  point_t b = { .x = 2, .y = 2 };

  point_t* closest_to_origin = get_closest_to_origin(&a, &b); ①

  TEST_ASSERT_EQUAL_PTR(&a, closest_to_origin); ②
}

① get_closest_to_origin takes in two point pointers and returns the one that is closest to the
origin.

② The pointer returned actually points to a (it’s the one we passed in to


get_closest_to_origin) so testing the pointer works here.

5.10.3. The Trouble with Comparing Structures as Memory

Using a memory comparison to compare two structures is a convenient shortcut (and it’s
used by CMock when expecting structures passed by reference). You need to watch out
though, because this can cause false errors in some situations.

In a memory comparison, it’s just two raw areas of memory being compared — without any
regard for what’s stored there. When you create a structure in C, it’s possible to end up with
holes when the members don’t align with word size of the machine — and you need to make
sure these get initialized correctly.

Consider an adc_config_t struct that is composed of an 8-bit value, followed by two 32-bit
values:

Chapter 5. Testing with Unity 47


typedef struct {
  uint8_t channel; ①
  uint32_t clock_rate; ②
  uint32_t sample_rate; ③
} adc_config_t;

① 8 bits

② 32 bits

③ 32 bits

When the compiler constructs this in memory on my 32-bit PC it’s going to use three 32-bit
values, which will look something like this:

Figure 1. Memory layout of adc_config_t structure

If I allocate one of these structures on the stack by creating a local function variable, none of
the structure memory will be initialized. If I then set the values of all the structure members,
this isn’t enough to initialize all of the memory allocated for the structure. The holes remain
uninitialized. This means that they could contain any values at all.

For example, if I create a struct on the stack and initialize it like this:

48 Chapter 5. Testing with Unity


Listing 42. Allocating a struct on the stack and initializing it

void test_function (void)


{
  adc_config_t config;
  config.channel = 3;
  config.clock_rate = 40000000;
  config.sample_rate = 1000;
}

Then only the bytes used by these fields are initialized — here is what happens to the
memory:

Figure 2. Setting all structure members does not initialize all memory

Usually this wouldn’t be a problem — if you’re just accessing a structure by its members. But
when we use a memory comparison to compare structures, these uninitialized memory holes
can cause false errors.

The way to deal with this is to always zero initialize your structures. Here’s a convenient way
to do this:

Chapter 5. Testing with Unity 49


Listing 43. Syntax for zero initializing a structure.

void test_function (void) {


  adc_config_t config = {0}; ①
  config.channel = 3;
  config.clock_rate = 40000000;
  config.sample_rate = 1000;
}

① This initializes all structure memory to zero.

This = {0} syntax is C99. You can also use a memset from string.h if you’re not
 using C99.

5.11. Arrays
Many of the Unity test assertions also have variants for testing arrays. For most
TEST_ASSERT_EQUAL_type(expected, actual) assertions, there is a corresponding
TEST_ASSERT_EQUAL_type_ARRAY(expected, actual, count) assertion. The array variant takes an
extra parameter — the number of elements in the array.

For example, test an array of integers with TEST_ASSERT_EQUAL_INT_ARRAY(expected, actual,


count):

Listing 44. Comparing two arrays of integers.

void test_int_array(void)
{
  int a[] = {1, 2, 3, 4, 5};
  int b[] = {1, 2, 3, 4, 5};

  TEST_ASSERT_EQUAL_INT_ARRAY(a, b, 5);
}

Array tests are supported for the integer types (INT8, UIN32, etc…), hex types (HEX, HEX8,
HEX16, etc..), pointers, strings and memory:

• TEST_ASSERT_EQUAL_INT_ARRAY (expected, actual, count)
• TEST_ASSERT_EQUAL_INT8_ARRAY (expected, actual, count)
• TEST_ASSERT_EQUAL_INT16_ARRAY (expected, actual, count)
• TEST_ASSERT_EQUAL_INT32_ARRAY (expected, actual, count)

50 Chapter 5. Testing with Unity


• TEST_ASSERT_EQUAL_INT64_ARRAY (expected, actual, count)
• TEST_ASSERT_EQUAL_UINT_ARRAY (expected, actual, count)
• TEST_ASSERT_EQUAL_UINT8_ARRAY (expected, actual, count)
• TEST_ASSERT_EQUAL_UINT16_ARRAY (expected, actual, count)
• TEST_ASSERT_EQUAL_UINT32_ARRAY (expected, actual, count)
• TEST_ASSERT_EQUAL_UINT64_ARRAY (expected, actual, count)
• TEST_ASSERT_EQUAL_HEX_ARRAY (expected, actual, count)
• TEST_ASSERT_EQUAL_HEX8_ARRAY (expected, actual, count)
• TEST_ASSERT_EQUAL_HEX16_ARRAY (expected, actual, count)
• TEST_ASSERT_EQUAL_HEX32_ARRAY (expected, actual, count)
• TEST_ASSERT_EQUAL_HEX64_ARRAY (expected, actual, count)
• TEST_ASSERT_EQUAL_PTR_ARRAY (expected, actual, count)
• TEST_ASSERT_EQUAL_STRING_ARRAY (expected, actual, count)
• TEST_ASSERT_EQUAL_MEMORY_ARRAY (expected, actual, size, count)

5.12. Ignoring Tests


Sometimes you’re working on something, and you don’t want a test (or a set of tests) to
generate failures. This happens to me sometimes when I’m test driving and decide I need to
refactor a few things. You can either comment out a test, or make Unity ignore it with
TEST_IGNORE().

Use TEST_IGNORE() by putting it first in your test:

Listing 45. How to ignore a test.

void test_that_is_ignored(void)
{
  TEST_IGNORE(); ①
  TEST_ASSERT_EQUAL_INT(0,1); ②
}

① Place the ignore as the first statement in your test.

② Even though this test would fail, we don’t get here because the ignore statement ends the
test at the line above.

An ignored test shows up in the IGNORED count report, and is not recorded in the TESTED, PASSED
or FAILED counts:

Chapter 5. Testing with Unity 51


Listing 46. The test report for a single ignored test

--------------------
OVERALL TEST SUMMARY
--------------------
TESTED: 0
PASSED: 0
FAILED: 0
IGNORED: 1

5.13. Failing Tests


Sometimes you just want a test to fail — usually under some special condition. In this case,
use TEST_FAIL():

Listing 47. Using TEST_FAIL to force a failure

void test_fail_under_special_condition(void)
{
  int i = 0;
  int j = 0;
  if ((i == 0) && (j == 0))
  {
  TEST_FAIL(); ①
  }
}

① If we don’t ever reach the TEST_FAIL() statement, then this test will pass.

5.14. Extra Messages


Sometimes you might like a test to give you a little more information when it fails. For these
cases, every TEST_ASSERT_type has a corresponding TEST_ASSERT_type_MESSAGE.

The TEST_ASSERT_type_MESSAGE takes one additional string argument — a message that is


printed if the test fails. This can be helpful for figuring out what went wrong when a test fails.

For example, TEST_ASSERT_EQUAL_INT(expected, actual) takes two parameters, so the message


variant TEST_ASSERT_EQUAL_INT_MESSAGE(expected, actual, message) takes three:

52 Chapter 5. Testing with Unity


Listing 48. Using a _MESSAGE test assertion to provide extra information on a failure

void test_integers_with_message (void)


{
  TEST_ASSERT_EQUAL_INT_MESSAGE(2, 1, "2 cannot equal 1!");
}

This failure reports our custom error message when it’s run:

Listing 49. The output from a failing _MESSAGE test assertion

Test: test_integers_with_message
 At line (108): "Expected 2 Was 1. 2 cannot equal 1!"

Chapter 5. Testing with Unity 53


Chapter 6. Mocking with CMock
CMock is the mocking framework used by default with Ceedling. Mocking is a way to replace
some functions with alternate implementations ("mocks") to aid testing. You can use these
mocks to simulate different return values and to verify that your code calls functions in a
particular order and with specific arguments.

6.1. What’s a Mock?


Mocks are alternate function implementations that that "stand in" for the real function
implementations during a test. Mocks are are used to break module dependencies so that
they can be tested in isolation. Mocks can be particularly useful in embedded software,
where we often have dependencies on the hardware. Mocking hardware dependencies
allows for unit testing on the host.

For example, here’s a function (from a temperature module) that calls an adc function:

Listing 50. temperature.c

#include "adc.h"

uint8_t is_temperature_over_limit (void)


{
  return (adc_get_sample() > 150);
}

This temperature module function has a dependency on another function — adc_get_sample,


which is implemented in the adc (analog-to-digital converter) module.

To test is_temperature_over_limit, we need to do something about this adc function. One


option would be to compile the adc module directly into our test and have the adc_get_sample
get called during this test. This probably isn’t going to work too well though, since the adc
module is likely heavily dependent on the hardware of our processor, e.g. reading/writing
processor registers. By actually using the adc module, we’d move our problem down the
dependency chain and have to figure out what to do with the dependencies in the adc module.

The other option (and the one detailed here) is to mock the adc module in this test. We can
use Ceedling and CMock to generate a mock version of the adc module and compile it into
our test for us. Once we mock a module with CMock we don’t need to worry about any of the
dependencies of the adc module anymore. We break the dependency chain and we can test the
original temperature module in isolation.

54 Chapter 6. Mocking with CMock


When mocking a module, we can test the interactions with it and simulate its behavior. In
tests of our temperature module we can ensure that specific adc calls are made (and in a
specific order) and simulate return values. So we can make adc_get_sample return whatever
value we want, which allows us to test all the possible cases of sample values.

Simulating hardware interactions like this analog-to-digital sample value


can be an extremely effective technique for testing embedded software.
Getting the real ADC hardware to return a specific sample value for testing
 might be difficult at best and impossible at worst. Even if we can set up the
conditions to do it, it’s not going to be very repeatable. Here though — by
mocking the interface in our unit test — we can easily test any value that we
want.

6.2. Creating a Mock


CMock creates mocks from the function prototypes in header files.

In our previous example, our temperature module used functions from the adc module by
including adc.h. This header file should contain prototypes for all of the adc functions that
can be used by other modules — i.e. the inteface for this module.

You must define your module interfaces in header files in order for CMock
to create mocks for them. This might be a common convention — but the C
 language doesn’t do anything to enforce it — so it’s possible (likely?) that you
may need to reorganize your code a bit to use CMock. See the Conventions
section of the Creating Modules and Running Tests chapter for more details.

To have generate mocks for a header file, all we need to do is include the mock header file
name in one of our unit test files. The mock header file name is the original header name
prepended with mock_.

For example, to create mocks for the functions in adc.h, we just include mock_adc.h in our
test instead:

#include "mock_adc.h"

Ceedling automatically creates this mock_adc.h file (and a corresponding implementation in


mock_adc.c) by using CMock to parse out the functions in adc.h. The test output shows that
the mock is created:

Chapter 6. Mocking with CMock 55


> rake test:temperature

Test 'test_temperature.c'
-------------------------
Creating mock for adc... ①
Generating runner for test_temperature.c...
Compiling test_temperature_runner.c...
Compiling test_temperature.c...
Compiling mock_adc.c... ②
Compiling unity.c...
Compiling temperature.c...
Compiling cmock.c...
Linking test_temperature.out...
Running test_temperature.out...

① The mock for the adc module is created.

② The mock is compiled.

Since CMock builds mocks from header files, you don’t even need an
implementation of a module to create a mock for it. This can be useful for
 defining (and testing against) interfaces that you’re going to need but don’t
exist yet. You can come back and implement the modules behind those
interfaces, but still develop unit tests for other modules that might use them.

6.3. Using a Mock


For each function CMock mocks, it generates a bunch of new functions that you use to control
the mock function. These new functions are based on the name and parameters of the
original function. For example when adc_get_sample() is mocked, this new function is
created:

void adc_get_sample_ExpectAndReturn(uint8_t return_value);

If you want to see all the mock control functions that CMock has generated
 for a particular module, you can find them in the .h files in
build/test/mocks.

You use this function in your tests to tell CMock that you expect adc_get_sample to be called
and what value you want it to return. You do this by calling adc_get_sample_ExpectAndReturn

56 Chapter 6. Mocking with CMock


before adc_get_sample would get called. Note that the return_value argument for the
ExpectAndReturn function is based on the return type of adc_get_sample. Here’s how you
would use it in a test:

Listing 51. Using a mock to expect a function call and return a value (test_temperature.c)

#include "unity.h"
#include "temperature.h"
#include "mock_adc.h"

void setUp(void) {}
void tearDown(void) {}

void test_temperature_over_limit(void)
{
  adc_get_sample_ExpectAndReturn(151); ①
  TEST_ASSERT_TRUE(is_temperature_over_limit()); ②
}

① Use CMock to mock a return value of 151 when adc_get_sample is called.

② Call the function under test. When this happens, the mock adc_get_sample is called and 151
is returned. Then we use a Unity test assertion to verify that is_temperature_over_limit
returns the correct value.

We can also test the other case (when the temperature is not over the limit) by returning a
different value from adc_get_sample:

Listing 52. Using a mock to return a different value (test_temperature.c)

void test_temperature_not_over_limit(void)
{
  adc_get_sample_ExpectAndReturn(150); ①
  TEST_ASSERT_FALSE(is_temperature_over_limit()); ②
}

① If the sample value is 150, then…

② …the temperature should not be over the limit.

It’s important to note that CMock is strict about expectations. If a function is called but you
don’t set up an expectation for it, CMock will fail your test. So if we write a test like this:

Chapter 6. Mocking with CMock 57


Listing 53. A test that will fail when a mock call occurs but hasn’t been expected

void test_that_fails_for_unexpected_call(void)
{
  is_temperature_over_limit();
}

it will fail because adc_get_sample will get called, but we didn’t tell CMock to expect it. Here’s
what the error output looks like when running:

Listing 54. CMock will fail a test if an unexpected call is made.

-------------------
FAILED TEST SUMMARY
-------------------
[test_temperature.c]
  Test: test_that_fails_for_unexpected_call
  At line (25): "Function adc_get_sample. Called more times than expected."

6.4. Multiple Expectations


You can expect multiple calls in a sequence. If you expect more than one mocked function to
called during a test, you just list all your expected calls in the expected order.

Listing 55. A test with multiple mocked function calls expected

#include "mock_bar.h"

void test_multiple_expected_calls (void)


{
  bar_a_Expect(); ①
  bar_b_Expect();
  bar_c_Expect();
  bar_b_Expect();

  foo_function_to_test(); ②
}

① Expect a bunch of calls in a particular order.

② The test passes if foo_function_to_test calls the expected functions in the expected order.

58 Chapter 6. Mocking with CMock


6.5. Mocking Return Values
If a mocked function returns a value, an ExpectAndReturn function is generated. If a function
returns void, an Expect function is generated. The ExpectAndReturn function always takes as
its first argument the return value to provide during a test.

6.6. Expecting Arguments


If your mock function takes arguments, your expectations specify what values you expect
with each function call. For example, consider another adc module function that configures
the the adc:

Listing 56. A function to initialize the adc — it takes a channel number as an argument

void adc_initialize (uint8_t channel);

The temperature module might want to initialize the adc from its own
temperature_initialize function:

Listing 57. A temperature module initialization function

void temperature_initialize (void)


{
  adc_initialize(3);
}

When adc_initialize(uint8_t channel) is mocked, it doesn’t create an ExpectAndReturn


function. Because there is nothing returned (adc_initialize returns void) it instead generates
an Expect function that looks like this:

void adc_initialize_Expect(uint8_t channel);

This Expect function takes a single argument — the channel number from the original
function. If the original function had more arguments, the generated Expect function would
have corresponding arguments for each of them.

When we call adc_initialize_Expect from a test, the value we pass for channel tells CMock
what value we expect to be passed to adc_initialize from the function under test.

Here’s a test for temperature_initialize that expects adc_initialize to be called with channel

Chapter 6. Mocking with CMock 59


3:

Listing 58. A test expecting adc_initialize to be called with 3 as an argument

void test_correct_channel_is_initialized(void)
{
  adc_initialize_Expect(3); ①
  temperature_initialize(); ②
}

① Expect adc_initialize to be called with channel = 3.

② Call the function under test.

There is no Unity test assertion (like TEST_ASSERT_EQUAL()) here because it’s


 not needed. All we’re checking is that adc_initialize is called with the
correct argument — and CMock does this for us.

If the mocked call isn’t called with the arguments that we expect, then the test will fail with a
message from CMock. If we changed the value we expect so that the test will fail:

Listing 59. Expect the wrong argument to to adc_initialize so that this test will fail

void test_that_fails_for_incorrect_channel_initialization(void)
{
  adc_initialize_Expect(4); ①
  temperature_initialize();
}

① Expect the wrong value, channel = 4.

This test now fails with a message showing us what went wrong and where:

Listing 60. Test failure for an unexpected mock argument value

-------------------
FAILED TEST SUMMARY
-------------------
[test_temperature.c]
  Test: test_that_fails_for_incorrect_channel_initialization
  At line (34): "Expected 0x04 Was 0x03. Function adc_initialize Argument
channel. Function called with unexpected argument value."

60 Chapter 6. Mocking with CMock


This message tells us everything we need to know:

• The line number of the Expect call in the test.

• The expected value and the actual value that was encountered.

• The name of function involved.

• The offending argument name.

6.7. Ignoring Arguments


If you don’t care about a specific argument value passed to your mocked function you can tell
CMock to ignore it.

 This feature is not enabled by default.

How to enable ignoring of arguments

Ignoring arguments requires enabling of the ignore_arg CMock plugin in your


project.yml file. By default, the CMock plugin configuration in project.yml should look
like this:

:cmock:
  :plugins:
  - :ignore
  - :callback

You want to add ignore_arg to the list:

:cmock:
  :plugins:
  - :ignore
  - :callback
  - :ignore_arg

For more details see Enabling CMock Plugins in Chapter 8: Changing the Configuration.

For each function with arguments, a new function is generated for each argument that lets
you tell CMock to ignore it. So for our adc_initialize function:

Chapter 6. Mocking with CMock 61


void adc_initialize (uint8_t channel);

This IgnoreArg function is created:

void adc_initialize_IgnoreArg_channel(void);

You use it by calling it after you’ve expected a call. So if we wanted to test adc initialization,
but didn’t care what channel was initialized, we’d do this:

Listing 61. Ignoring an argument in an expected mock call

void test_initializaton_but_dont_care_about_channel(void)
{
  adc_initialize_Expect(0); ①
  adc_initialize_IgnoreArg_channel(); ②
  temperature_initialize();
}

① Expect adc_initialize. It doesn’t matter we pass in for channel here since we’re going to
ignore it.

② Tell CMock to ignore any channel argument received.

6.8. Arguments Passed by Reference


If the adc module could be configured by passing in reference to a structure, it might have a
structure and configure function signature that look like this:

Listing 62. A function that takes a reference to an structure as an argument

typedef struct {
  uint8_t channel;
  uint32_t clock_rate;
  uint32_t sample_rate;
} adc_config_t; ①

void adc_initialize_configuration (adc_config_t * configuration); ②

① A new configuration struct type.

② This initialization function takes a pointer to the adc_config_t structure as an argument.

62 Chapter 6. Mocking with CMock


Our temperature module might configure it from a different initialization function, by
passing in a reference to a structure that we create:

Listing 63. Calling a function that takes a reference to a structure as an argument

void temperature_initialize_with_config (void)


{
  adc_config_t config = {0}; ①

  config.channel = 3; ②
  config.clock_rate = 40000000;
  config.sample_rate = 1000;

  adc_initialize_configuration(&config); ③
}

① Create a new adc_config_t struct. Note the zero initialization of the structure with = {0},
this is very important see The Trouble with Comparing Structures as Memory in Chapter 5:
Testing with Unity for details.

② Set the values to pass to the config function.

③ Pass a reference to this struct to the initialization function.

How would we test (with a mock) that adc_initialize_configuration was called with a struct
containing our expected values? Well, CMock makes it easy because you can expect a pointer
argument and it will automatically test the data the pointer points to and not the pointer
value itself. Pretty slick! It looks like this:

Listing 64. Testing a structure passed as an argument to a mock

void test_initialization_with_pointer_to_config_struct (void)


{
  adc_config_t expected_config = {0}; ①

  expected_config.channel = 3; ②
  expected_config.clock_rate = 40000000;
  expected_config.sample_rate = 1000;

  adc_initialize_configuration_Expect(&expected_config); ③

  temperature_initialize_with_config(); ④
}

Chapter 6. Mocking with CMock 63


① Create our own adc_config_t struct to compare to the actual one used in the call. Again
note the zero initialization of the structure with = {0}.

② Set our structure to contain the values we expect.

③ Set up the expectation for CMock that adc_initialize_configuration will be called.

④ Call the function under test.

When this test is run, CMock compares the memory pointed to by expected_config with the
memory provided in the call to adc_initialize_configuration through the configuration
argument pointer. This tests that we passed the data we expected.

6.9. Expecting a Pointer Value

 This feature is not enabled by default.

When you expect a pointer argument, by default CMock will compare the data referenced by
the pointer. If you want to verify the value of a pointer and not the data it points to, you need
to change the CMock configuration in project.yml.

This is a global setting. If you change this, you won’t be able to compare
 pointer data in any other tests.

To enable this you need to set :when_ptr: to :compare_ptr under the :cmock: settings in
project.yml. For more details see the CMock Options section in Chapter 8: Changing the
Configuration.

Listing 65. A project configuration file (project.yml) set to compare pointer values

:cmock:
  :when_ptr: :compare_ptr

Try not to need this. The default behavior — to compare the data — is much
 more useful in most circumstances.

6.10. Arguments Returned by Reference

 This feature is not enabled by default.

64 Chapter 6. Mocking with CMock


How to enable returning data through pointer arguments

This requires enabling of the return_thru_ptr CMock plugin in your project.yml file.
Add it to the this list:

:cmock:
  :plugins:
  # ... other plugins ...
  - :return_thru_ptr

For more details see the Enabling CMock Plugins section in Chapter 8: Changing the
Configuration.

In C, it’s common to return data from functions by putting it into structures passed in
through pointers. If we wanted to get the current configuration of our adc module, we might
have a function called adc_get_configuration that takes a pointer argument to an
adc_config_t:

Listing 66. A function that returns data in a structure passed in by a pointer

typedef struct {
  uint8_t channel;
  uint32_t clock_rate;
  uint32_t sample_rate;
} adc_config_t;

void adc_get_configuration (adc_config_t * configuration);

This function works by returning the current configuration settings in the adc_config_t
structure passed in. It might be used from the temperature module in
is_temperature_configured:

Chapter 6. Mocking with CMock 65


Listing 67. A function that uses a function that returns a structure through a pointer argument

// Check that things are configured correctly.


bool is_temperature_configured(void)
{
  adc_config_t config = {0};
  adc_get_configuration(&config);

  return (config.channel == 3)
  && (config.clock_rate == 40000000)
  && (config.sample_rate == 1000);
}

If we want to test this function, we need to do three things:

1. Expect adc_get_configuration to be called.

2. Return a "mocked" adc_config_t to the caller through the configuration argument.

3. Ignore the configuration argument passed in to adc_get_configuration. We don’t actually


care about the pointer value or its data is when it is passed in — it’s only valid on the way
out.

In a test, this looks like:

Listing 68. A test mocking a structure returned through a pointer argument

void test_temperature_configuration_check(void)
{
  adc_config_t mock_config = {0}; ①
  mock_config.channel = 3;
  mock_config.clock_rate = 40000000;
  mock_config.sample_rate = 1000;

  adc_get_configuration_Expect(0); ②
  adc_get_configuration_IgnoreArg_configuration(); ③
  adc_get_configuration_ReturnThruPtr_configuration(&mock_config); ④

  TEST_ASSERT_TRUE(is_temperature_configured()); ⑤
}

① Create a "mock" configuration structure. This is what will be returned to the caller when
adc_get_configuration is mocked.

66 Chapter 6. Mocking with CMock


② Expect adc_get_configuration to be called. Note that we set the expected configuration
argument to zero. We could put anything here though because we’re going to ignore it.

③ Ignore the configuration argument passed in to adc_get_configuration. This is most likely


a pointer to an uninitialized struct on the stack, and we don’t care about this pointer or its
data before adc_get_configuration executes.

④ Return our "mock" configuration when the function is called.

⑤ Use a Unity test to verify that is_temperature_configured() returns the correct value.

6.11. Strings as Arguments


Strings are checked with string compararisons, and you can put them right in your
expectations. Here’s (yet) another adc initialization function — one that just logs a message:

Listing 69. A dependency on a function with a string argument

#include "logger.h" ①

void temperature_initialize_with_log (void)


{
  log_message("temperature module initialized"); ②
}

① The log_message function is provided in the logger module.

② Log a message during initialization.

In a test, we can expect log_message to be called with whatever string we want:

Listing 70. A test expecting a mock function call with a string argument

#include "mock_logger.h" ①

void test_initialization_with_log_of_string(void)
{
  log_message_Expect("temperature module initialized"); ②

  temperature_initialize_with_log();
}

① We need to mock the logger module

② Expect this string to be logged.

Chapter 6. Mocking with CMock 67


Don’t use the name "log" for any of your modules. There’s something
 internal to CMock that uses that name. I found out the hard way when
creating this example.

6.12. Ignoring Function Calls


Sometimes it’s handy to ignore function calls to mocked modules during a test. Consider this
function that logs a couple messages while it does it’s work:

Listing 71. A function that logs messages while executing

bool is_temperature_over_limit_with_log (void)


{
  log_message("Getting sample...");
  uint8_t sample_value = adc_get_sample();
  log_message("...got sample");

  return (sample_value > 150);


}

If we don’t really care about what the log messages are, we can ignore these function calls in
our tests. This makes the tests simpler.

Listing 72. Ignore specific a function call during a test

void test_while_ignoring_log_functions(void)
{
  log_message_Ignore(); ①
  adc_get_sample_ExpectAndReturn(151);

  TEST_ASSERT_TRUE(is_temperature_over_limit_with_log()); ②
}

① Ignore all log_message calls.

② Call the function under test.

Once you ignore a function it gets ignored for the rest of the test. You can’t
 go back and "unignore" it.

68 Chapter 6. Mocking with CMock


6.13. Arrays as Arguments
This feature is not enabled by default. The CMock array plugin must be
 enabled in the project.yml configuration file. See the Enabling CMock
Plugins section in Chapter 8: Changing the Configuration for more details.

When the array plugin is enabled, CMock provides an ExpectWithArray function your each
mocked function that contain pointer arguments. For each pointer argument the
ExpectWithArray function takes an additional argument which specifies the number of array
elements to compare. For example, mocking this function:

void bar_with_int_array(int * data);

creates this function:

void bar_with_int_array_ExpectWithArray(int * data, int number_of_elements);

If we had a function that called bar_with_int_array like this:

#include "bar.h"

void foo_function_with_array_argument_call(void)
{
  int luggage_code[5] = {1, 2, 3, 4, 5};
  bar_with_int_array(luggage_code);
}

we could write a test that checks the array passed in like this:

Chapter 6. Mocking with CMock 69


Listing 73. Test an array passed to a mocked function

#include "mock_bar.h"

void test_call_with_array_argument (void)


{
  int expected_data[] = {1, 2, 3, 4, 5}; ①
  bar_with_int_array_ExpectWithArray(expected_data, 5); ②

  foo_function_with_array_argument_call();
}

① The array we expect to be passed to bar_with_int_array.

② Use the ExpectWithArray function to this. Note that we need to provide the number of
elements to test — 5 in this case.

 You can use array checking with pointers to custom structures too.

6.14. Using Callbacks to Take Control


When you need to mock something complicated, you always have the option to use CMock’s
StubWithCallback functionality. This allows you to create your own function to be called in
place of any mocked function.

For each mocked function, CMock creates a corresponding StubWithCallback function which
takes a function pointer as argument. You define and provide this function to implement a
custom callback. The signature for this function is the same as the original function, with one
additional argument — an int which provides the number of times the callback has been
called.

So if you have function that looks like this:

bool my_function (uint8_t a_value);

Your callback function needs to look like this:

bool my_function_mock (uint8_t a_value, int call_count);

70 Chapter 6. Mocking with CMock


The name of your custom callback function isn’t important — it just needs to
 be different than the original.

Whatever you return from your callback is what is provided to the calling function during
your test — just like the custom callback was called instead of the mock function.

In the example below, is_temperature_over_limit calls adc_get_sample. If the value read is


greater than 150, it returns true. A mock callback function is created here to be called in
place of adc_get_sample. This mock function simply returns the call_count, so that it will
return an increasing series of positive integers.

 The call_count starts at zero.

Listing 74. Using a custom callback function as a mock

uint8_t mock_adc_get_sample(int call_count) ①


{
  return call_count;
}

void test_temperature_is_filtered(void)
{
  adc_get_sample_StubWithCallback(mock_adc_get_sample); ②
  for (int i = 0; i <= 150; i++)
  {
  TEST_ASSERT_FALSE(is_temperature_over_limit()); ③
  }
  TEST_ASSERT_TRUE(is_temperature_over_limit()); ④
}

① Create a custom mock callback function.

② Register your callback to be called whenever adc_get_sample is called.

③ Each adc_get_sample returns 0 through 150.

④ Once we’ve called our callback 151 times (0 through 150), it will start returning a value
over the limit (151).

You can also verify arguments (or anything else) in your custom callback
 functions with TEST_ASSERT calls.

Chapter 6. Mocking with CMock 71


Chapter 7. How Ceedling Knows What to
Test
One of the things that’s so great about Ceedling is how you can easily control what source
files are built into each of your tests. Understanding how this works is important so that you
can break dependencies for testing modules in isolation. Breaking dependencies is also
critical for making embedded software compilable and runnable on your host computer. In
this chapter we look at how this works in more detail.

When you build your application, you typically have a whole bunch of source files that are
compiled and linked into a single binary that are then loaded on the the target.

Figure 3. Building your complete application.

The goal of a unit test is to test a software module in isolation. This what you get by default
when you use Ceedling to create a module with rake module:create[]. A corresponding test
file is created for each source module file. When Ceedling "runs" each of these tests, it
compiles a separate binary for each test and runs it on the host:

72 Chapter 7. How Ceedling Knows What to Test


Figure 4. Building individual tests with Ceedling.

So… why does Ceedling create all these separate test binaries, instead of just compiling
into one monolithic test binary? Well, one reason is that it’s easy to run a single test just
by running the binary for that test. The other reason has to do with breaking the
dependencies between the modules in your application, which we’ll see in a moment.

When Ceedling runs, how does it know that test_module_a.c needs to be compiled and linked
with module_a.c — and not any of the other files in your application? Well, it has to do with
the files that are included (with #include) in the test source file.

Chapter 7. How Ceedling Knows What to Test 73


When you use rake module:create[module_a], it creates default test file for module_a that
looks like this:

Listing 75. Basic test file (test_module_a.c) created by the module generator

#include "unity.h"
#include "module_a.h" ①

void setUp(void)
{
}

void tearDown(void)
{
}

void test_module_generator_needs_to_be_implemented(void)
{
  TEST_IGNORE_MESSAGE("Implement me!");
}

① module_a.h is included by default.

You can see that the module generator included module_a.h for us by default. This is the
convention used to tell Ceedling to look for module_a.c and build it into our test. Ceedling
assumes that there is corresponding .c file for each header file included in the test. This is
convenient because we’re using Ceedling to manage the building of the test right from the
test source code. We don’t need to mess with Make or some other tool to specify how to build
the tests.

This will become even more important when we have modules with dependencies.

The name of the test source file (test_module_a.c) does not influence what
Ceedling actually tries to build into the test. This name is just a convention
 for us to use, so that we know what is being tested. If the test file named
test_module_a.c had a #include "module_b.h", Ceedling would compile and
link module_b into the test instead.

7.1. Dealing with Dependencies


In the previous diagrams, there where no dependencies between module_a, module_b or
module_c in the application. This isn’t how things are in the real world though. Modules

74 Chapter 7. How Ceedling Knows What to Test


need to call functions in other modules. When module_a calls functions in module_b, we say
that module_a is dependent on module_b. We can’t run (or build) module_a without
module_b.

Figure 5. module_a depends on module_b.

So if we start calling module_b functions from module_a, we can’t build and run our tests
for module_a like we used to. Ceedling needs a little more information to determine how to
deal with module_b.

There are two options at this point. You select the one you want by how you include the
module_b.h header in the test file.

1. Compile module_b.c into the test.

Ceedling does this if you include module_b.h into the test with #include "module_b.h".
When functions in module_b are called, the code in module_b is executed.

Figure 6. Test module_a and module_b in the same test.

Here we’ve actually created an integration test — testing both module_a


 and module_b at the same time. This also requires that module_b
doesn’t have any of its own dependencies.

2. "Mock" module_b in the test, breaking the dependency on module_b.

Ceedling does this if you #include "mock_module_b.h" in the test file. This header file
doesn’t exist but Ceedling automatically creates it. Ceedling also creates the
corresponding mock_module_b.c, which is what gets linked in to the test.

Chapter 7. How Ceedling Knows What to Test 75


Figure 7. Test module_a in isolation by mocking module_b.

The mock module doesn’t contain any of the implementation of the original module.
Instead it contains mocks (or stubs) for each of the module_b function calls, which satisfy
the dependencies of module_a, and allow us to compile the test. The mock also allows us
to simulate and test the interactions between module_a and module_b.

Ceedling knows what functions it must create stubs for by the functions that are declared
in module_b.h.

Being able to independently unit test the modules in your application is all
about managing the dependencies between them. The more dependencies a
unit has, the more difficult it is going to be to test it in isolation.

This means you’re going to want to design your software to minimize


 dependencies. This includes how many modules a module is dependent on (is
module_a dependent only on b, or does it depend on b, c, d and e?) as well
as the details of each module dependency (does module_a call only a few,
well-defined functions in b, or is it calling 30 different functions in a
complicated sequence?).

7.2. Integration Testing


Sometimes unit testing isn’t going to work for you. If you have a few modules that work
closely together, it may be difficult to mock all of their interactions so that you can test them
independently. In this case you’ll want to create an integration test. For our purposes here an
integration test is simply a test of multiple modules at the same time.

Consider an application with multiple dependencies:

76 Chapter 7. How Ceedling Knows What to Test


Figure 8. More complicated dependencies.

In this example, module_a depends on both module_b and module_c. Both module_b and
module_c depend on module_d.

As discussed previously in this chapter, we control what files are tested by the way we
include header files in the test file.

You could create a unit test for module_a by mocking module_a and module_b. The headers
included in this case would be:

Listing 76. Include statements to create an isolated unit test for module_a

#include "module_a.h"
#include "mock_module_b.h"
#include "mock_module_c.h"

Figure 9. Testing module_a in isolation by mocking module_b and module_c.

Chapter 7. How Ceedling Knows What to Test 77


However, what if module_a, module_b, and module_c are "tightly-coupled," i.e. they’ve got a
lot of complicated functions calls going back and forth? Since both module_b and module_c
are only dependent on module_d, it may make sense to break the dependencies at module_d
and test modules a, b and c together (and mock module_d).

You might name your test file test_module_a_b_c.c, and the included headers would look
like:

Listing 77. Include statements to create an integration test for modules a, b and c

#include "module_a.h"
#include "module_b.h"
#include "module_c.h"
#include "mock_module_d.h"

Figure 10. An integration test built by including module_a, module_b, and module_c (and mocking
module_d)

Grouping modules together like this is a strategy that can be useful when trying to add tests
to existing code. If there are many, complicated inter-module dependencies, it may make
sense to attempt to break dependencies only where they are a bit simpler.

Summary
• You can test one or more software modules at a time with Ceedling.

• Testing units in isolation requires breaking the dependencies between modules with
mocks.

• Include a module in your test by including it in the test file (e.g. #include "module_b.h").

78 Chapter 7. How Ceedling Knows What to Test


• Mock a module in your test by including it in the test file with the mock_ prefix (e.g.
#include "mock_module_b.h").

• Create integration tests by including multiple module headers in your test, creating mocks
for modules only where you need to break dependencies.

Chapter 7. How Ceedling Knows What to Test 79


Chapter 8. Changing the Configuration
8.1. The Configuration File (project.yml)
Ceedling has a lot of configurable options. The way you change them is in the project.yml
configuration file. When you install Ceedling in a project the project.yml file is created in the
top level folder.

The project.yml file is a YAML file. YAML a simple file syntax for setting key-value pairs.
Indentation is used to create hierarchies and sequences (lists) are created with dash (-)
characters. You should be able to understand the format by looking at the file that Ceedling
creates by default — it has a enough entries in there to see the patterns.

In this section, heirarchies are indicated with the arrow (→) character. So
 :project: → :build_root: is the setting for the :build_root: located under
the :project: setting.

Comments start with # characters. Comments can start anywhere in a line and go to the end
(just like // in C).

One thing to watch out for is that YAML files must uses spaces for
 indentation. Do not use tabs for indentation in your project.yml file or you
may run into problems.

Here’s an example of some YAML:

Listing 78. Sample YAML file content

# This is a comment.
:project:
  :build_root: build ①
  :test_file_prefix: test_
:plugins:
  :enabled:
  - stdout_pretty_tests_report ②
  - module_generator # Here is another comment.

① A simple key-value pair (nested below :project:. This tells Ceedling the name to use for
the "build" folder.

② Sequences (or lists) are created with dashes.

80 Chapter 8. Changing the Configuration


For nested settings, the order in which they are listed doesn’t matter. In the above example
:build_root: is listed before :test_file_prefix:, but their positions could be reversed and
everything would work the same. The hierarchy is what is really important. Both of these
settings must be under the :project: setting (which is done with indentation).

For list elements, the order may be important — like when setting the compiler and linker
command line flags.

YAML uses colons between the key and the value, but the colons at the
beginning of each key (like in :project:) are a Ruby thing. If you look for
 YAML help elsewhere you won’t see these extra leading colons, but Ceedling
needs them.

Ceedling also has internal default settings which are used if you don’t set everything in
project.yml. This also means that the default project.yml created does not have all of the
possible settings. Anything that you put in project.yml takes precedence over any default
internal setting.

We’ll cover some of the important options here, but check out the Ceedling documentation in
vendor/ceedling/docs to find out about more options.

8.2. Changing Source File Paths


By default, Ceedling expects your test files to be in the test folder and your source files to be
in the src folder. You can change this easily though with the :paths: section of the
configuration file. There are three different path settings: :test:, :source: and :support:.
Here’s what the default settings look like:

Listing 79. The default file path settings

:paths:
  :test: ①
  - +:test/**
  - -:test/support
  :source: ②
  - src/**
  :support: ③
  - test/support

① The :test: paths are where Ceedling looks for test files to run.

② The :source: paths are where Ceedling looks for your source code.

Chapter 8. Changing the Configuration 81


③ The :support folder is where you can put helper modules to use in your tests. This folder is
not searched for test files, but it’s contents can get compiled into tests.

 All relative paths are relative to the project root.

8.2.1. Recursively Including All Paths

The ** is a special glob syntax to include the entire tree below a particular folder. Thus src/**
includes the folder named src as well as all folders (at any depth) below it.

8.2.2. Adding Paths

Each of the path configurations takes a list of paths below it, using the dash (-) to start each
line. You can add to paths just by adding a line. To add an additional source file path — maybe
for a third party library stored elsewhere on your machine — just add another item to the
:source: entry:

Listing 80. Adding an absolute path to the source file paths

:paths:
  :source:
  - src/**
  - C:\path\to\libary ①

① Add another source path. Note that you can use absolute paths too like this Windows
machine path.

8.2.3. Excluding Paths

The default settings for the :test: paths show us how to select and exclude particular
folders — put a +: in front of an item to add it, and use a -: to exclude it:

Listing 81. The default test path settings show how to select individual paths for inclusion and
exclusion

:paths:
  :test:
  - +:test/** ①
  - -:test/support ②

① Include the entire tree below the test folder…

82 Chapter 8. Changing the Configuration


…execept for the folder test/support (we don’t want to look for unit tests in there).

To see the configured paths — includling an explict listing of all recursively


 include paths — run rake paths:source, rake paths:test or rake
paths:support.

8.3. Including/Excluding Individual Files


Usually you want to use the :paths: settings to tell Ceedling where your source is. That way
you don’t have to change the configuration every time you add a file.

Sometimes though, you might want to include or exclude a particular file. In this case there is
the :files: setting. It’s not included in the default project.yml, but you can add it anywhere
at the top level of the YAML hierarchy (i.e. no indentation before :files:). You can use it to
add and remove :source:, :test: or :include: files:

Listing 82. Including and excluding individual files

:files:
  :source:
  - -:src/temp.c ①
  :test:
  - -:test/*_temp.c ②
  :include:
  - +:C:\path\to\library\api.h ③

① Exclude a single source file from the source folder.

② Exclude all files in the test folder that end with _temp.

③ Add a single file from an absolute path.

To see the files found in each of your configured paths try rake files:source,
 rake files:test or rake files:header.

8.4. Changing the Build Output Paths


By default all of Ceedling’s build output — object files, binaries, generated code like mocks
and test runners — are all created in the build folder. You can change this though with the
:build_root: setting. It’s under the :project::

Chapter 8. Changing the Configuration 83


:project:
  :build_root: build ①

① Change this to change the name of the build folder.

8.5. Changing Test File Names


The default is that test file names need to start with test_. But what if you hate that naming
convention? If you’re really into CamelCase you might change the test file name prefix to
Test:

:project:
  :test_file_prefix: Test ①

① This is what every test file name must start with.

8.6. Setting Preprocessor Definitions


Sometimes you need to set a #define in order to get your test code to build. This is especially
common with hardware interfaces, where vendor code uses this as a way to set the processor
type (or other hardware-specific options).

For this there is the top-level :defines: section. To set a definition during your unit tests, add
it to the :defines: → :test: list.

Listing 83. Adding preprocessor definitions for unit tests

:defines:
  :commmon: &common_defines []
  :test: ①
  - *common_defines
  - TEST
  - STM32F4 ②
  - USE_RTOS=1 ③

① Add your own preprocessor definitions to the :test: list under :defines:.

② Here’s an example equivalent to #define STM32F4.

③ This is equivalent to #define USE_RTOS 1.

84 Chapter 8. Changing the Configuration


These definitions get passed to the compiler at the command line.

Notice that TEST define up there? By default TEST is defined when running
your unit tests. That means you can do special things in your code inside
#ifdef TEST blocks that will only get included when your unit tests are run.
 This isn’t always a great idea, but can be useful in some situations. Also note
that this can cause a problem in some builds… if you already define TEST in
your own code. You can remove it from here (or change its name) to address
this problem.

8.7. Enabling Plugins


Ceedling has a plugin architecture that allows it to be extended more easily. A few of its more
common features are actually implemented as plugins. Plugins can be enabled from the
project.yml file under :plugins:. Here are the default settings:

Listing 84. The default Ceedling plugin configuration

:plugins:
  :load_paths: ①
  - vendor/ceedling/plugins
  :enabled: ②
  - stdout_pretty_tests_report
  - module_generator

① The :load_paths: are where Ceedling will attempt to load plugins from. If you take a look
in the default vendor/ceedling/plugins folder, you’ll see a few plugins to choose from.

② This is the list of plugins to enable. These are the names of folders found in the
:load_paths:.

CMock also has plugins. When working with Ceedling plugins, make sure
 you’re at the top level of the YAML file heiarchy — and not under :cmock:.

Many of the included plugins modify the reporting functionality — e.g. there are plugins for
gtest, ide, teamcity, xml, etc. There is also a plugin for gcov (the GNU test coverage tool) that
looks like it could be interesting.

Also there is a fake_function_framework plugin (full disclosure: I wrote it) that uses the fake
function framework (FFF) as a mocking library instead of CMock.

Where possible, look for a README file in the plugin folder that explains what it is or how to

Chapter 8. Changing the Configuration 85


use it.

8.8. Unity Options


Unity has many options as described in the Unity documentation
(vendor/ceedling/docs/UnityConfigurationGuide.pdf). These are all set with preprocessor
definitions, and can be set in project.yml in the :unity: → :defines: section:

Listing 85. Configuring Unity with preprocessor definitions

:unity:
  :defines:
  - UNITY_INT_WIDTH=16 ①
  - UNITY_INCLUDE_DOUBLE ②

① Set the width of an int to 16 bits.

② Enable all of the TEST_ASSERT_DOUBLE_ test assertions.

You should prefer to use stdint.h types like uint16_t instead of plain int, so
 settings like UNITY_INT_WIDTH=16 won’t be necessary. I do… and I don’t
typically find that I need to set Unity configuration options like this.

8.9. CMock Options


CMock also has a variety of options that can be configured in project.yml in the :cmock:
section. These options are detailed in vendor/ceedling/docs/CMock Summary.pdf, but we’ll
cover the basics here.

8.9.1. Enabling CMock Plugins

CMock has its own plugins, enabled by adding them to the list in :cmock: → :plugins:. The
default plugins are:

Listing 86. The default CMock plugin configuration

:cmock:
  :plugins:
  - :ignore
  - :callback

86 Chapter 8. Changing the Configuration


There are few plugins to choose from and some of them are necessary for using the
functionality described in Chapter 6: Mocking with CMock. Here are all of the options:

ignore
Enables Ignore functions for ignoring calls to mocked functions.

ignore_arg
Enables IgnoreArg functions for ignoring specific arguments passed to mock functions.

array
Enables ExpectWithArray functions for testing arrays passed to mocked functions.

callback
Enable StubWithCallback functions for providing your own callback functions to be called
instead of mocked functions.

return_thru_pointer
Enable ReturnThruPtr functions for returning data through pointer arguments provided to
mocked functions. This also enables ReturnArrayThruPtr and ReturnMemThruPtr
functions.

8.9.2. Change the Naming Convention of Mock Files

The default name for mock files is to prepend a header file name with mock_. If you want to
change this, set :cmock: → :mock_prefix: to whatever convention you want:

:cmock:
  :mock_prefix: Mock ①

① The mock for SpiHal.h will be named MockSpiHal.h.

8.9.3. Mocking extern Functions

By default, Ceedling won’t mock a function that is prototyped with an extern. So if the
function below was found in a header file you wanted to mock, a mock wouldn’t be
generated for it:

extern uint8_t read_uart (void);

To mock this function set :cmock: → :treat_externs:

Chapter 8. Changing the Configuration 87


:cmock:
  :treat_externs: :include ①

① This will generate mocks for extern function prototypes.

This is sometimes needed for legacy code that uses extern statements in its function
prototypes.

8.9.4. Including Additional Header Files in Mocks

Sometimes the header files you want to mock don’t #include all of the header files that they
need — and you can’t change the code for whatever reason to fix it.

For example, I’ve worked with libraries that define functions like this:

bool is_spi_ready(void);

but don’t #include <stdbool.h>. This will cause CMock problems during compilation. But you
can use the :cmock: → :includes: list to have CMock include additional header files in each of
the generated mock files.

:cmock:
  :includes:
  - <stdbool.h>
  - <stdint.h>

Whatever you list here will be included with a #include in the generated mock files, allowing
them to compile correctly.

You can use this to include your own header files too — not just standard
 ones like stdbool and stdint.

8.9.5. Pointer Handling Options

When you expect a pointer argument in a mocked function the default behavior is to test the
data referenced by the pointer. If you want to test the value of the pointer instead, set :cmock:
→ :when_ptr: to :compare_ptr

88 Chapter 8. Changing the Configuration


:cmock:
  :when_ptr: :compare_ptr

 This is a global setting, so changing it could break other tests.

8.9.6. Disable Call Order Checking

By default, Ceedling configures CMock to use strict call order checking. That means that your
function under test must call your mocks in the exact order that you expect them. For
example, if your function under test looks like this:

Listing 87. A function that calls three functions in a particular order

void functions_called_out_of_order(void)
{
  function3();
  function2();
  function1();
}

And your test expects them in a different order:

Listing 88. A test expecting mocked functions to be called in a different order

void test_functions_out_of_order(void)
{
  function1_Expect();
  function2_Expect();
  function3_Expect();

  functions_called_out_of_order();
}

then your tests will fail.

You can turn off this "order checking" by setting :cmock: → :enforce_strict_ordering: to
FALSE:

Chapter 8. Changing the Configuration 89


:cmock:
  :enforce_strict_ordering: FALSE

This will allow the test above to pass.

8.10. Customizing the Complier and Linker


8.10.1. Checking the Current Settings

Usually you can only see the complier commands executed by Ceedling when there is an
error. To see them during a successful run, set the verbosity to 4. Set it when running a rake
task like this:

> rake verbosity[4] test:all

You need to set the verbosity each time you run rake. The setting is not saved
 between runs.

When looking at the output, you’ll notice that Ceedling does a few different types of
operations, and they use different compiler options:

90 Chapter 8. Changing the Configuration


Test 'test_functions.c'
-----------------------
> Shell executed command: ①
gcc.exe -E -MM -MG -I"test" -I"test/support" -I"src" -I"C:/dev/unity/src"
-I"C:/field-manual-for-ceedling/examples/8-changing-the
-configuration/vendor/ceedling/vendor/unity/src" -I"C:/field-manual-for
-ceedling/examples/8-changing-the-configuration/vendor/ceedling/vendor/cmock/src"
-I"build/test/mocks" -DTEST -DTEST -DGNU_COMPILER -w
"build/temp/_test_functions.c"
> Produced output:
_test_functions.o: build/temp/_test_functions.c C:/dev/unity/src/unity.h \
 C:/dev/unity/src/unity_internals.h @@@@unity.h src/functions.h \
 @@@@functions.h

Compiling test_functions.c...
> Shell executed command: ②
gcc.exe -I"test" -I"test/support" -I"src" -I"C:/dev/unity/src" -I"C:/field-manual
-for-ceedling/examples/8-changing-the
-configuration/vendor/ceedling/vendor/unity/src" -I"C:/field-manual-for
-ceedling/examples/8-changing-the-configuration/vendor/ceedling/vendor/cmock/src"
-I"build/test/mocks" -DTEST -DGNU_COMPILER -g -c "test/test_functions.c" -o
"build/test/out/test_functions.o"

Linking test_functions.out...
> Shell executed command: ③
gcc.exe "build/test/out/test_functions_runner.o"
"build/test/out/test_functions.o" "build/test/out/unity.o"
"build/test/out/cmock.o" -o "build/test/out/test_functions.out"

① This is using gcc to determine dependencies. It uses options like -E, -MM, and -MG to do this.

② This is an actual compilation — note how it’s under Compiling test_functions_runner.c….

③ Linking the test binary.

8.10.2. Changing Compiler Settings

To change the compiler or its settings, you need to add the :tools: → :test_compiler section
to project.yml. The default settings are listed below. The :argmuments: section is a little
complicated, so you’ll probably want to copy this whole section into project.yml and modify
it as you need.

Chapter 8. Changing the Configuration 91


Listing 89. Default compiler settings

:tools:
  :test_compiler:
  :executable: gcc ①
  :name: test_compiler
  :arguments: ②
  - -I"$": COLLECTION_PATHS_TEST_TOOLCHAIN_INCLUDE ③
  - -I"$": COLLECTION_PATHS_TEST_SUPPORT_SOURCE_INCLUDE_VENDOR
  - -D$: COLLECTION_DEFINES_TEST_AND_VENDOR
  - -DGNU_COMPILER
  - -g
  - -c ${1} ④
  - -o ${2} ⑤

① The compiler application that is executed. gcc is the default. This is an example using gcc
in the system path, but you can provide an absolute path here too.

② This is where the arguments to the compiler are specified. You’ll want to add any
additional arguments to this list. Note that the order of these is the order in which they are
passed to the compiler.

③ These are all the include paths. These "all caps" names like
COLLECTION_PATHS_TEST_TOOLCHAIN_INCLUDE are the lists of paths created by Ceedling based
on other configuration settings.

④ The input source file name.

⑤ The object file name to compile produce.

So if you wanted to turn on most gcc warnings you’d add -Wall to the list:

92 Chapter 8. Changing the Configuration


Listing 90. Adding a compiler flag (-Wall) to enable warnings

:tools:
  :test_compiler:
  :executable: gcc
  :name: test_compiler
  :arguments:
  - -I"$": COLLECTION_PATHS_TEST_TOOLCHAIN_INCLUDE
  - -I"$": COLLECTION_PATHS_TEST_SUPPORT_SOURCE_INCLUDE_VENDOR
  - -D$: COLLECTION_DEFINES_TEST_AND_VENDOR
  - -DGNU_COMPILER
  - -g
  - -c ${1}
  - -o ${2}
  - -Wall ①

① Provide the -Wall flag to the compiler to enable warnings.

You need to use two - characters for each flag like - -Wall. The first is for the
 YAML list, and the second is part of the option passed to gcc.

8.10.3. Changing Linker Settings

The linker is configured in the :tools: → :test_linker: section of project.yml. Below are the
default settings. The :argmuments: section is a little complicated, so you’ll probably want to
copy this whole section into project.yml and modify it as you need.

:tools:
  :test_linker:
  :executable: gcc ①
  :name: 'test linker'
  :arguments: ②
  - ${1} ③
  - -o ${2} ④

① The name of the linker application. Either put it in the path like here or use an absolute
path.

② The list of arguments to the linker. Add additional arguments or flags here.

③ The list of object files to link (as determined "auto-magically" by Ceedling).

④ The binary output file name.

Chapter 8. Changing the Configuration 93

You might also like