news 2026/9/7 10:15:51

博客系统接口自动化测试

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
博客系统接口自动化测试

目录

摘要

1. 前后端分离后,接口是唯一的契约

2. 能发现 UI 测试发现不了的问题

3. 测试左移,更早发现问题

4. 自动化后可重复执行,回归测试利器

一、接口测试用例设计---思维导图

​编辑

二、本次测试所需要的工具以及环境

1、开发环境

2、Python 第三方库

3、Python 标准库

4、测试报告工具

三、搭建接口测试框架

1.测试架构

2.编写代码

(1)封装工具类

封装日志类:

​ 封装请求类:

封装yaml类:

(2)用例编写

登录接口:

列表页接口:

详情页接口:

用户认证接口:

编辑页接口:

用户信息接口:

执行测试用例

指定测试用例执行顺序:

生成测试报告并分析结果

本次测试的gitee仓库:https://gitee.com/sddsfsdf455/blog_-api_-auto-test


摘要

接口测试是测试系统各组件之间的接口(API)是否符合预期,是软件测试里性价比最高的一层。------>比单元测试覆盖范围大(能测模块间交互),比 UI 测试快且稳定(不依赖页面元素)

1. 前后端分离后,接口是唯一的契约

现在的系统基本都是前后端分离:

  1. 前端(页面)调用后端接口拿数据
  2. 后端接口返回 JSON 给前端渲染
  • 接口就是前后端之间的合同,如果接口返回的数据格式不对、字段缺失、类型错误,前端页面直接崩。接口测试就是提前验证这个 "合同" 有没有履行。

2. 能发现 UI 测试发现不了的问题

很多问题在页面上看不出来,但接口层面已经暴露了:

  1. 越权漏洞:A 用户能看 B 用户的数据(本次测试)
  2. 参数校验缺失:传非法值(字符串、特殊字符、空值)接口不报错,直接 500
  3. 数据泄露:接口返回了不该返回的敏感字段(密码、手机号)
  4. 并发 / 性能问题:接口响应慢、高并发下报错
  5. 状态码不规范:业务失败也返回 200,前端无法判断
  • 这些问题点页面可能 "看起来正常",但接口层面已经有严重隐患

3. 测试左移,更早发现问题

  1. 单元测试:开发写完函数就能测
  2. 接口测试:后端接口开发完、前端页面还没写好,就能测
  3. UI 测试:必须等前端页面全部开发完才能测
  • 接口测试可以在项目早期就介入,不用等页面做好。越早发现 bug,修复成本越低—— 需求阶段改只要 1 小时,上线后改可能要几天。

4. 自动化后可重复执行,回归测试利器

接口一旦写好,变化频率比 UI 低得多。以后每次代码更新、版本发布,跑一遍就知道有没有把老功能搞坏(回归测试)。

如果靠人工点页面测,费时费力还容易漏。

接口测试是用最低的成本,最早、最稳定地发现系统模块间交互问题的测试手段。 前后端分离的项目里,接口是系统的骨架,骨架稳了,页面才不会出大问题。

一、接口测试用例设计---思维导图

二、本次测试所需要的工具以及环境

1、开发环境

  1. 操作系统:Windows
  2. 编程语言:Python 3.10
  3. 开发工具:PyCharm Community Edition 2022.1.3以及postman
  4. 虚拟环境:venv(项目内置,用于隔离依赖)

2、Python 第三方库

  1. pytest:测试框架,负责收集和运行测试用例
  2. requests:发送 HTTP 请求,支持 GET、POST 等请求方式
  3. PyYAML:读写 YAML 格式的测试数据文件
  4. jsonschema:对接口返回的 JSON 数据进行结构校验
  5. allure-pytest:生成 allure 测试结果数据
  6. pytest-order:控制测试用例的执行顺序,通过 @pytest.mark.order 装饰器实现

通过在控制台输入pip install ......来导入对应的包

