ceedling是针对C语言的核心测试框架,专用于嵌入式
环境:window10 x64
官方简易教程点击跳转
以下命令无特殊说明都是在cmd下执行的,本篇文章是基础的配置与使用,在具体工程中的技巧和使用见下篇
文章目录
Ceedling介绍
Ceedling
提供了一个简单的接口,用于配置从基础到复杂的项目结构。从而可以使用ceedling
进行简单工程、有多个依赖库依赖项的工程、多个依赖工具工具的工程的测试代码构建;Ceedling
是基于unity
的框架,是针对C语言的核心测试框架,专用于嵌入式的;Ceedling
是对Ruby的Rake (make-ish
)构建系统的扩展,Ceedling
主要针对C语言中的测试驱动开发(Test-Driven Development
),旨在整合CMock(生成模拟接口)、unity(单元测试框架)和 CException(异常处理框架) 这三个很棒的开源项目。
搭建环境
1.安装Ruby
在官网上下载安装Ruby
,建议使用2.7.2
版本,然后将其bin路径添加到系统环境变量PATH
中,最后检测是否安装成功。
ruby -v #检测是否安装成功
2.安装Ceeding
ceedling
是在ruby gem
镜像源中的一个软件,我们打开命令窗口输入一下指令来在ruby
的安装路径中添加ceedling
组件,建议使用0.30.0
版本,否则有可能出现预料范围之外的错误。
gem install ceedling --version=0.30.0 #安装0.30.0版本
3.安装gcc
为了在window
平台下运行gcc
命令我们需要 安装MinGW-w64下载MinGW-W64-install.exe。
安装详细教程:一篇参考文章,安装过程中如果报错,可以尝试关闭防火墙和杀毒程序,或降低安装版本;安装一开始报错则可能是包不是最新的,重新下载一下试试;
安装过程中,进行如下选择。
安装完毕后同样将MinGW-w64
安装目录下的bin
路径加入 PATH
中。最后检测是否安装成功。
gcc -v
4.安装gcov
使用pip
命令需要安装python支持,之后pip
命令来安装gcov
,用于生成覆盖率报表。
pip install gcovr
ceedling使用过程
官方给了一些参考工程可供参考,使用如下命令就可以下载下来。
ceedling help
ceedling examples #查看官方给的示例
ceedling example blinky #选择其中一个示例刨下来
1.创建测试项目树
ceedling
支持使用命令创建项目树:
ceedling new test_ceedling #test_ceedling工程名可以按你自己想要的取
创建的几个目录和文件介绍:
build:构建的测试程序存放目录,在执行测试之后自动生成,build存放了
CMock
文件,覆盖率报表等等src:
ceedling
预留的源文件存放目录,这里我们将源文件放在了我们创建的src
文件夹中,可以不使用此处的src
作为源文件存放目录,但是这就需要在project.yml
中对source
进行修改test:存放我们需要测试的C文件
project.yml:实际的项目文件,可以根据需要进行调整配置
ceedling
2.查看与修改项目配置文件project.yml
-
测试架构
ceedling
的核心文件是project.yml
,它包含有项目源文件的路径,若没有使用默认路径,需要将project.yml
中的paths
稍作修改,替换成从我们的源文件路径::paths: :test: - +:test/** - -:test/support :source: - ../yourPath/balabala/** :support: - test/support :libraries: [] :include: - +:../../Stepper Motor/STM32F0 - +:../../Stepper Motor/STM32F0/Libraries/inc
-
用于测试的文件需要有指定的开头,其配置语句如下,也就是我们测试文件命名为
test_xxx.c
。:project: :test_file_prefix: test_
-
调用
CMock
生成mock
文件解决依赖关系,需要引用对应的头文件并添加配置文件中的mock_
前缀,也就是测试目标Add.c
文件的被测试部分有调用Sub.c
中的方法,就需要在测试文件中添加#include “mock_Sub.h”
以虚拟Sub.c
中的方法。:cmock: :mock_prefix: mock_
经过如上的简单配置就可以初步使用了,要使用具体的插件(plugin)还需要具体的配置哦😄
3.编写测试文件
-
在上一步
project.yml
中配置的:test:
目录下创建名为test_xxxx.c
的文件,开始填充内容:
一个空的参考模板如下,在运行每个测试之前调用setUp()
,在运行每个测试之后调用tearDown()
,test_first
是测试内容的主体#include "unity.h" void setUp(void){} // void tearDown(void){} //这两个函数必须包含 void test_first(void) //测试函数需要以test_开头,一个测试函数就是一个测试任务 { //TO DO }
-
为了更直观的展示测试流程,在
src
目录下添加了四个文件Add.c
、Add.h
、Sub.c
、Sub.h
,其中Add.c
中的int Add(int a,int b)
为被测试模块,在被测试模块中调用了Sub.c
中的int Sub(int a,int b)
方法//Add.c的内容 #include "Add.h" #include "Sub.h" int Add(int a,int b) { //Sub(a,b); //在(3).使用CMock构建虚拟库时取消注释,对Sub进行mock return a+b; }
这里,在
test
目录下创建test_Add.c
,开始修改其内容(1).添加被测试文件的头文件
#include "Add.h"
(2).添加测试语句
将
test_first
函数修改为test_Add
void test_Add(void) { TEST_ASSERT_EQUAL_INT(5,Add(2,3)); //宏在unity.h中定义 }
至此我们就可以进行第一次测试了,在
project.yml
同级目录下执行如下命令ceedling test:all
Test 'TestAdd.c' ---------------- Generating runner for TestAdd.c... Compiling TestAdd_runner.c... Compiling TestAdd.c... Linking TestAdd.out... Running TestAdd.out... -------------------- OVERALL TEST SUMMARY -------------------- TESTED: 1 PASSED: 1 FAILED: 0 IGNORED: 0
(3).使用CMock构建虚拟库,使有依赖项的测试模块可以编译通过
在
Add.c
中引用了Sub.h
并调用其Sub
方法,这时候就需要使用CMock
来虚拟Sub
方法,才能使ceedling
测试通过,在peoject.yml
中关于CMock
的配置语句::cmock: :mock_prefix: mock_
在测试文件加入对应的Mock头文件,需要知道的是只用添加被引用文件的对应头文件便,并添加mock_前缀,ceedling框架会自动为我们生成mock_xxx.h,以及对应的mock_xxx.c并且实现其方法,不过这些方法在使用前,需要我们手动去调用,给定预期的参数和返回值才能正常使用,经过一系列的修改文件就成了下面的样子
#include "unity.h" #include "Add.h" #include "mock_Sub.h" //Sub.h对应的Mock文件,测试执行后自动生成 void setUp(void){} void tearDown(void){} void test_Add(void) { Sub_CMockExpectAndReturn(0,2,3,5); //Sub函数对应的Mock函数 TEST_ASSERT_EQUAL_INT(5,Add(2,3)); }
4.生成覆盖率报表
-
生成覆盖率报表是在生产环节中是不可少的
(1).激活gcov
在
project.yml
中添加对gcov
插件的使用:plugins: :enabled: - gcov
之后可以在
bulid/artifacts/gcov
目录下查看覆盖率测试结果(2).gcov参数优化
生成报表提供更多的参考信息
:gcov: :reports: - Cobertura - HtmlDetailed - SonarQube - Badges :gcovr: :html_medium_threshold: 75 :html_high_threshold: 90 :xml_pretty: true
(3).进行测试
ceedling gcov:all utils:gcov
附录
工程配置文件project.yml
首先需要对yaml语法有一个基础的认识 菜鸟教程-点击跳转
project.yml注意与约定
project.yml中应该至少包含如下3个对象
:project
:build_root :
***
:paths
:source :
***
:test :
***
各个对象的解释
project
对象 | 属性 | 功能 | 默认值 |
---|---|---|---|
project | use_exceptions | 构建环境,异常处理使用CExceptions | TRUE |
project | bulid_root | 生成文件的目录 | none |
project | use_mocks | 构建环境,异常处理使用CMock | TRUE |
project | use_test_preprocessor | 这个选项允许Ceedling处理包含条件编译语句(例如#ifdef)的测试文件 和将要模拟的包含条件预处理器语句、宏和头文件 | FALSE |
project | use_preprocessor_directives | use_test_preprocessor置TRUE才可使用此项, 置TRUE后宏将将展开为C代码 | FALSE |
project | use_deep_dependencies | 这个选项允许适当的增量构建操作同时发生在测试执行和发布构建中。 | FALSE |
project | generate_deep_dependencies | use_deep_dependencies置TRUE此项才可使用 此项生成深度依赖项,以避免额外的构建步骤 | TRUE |
project | test_file_prefix | 测试文件的前缀 | test_ |
project | options_paths | 读取其他yml文件的路径,并在运行时合并这些路径中的项目配置 | [ ] (empty) |
project | release_build | 置TRUE一个发行版本的Rake任务将被执行 | FALSE |
release_bulid
对象 | 属性 | 功能 | 默认值 |
---|---|---|---|
release_bulid | output | 输出的二进制文件名称 | project.out |
release_bulid | use_assembly | 在项目树中有汇编代码时,置TRUE时Ceedling会生成指定目录并使用汇编工具 | FALSE |
release_build | artifacts | 有选择地复制文件可以防止偶然的构建工具不必要地出现在artifacts 目录中 | [ ] (empty) |
paths
对象 | 属性 | 功能 | 默认值 |
---|---|---|---|
paths | test | 测试文件存放的目录 | [ ] (empty) |
paths | source | 工程代码,需要被测试的代码存放位置 | [ ] (empty) |
paths | support | 支持单元测试的文件存放位置,一般目录为test/support | [ ] (empty) |
paths | include | 一些不存在于源搜索路径中的头文件,在这里进行添加 | [ ] (empty) |
paths | llibraries | 添加链接时要包含的库路径集合 | [ ] (empty) |
extension
对象 | 属性 | 功能 | 默认值 |
---|---|---|---|
extension | header | C头文件 | .h |
extension | source | C文件 (无论是源文件还是测试文件) | .c |
extension | assembly | 汇编文件 | .s |
extension | object | C和汇编文件输出的*.o文件输出目录 | .o |
extension | executable | 目标硬件上可执行的二进制文件 | .exe/.out |
extension | testpass | 测试结果文件 | .pass |
extension | testfail | 测试结果文件 | .fail |
extension | dependencies | gcc预处理器生成的包含make-style依赖的文件 | .d |
defines
添加公共定义
对象 | 属性 | 功能 | 默认值 |
---|---|---|---|
defines | test | 1.对包含条件编译的测试文件有效 | [ ] (empty) |
defines | test_preprocess | 如果project:use_test_preprocessor或project:use_deep_dependencies置TRUE,gcc可能需要一些定义来处理预处理文件,用来mock和解决深度依赖的的问题 | [ ] (empty) |
defines | <test_name> | 为指定的test_name定义替换标准的test定义 | [ ] (empty) |
如下test_foo_config将拥有FOO_SPECIFIC_CONFIG定义,其他tests都不会拥有
:defines:
:test:
- FOO_STANDARD_CONFIG
:test_foo_config:
- FOO_SPECIFIC_CONFIG
defult
对象 | 属性 | 功能 | 默认值 |
---|---|---|---|
defult | release | 定义发布构建二进制文件 所需的内容 | [ ] (empty) |
defult | release | 如果设置了project:use_deep_dependencies,那么由于深度依赖,gcc预处理器可能需要用符号定义来正确地预处理增量发布而构建的文件。 | [ ] (empty) |
defult | use_test_definition | 当使用此选项时,将把-D<test_name>标志添加到构建选项中。 | FALSE |
libraries
引入特定的库
对象 | 属性 | 功能 | 默认值 |
---|---|---|---|
libraries | test | 链接时注入到测试文件中的库文件 | [ ] (empty) |
libraries | release | 链接时发布版本的库文件目录,以动态依赖构建 | [ ] (empty) |
libraries | system | 这里添加的库将被注入到发布版和测试中。 例如,如果指定-lm,可以包含数学库。 在:flag前缀没有为其他库指定-l时,仅需要-m就可添加数学库。 | [ ] (empty) |
libraries | flag | 为每个库添加指定参数 | |
libraries | path_flag | 为每个库路径添加指定的参数 |
:libraries: :placement: :end :flag: "-l${1}" :path_flag: "-L ${1}" :system: [] # for example, you might list 'm' to grab the math library :test: [] :release: []
flag
配置添加每个文件的编译和链接目标
对象 | 属性 | 功能 | 默认值 |
---|---|---|---|
flag | release | [:compile] or [:link] flags for release build | |
flag | test | [:compile] or [:link] flags for release build | |
使用时注意的点: |
1.文件说明符不包含路径和文件后缀2.文件说明符区分大小写3.用引号括起来,就可以用正则表达式来表达文件说明符4.`*`表示所有文件
:flags: :release: :compile: :main: # add '-Wall' to compilation of main.c - -Wall :fan: # add '--O2' to compilation of fan.c - --O2 :'test_.+': # add '-pedantic' to all test-files - -pedantic :*: # add '-foo' to compilation of all files not main.c or fan.c - -foo :test: :compile: :main: # add '--O1' to compilation of main.c as part of test builds including main.c - --O1 :link: :test_main: # add '--bar --baz' to linking of test_main.exe - --bar - --baz
import
添加附加的配置文件
:import: - path/to/config.yml - path/to/another/config.yml
cmock
对虚拟模块工具CMock进行配置
对象 | 属性 | 功能 | 默认值 |
---|---|---|---|
cmock | enforce_strict_ordering | 如果预期的调用顺序与源顺序不相同,则测试将失败 | TRUE |
cmock | mock_path | 生成的mock路径 | bulid//tests/mocks |
cmock | defines | 定义的符号只会用来编译CMock生成的C代码 | [ ] (empty) |
cmock | verbosity | 冗长级别 | 默认为ceedling的冗长级别 |
cmock | plugins | 指定cmock所需要的附加插件 | |
cmock | inlcude | 指定cmock的包含目录 |
宏定义的使用
这一部分在对已经存在的方法进行测试时尤为重要,如下就定义了全局宏UNIT_TEST
和WIN32
,我们就可以用这些宏进行条件编译了
:defines:
# in order to add common defines:
# 1) remove the trailing [] from the :common: section
# 2) add entries to the :common: section (e.g. :test: has TEST defined)
:common: &common_defines
- UNIT_TEST
- WIN32
:test:
- *common_defines
- TEST
- WIN32
:test_preprocess:
- *common_defines
- TEST
其他
工具/插件/异常处理这里不再列出,详见tools/pluginsc/exception
关于gcov覆盖率报表生成失败
- 确定安装好了
gcov
,执行了正确的命令,查看配置,详见上文 - 必须是引用测试文件的.h文件,再对.c文件中的方法进行测试,一次直接引用.c文件导致未能生成覆盖率报表,至于破坏了原来文件的隔离性(所测试函数不提供给外部使用,是用static修饰的),只需要用宏进行条件编译就好了,宏的定义子
project.yaml
中的define中,具体的使用见上文
参考链接
本文作者 @CSDN arize 未经授权禁止转载