一个简单好用的C语言单元测试框架-Unity

news/2025/3/16 6:23:15/

Unity简介:

Unity是一个用于C语言的轻量级单元测试框架。它由Throw The Switch团队开发,旨在简化嵌入式系统的单元测试。单元测试中单元的含义,单元就是人为规定的最小的被测功能模块,如C语言中单元指一个函数,Java里单元指一个类,图形化的软件中可以指一个窗口或一个菜单等。在实际项目中,单元测试往往由开发人员完成。
Unity的设计目标是易于使用、轻便、可移植,并能够在各种嵌入式和非嵌入式系统中运行。核心项目是一个 C 文件和一对头文件,允许将其添加到现有的构建设置中,而不会太麻烦。 可以使用任何想用的编译器,并且可以使用大多数现有的构建系统,包括 Make、CMake 等。

Unity简单使用方法:

1、下载unity框架

去GitHub下载,或者gti clone到本地,链接地址:unity。

Unity 本身非常小。 其他剩下的只是可选附加组件。 可以忽略它或在方便时使用它。 以下是项目中所有内容的概述。

  • src- 这是你关心的代码!此文件夹包含一个 C 文件和两个头文件。 这三个文件是 Unity。
  • docs- 在这里可以找到所有方便的文档。
  • examples- 这包含一些使用 Unity 的示例。
  • extras- 这些是 Unity 的可选附加组件,不属于核心项目。
  • test- 这就是 Unity 及其脚本的测试方式。 如果你只是使用Unity,你可能永远不需要进入这里。
  • auto- 在这里,你将找到有用的 Ruby 脚本,以简化你的测试工作流程。 它们完全是可选的,不需要使用 Unity。

2、使用unity框架

测试文件是 C 文件。 大多数情况下,将为每个要测试的 C 模块创建一个测试文件。 测试文件应包含 unity.h 和要测试的 C 模块的标头。

例如测试下面两个函数:

ProductionCode.c

#include "ProductionCode.h"int Counter = 0;
int NumbersToFind[9] = { 0, 34, 55, 66, 32, 11, 1, 77, 888 }; /* some obnoxious array to search that is 1-based indexing instead of 0. *//* This function is supposed to search through NumbersToFind and find a particular number.* If it finds it, the index is returned.  Otherwise 0 is returned which sorta makes sense since* NumbersToFind is indexed from 1.  Unfortunately it's broken* (and should therefore be caught by our tests) */
int FindFunction_WhichIsBroken(int NumberToFind)
{int i = 0;while (i < 8) /* Notice I should have been in braces */i++;if (NumbersToFind[i] == NumberToFind) /* Yikes!  I'm getting run after the loop finishes instead of during it! */return i;return 0;
}int FunctionWhichReturnsLocalVariable(void)
{return Counter;
}

ProductionCode.h

#ifndef  _PRODICTIONCODE_H_
#define  _PRODICTIONCODE_H_extern int Counter;int FindFunction_WhichIsBroken(int NumberToFind);
int FunctionWhichReturnsLocalVariable(void);#endif

接下来,测试文件将包含测试例程函数。 setUp 函数可以包含你希望在每次测试之前运行的任何内容。 tearDown 函数可以包含你希望在每次测试后运行的任何内容。 这两个函数都不接受任何参数,也不返回任何内容。 如果不需要它们,将其设置为空。setUp() tearDown()

TestProductionCode.h


#include "ProductionCode.h"
#include "unity.h"/* sometimes you may want to get at local data in a module.* for example: If you plan to pass by reference, this could be useful* however, it should often be avoided */
extern int Counter;void setUp(void)
{/* This is run before EACH TEST */Counter = 0x5a5a;
}void tearDown(void)
{
}void test_FindFunction_WhichIsBroken_ShouldReturnZeroIfItemIsNotInList_WhichWorksEvenInOurBrokenCode(void)
{/* All of these should pass */TEST_ASSERT_EQUAL(0, FindFunction_WhichIsBroken(78));TEST_ASSERT_EQUAL(0, FindFunction_WhichIsBroken(2));TEST_ASSERT_EQUAL(0, FindFunction_WhichIsBroken(33));TEST_ASSERT_EQUAL(0, FindFunction_WhichIsBroken(999));TEST_ASSERT_EQUAL(0, FindFunction_WhichIsBroken(-1));
}void test_FindFunction_WhichIsBroken_ShouldReturnTheIndexForItemsInList_WhichWillFailBecauseOurFunctionUnderTestIsBroken(void)
{/* You should see this line fail in your test summary */TEST_ASSERT_EQUAL(1, FindFunction_WhichIsBroken(34));/* Notice the rest of these didn't get a chance to run because the line above failed.* Unit tests abort each test function on the first sign of trouble.* Then NEXT test function runs as normal. */TEST_ASSERT_EQUAL(8, FindFunction_WhichIsBroken(8888));
}void test_FunctionWhichReturnsLocalVariable_ShouldReturnTheCurrentCounterValue(void)
{/* This should be true because setUp set this up for us before this test */TEST_ASSERT_EQUAL_HEX(0x5a5a, FunctionWhichReturnsLocalVariable());/* This should be true because we can still change our answer */Counter = 0x1234;TEST_ASSERT_EQUAL_HEX(0x1234, FunctionWhichReturnsLocalVariable());
}void test_FunctionWhichReturnsLocalVariable_ShouldReturnTheCurrentCounterValueAgain(void)
{/* This should be true again because setup was rerun before this test (and after we changed it to 0x1234) */TEST_ASSERT_EQUAL_HEX(0x5a5a, FunctionWhichReturnsLocalVariable());
}void test_FunctionWhichReturnsLocalVariable_ShouldReturnCurrentCounter_ButFailsBecauseThisTestIsActuallyFlawed(void)
{/* Sometimes you get the test wrong.  When that happens, you get a failure too... and a quick look should tell* you what actually happened...which in this case was a failure to setup the initial condition. */TEST_ASSERT_EQUAL_HEX(0x1234, FunctionWhichReturnsLocalVariable());
}