3、Python 标准库

  1. logging:日志记录
  2. os:文件路径与目录操作
  3. time:时间格式化,用于生成带日期的日志文件名
  4. base64:图片转 base64 编码(如需传图片参数)

4、测试报告工具

  1. Java 运行环境(JDK):allure 命令行工具的运行依赖,需配置 JAVA_HOME 环境变量
  2. allure 命令行工具:将测试结果数据转换为可视化的 HTML 报告,可通过 scoop install allure 安装,或手动下载后配置 Path 环境变量

三、搭建接口测试框架

在虚拟环境下编写代码 避免污染全局环境

1.测试架构

pytest.ini 的内容为addopts = -vs --alluredir allure-results

2.编写代码

(1)封装工具类

封装日志类:
import logging import os.path import time class info_filter(logging.Filter): def filter(self, record): return record.levelno == logging.INFO class err_filter(logging.Filter): def filter(self, record): return record.levelno == logging.ERROR class logger: # 使用类方法 为类新增一个类方法 @classmethod def getlog(cls): # 获取日志记录器对象 指向Logger 名称为root cls.my_logger = logging.getLogger() # 定义日志级别最低为DEBUG cls.my_logger.setLevel(level=logging.DEBUG) ''' logs 2026-8-27.log 2026-8-27-info.log 2026-8-27-err.log ''' # 创建logs文件夹,将日志文件储存进去 方便查看 LOG_PATH = "./logs/" if not os.path.exists(LOG_PATH): os.mkdir(LOG_PATH) now = time.strftime("%Y-%m-%d") log_name = LOG_PATH + now + ".log" info_log_name = LOG_PATH + now + "-info.log" err_log_name = LOG_PATH + now + "-err.log" # (创建一个日志文件处理器 将日志信息填入指定文件中) # 创建⼀个 FileHandler 对象,指定⽇志⽂件的名称为 "log_name" # 这个处理器会将⽇志信息写⼊到指定的⽂件中 all_handler = logging.FileHandler(filename=log_name, encoding="utf-8") info_handler = logging.FileHandler(filename=info_log_name, encoding="utf-8") err_handler = logging.FileHandler(filename=err_log_name, encoding="utf-8") # 创建⼀个⽇志格式器对象 formatter = logging.Formatter( "%(asctime)s %(levelname)s [%(name)s] [%(filename)s (%(funcName)s:%(lineno)d)] - %(message)s" ) # 将格式器设置到处理器上 all_handler.setFormatter(formatter) info_handler.setFormatter(formatter) err_handler.setFormatter(formatter) # 设置文件过滤器 info_handler.addFilter(info_filter()) err_handler.addFilter(err_filter()) # 添加处理器到记录器当中 # 这样日志记录器就会使用处理器处理日志信息 cls.my_logger.addHandler(all_handler) cls.my_logger.addHandler(info_handler) cls.my_logger.addHandler(err_handler) return cls.my_logger

自定义 logger 日志封装类,通过getlog类方法完成日志记录器的统一配置:设置日志级别为 DEBUG,自动创建 logs 目录并按日期生成总日志、INFO 日志、ERROR 日志三个文件;创建三个 FileHandler 分别写入对应文件,配置统一的 Formatter 格式(含时间、级别、文件名、函数名、行号、消息内容);通过自定义 info_filter 和 err_filter 过滤器实现分级输出,info.log 仅存 INFO 级别、err.log 仅存 ERROR 级别。最终返回配置完成的 logger 实例,调用方直接使用即可,无需重复配置,实现日志功能的统一封装与复用。

设置日志过滤器 将其放在日志处理器上:把对应级别的日志放入专门设置的对应的日志文件

封装请求类:
import requests from utils.logging_utils import logger host = "http://49.235.61.184:19090/" class Request: log = logger.getlog() def get(self, url, **kwargs): self.log.info("准备发起GET请求,url:"+url) self.log.info("接口信息:{}".format(kwargs)) r = requests.get(url=url, **kwargs) self.log.info("接口响应状态码:{}".format(r.status_code)) self.log.info("接口的响应数据是:{}".format(r.text)) return r def post(self, url, **kwargs): self.log.info("准备发起POST请求,url:" + url) self.log.info("接口信息:{}".format(kwargs)) r = requests.post(url=url, **kwargs) self.log.info("接口响应状态码:{}".format(r.status_code)) self.log.info("接口的响应数据是:{}".format(r.text)) return r

