unittest自动化测试框架讲解以及实战
为什么要学习unittest
按照测试阶段来划分,可以将测试分为单元测试、集成测试、系统测试和验收测试。单元测试是指对软件中的最小可测试单元在与程序其他部分相隔离的情况下进行检查和验证的工作,通常指函数或者类,一般是开发完成的。
单元测试可以将测试工作前移,及早发现问题,降低解决问题的成本。同时单元测试还可以保证单个模块的功能没有问题,为后续的集成测试提供准备,可以减少合成后的出现的问题。
对于测试来说,单元测试就是执行用例。为了更好的管理用例,我们需要学习Python自带的单元测试框架unittest.
unittest框架及原理
unittest是python自带的一套测试框架,学习起来也相对容易,unittest框架最核心的概念有四个:
- test case:测试用例。unittest中提供了一个基本类TestCase,可以用来创建新的测试用例,一个TestCase的示例就是一个测试用例;unittest中测试用例的方法都是以 test开头的,且执行顺序会按照方法名的ASCII值排序。
- test fixure:测试夹具。用于测试用例换进的搭建和销毁,即用例测试前环境的大件(SetUp前置条件),测试后环境的恢复(TearDown后置条件)。比如测试前需要登陆获取token等就是测试用例需要的环境,运行完成后执行下一个用例前需要还原环境,以免影响下一条用例的测试结果。
- test suit:测试套件。用来把需要一起执行的测试用例几种放到一块执行,相当于一个篮子。我们一般使用 TestLoader来加载测试用例到测试套件中。
- test runner:测试运行。用来执行测试用例的,并返回测试用例的执行结果。可以结合图形或者文本接口,把返回的测试结果更形象的展示出来,如 HTMLTestRunner.
unittest断言
断言是测试用例的中很重要的一部分内容,可以用来检查操作是否正确。比如说登入处理,成功后的页面一定有类似于用户名称之类的元素,这个时候我们就可以使用断言判断预期结果与实际是否一致,如果吻合,就可以认为测试用例通过。
在Python基础中,有一个 assert断言方法,基本使用格式为 assert 表达式,基本信息。在unittest框架中,也提供了一个自带的断言方式,如果断言失败即不通过就会抛出一个 AssertionError断言错误;成功则标识通过。
以下的断言方法都有一个 msg=None参数(表中只列出了第一个,其实都有),默认返回 None。但是如果指定msg参数的值,则将该信息作为失败的错误信息返回。
TestCase测试用例
编写测试用例前,我们需要创建一个类继承unittest里面的TestCase类,之后我们才能真正的使用unittest框架去编写测试用例.
步骤如下:
导入unittest模块
创建一个测试类,继承unittest.TestCase类
定义测试方法,方法名必须以test_开头
调用unittest.main()方法来运行测试用例。unittest.main()方法会搜索该模块下所有以test开头的测试用例和方法,并自动执行。
代码示例
# 注册功能代码# users列表存储成功注册的用户
users = [{'username': 'testing', 'password': '123456'}] def register(username, password1, password2):if not all([username, password1, password2]):return {'code': 0, 'msg': '所有参数不能为空.'}# 注册for user in users:if username == user['username']:return {'code': 0, 'msg': '用户名已存在!'}else:if password1 != password2:return {'code': 0, 'msg': '两次密码输入不一致!'}else:if 6 <= len(username) <= 18 and 6 <= len(password1) <= 18:# 追加到users列表users.append({'username': username, 'password': password2})return {'code': 0, 'msg': '注册成功.'}else:return {'code': 0, 'msg': '用户名和密码的长度必须在6~18位之间.'}import unittest
from demo import register # 导入被测试代码class RegisterTest(unittest.TestCase):'''注册接口测试类'''def test_register_success(self):'''注册成功'''data = ('palien', 'palien', 'palien') # 测试数据result = register(*data) # 测试结果expected = {'code': 0, 'msg': '注册成功.'} # 预期结果self.assertEqual(result, expected) # 断言测试结果与预期结果一致# passdef test_username_exist(self):'''注册失败-用户名已存在'''data = ('testing', '123456', '123456')result = register(*data)expected = {'code': 0, 'msg': '用户名已存在!'}self.assertEqual(result, expected)def test_username_isnull(self):'''注册失败-用户名为空'''data = ('', 'palien', 'palien')result = register(*data)expected = {'code': 0, 'msg': '所有参数不能为空.'}self.assertEqual(result, expected)# passdef test_username_lt18(self):'''注册失败-用户名长度大于18位'''data = ('palienpalienpalienpalien', 'palien', 'palien')result = register(*data)expected = {'code': 0, 'msg': '用户名和密码的长度必须在6~18位之间.'}self.assertEqual(result, expected)# passdef test_password1_not_password2(self):'''注册失败-两次输入密码不一致'''data = ('palien', 'palien1', 'palien2')result = register(*data)expected = {'code': 0, 'msg': '两次密码输入不一致!'}self.assertEqual(result, expected)# pass# 如果要直接运行这个测试类,需要使用unittest中的main函数来执行测试用例
if __name__ == '__main__':unittest.main()# Output
Windows PowerShell
版权所有 (C) Microsoft Corporation。保留所有权利。尝试新的跨平台 PowerShell https://aka.ms/pscore6PS D:\d_02_study\01_git> cd d:/d_02_study/01_git/papers/system/02automation
PS D:\d_02_study\01_git\papers\system\02automation> & C:/Users/TDH/AppData/Local/Programs/Python/Python310-32/python.exe d:/d_02_study/01_git/papers/system/02automation/demo.py
.....
----------------------------------------------------------------------
Ran 5 tests in 0.001sOK
PS D:\d_02_study\01_git\papers\system\02automation>
TestFixture测试夹具
unittest的测试夹具有两种使用方式,一种是以测试用例的方法为维度的:setUp()和 tearDown();另一种是以测试类为维度的:setUpClass()和 tearDownClass()。
代码示例:
# users列表存储成功注册的用户
users = [{'username': 'testing', 'password': '123456'}] def register(username, password1, password2):if not all([username, password1, password2]):return {'code': 0, 'msg': '所有参数不能为空.'}# 注册for user in users:if username == user['username']:return {'code': 0, 'msg': '用户名已存在!'}else:if password1 != password2:return {'code': 0, 'msg': '两次密码输入不一致!'}else:if 6 <= len(username) <= 18 and 6 <= len(password1) <= 18:# 追加到users列表users.append({'username': username, 'password': password2})return {'code': 0, 'msg': '注册成功.'}else:return {'code': 0, 'msg': '用户名和密码的长度必须在6~18位之间.'}import unittest
from demo import register # 导入被测试代码class RegisterTest(unittest.TestCase):'''注册接口测试类'''@classmethod # 指明这是个类方法,以类为维度去执行的def setUpClass(cls) -> None:'''整个测试用例类中的用例执行之前,会先执行此方法'''print('-----setup---class-----')@classmethoddef tearDownClass(cls) -> None:'''整个测试用例类中的用例执行完成后,会执行此方法'''print('-----teardown---class-----')def setUp(self):'''每条测试用例执行前都会执行'''print('用例{}开始执行...'.format(self))def tearDown(self):'''每条测试用例执行结束后都会执行'''print('用例{}执行结束...'.format(self))def test_register_success(self):'''注册成功'''data = ('palien', 'palien', 'palien') # 测试数据result = register(*data) # 测试结果expected = {'code': 0, 'msg': '注册成功.'} # 预期结果self.assertEqual(result, expected) # 断言测试结果与预期结果一致# passdef test_username_exist(self):'''注册失败-用户名已存在'''data = ('testing', '123456', '123456')result = register(*data)expected = {'code': 0, 'msg': '用户名已存在!'}self.assertEqual(result, expected)def test_username_isnull(self):'''注册失败-用户名为空'''data = ('', 'palien', 'palien')result = register(*data)expected = {'code': 0, 'msg': '所有参数不能为空.'}self.assertEqual(result, expected)# passdef test_username_lt18(self):'''注册失败-用户名长度大于18位'''data = ('palienpalienpalienpalien', 'palien', 'palien')result = register(*data)expected = {'code': 0, 'msg': '用户名和密码的长度必须在6~18位之间.'}self.assertEqual(result, expected)# passdef test_password1_not_password2(self):'''注册失败-两次输入密码不一致'''data = ('palien', 'palien1', 'palien2')result = register(*data)expected = {'code': 0, 'msg': '两次密码输入不一致!'}self.assertEqual(result, expected)# pass# 如果要直接运行这个测试类,需要使用unittest中的main函数来执行测试用例
if __name__ == '__main__':unittest.main()### Output
PS D:\d_02_study\01_git> cd d:/d_02_study/01_git/papers/system/02automation
PS D:\d_02_study\01_git\papers\system\02automation> & C:/Users/TDH/AppData/Local/Programs/Python/Python310-32/python.exe d:/d_02_study/01_git/papers/system/02automation/demo.py
-----setup---class-----
用例test_password1_not_password2 (__main__.RegisterTest)开始执行...
用例test_password1_not_password2 (__main__.RegisterTest)执行结束...
.用例test_register_success (__main__.RegisterTest)开始执行...
用例test_register_success (__main__.RegisterTest)执行结束...
.用例test_username_exist (__main__.RegisterTest)开始执行...
用例test_username_exist (__main__.RegisterTest)执行结束...
.用例test_username_isnull (__main__.RegisterTest)开始执行...
用例test_username_isnull (__main__.RegisterTest)执行结束...
.用例test_username_lt18 (__main__.RegisterTest)开始执行...
用例test_username_lt18 (__main__.RegisterTest)执行结束...
.-----teardown---class---------------------------------------------------------------------------
Ran 5 tests in 0.004sOK
PS D:\d_02_study\01_git\papers\system\02automation>
TestSuit测试套件
unittest.TestSuit()类用来表示一个测试用例集,把需要执行的用例类或模块集合在一起。
常用的方法:
unittest.TestSuit()
addTest():添加单个测试用例方法
addTest([...]):添加多个测试用例方法,方法名存在一个列表
unittest.TestLoader()
loadTestsFromTestCase(测试类名):添加一个测试类
loadTestsFromMdule(模块名):添加一个模块
discover(测试用例所在的目录):指定目录去加载,会自动寻找这个目录下所有符合命名规则的测试用例
代码示例:
'''以下三个文件必须在同一文件夹下:demo.pytest_demo.pyrun_test.py
'''import os
import unittest
import test_demo# 第一步,创建一个测试套件
suit = unittest.TestSuite()# 第二步,将测试用例加载到测试套件中# # 方式一,添加单条测试用例
# case = test_demo.RegisterTest('test_register_success')
# '''
# 创建一个用例对象。
# 注意:通过用例类去创建测试用例对象的时候,需要传入用例的方法名(字符串类型)
# 这里不是像调用普通类中的方法那样通过类名.方法名调用,可以理解为unittest框架的特殊之处
# '''
# suit.addTest(case) # 添加用例到测试套件中# # 方式二:添加多条用例
# case1 = test_demo.RegisterTest('test_username_exist')
# case2 = test_demo.RegisterTest('test_username_isnull')
# suit.addTest([case1, case2]) # 添加用例到测试套件中。注意这里使用的是suit.addTest()方法而不是suit.addTests()方法# # 方式三:添加一个测试用例集
# loader = unittest.TestLoader() # 创建一个加载对象
# suit.addTest(loader.loadFromTestCase(test_demo.RegisterTest)) # 通过加载对象从测试类中加载用例到测试套件中# '''
# 通产我们使用方式4、5比较多,可以根据实际情况来运用。
# 其中方式5还可以自定义匹配规则,默认是会寻找目录下的test*.py文件,即所有以test开头命名的py文件。
# '''
# # 方式四:添加一个模块(其实就是一个后缀名为.py文件,这里就是test_demo.py文件)
# loader = unittest.TestLoader() # 创建一个加载对象
# suit.addTest(loader.loadTestsFromModule(test_demo)) # 通过加载对象从模块中加载测试用例到测试套件中# 方式五:指定测试用例的所在目录路径,进行加载
loader = unittest.TestLoader() # 创建一个加载对象
case_path = os.path.dirname(os.path.abspath(__file__)) # 文件路径
# print('用例所在的目录路径为:', case_path)
# suit.addTest(loader.discover(case_path)) # 通过加载对象从用例所在目录加载测试用例到测试套件
suit.addTest(loader.discover(start_dir=case_path, pattern='test_demo*.py')) # 两个参数:路径和匹配规则
TestRunner执行用例
testRunner用来执行用例,并且可以生成相应的测试报告。测试报告有两种形式:一种是 text文本;一种是 html格式。
html格式是 HTMLTestRunner插件辅助生成的,是Python标准库的unittest框架的一个拓展,可以生成一个清晰直观的html测试报告。使用的前提就是需要下载 HTMLTestRunner.py,下载完成后放在python安装目录下的scripts目录下即可。
text文本相对于html来说过于简陋,与输出控制台没有什么区别,也几乎不适用。
代码示例:
# demo.py,与test_demo.py和run_test.py在同一目录下# 导入模块
import unittest
import os
import test_demo
from HTMLTestReportCN import HTMLTestRunner# 用例文件所在目录
base_path = os.path.dirname(os.path.abspath(__file__))
# report_path = base_path + 'report.html'# 打开报告文件# 创建测试套件
suit = unittest.TestSuite()# 通过模块加载测试用例
loader = unittest.TestLoader()
suit.addTest(loader.discover(start_dir=base_path, pattern='test_demo*.py'))# 创建测试运行程序启动器
runner = HTMLTestRunner(stream=open('report.html', 'w', encoding='utf-8'), # 打开一个报告文件,并将句柄传给streamtester='palien', # 报告中显示的测试人员 description='注册接口测试报告', # 报告中显示的描述信息title='自动化测试报告' # 测试报告标题
)# 使用启动器去执行测试套件里面的测试用例
runner.run(suit)
相关参数说明:
stream:指定输出方式
tester:报告中要显示的测试人员的名字
description:报告中要显示的描述信息
title:测试报告的标题
verbosity:表示测试报告信息的详细程度,一共三个值,默认为2
0(静默模式):只能获得总的测试用例书和总的结果,如:总共100个,失败90
1(默认模式):类似静默模式,只是在在每个成功的用例面前有个. 每个失败的用例前面有个F
2(详细模式):测试结果会显示每个测试用例的所有相关信息
运行完毕,在项目目录下面会生成一个report.html文件,在浏览器中打开,就可以看到测试报告了。

