STM32 | 分享几个开源的测试框架

1024G 嵌入式资源大放送!包括但不限于C/C++、单片机、Linux等。关注微信公众号【嵌入式大杂烩】,回复1024,即可免费获取!

这是一篇测试相关的笔记。我们软件开发最终都离不开测试的,可以通过测试来发现很多问题。在这之前先扯谈一波:

在这给还没找工作的朋友提个醒,能找开发的职位就别找测试的职位,除非你真的很喜欢测试。亲身经历,做测试很不好受。测试其实有两类,一种是自己去测自己测的东西,另一种是去测别人做的东西。如果是第一种,我倒是很愿意做,因为我们本质上还是开发工程师,大概80%的工作时间在做开发,20%的工作时间在测自己开发的东西。这个测试的时间值得花,可以通过测试来发现我们在开发过程中没有注意到的点。如果是第二种,我们本质上就是测试工程师了,大概80%的时间在测别人的东西,20%的时间在想着怎么测别人的东西。如果是这一种的话,那我们就只能当别人的配角了。

找工作时,有一点要注意:有些职位写着嵌入式软件工程师,实则测试工程师,这个得问清楚。

回归正题,下面开始我们的这篇笔记:

几个开源的测试框架

框架(framework)是一个框子——指其约束性,也是一个架子——指其支撑性。是一个基本概念上的结构,用于去解决或者处理复杂的问题。 框架这个广泛的定义使用的十分流行,尤其在软件概念。比如我们套用一些测试框架来测试我们写的一些功能函数,的出来的测试结果大概是这样子的:

======000

从输出结果中看,我们可以清晰地看出测试的情况。下面分享几个开源的测试框架:

1、 Unity测试框架

这里的Unity并不是那个游戏开发工具 ,而是一个开源的测试框架。Unity测试框架的项目链接:

https://github.com/ThrowTheSwitch/Unity/releases

目前更新到v2.5.0:

======001

Unity 是一个轻量级的测试框架,它使用 C 语言实现, 代码本身很小 。其代码中大多数是宏定义,所以实际编译后的代码会更小, 比较适合在嵌入式测试应用

2、 CuTest测试框架

CuTest项目链接:

https://sourceforge.net/projects/cutest/

CuTest是一款微小的C语言单元测试框,只有2个文件,CuTest.c和CuTest.h,全部代码加起来不到一千行。麻雀虽小,五脏俱全,测试的构建、测试的管理、测试语句,都全部包含在内。

3、 Embedded Unit测试框架

Embedded Unit项目链接:

https://sourceforge.net/projects/embunit 

Embedded Unit是个纯标准c构建的单元测试框架,主要用在嵌入式c的单体测试上,其主要特点是不依赖于任何C的标准库,所有的对象都是静态分配。

4、 gtest 测试框架

gtest项目链接:

https://github.com/google/googletest/releases/tag/release-1.8.0

gtestgoogle 公司开发的一个开源的单元测试框架,基于 C++开发,可以对 C++语言和 C 语言进行单元测试。 gtest 有以下特点:

  • 提供强大的断言集,支持布尔型、整型、浮点型、字符串以及所有实现了比较运
    算符和输出运算符的自定义类的判断;

  • 提供断言扩展功能,当所需要的断言在 gtest 中没有提供时,可以使用 gtest 提供
    的方法进行扩展;

  • gtest 会自动收集我们的测试用例,开发者不需要对测试用例进行组织;

  • 提供死亡测试的功能,用于测试代码在特定情况下异常崩溃的情况;

  • 可将公共的用例初始化和清理工作放入测试夹具中,由 gtest 自动调用;

  • 使用参数化自动生成多个相似的测试用例

Unity的使用分享

这里分享UnitySTM32平台上的移植与使用(keil工程)。移植的过程很简单,一般这些测试框架都是打印的方式把测试结果输出。在STM32中 ,我们一般是通过串口输出到串口上位机,所以我们在移植Unity的时候,处理好这个问题就可以用在STM32上了。

首先,把Unity源码目录下的unity.c、unity.h、unity_internals.h三个文件复制至我们的工程目录下,并把unity.c添加到我们的keil工程中,然后添加文件路径:

======003

======004

我们打开unity_internals.h文件,发现其有包含一个头文件unity_config.h

======005

这个文件是配置文件,我们与平台相关的特性放在这个文件中。而这个文件Unity源码中并未提供,所以需要我们自己建立,我这边新建的unity_config.h文件的内容如下:

======006

主要在这里面放了硬件相关的头文件包含以及两个必要的宏定义。第一个宏定义用于重定向输出至串口,第二个宏定义就是我们的串口初始化。

unity_internals.h中我们发现unity_config.h文件被条件编译屏蔽掉了,我们需要定义宏把它打开:

======007