自定义一个 Request 请求类,在类内封装 get 和 post 请求方法,接收 url 及其他请求参数。该方法在调用原生 requests.get/requests.post 发送请求的前后,通过 logging 日志记录器分别记录请求信息(URL、请求参数)和响应信息(状态码、响应数据),实现了在不修改原生 requests 方法的前提下,为请求过程增加统一的日志记录功能。日志可同时输出到控制台和日志文件,便于排查问题。

封装yaml类:
''' 使用yaml ''' import os import yaml # 在yaml文件中存储数据 def write_yml(filename, data): with open(os.getcwd() + "/data/" + filename, mode="a+", encoding="utf-8") as f: yaml.safe_dump(data, stream=f) # 在yaml文件中读取数据并返回需要的信息-token登录凭证 def read_yml(filename, key): with open(os.getcwd() + "/data/" + filename, mode="r", encoding="utf-8") as f: data = yaml.safe_load(stream=f) return data[key] # 清理yaml中的数据 def clear_yml(filename): with open(os.getcwd() + "/data/" + filename, mode="w", encoding="utf-8") as f: f.truncate()

自定义 YAML 数据读写工具,封装write_ymlread_ymlclear_yml三个函数实现测试数据的统一管理:write_yml以追加模式打开文件,通过yaml.safe_dump将接口返回的动态数据(如 token、blogId)写入 YAML 文件,实现跨用例数据传递;read_yml以只读模式打开文件,通过yaml.safe_load解析内容并按 key 返回对应数据值,供测试用例读取使用;clear_yml通过truncate清空文件内容,用于测试前重置数据,避免历史数据干扰。三个函数统一一起使用os.getcwd()拼接 data 目录路径,实现数据与代码分离,便于维护和扩展。

(2)用例编写

登录接口:
''' 登录-接口自动化测试 url-->"... " 请求数据为json格式{"userName":"zhangsan","password":"123456"} 利用jsonSchema校验接口返回数据 保存登陆凭证数据 ''' import re import pytest from jsonschema.validators import validate from utils.request_utils import host, Request from utils.yaml import write_yml @pytest.mark.order(1) class TestLogin: url = host + "user/login" schema = { "type": "object", "additionalProperties": False, "properties": { "code": { "type": "number" }, "errMsg": { "type": ["null", "string"] }, "data": { "type": ["object", "null"], "properties": { "userId": { "type": "number" }, "token": { "type": "string" } }, "required": [ "userId", "token" ] } }, "required": [ "code", "errMsg", "data" ] } @pytest.mark.parametrize("login", [ { "userName": "*****", "password": "********", }, { "userName": "********", "password": "********", } ]) def test_login_success(self, login): json = { "userName": login["userName"], "password": login["password"] } r = Request().post(url=self.url, json=json) validate(instance=r.json(), schema=self.schema) assert r.json()["code"] == 200 assert re.match(r'\S{100,}', r.json()["data"]["token"]) # 保存用户的登录凭证token data = { "user_token": r.json()["data"]["token"] } write_yml("data.yaml", data) @pytest.mark.parametrize('login', [ { "userName": "*****", "password": "***", }, { "userName": "********", "password": "*******", }, { "userName": "******", "password": "******", }, { "userName": "", "password": "****", }, { "userName": "********", "password": "", }, { "userName": "", "password": "", }, { "userName": "******", "password": "*******1111", }, { "userName": "*********11111111111111111", "password": "*********", } ]) def test_login_fail(self, login): json = { "userName": login["userName"], "password": login["password"] } r = Request().post(url=self.url, json=json) validate(instance=r.json(), schema=self.schema) assert r.json()["code"] == -1 assert r.json()["data"] is None