问题记录
在学习的过程中遇到了一些问题,记录一下。
HTMLTestRunner下载
经验证,以下两个文件就支持生成上面截图的报告。

- 报错
TypeError: a bytes-like object is required, not 'str'解决
- 中文报告乱码问题
问题如图:

【2023最新】Python自动化测试,7天练完60个实战项目,全程干货。【自动化测试/接口测试/性能测试/软件测试】
最后感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:
这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!
相关文章:
unittest自动化测试框架讲解以及实战
为什么要学习unittest 按照测试阶段来划分,可以将测试分为单元测试、集成测试、系统测试和验收测试。单元测试是指对软件中的最小可测试单元在与程序其他部分相隔离的情况下进行检查和验证的工作,通常指函数或者类,一般是开发完成的。 单元…...
数学建模之Matlab基础操作
作者由于后续课程也要学习Matlab,并且之前也进行了一些数学建模的练习(虽然是论文手),所以花了几天零碎时间学习Matlab的基础操作,特此整理。 基本运算 a55 %加法,同理减法 b2^3 %立方 c5*2 %乘法 x 1; …...
【Nuxt】04 Nuxt2-SEO: sitemap.xml、seo优化、robots.txt
1 SiteMap设置 环境准备 注意生成sitemap依赖于nuxtjs/sitemap,并且需要用axios进行请求,不要使用nuxtjs/axios,不然会报错 sitemap.xml配置 在nuxt.config.js中配置下面的内容 npm install nuxtjs/sitemap npm install axios在static/s…...
VMware VSAN 入门
一、虚拟化的存储 1.1、对于数据中心来说最重要的是数据,而承载数据的设备就是存储设备(Storage) 1.2、物理服务器的本地存储阵列 与 虚拟化服务器的本地存储阵列 对比 1.3、避免单台服务器故障的虚拟化高级特性:vSphere HA技术 …...
【设计模式】备忘录模式
文章目录 1.备忘录模式定义2.备忘录模式的角色3.备忘录模式实现3.1.场景说明3.2.结构类图3.3.代码实现 4.备忘录模式优缺点5.备忘录模式适用场景6.备忘录模式总结 主页传送门:💁 传送 1.备忘录模式定义 备忘录(Memento Pattern)模…...
vue3+elementUiPlus表格导出功能
1.下载需要的组件包 npm install file-saver xlsx 2.页面中导入 import FileSaver from file-saver import * as XLSX from xlsx; 3.页面中的表格加一个id <el-table :data"tableData" ref"multipleTableRef" style"width…...
专题五:优先级队列
"你了解我,最干净的轮廓, 握住小小风车和放肆的梦~" 堆是一个不错的数据结构,而在计算机中,无法表示二叉分支结构,因此我们经常会看到使用线性表来作为堆的存储容器。在接触堆的时候,我们是把它…...
游戏设计模式专栏(一):工厂方法模式
引言 大家好,我是亿元程序员,一位有着8年游戏行业经验的主程。 本系列是《和8年游戏主程一起学习设计模式》,让糟糕的代码在潜移默化中升华,欢迎大家关注分享收藏订阅。 在游戏开发中,代码的组织和结构对于项目的可…...
element中使用el-steps 进度条效果demo(整理)
<template><div class"margin-top20"><!-- align-center 不要居中就去掉 --><!-- process-status 这几个参数值:改变颜色 wait / process / finish / error / --><!-- active 到第几个是绿色 --><el-steps :space&qu…...
038:mapboxGL 旋转地图(rotateTo)
第038个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+mapbox中旋转地图。 直接复制下面的 vue+mapbox源代码,操作2分钟即可运行实现效果 文章目录 示例效果配置方式示例源代码(共68行)相关API参考:专栏目标示例效果 配置方式 1)查看基础设置:https://xiaozhuan…...
java遇到的问题
java遇到的问题 Tomcat与JDK版本问题 当使用Tomcat10的版本用于springmvc借用浏览器调试时,使用JDK8浏览器会报异常。 需要JDK17(可以配置多个JDK环境,切换使用)才可以使用,配置为JAVA_HOME路径,否则&a…...
LLM(二)| LIMA:在1k高质量数据上微调LLaMA1-65B,性能超越ChatGPT
本文将介绍在Lit-GPT上使用LoRA微调LLaMA模型,并介绍如何自定义数据集进行微调其他开源LLM 监督指令微调(Supervised Instruction Finetuning) 什么是监督指令微调?为什么关注它? 目前大部分LLM都是decoder-only&…...
Android AMS——创建Application(七)
与在 App 内部启动一个 Activity 的不同之处在于,点击桌面 Launcher 首次启动一个应用程序的时候,会先去创建一个该应用程序对应的进程,然后执行 ActivityThread 的 main() 方法去创建该应用对应的 Application,然后再去启动首页 Activity。前面已经分析了进程的创建和启动…...
html 边缘融合加载
html 代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>边缘融合加载</title><style>* {margin: 0;padding: 0;box-sizing: border-box;}body {height: 100vh;padding-bottom: 80px;b…...
ElasticSearch - 在 微服务项目 中基于 RabbitMQ 实现 ES 和 MySQL 数据异步同步(考点)
目录 一、数据同步 1.1、什么是数据同步 1.2、解决数据同步面临的问题 1.3、解决办法 1.3.1、同步调用 1.3.2、异步通知(推荐) 1.3.3、监听 binlog 1.3、基于 RabbitMQ 实现数据同步 1.3.1、需求 1.3.2、在“酒店搜索服务”中 声明 exchange、…...
Springboot+vue的企业人事管理系统(有报告),Javaee项目,springboot vue前后端分离项目。
演示视频: Springbootvue的企业人事管理系统(有报告),Javaee项目,springboot vue前后端分离项目。 项目介绍: 本文设计了一个基于Springbootvue的前后端分离的企业人事管理系统,采用M(model&am…...
初识Java 11-1 函数式编程
目录 旧方式与新方式 lambda表达式 方法引用 Runnable 未绑定方法引用 构造器方法引用 函数式接口 带有更多参数的函数式接口 解决缺乏基本类型函数式接口的问题 本笔记参考自: 《On Java 中文版》 函数式编程语言的一个特点就是其处理代码片段的简易性&am…...
【Ambari】银河麒麟V10 ARM64架构_安装Ambari2.7.6HDP3.3.1问题总结
🍁 博主 "开着拖拉机回家"带您 Go to New World.✨🍁 🦄 个人主页——🎐开着拖拉机回家_大数据运维-CSDN博客 🎐✨🍁 🪁🍁 希望本文能够给您带来一定的帮助🌸文…...
李宏毅机器学习第一课(结尾附作业模型详细分析)
机器学习就是让机器找一个函数f,这个函数f是通过计算机找出来的 如果参数少的话,我们可以使用暴搜,但是如果参数特别多的话,我们就要使用Gradient Descent Regression (输出的是一个scalar数值) Classification (在…...
对日项目工作总结
从18年8月到23年中秋节,目前已经入职主营对日车载项目的公司满5年了,一般来说,在一家公司工作工作超过3年,如果是在比较大型以及流程规范的公司,那么该公司的工作流程,工作思维会深深地烙印在该员工的脑海中…...
Pterodactyl-installer数据库配置:MySQL用户权限管理与安全最佳实践
Pterodactyl-installer数据库配置:MySQL用户权限管理与安全最佳实践 【免费下载链接】pterodactyl-installer :bird: Unofficial installation scripts for Pterodactyl Panel 项目地址: https://gitcode.com/gh_mirrors/pt/pterodactyl-installer Pterodact…...
终极指南:5分钟搞定MASA模组全家桶中文汉化,告别英文困扰
终极指南:5分钟搞定MASA模组全家桶中文汉化,告别英文困扰 【免费下载链接】masa-mods-chinese 一个masa mods的汉化资源包 项目地址: https://gitcode.com/gh_mirrors/ma/masa-mods-chinese 还在为Minecraft技术模组的英文界面而头疼吗࿱…...
35+ 程序员必读:收藏!AI时代如何逆袭,成为最抢手的技术人才?
AI时代对程序员的影响是深远的,尤其是对于35岁的程序员,面临着被淘汰的压力。然而,AI也可以成为他们的优势,通过利用AI工具,他们可以放大自己的长板,如架构经验、业务理解和工程判断力。文章建议35岁的程序…...
保姆级教程:用Node-RED把传感器数据传到ThingsBoard仪表盘(MQTT全流程)
从零构建物联网数据可视化:Node-RED与ThingsBoard的实战融合 在智能家居、工业监测等物联网场景中,如何将物理世界的传感器数据转化为直观的可视化图表?本文将手把手带您完成从硬件数据采集到云端展示的完整链路实现。不同于单纯的理论讲解&a…...
3步快速上手Univer:从零构建企业级办公套件的完整指南
3步快速上手Univer:从零构建企业级办公套件的完整指南 【免费下载链接】univer Build AI-native spreadsheets. Univer is a full-stack framework for creating and editing spreadsheets on both web and server. With Univer Platform, Univer Spreadsheets is d…...
模力方舟与口袋龙虾:开源中国的AI云端与端侧协同生态解析
本文解析开源中国通过“模力方舟”与“口袋龙虾”平台构建的AI协同生态。该生态旨在解决AI开发与落地中的资源分散与端侧部署难题,为开发者、企业及终端用户提供从云端资源调用到边缘智能部署的一站式通路。核心结论是,这种“云-边-端”协同模式降低了技…...
从QRegExp迁移到QRegularExpression避坑全记录:我们项目踩过的雷和最佳实践
从QRegExp迁移到QRegularExpression避坑全记录:我们项目踩过的雷和最佳实践 当团队决定将代码库从Qt4/Qt5升级到Qt6时,正则表达式模块的迁移往往是最容易被低估的挑战之一。我们项目组在重构过程中,曾因QRegExp到QRegularExpression的语法差异…...
如何免费下载中国大学MOOC视频:MoocDownloader完整使用指南
如何免费下载中国大学MOOC视频:MoocDownloader完整使用指南 【免费下载链接】MoocDownloader An MOOC downloader implemented by .NET. 一枚由 .NET 实现的 MOOC 下载器. 项目地址: https://gitcode.com/gh_mirrors/mo/MoocDownloader 你是否曾经因为网络不…...
解决企业IT服务管理复杂性的iTop开源CMDB架构实践
解决企业IT服务管理复杂性的iTop开源CMDB架构实践 【免费下载链接】iTop A simple, web based CMDB & IT Service Management tool 项目地址: https://gitcode.com/gh_mirrors/it/iTop 在数字化转型时代,企业面临IT配置信息分散、工单流转低效、资产跟踪…...
【DBC专题】-12-基于Cantools的CAN/CANFD DBC文件自动化C代码生成实战指南
1. 环境准备与工具链搭建 第一次接触CAN总线开发时,我被DBC文件到C代码的手动转换折磨得够呛。直到发现Cantools这个神器,才真正体会到什么叫"一劳永逸"。这个Python工具链能自动将DBC描述文件转换为可直接编译的C代码,特别适合需要…...