最后在我们的main.c中包含头文件unity.h即可使用unity测试框架。在unity_internals.h中有很多可修改的配置,比如在不同的平台中,整数的长度是不一样的,在 Unity 中,允许开发者设置整数的长度。如果没有设置, Unity 指定的默认值是 32 位。我们的STM32就是32位的,所以我们不需要修改。

下面开始编写测试用例。 在 Unity 中,每个测试用例是一个函数, 该函数没有参数和返回值。下面我们来测试一个闰年判断函数:

======008

在测试函数中用到 TEST_ASSERT_TRUETEST_ASSERT_FALSE , 是 Unity 实现的两个断言, 用于判断
布尔型表达式的值为真或为假。这些测试框架一般都是用断言来进行测试的,包括以上分享的几个框架都是如此。本例中只用到了两个断言,在 Unity 中还有很多断言,如下是部分断言列举:

======009

======010

Unity 默认需要实现用例初始化函数 setUp 和用例清理函数 tearDown,这两个函数均没有参数和返回值。 在闰年判断函数的测试用例中,由于不需要初始化和清理操作,实现的两个函数如下:

======021

在编写了测试用例后, 接下来就可以在 main 函数中运行测试用例。在 Unity 中,使用宏 RUN_TEST 运行测试用例,参数为要运行的测试用例的函数名称。主函数如下:

======011

UNITY_BEGIN函数就是UNITY初始化函数,我们的串口初始化也是在这里面被调用的:

======012

======013

RUN_TEST函数用于运行我们的测试用例。UNITY_END函数就是返回我们的测试结果。最终,运行得到如下结果:

======014

假如,我们把测试闰年的测试函数里的2000改为2001:

======015

那么输出结果就变为:

======016

可以从结果看出没有通过的用例相关的代码所在行。在进行这样子的测试之前,我们当然得要明白我们的功能函数的功能及其预期输出,我们才能去进行测试用例的设计及进行测试。

以上就是关于Unity测试框架的使用分享,这只是这个测试框架的基本使用。有兴趣的、有用得上的朋友可以自己进行深入研究及使用。

相关书籍

第一本书:《软件单元测试入门与实践》周立功

这个Unity测试框架是我在周立功周工几个月前出版的新书《软件单元测试入门与实践》中看到的。之前刚出版的时候,他们有送书活动,我申请了一本纸质版书籍,没来得及看,最近仔细翻了一下,发现还实用,又学到了很多新东西。这不只是一本讲测试的书,也是一本教我们如何调高软件质量的书。书中有理论和实践大概各占一半,介绍了很多实用的工具和技巧。

======017

前面几章很详细地介绍了一些测试相关的知识,比如黑盒测试、白盒测试、灰盒测试、静态测试,动态测试等。介绍了静态测试工具:pc-lint 编码规则检查工具与 SourceMonitor 代码结构检查工具。其中pc-lint可以集成到keil中进行使用。从某种意义上说,PC-Lint 是一种更加严格的编译器,它除了可以检查出一般的语法错误外,还可以检查出那些虽然符合语法要求,但很可能是潜在的、不易发现的错误。

======018

后面几章分享一些实用工具的使用,比如Unity测试框架、cmake自动构建工具、持续集成系统 gitlab 等。

第二本书:《测试驱动的嵌入式C语言开发》尹哲翻译

这本是在周工那本书的参考文献里的其中一本。

======019

======020

这是老外写的书。看目录好像还不错,有空的时候可以当做课外书来读。

以上就是本次的笔记分享,希望各位喜欢!

本笔记的keil工程及以上两本书的电子档可以关注公众号【嵌入式大杂烩】,回复【unity】进行获取。