定义一个登录测试类------>未登录状态下访问测试用例,登录状态下访问的测试用例并分为正常测试用例和异常测试用例

使用pytest中内置的pytest.mark.parametrize装饰器对测试函数的参数进⾏参数化。
以达到测试多组数据的目的
获取到登录接口的请求数据之后 将它写入yaml文件中并保存起来,以便之后调用所需数据
JSON Schema⼀个⽤来定义和校验JSON的web规范,简⽽⾔之,JSON Schema是⽤来校验json是否符合预期。
根据json创建 JSON Schema 后,在类中定义一个schema并 将得出的数据放入其中
validate方法可以将识别得到的数据类型和请求的url的请求信息进行匹配,验证请求信息是否出现错误
如何获取测试用例的json数据:
1.打开postman 输入正确的网址以及添加所需的请求数据
比如:
复制整段json数据 并且打开:https://json.tqji.cn/to-schema
这样就可以获取相应的Schema数据了,最后使用validate方法将请求到的的网页数据和取得的Schema数据比较来验证数据类型是否匹配
最后使用assert 进行断言具体的数据内容是否符合预期。
列表页接口:
import pytest from jsonschema.validators import validate from utils.request_utils import host, Request from utils.yaml import read_yml, write_yml @pytest.mark.order(2) class TestList: url = host + "blog/getListByPage" Schema = { "type": "object", "additionalProperties": False, "properties": { "code": { "type": "number" }, "errMsg": { "type": "null" }, "data": { "type": "object", "properties": { "total": { "type": "number" }, "pages": { "type": "number" }, "pageNum": { "type": "number" }, "pageSize": { "type": "number" }, "records": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "number" }, "title": { "type": "string" }, "content": { "type": "string" }, "createTime": { "type": "string" } }, "required": [ "id", "title", "content", "createTime" ] } } }, "required": [ "total", "pages", "pageNum", "pageSize", "records" ] } }, "required": [ "code", "errMsg", "data" ] } def test_List_No_login(self): r = Request().get(url=self.url) assert r.status_code == 401 def test_List_login(self): token = read_yml("data.yaml", "user_token") header = { "User_token": token } r = Request().get(url=self.url, headers=header) validate(instance=r.json(), schema=self.Schema) # 获取列表页的具体blogID data = { "blogId": r.json()["data"]["records"][0]["id"] } write_yml("data.yaml", data)
列表接口的测试方式也和登录接口的测试步骤差不多,包括未登录状态下访问和登录状态下访问。
获取后续接口测试所需要的blogId数据:调用write_yaml方法将在列表页接口的数据中获得的blogId存入yaml文件中,方便后续测试
详情页接口:
import pytest from jsonschema.validators import validate from utils.request_utils import host, Request from utils.yaml import read_yml class TestDetail: url = host + "blog/getBlogDetail?" Schema = { "type": "object", "properties": { "code": { "type": "number" }, "errMsg": { "type": ["null", "string"] }, "data": { "type": ["object", "null"], "properties": { "id": { "type": ["number", "null"] }, "title": { "type": "string" }, "content": { "type": "string" }, "userId": { "type": "number" }, "createTime": { "type": "string" } }, "required": [ "id", "title", "content", "userId", "createTime" ] } }, "required": [ "code", "errMsg", "data" ] } # 未登录状态下访问详情页接口 def test_detail_no_login(self): r = Request().get(url=self.url + "blogId=" + str(123333)) assert r.status_code == 401 # 登录状态下访问 def test_detail_login_success(self): BlogId = read_yml("data.yaml", "blogId") token = read_yml("data.yaml", "user_token") header = { "User_token": token } r = Request().get(url=self.url + "blogId=" + str(BlogId), headers=header) validate(instance=r.json(), schema=self.Schema) assert r.json()["code"] == 200 assert r.json()["errMsg"] is None @pytest.mark.parametrize("blog_id", [ { "blogId": 666666, "errMsg": "Source must not be null" }, { "blogId": "", "errMsg": "参数校验失败" }, { "blogId": -1, "errMsg": "Source must not be null" }, { "blogId": 0, "errMsg": "Source must not be null" }, { "blogId": "qq1@!!!", "errMsg": "Method parameter 'blogId': Failed to convert value of type 'java.lang.String' to required type 'java.lang.Integer'; For input string: \"qq1@!!!\"" }, { "blogId": 222, "errMsg": "Source must not be null" }, { "blogId": 222222222222222222222222222222222222222, "errMsg": "Method parameter 'blogId': Failed to convert value of type 'java.lang.String' to required type 'java.lang.Integer'; For input string: \"222222222222222222222222222222222222222\"" } ]) def test_detail_login_fail(self, blog_id): token = read_yml("data.yaml", "user_token") header = { "User_token": token } r = Request().get(url=self.url + "blogId=" + str(blog_id["blogId"]), headers=header) validate(instance=r.json(), schema=self.Schema) assert r.json()["code"] == -1 assert r.json()["errMsg"] == blog_id["errMsg"] def test_detail_login_fail_withoutBlogId(self): token = read_yml("data.yaml", "user_token") header = { "User_token": token } r = Request().get(url=self.url, headers=header) validate(instance=r.json(), schema=self.Schema) assert r.json()["code"] == -1 assert r.json()["errMsg"] == "参数校验失败"
测试在未登录状态下访问 状态码是否为401 ,正常登录下的输入正确测试用例数据,之后按照接口测试用例中的数据输入异常登录的不同blogId数据测试是否符合预期
要注意BlogId是数字,不能将其正常和字符串拼接,必须要将其强制转换成字符型
在使用@pytest.mark.parametrize装饰器参数化时可以内输入所需要的具体匹配数据,来看看使用Request类请求所得的信息是否和期望数据一样
这是单独分出来的一个异常测试用例:在缺失关键参数字段的情况下正常访问接口
所以要将中的url拆分成多个部分,以方便按照设计的测试用例进行测试
用户认证接口:
import pytest from jsonschema.validators import validate from utils.request_utils import host, Request from utils.yaml import read_yml class Test_getAuthorInfo: url = host + "user/getAuthorInfo?" Schema = { "type": "object", "properties": { "code": { "type": "number" }, "errMsg": { "type": ["null", "string"] }, "data": { "type": ["object", "null"], "properties": { "id": { "type": "number" }, "userName": { "type": "string" }, "githubUrl": { "type": "string" } }, "required": [ "id", "userName", "githubUrl" ] } }, "required": [ "code", "errMsg", "data" ] } # 未登陆下访问 def test_get_authorInfo_no_login(self): url = self.url + "blogId=" + str(12222) r = Request().get(url=url) assert r.status_code == 401 def test_get_authorInfo_login_success(self): BlogId = read_yml("data.yaml", "blogId") token = read_yml("data.yaml", "user_token") header = { "User_token": token } r = Request().get(url=self.url + "blogId=" + str(BlogId), headers=header) validate(instance=r.json(), schema=self.Schema) assert r.json()["code"] == 200 assert r.json()["errMsg"] is None @pytest.mark.parametrize("blog_id", [ { "blogId": 666666, "errMsg": "blogId 不合法" }, . . . . . ]) def test_get_authorInfo_login_fail(self, blog_id): token = read_yml("data.yaml", "user_token") header = { "User_token": token } r = Request().get(url=self.url + "blogId=" + str(blog_id["blogId"]), headers=header) validate(instance=r.json(), schema=self.Schema) assert r.json()["code"] == -1 assert r.json()["errMsg"] == blog_id["errMsg"] def test_get_authorInfo_login_fail_withoutBlogId(self): token = read_yml("data.yaml", "user_token") header = { "User_token": token } r = Request().get(url=self.url, headers=header) validate(instance=r.json(), schema=self.Schema) assert r.json()["code"] == -1 assert r.json()["errMsg"] == "参数校验失败"