最后,在编写main函数,然后调用每个测试例程函数。 这实际上会触发每个测试例程函数运行,因此每个函数都有自己的调用非常重要。

/* AUTOGENERATED FILE. DO NOT EDIT. */
/*=======Automagically Detected Files To Include=====*/
#include "unity.h"
#include <setjmp.h>
#include <stdio.h>
#include "ProductionCode.h"/*=======External Functions This Runner Calls=====*/
extern void setUp(void);
extern void tearDown(void);
extern void test_FindFunction_WhichIsBroken_ShouldReturnZeroIfItemIsNotInList_WhichWorksEvenInOurBrokenCode(void);
extern void test_FindFunction_WhichIsBroken_ShouldReturnTheIndexForItemsInList_WhichWillFailBecauseOurFunctionUnderTestIsBroken(void);
extern void test_FunctionWhichReturnsLocalVariable_ShouldReturnTheCurrentCounterValue(void);
extern void test_FunctionWhichReturnsLocalVariable_ShouldReturnTheCurrentCounterValueAgain(void);
extern void test_FunctionWhichReturnsLocalVariable_ShouldReturnCurrentCounter_ButFailsBecauseThisTestIsActuallyFlawed(void);/*=======MAIN=====*/
int main(void)
{UnityBegin("test/TestProductionCode.c");RUN_TEST(test_FindFunction_WhichIsBroken_ShouldReturnZeroIfItemIsNotInList_WhichWorksEvenInOurBrokenCode);RUN_TEST(test_FindFunction_WhichIsBroken_ShouldReturnTheIndexForItemsInList_WhichWillFailBecauseOurFunctionUnderTestIsBroken);RUN_TEST(test_FunctionWhichReturnsLocalVariable_ShouldReturnTheCurrentCounterValue);RUN_TEST(test_FunctionWhichReturnsLocalVariable_ShouldReturnTheCurrentCounterValueAgain);RUN_TEST(test_FunctionWhichReturnsLocalVariable_ShouldReturnCurrentCounter_ButFailsBecauseThisTestIsActuallyFlawed);return (UnityEnd());
}

3、构建运行环境

创建一个目录 TDDUnityExample,在TDDUnityExample 目录下创建 CMakeLists.txt 文件。

# 最低CMake版本要求
cmake_minimum_required(VERSION 3.15)#将Unity/src工作目录的源文件赋给UNITY_SRC_LIST
#将Tests工作目录的源文件赋给APP_SRC_DIR
aux_source_directory (Unity/src UNITY_SRC_LIST)
aux_source_directory (Tests APP_SRC_DIR)# 项目名称
project(TDD_test)# 头文件路径
include_directories(Unity/src)#将所有源文件生成一个可执行文件
add_executable(TDD_test  ${APP_SRC_DIR} ${UNITY_SRC_LIST})
set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

在 TDDUnityExample 下创建 Unity 目录,然后将Unity 框架源码中的 src 目录拷贝到 Unity目录中。

在 TDDUnityExample 下创建 Tests 目录,然后将第二点中举例的测试文件都放在 Tests 目录中。

在 TDDUnityExample 下创建 build 目录,然后在 build 目录下也创建一个 CMakeLists.txt 文件。

cmake_minimum_required (VERSION 2.8)project (TDD_test)add_subdirectory (Tests)

最后整体文件结构如下:

├── build
│   └── CMakeLists.txt
├── CMakeLists.txt
├── Tests
│   ├── ProductionCode.c
│   ├── ProductionCode.h
│   ├── TestProductionCode.c
│   └── TestProductionCode_Runner.c
└── Unity└── src├── meson.build├── unity.c├── unity.h└── unity_internals.h

4、运行测试程序

在 build 目录下输入 cmake ..命令,会自动生成 Makefile 文件,然后输入make,就会自动编译生成可执行文件 TDD_test 在 TDDUnityExample /bin 下。

进入 TDDUnityExample /bin 下,输入./TDD_test

输出结果为:

test/TestProductionCode.c:0:test_FindFunction_WhichIsBroken_ShouldReturnZeroIfItemIsNotInList_WhichWorksEvenInOurBrokenCode:PASS
test/TestProductionCode.c:33:test_FindFunction_WhichIsBroken_ShouldReturnTheIndexForItemsInList_WhichWillFailBecauseOurFunctionUnderTestIsBroken:FAIL: Expected 1 Was 0
test/TestProductionCode.c:0:test_FunctionWhichReturnsLocalVariable_ShouldReturnTheCurrentCounterValue:PASS
test/TestProductionCode.c:0:test_FunctionWhichReturnsLocalVariable_ShouldReturnTheCurrentCounterValueAgain:PASS
test/TestProductionCode.c:61:test_FunctionWhichReturnsLocalVariable_ShouldReturnCurrentCounter_ButFailsBecauseThisTestIsActuallyFlawed:FAIL: Expected 0x00001234 Was 0x00005A5A-----------------------
5 Tests 2 Failures 0 Ignored 
FAIL

从结果来看第二个和第五个测试例程出现错误,错误原因也标记出来了。

5、常用断言

TEST_PASS(); /* 中止测试的其余部分, 但将测试计为通过 */
TEST_PASS_MESSAGE("message")TEST_IGNORE(); /* 将测试用例标记为忽略, 但将测试计为通过 */
TEST_PASS_MESSAGE("message")TEST_FAIL(); /* 中止测试的其余部分, 但将测试计为失败 */
TEST_FAIL_MESSAGE("message")TEST_MESSAGE(""); /* 将消息输出 */

以上断言宏都是放在编写的测试函数中,TEST_IGNORE 一般放在函数的顶部,用来表示将测试用例忽略,其他宏可以放在函数的任意位置。

总结:

unity单元测试框架核心是一个 C 文件和一对头文件,特点是简洁易用轻量级可移植性支持测试断言等。其中有许多测试断言需要多了解多使用。

好了以上就是Unity单元测试框架的简易使用方法,有什么疑问和建议欢迎在评论区中提出,想要了解更多的Unity知识可以去官网上查看,官网上也有详细的教程和实例。


http://www.ppmy.cn/news/1325106.html

相关文章

PostgreSQL以查询的结果集创建表

select * into [新数据表名] from [旧数据表名]

AD导出BOM表 导出PDF

1.Simple BOM: 这种模式下&#xff0c;最好在pcb界面&#xff0c;这样的导出的文件名字是工程名字&#xff0c;要是在原理图界面导出&#xff0c;会以原理图的名字命名表格。 直接在菜单栏 报告->Simple BOM 即可导出物料清单&#xff0c;默认导出 comment pattern qu…

【JavaWeb后端开发-第七章】SpingBoot原理

文章目录 前言1. 配置优先级2. Bean管理2.1. 获取Bean2.2. Bean作用域2.3. 第三方Bean 3. SpringBoot原理3.1. 起步依赖3.2. 自动配置3.2.1. 概述3.2.2. 常见方案概述方案一方案二 3.2.3. 原理分析3.2.3.1. 源码跟踪3.2.3.2. Conditional 3.2.4. 案例3.2.4.1. 自定义starter分析…

❤ Uniapp使用二 ( 日常使用篇)

❤ Uniapp使用二 ( 日常使用篇) 一、表单 1、基础表单验证 form <form submit"formSubmit" reset"formReset"> <view class"uni-form-item uni-column"><view class"title">请选择类型{{selectvalue}}</view&…

STM32-调用 vTaskStartScheduler API 后出现 HardFault

STM32 移植 FreeRTOS 后调用 vTaskStartScheduler() 后出现 HardFault 异常。 原因分析&#xff1a; FreeRTOS 配置头文件 FreeRTOSConfig.h 中与中断有关的配置和通过系统接口 void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup) 设置的中断分组冲突。 /* The lo…

【手把手带你玩转MyBatis】进阶篇:探索未知领域,揭秘那些你可能未曾触及的高级特性

1. 复杂结果集映射&#xff08;ResultMap&#xff09; 在处理复杂查询结果时&#xff0c;可以使用<resultMap>标签进行精细化映射。它可以处理嵌套结果集、一对一关联、一对多关联和多对多关联等情况。 xml <resultMap id"userAndOrdersResultMap" type&q…

openssl3.2 - 官方demo学习 - smime - smdec.c

文章目录 openssl3.2 - 官方demo学习 - smime - smdec.c概述笔记END openssl3.2 - 官方demo学习 - smime - smdec.c 概述 从pem证书中得到x509*和私钥, 用私钥和证书解密MIME格式的PKCS7密文, 并保存解密后的明文 MIME的数据操作, 都是PKCS7相关的 笔记 /*! \file smdec.c …

第三天业务题

3-1 你们的项目是如何进行参数校验的 在我们的项目中&#xff0c;通常使用以下2种方式进行参数校验&#xff1a; 1.手动校验&#xff1a;在方法内部&#xff0c;我们可以手动编写代码来对参数进行校验。例如&#xff0c;使用条件判断语句&#xff08;if-else&#xff09;来检…