### 回答1: 好的,下面是一个基于 stm32 的激光测距仪程序 modbus 协议的示例代码: ``` #include "stm32f10x.h" #include "modbus.h" #define SLAVE_ADDR 0x01 #define UART_BUFFER_SIZE 256 #define LASER_ON_TIME 100 uint8_t uart_rx_buffer[UART_BUFFER_SIZE]; uint8_t uart_tx_buffer[UART_BUFFER_SIZE]; uint16_t uart_rx_ptr = 0; uint16_t uart_tx_ptr = 0; void uart_init(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); USART_InitStructure.USART_BaudRate = 9600; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART2, &USART_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); USART_Cmd(USART2, ENABLE); } void laser_init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); GPIO_SetBits(GPIOB, GPIO_Pin_0); } void laser_on(void) { GPIO_ResetBits(GPIOB, GPIO_Pin_0); delay_ms(LASER_ON_TIME); GPIO_SetBits(GPIOB, GPIO_Pin_0); } void laser_off(void) { GPIO_SetBits(GPIOB, GPIO_Pin_0); } void delay_ms(uint32_t ms) { uint32_t i, j; for (i = 0; i < ms; i++) for (j = 0; j < 7200; j++); } void USART2_IRQHandler(void) { if (USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) { uint8_t data = USART_ReceiveData(USART2); uart_rx_buffer[uart_rx_ptr++] = data; if (uart_rx_ptr >= UART_BUFFER_SIZE) uart_rx_ptr = 0; } if (USART_GetITStatus(USART2, USART_IT_TXE) != RESET) { USART_SendData(USART2, uart_tx_buffer[uart_tx_ptr++]); if (uart_tx_ptr >= UART_BUFFER_SIZE) uart_tx_ptr = 0; if (uart_tx_ptr == uart_rx_ptr) USART_ITConfig(USART2, USART_IT_TXE, DISABLE); } } int main(void) { uint16_t distance = 0; modbus_init(SLAVE_ADDR); uart_init(); laser_init(); while (1) { distance = measure_distance(); modbus_set_register(0x0000, distance); } } uint16_t measure_distance(void) { uint16_t distance = 0; laser_on(); delay_ms(10); // read distance from sensor laser_off(); return distance; } ``` 这段代码实现了一个基于 stm32 的激光测距仪程序,使用 modbus 协议进行通信。程序中使用了 USART2 串口通信,同时使用 GPIOB 的 PB0 引脚控制激光的开关。在主函数中,程序不断调用 `measure_distance` 函数获取距离,并将其存储到 modbus 寄存器中。 ### 回答2: 要编写一个基于STM32的激光测距仪程序,使用Modbus协议。Modbus协议是一种通信协议,用于在不同设备之间进行数据传输。在这个程序中,我们将使用STM32微控制器作为主机,通过串口与激光测距仪进行通信。以下是实现该程序的步骤: 1. 配置STM32串口通信:在STM32微控制器上选择一个可用的串口,并将其配置为Modbus协议所需的参数,例如波特率、数据位、奇偶校验位和停止位等。 2. 实现Modbus协议:根据Modbus协议的规范,编写STM32的Modbus库文件,包括读取和写入寄存器的功能。这些函数将处理与激光测距仪之间的通信,并将其发送和接收的数据进行解析。 3. 设计激光测距仪数据处理功能:编写程序以读取并处理激光测距仪返回的数据,例如距离、强度和信号处理等。 4. 编写主程序:在主程序中,通过调用Modbus库文件中的函数,使用Modbus协议与激光测距仪进行通信。根据需要,可以实现单次测量或连续测量功能,并将测量结果显示在适当的显示设备上,如液晶屏或串口终端等。 5. 进行单元测试和调试:在程序开发过程中,进行单元测试和调试以确保程序的正确性和稳定性。特别关注与Modbus通信相关的问题,如数据解析错误或通信超时等。 6. 集成和优化:根据实际需求和性能要求,对程序进行适当的优化,如减少通信延迟、提高测量精度或增加其他功能等。 最后,通过编译和下载程序到STM32微控制器中,并将STM32与激光测距仪连接起来,即可实现基于STM32的激光测距仪程序,使用Modbus协议进行通信。 注意:根据问题描述,程序的详细实现细节可能会有所不同,这里仅提供了一个大致的框架和步骤。具体的实现需要根据实际情况进行调整和优化。 ### 回答3: 激光测距仪是一种通过激光发射器发射激光,然后通过接收器接收并计算出物体与测距仪的距离的仪器。基于STM32的激光测距仪程序需要使用Modbus协议来实现与其他设备的通信。 首先,我们需要通过激光发射器发送激光信号,并通过接收器接收反射回来的激光信号。然后,我们需要使用STM32的ADC模块来将接收到的模拟信号转换为数字信号。接下来,我们需要根据接收到的信号计算出物体与测距仪的距离。 在激光测距仪程序中,我们需要使用Modbus协议来实现与其他设备(如PC或PLC)的通信。Modbus协议是一种常用的串行通信协议,用于在工业领域的控制系统中进行数据传输。 首先,我们需要在STM32上实现Modbus协议的通信功能。我们可以使用开源的Modbus库,如"FreeModbus",来实现这一功能。通过使用该库,我们可以轻松地在STM32上实现Modbus协议的通信。 具体地,我们可以将激光测得的距离数据存储在STM32的寄存器中,并通过Modbus协议提供读写功能来与其他设备进行通信。我们可以定义几个寄存器,如"distance_h"和"distance_l",用于存储距离的高位和低位数据。然后,我们可以使用Modbus的读写命令来访问这些寄存器。 在PC或PLC端,我们也需要使用Modbus协议的库来实现与STM32的通信。通过使用Modbus库,我们可以以易于理解和操作的方式与STM32进行通信。例如,我们可以使用Modbus的读命令来读取STM32中存储的距离数据,并在PC或PLC上显示或处理这些数据。 总结来说,基于STM32的激光测距仪程序需要使用Modbus协议来实现与其他设备的通信。通过使用Modbus库,我们可以在STM32上实现Modbus协议的通信功能,并在PC或PLC上使用相应的库来与STM32进行通信。这样,我们就可以实现激光测距仪与其他设备之间的数据传输和控制。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嵌入式大杂烩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值