该接口和详情页接口的测试有很大一部分是一样的,因为所需的参数都为blogId,所以 只需按照postman中获取的json数据进行微调 ,Schema值也需适当调整。

编辑页接口:
import pytest from jsonschema.validators import validate from utils.request_utils import host, Request from utils.yaml import read_yml class TestAdd: url = host + "blog/add" Schema = { "type": "object", "properties": { "code": { "type": "number" }, "errMsg": { "type": ["null", "string"] }, "data": { "type": ["boolean", "null"] } }, "required": [ "code", "errMsg", "data" ] } # 未登录状态下访问 def test_add_no_login(self): r = Request().post(url=self.url) assert r.status_code == 401 # 登录状态下访问 @pytest.mark.parametrize("add", [ { "userId": "3", # 用户本人自己的Id "title": "1111", "content": "11111" }, . . . . . . ]) def test_add_login_success(self, add): token = read_yml("data.yaml", "user_token") header = { "User_token": token } json = { "userId": add["userId"], "title": add["title"], "content": add["content"] } r = Request().post(url=self.url, headers=header, json=json) validate(instance=r.json(), schema=self.Schema) assert r.json()["code"] == 200 assert r.json()["errMsg"] is None assert r.json()["data"] is True # 异常用例 @pytest.mark.parametrize("add_fail", [ { "userId": "3", # 用户本人自己的Id "title": "", "content": "", "errMsg": ["博客正文不能为空", "标题不能为空"] }, . . . . . . ]) def test_add_login_fail(self, add_fail): token = read_yml("data.yaml", "user_token") header = { "User_token": token } json = { "userId": add_fail["userId"], "title": add_fail["title"], "content": add_fail["content"] } r = Request().post(url=self.url, headers=header, json=json) validate(instance=r.json(), schema=self.Schema) assert r.json()["errMsg"] in add_fail["errMsg"] assert r.json()["code"] == -1 assert r.json()["data"] is None

当postman出现“null"或者ture这种Boolean类型的数据,

在判断时,需要使用is来判断请求网页的json数据是否和对应的Schema数据匹配:“null”对应None,ture对应“Ture”。

用户信息接口:
import pytest from jsonschema.validators import validate from utils.request_utils import host, Request from utils.yaml import read_yml class Test_UserInfo: url = host + "user/getUserInfo?" Schema = { "type": "object", "properties": { "code": { "type": "number" }, "errMsg": { "type": ["null", "string"] }, "data": { "type": ["object", "null"], "properties": { "id": { "type": "number" }, "userName": { "type": "string" }, "githubUrl": { "type": "string" } }, "required": [ "id", "userName", "githubUrl" ] } }, "required": [ "code", "errMsg", "data" ] } # 未登陆下访问 def test_get_authorInfo_no_login(self): url = self.url + "userId=" + str(12222) r = Request().get(url=url) assert r.status_code == 401 def test_get_authorInfo_login_success(self): token = read_yml("data.yaml", "user_token") header = { "User_token": token } r = Request().get(url=self.url + "userId=" + str(3), headers=header) validate(instance=r.json(), schema=self.Schema) assert r.json()["code"] == 200 assert r.json()["errMsg"] is None @pytest.mark.parametrize("user_id", [ { "blogId": 666666, "errMsg": "Source must not be null" }, . . . . . . ]) def test_get_authorInfo_login_fail(self, user_id): token = read_yml("data.yaml", "user_token") header = { "User_token": token } r = Request().get(url=self.url + "userId=" + str(user_id["blogId"]), headers=header) validate(instance=r.json(), schema=self.Schema) assert r.json()["code"] == -1 assert r.json()["errMsg"] == user_id["errMsg"] def test_get_authorInfo_login_fail_withoutBlogId(self): token = read_yml("data.yaml", "user_token") header = { "User_token": token } r = Request().get(url=self.url, headers=header) validate(instance=r.json(), schema=self.Schema) assert r.json()["code"] == -1 assert r.json()["errMsg"] == "参数校验失败"

执行测试用例

指定测试用例执行顺序:

如果用例的测试顺序不是我们想要的

我们可以下载这个插件

在测试类之前或者测试方法之前添加对应的代码即可按照指定顺序执行测试用例

这里我们所必须要的是登陆凭证和所需的列表页中的blogId,所以我们优先执行这两个接口测试用例。

生成测试报告并分析结果

我们需要下载Windows版Allure报告 下载地址https://github.com/allure-framework/allure2/releases/download/2.30.0/allure- 2.30.0.zip

下载完成之后并解压

之后需要将allure-2.30.0对应bin⽬录添加到系统环境变量中

添加好之后重新打开pycharm

我们需要在两个地方进行验证是否安装成功一个是cmd ,一个是py控制台,如果显示结果按照上面一样则说明下载成功
接着输入上述指令 读取allure-results目录里的测试结果数据,生成 HTML 报告,输出到allure-reports目录。
打开这个文件:
指定浏览器来查看本地报告
测试执⾏时间从22:01:24持续到22:01:33,总共耗时 8 秒 899 毫秒。测试时间与测试⽤例数量成正比,⽤例数量越多,测试时间越⻓。
饼图显⽰了测试的通过率为 100%,这意味着所有 55个测试⽤例都成功执⾏,没有失败的测试⽤例。
这里可以查看所有执行过的测试用例的详细信息 以及执行所需的时间。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/5 14:45:06

30种鸟类图像数据集实战:从采集清洗到模型部署全流程

简介:本资源是一份面向深度学习初学者与计算机视觉实践者的鸟类图像分类数据集,适用于图像识别模型训练、迁移学习实验及课程设计项目。数据集覆盖30个鸟类目级分类(如雁形目、雨燕目、鹤形目等),每类约100张图像&…

作者头像 李华
网站建设 2026/9/6 0:39:05

字节Agent实习一面已过,坐等二面!

面试官没有让他背概念,而是一直追着项目问:为什么这样设计?效果提升了多少?数据怎么测的?到底有多少人用?出错了怎么办? 只答“混合检索效果更好”“多 Agent 可以分工”,基本撑不过…

作者头像 李华
网站建设 2026/9/6 6:49:47

STM32H743 + TouchGFX 综合Demo全解析:从环境搭建到显存优化与坑点避让

简介:本资源是面向嵌入式开发工程师与STM32进阶学习者的TouchGFX实战例程,聚焦于高性能Cortex-M7平台的人机交互界面开发。针对STM32H743IIT6芯片在480272分辨率LCD上实现流畅图形渲染与触摸响应的核心需求,提供一套开箱即用的综合Demo工程&a…

作者头像 李华
网站建设 2026/9/4 8:22:06

Java面试核心考点与项目场景实战指南

前几天有位读者私信我,说自己在牛客上刷了半个月的面经,知识点也背了不少,可一到现场面试,面试官问一句“你们项目里的缓存穿透是怎么解决的”,脑子就一片空白,只能零散蹦出几个术语,最后草草收…

作者头像 李华
网站建设 2026/9/6 10:23:19

所有贸易冲击,都在倒逼一场认知重组

《裁员是结果,停招才是信号》——真正的稳就业,是稳住企业明天还敢招人的能力关税打到最后,先疼的不是失业表,而是招聘栏。数据很冷静:关税暴露度比均值高10%,城市月均招聘广告少3%,招聘人数少4…

作者头像 李华
网站建设 2026/9/6 8:40:47

ROS2开发必学:Python数据结构、异步编程与OpenCV图像处理

很多初学者接触 ROS2 的时候,经常被安装教程、通信机制、功能包结构搞得一头雾水。但真正动手写节点、做视觉处理、处理传感器数据时,才发现最大的绊脚石往往不是 ROS2 本身,而是 Python 基本功不牢。本文就针对 ROS2 开发中最常用的三块 Pyt…

作者头像 李华