当前位置: 首页 > news >正文

Pytest-BDD实现接口自动化测试,并附全部代码

引言

在之前的文章中简单的介绍了怎么使用Pytest-BDD进行接口测试,可以参考《pytest-bdd 行为驱动自动化测试》。本篇文章主要介绍使用Pytest-BDD实现接口自动化测试。后面的文章会介绍生成测试报告,和流程性接口测试。

feature文件

首先我们先整理好一个接口测试,需要用到的场景、参数等信息,写到feature文件中,以便我们根据各种测试中的行为来进行脚本实现。

demo.feature

Feature: 测试模块Scenario: demo接口测试Given 初始化Given 登录When 调用 "/test" 接口# 这里只配置接口路径,具体的域名和IP,通过config文件配置,以便用了切换环境When 使用 "post" 请求# 目前支持的请求方式 post get put deleteWhen 参数类型 "json"# 目前支持的参数类型 params json form-data form-urlencodedWhen 请求头 "{'Content-type': 'application/json'}"When 参数 "{'id': '1'}"When 校验类型 "包含校验"# 支持的校验类型: 包含校验 相等校验 字段类型校验 字段值校验 状态码校验When 校验文本 "成功"When 校验字段类型为 "int"# 字段类型:int str float list dict boolWhen 校验字段 "loginInfo"When 上传文件 "test.txt"When 退出Then 调用成功

从上面看出,我们要实现的行为有初始化、登录,步骤中需要实现接口地址、请求方式、参数和断言。目前我们需要的接口测试行为已经列出来,下面就是对这些行为的编码实现。

py文件

首先,我们来实现feature文件中的{初始化}。将feature文件中的参数进行初始化。初始化调用api工具的函数。

class ApiTest:def __init__(self):self.api = Noneself.methods = 'get'self.data_type = 'params'self.params = {}self.headers = {}self.test_result_type = Noneself.result_text = Noneself.result_type = Noneself.result_data_type = Noneself.result_key = Noneself.file = Noneself.is_logout = Falsedef call_api(self, test_body):response = api_test(test_body)return response@pytest.fixture
@given('初始化')
def api_tool():return ApiTest()

实现feature文件中的{登录}。登录的目的是拿到接口的授权,所以我们调用登录方法,拿到鉴权后,将鉴权保存到headers中,以便后面进行接口请求时使用。具体实现登录的方法就不展示了,根据自己的项目来实现就可以。

@given('登录')
def login(api_tool):headers = get_token()for key, value in headers.items():api_tool.headers[key] = value

实现feature文件中{调用 “/test” 接口},拿到feature文件中的接口路径,然后赋值给api,后面接口工具会根据环境和接口路径拼接成完整的接口地址。

@when(parsers.parse('调用 "{url}" 接口'))
def api(api_tool, url):api_tool.api = url

实现feature文件中{使用 “post” 请求},拿到feat文件中的请求方法,赋值给methods,后面接口工具使用该请求方法。

@when(parsers.parse('使用 "{methods}" 请求'))
def methods(api_tool, methods):api_tool.methods = methods

实现feature文件中{参数类型 “json”}

@when(parsers.parse('参数类型 "{data_type}"'))
def data_type(api_tool, data_type):api_tool.data_type = data_type

实现feature文件中{请求头 “{‘Content-type’: ‘application/json’}”},因为通过feature文件拿到的参数都是字符串类型,所以要将字符串转成dict,并添加到请求头中。

@when(parsers.parse('请求头 "{headers}"'))
def headers(api_tool, headers):headers_dict = ast.literal_eval(headers)for key, value in headers_dict.items():api_tool.headers[key] = value

实现feature文件中{参数 “{‘id’: ‘1’}”},将字符串类型的参数转成dict。

@when(parsers.parse('参数 "{params}"'))
def params(api_tool, params):api_tool.params = ast.literal_eval(params)

实现feature文件中{校验类型 “包含校验”},根据映射关系,将校验类型数据赋值给test_result_type。

test_result_type_list = {'相等校验': 0,'包含校验': 1,'字段类型校验': 2,'字段值校验': 3,'状态码校验': 4
}@when(parsers.parse('校验类型 "{test_result_type}"'))
def result_test(api_tool, test_result_type):api_tool.test_result_type = test_result_type_list[test_result_type]

实现feature文件中的{校验文本 “成功”}、{校验字段类型为 “int”}、{校验字段 “loginInfo”},这三个加上校验类型,是断言工具需要的,如果是相当校验,会将测试接口返回的response和result_text进行相等校验,如果是包含校验,会校验测试接口的response是否包含result_text,如果是字段类型校验,会校验result_key的类型是否为result_type,如果是字段值校验,会校验测试接口的response返回的result_key的value值,是否等于result_text,如果是状态码校验,会校验response的状态码,是否为result_text。

@when(parsers.parse('校验文本 "{result_text}"'))
def result_text(api_tool, result_text):api_tool.result_text = result_text@when(parsers.parse('校验字段类型为 "{result_type}"'))
def result_type(api_tool, result_type):api_tool.result_type = result_type@when(parsers.parse('校验字段 "{result_key}"'))
def result_key(api_tool, result_key):

实现feature文件中{When 上传文件 “test.txt”},这个步骤只有接口需要上传文件时,才需要写,上传文件放到项目的statics目录下。

@when(parsers.parse('上传文件 "{files}"'))
def file(api_tool, files):api_tool.file = files

实现feature文件中的{退出},is_logout为True时,调用完测试接口会执行退出登录操作。

@when(parsers.parse("退出"))
def logout(api_tool):api_tool.is_logout = True

实现feature文件中{调用成功},将上面步骤保存的参数,拼装成两个dict,test_data为断言工具需要的参数,test_body为接口请求需要的参数,然后调用封装好的接口测试工具和断言工具,进行接口测试。

@then('调用成功')
def asserts(api_tool):test_data = {'test_result': {'text': api_tool.result_text, 'type': api_tool.result_type, 'result_type': api_tool.result_type, 'key': api_tool.result_key}}test_body = {'URL': api_tool.api, 'method': api_tool.methods, 'data_type': api_tool.data_type, 'headers': api_tool.headers, 'params': api_tool.params, 'file': api_tool.file}response = api_tool.call_api(test_body)if api_tool.is_logout:do_logout()if api_tool.test_result_type is not None:assert_tool(response, test_data)

登录和退出的函数就不介绍了,根据自己的项目来实现就可以,主要介绍一下接口测试工具和断言工具。

接口测试工具

接口测试工具中,读取配置文件的参数,放到文章最后,会把测试文件的内容,还有yaml工具的代码附上。

import requests
import os
import json
from common.yaml_tool import read_yamldef api_test(test_body):config = read_yaml('config.yaml')config_env = config['env']data_type = test_body['data_type'].lower()url = config_env + test_body['URL']method = test_body['method'].lower()data = test_body['params']headers = test_body['headers']if headers is None:headers = {}file = test_body['file']if data_type.lower() == 'params':response = params(url, method, data, headers)elif data_type == 'json':response = json(url, method, data, headers)elif data_type == 'form-data':response = formdata(url, method, data, headers, file)elif data_type == 'form-urlencoded':response = urlencoded(url, method, data, headers)else:return {'code': '5000', 'msg': '测试脚本暂时不支持的参数类型'}# print('接口返回内容:' + response.text)return responsedef params(url, method, data, headers):try:if method == 'post':response = requests.post(url, params=data, headers=headers)elif method == 'put':response = requests.put(url, params=data, headers=headers)elif method == 'get':response = requests.get(url, params=data, headers=headers)elif method == 'delete':response = requests.delete(url, params=data, headers=headers)else:response = {'code': '5000', 'msg': '测试脚本暂时不支持的请求方法'}response.encoding = 'utf-8'return responseexcept Exception as e:return {"code": "5000", "msg": str(e)}def json(url, method, data, headers):try:if method == 'post':response = requests.post(url, data=data, headers=headers)elif method == 'put':response = requests.put(url, data=data, headers=headers)elif method == 'get':response = requests.get(url, data=data, headers=headers)elif method == 'delete':response = requests.delete(url, data=data, headers=headers)else:response = {'code': '5000', 'msg': '测试脚本暂时不支持的请求方法'.encode('utf-8')}return responseexcept Exception as e:return json.dumps({'code': '5000', 'msg': '测试脚本报错:' + str(e)})def urlencoded(url, method, data, headers):try:if method == 'post':response = requests.post(url, data=data, headers=headers)elif method == 'put':response = requests.put(url, data=data, headers=headers)elif method == 'get':response = requests.get(url, data=data, headers=headers)elif method == 'delete':response = requests.delete(url, data=data, headers=headers)else:response = {'code': '5000', 'msg': '测试脚本暂时不支持的请求方法'.encode('utf-8')}return responseexcept Exception as e:return {'code': '5000', 'msg': '测试脚本报错:' + str(e)}def formdata(url, method, data, headers, file):if file:file_name = filename = os.path.splitext(file_name)[-1]if name == '.doc':file_type = 'application/msword'elif name == '.docx':file_type = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'elif name == '.pdf':file_type = 'application/pdf'elif name == '.jpg' or name == 'jpeg':file_type = 'image/jpeg'elif name == '.png':file_type = 'image/png'elif name == '.ppt':file_type = 'application/vnd.ms-powerpoint'elif name == '.xls':file_type = 'application/vnd.ms-excel'elif name == '.xlsx':file_type = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'elif name == '.txt':file_type = 'text/plain'path = os.path.dirname(os.path.dirname(__file__))file = [('file', ('Test'+name, open(path + '/statics/' + file_name, 'rb'), file_type))]try:if method == 'post':response = requests.post(url, data=data, files=file, headers=headers)elif method == 'put':response = requests.put(url, data=data, files=file, headers=headers)elif method == 'get':response = requests.get(url, data=data, files=file, headers=headers)elif method == 'delete':response = requests.delete(url, data=data, files=file, headers=headers)else:response = {'code': '5000', 'msg': '测试脚本暂时不支持的请求方法'.encode('utf-8')}return responseexcept Exception as e:return {'code': '5000', 'msg': '测试脚本报错:' + str(e)}

断言工具

import json
from requests import Responsedef assert_tool(response, test_result):response_body = Noneif type(response) is dict:response_body = responseelse:response_body = response.texttext = test_result['test_result']['text']types = test_result['test_result']['type']type_map = {'int': int,'float': float,'str': str,'list': list,'dict': dict,'bool': bool,}if types == 0:assert text == str(response_body)elif types == 1:assert text in str(response_body)elif types == 2:result_type = test_result['test_result']['result_type']key_str = test_result['test_result']['key']assert isinstance(analysis_dict(json.loads(response_body), key_str), type_map.get(result_type))elif types == 3:key_str = test_result['test_result']['key']assert text == str(analysis_dict(json.loads(response_body), key_str))elif types == 4:if type(response) is Response:status = response.status_codeassert text == statuselse:assert text == str(response_body)def analysis_dict(response: dict, key_str):if key_str in response:return response[key_str]for key, value in response.items():if isinstance(value, dict):result = analysis_dict(value, key_str)if result is not None:return resultelif isinstance(value, list):for item in value:if isinstance(item, dict):result = analysis_dict(item, key_str)if result is not None:return resultreturn None

本接口自动化测试工具,基本实现了接口测试和断言。后面会加上测试报告,还有流程性接口测试(如接口2的参数,需要从接口1中获取)

配置文件

env: http://yourdomin.com
login:URL: /loginheaders:content-type: application/jsonparams:username: usernamepassword: password
logout:URL: /logoutheaders:content-type: application/json

yam文件工具

import yamldef read_yaml(yaml_file):with open(yaml_file, 'r', encoding='utf-8') as file:test_data = yaml.safe_load(file)return test_datadef wirte_config(config, config_file):with open(config_file, 'w', encoding='utf-8') as file:yaml.dump(config, file, default_flow_style=False)

python文件

实现feature文件的整体python文件

import pytest
from pytest_bdd import scenarios, given, when, then, parsersfrom common.api_tool import api_test
from common.token_api import get_token, get_cms_token, do_logout
from common.assert_tool import assert_tool
import ast
import ostest_result_type_list = {'相等校验': 0,'包含校验': 1,'字段类型校验': 2,'字段值校验': 3,'状态码校验': 4
}class ApiTest:def __init__(self):self.api = Noneself.methods = 'get'self.data_type = 'params'self.params = {}self.headers = {}self.test_result_type = Noneself.result_text = Noneself.result_type = Noneself.result_data_type = Noneself.result_key = Noneself.file = Noneself.is_logout = Falsedef call_api(self, test_body):response = api_test(test_body)return responsefor root, dirs, files in os.walk('case/'):for case in files:scenarios(root + case)@pytest.fixture
@given('初始化')
def api_tool():return ApiTest()@given('登录')
def login(api_tool):headers = get_token()for key, value in headers.items():api_tool.headers[key] = value@when(parsers.parse('调用 "{url}" 接口'))
def api(api_tool, url):api_tool.api = url@when(parsers.parse('使用 "{methods}" 请求'))
def methods(api_tool, methods):api_tool.methods = methods@when(parsers.parse('参数类型 "{data_type}"'))
def data_type(api_tool, data_type):api_tool.data_type = data_type@when(parsers.parse('请求头 "{headers}"'))
def headers(api_tool, headers):headers_dict = ast.literal_eval(headers)for key, value in headers_dict.items():api_tool.headers[key] = value@when(parsers.parse('参数 "{params}"'))
def params(api_tool, params):api_tool.params = ast.literal_eval(params)@when(parsers.parse('校验类型 "{test_result_type}"'))
def result_test(api_tool, test_result_type):api_tool.test_result_type = test_result_type_list[test_result_type]@when(parsers.parse('校验文本 "{result_text}"'))
def result_text(api_tool, result_text):api_tool.result_text = result_text@when(parsers.parse('校验字段类型为 "{result_type}"'))
def result_type(api_tool, result_type):api_tool.result_type = result_type@when(parsers.parse('校验字段 "{result_key}"'))
def result_key(api_tool, result_key):api_tool.result_key = result_key@when(parsers.parse('上传文件 "{files}"'))
def file(api_tool, files):api_tool.file = files@when(parsers.parse("退出"))
def logout(api_tool):api_tool.is_logout = True@then('调用成功')
def asserts(api_tool):test_data = {'test_result': {'text': api_tool.result_text, 'type': api_tool.result_type, 'result_type': api_tool.result_type, 'key': api_tool.result_key}}test_body = {'URL': api_tool.api, 'method': api_tool.methods, 'data_type': api_tool.data_type, 'headers': api_tool.headers, 'params': api_tool.params, 'file': api_tool.file}response = api_tool.call_api(test_body)if api_tool.is_logout:do_logout()if api_tool.test_result_type is not None:assert_tool(response, test_data)

相关文章:

Pytest-BDD实现接口自动化测试,并附全部代码

引言 在之前的文章中简单的介绍了怎么使用Pytest-BDD进行接口测试,可以参考《pytest-bdd 行为驱动自动化测试》。本篇文章主要介绍使用Pytest-BDD实现接口自动化测试。后面的文章会介绍生成测试报告,和流程性接口测试。 feature文件 首先我们先整理好…...

Sqli-labs-master靶场--布尔盲注

目录 1、布尔盲注 2、布尔盲注的流程(以靶场less-8为例) 2.1输入id尝试是否存在注入点 2.1.1通过以上尝试,联想到可能是布尔盲注 2.2猜测数据库长度 2.3获取数据库名 2.3.1python脚本获取 代码: 获取结果为: …...

【QGroundControl二次开发】十. QT添加GStreamer视频播放同时保存

上一章介绍使用QT播放GStreamer视频流 【QGroundControl二次开发】八. QT实现播放gstreamer视频。 这章介绍如何在原有基础上保存为视频,同时保存为一个个规定大小的小视频。 先展示代码: #include <QApplication> #include <QWidget> #include <QtConcurrent…...

double类型 精度丢失的问题

前言 精度丢失的问题是在其他计算机语言中也都会出现&#xff0c;float和double类型的数据在执行二进制浮点运算的时候&#xff0c;并没有提供完全精确的结果。产生误差不在于数的大小&#xff0c;而是因为数的精度。 一、double进行运算时,经常出现精度丢失 0.10.2使用计算…...

C++ 重要特性探究

shared_from_this 使用分析 场景 类的成员函数需要获取指向自身的shared_ptr的时候类成员函数传递shared_ptr给其他函数或者对象的时候&#xff0c;目的是为了管理对象生命周期使用方法 首先类必须继承 std::enable_shared_from_this<T>必须使用 shared_from_this 获取指…...

c++_游戏_狼人杀

思路主要包括以下几个部分&#xff1a; 角色分配&#xff1a;代码中通过随机数的方式给狼人、平民、预言家和法师等角色进行分配&#xff0c;保证每个角色的数量和身份的随机性。 游戏进行&#xff1a;根据狼人、平民、预言家和法师等角色的身份&#xff0c;游戏进行了夜晚和白…...

MySQL——数据类型、索引的建立、数据的约束

文章目录 数据类型索引的建立普通索引唯一索引使用ALTER 命令添加和删除索引使用ALTER 命令添加和删除主键显示索引信息 数据的约束非空约束&#xff1a;not null&#xff0c;值不能为null唯一约束&#xff1a;unique&#xff0c;值不能重复主键约束&#xff1a;primary key外键…...

常见框架漏洞详解③!!

Apache Apache 是世界使⽤排名第⼀的 Web 服务器软件。它可以运⾏在⼏乎所有⼴泛使⽤的计算 机平台上&#xff0c;由于其跨平台和安全性被⼴泛使⽤&#xff0c;是最流⾏的 Web 服务器端软件之⼀。 apache⽬录结构&#xff1a; bin&#xff1a;存放常⽤命令⼯具&#xff0c;如h…...

大数据基础知识

大数据&#xff08;Big Data&#xff09;是指无法用传统数据处理工具和技术有效处理的大规模、复杂的数据集。大数据技术通过对这些数据进行存储、处理和分析&#xff0c;从中提取有价值的信息和见解。 1. 大数据的特点 大数据通常具有以下四个主要特点&#xff0c;被称为“4…...

SQL Server 的透明数据加密

透明数据加密是SQL Server数据库安全众多特性中的一个&#xff0c;本文只针对透明数据加密。 在此测试之前&#xff0c;已经按照文档如何快速获得一个测试用SQL Server企业版创建了一个SQL Server 2019&#xff0c;并按照文档为SQL Server安装示例数据库AdventureWorks安装了…...

Windows图形界面(GUI)-MFC-C/C++ - 列表视图(List Control) - CListCtrl

公开视频 -> 链接点击跳转公开课程博客首页 -> ​​​链接点击跳转博客主页 目录 列表视图(List Control) - CListCtrl 创建列表视图 设置列表视图属性 成员函数 注意事项 示例代码 列表视图(List Control) - CListCtrl 创建列表视图 在对话框编辑器中&#xff…...

一机两用的简单介绍

电子政务外网终端使用过程的风险与挑战 1、终端防护弱&#xff0c;失陷风险大 政务外网终端具备访问互联网能力&#xff0c;造成政务外网终端极易感染僵木蠕病毒&#xff0c;破坏正常办公 政务外网终端易被攻击失陷&#xff0c;成为从互联网攻击政务外网的跳板机 2、VPN漏洞…...

uniapp离线打包热更新失败-AndroidStudio离线打包apk后无法下载打开-热更新失败-plus.runtime.install失败

效果图 仅安卓 前言 1.plus.runtime.install一直fail(20240808), uni.openDocument可以打开本地apk文件 2.权限问题需小心 跑通前提 1.先确定apk地址有效&#xff0c;浏览器中手动下载可安装 2.确保已添加离线打包AndroidStudio的“android.permission.INSTALL_PACKAGES”权…...

深植根基、蓬勃向上 | openKylin 2.0正式发布!

2024年8月8日&#xff0c;openKylin 2.0版本正式发布&#xff01;该版本默认搭载Linux 6.6 LTS内核&#xff0c;完成180操作系统核心组件自主选型升级&#xff0c;深度融合AI技术&#xff0c;上线麒麟AI助手等实用AI功能&#xff0c;并为用户带来包括开明软件包格式、不可变系统…...

【Material-UI】按钮组:尺寸与颜色详解

文章目录 一、按钮组概述1. 组件介绍2. 基本用法 二、按钮组的尺寸&#xff08;Sizes&#xff09;1. 小尺寸&#xff08;Small&#xff09;2. 中等尺寸&#xff08;Medium&#xff09;3. 大尺寸&#xff08;Large&#xff09; 三、按钮组的颜色&#xff08;Colors&#xff09;1…...

app抓包 burp配置

证书导出 模拟器安装证书 点击安装证书 将证书直接拖进来就行 配置代理 打开浏览器抓包...

图像与像素:利用ImageJ分析荧光显微镜图像|QuPath基础教程1|24-08-08

内容概要 数字图像由像素组成&#xff0c;每个像素具有一个数值&#xff0c;通常与检测到的光线相关。相同的像素值可以通过不同的方式进行显示。在科学图像处理中&#xff0c;可以通过修改查找表来独立于像素值改变图像外观。 一、引言 图像由其最小的组成单位——像素构成。…...

Prompt Fuzzer:用于增强 GenAI 应用程序的开源工具

Prompt Fuzzer 是一个开源工具&#xff0c;可以评估GenAI应用程序的系统提示针对基于动态 LLM 的威胁的安全性。 Prompt Fuzzer 功能&#xff1a; 1. 模拟十几种类型的 GenAI 攻击。 2. 该工具会根据系统提示自动进行情境化&#xff0c;针对与 GenAI 应用程序相关的特定主题或行…...

Vision Pro使用GLFT 加载模型shader错误解决办法

Glft shader在vision pro上加载错误 前言相关背景解决办法 参考文章 前言 之前在Vision Pro上尝试加载Glb文件&#xff0c;但是加载完成后发现加载出来的Glb文件材质不正确。材质是黑色的。因此整理一下解决方案。 相关背景 使用Unity开发&#xff0c;Glb的加载插件为gltf F…...

Netty技术全解析:MessageToMessageCodec类深度解析

❃博主首页 &#xff1a; 「码到三十五」 &#xff0c;同名公众号 :「码到三十五」&#xff0c;wx号 : 「liwu0213」 ☠博主专栏 &#xff1a; <mysql高手> <elasticsearch高手> <源码解读> <java核心> <面试攻关> ♝博主的话 &#xff1a…...

生成xcframework

打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式&#xff0c;可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...

Spring AI 入门:Java 开发者的生成式 AI 实践之路

一、Spring AI 简介 在人工智能技术快速迭代的今天&#xff0c;Spring AI 作为 Spring 生态系统的新生力量&#xff0c;正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务&#xff08;如 OpenAI、Anthropic&#xff09;的无缝对接&…...

IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)

文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...

爬虫基础学习day2

# 爬虫设计领域 工商&#xff1a;企查查、天眼查短视频&#xff1a;抖音、快手、西瓜 ---> 飞瓜电商&#xff1a;京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空&#xff1a;抓取所有航空公司价格 ---> 去哪儿自媒体&#xff1a;采集自媒体数据进…...

OpenLayers 分屏对比(地图联动)

注&#xff1a;当前使用的是 ol 5.3.0 版本&#xff0c;天地图使用的key请到天地图官网申请&#xff0c;并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能&#xff0c;和卷帘图层不一样的是&#xff0c;分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...

有限自动机到正规文法转换器v1.0

1 项目简介 这是一个功能强大的有限自动机&#xff08;Finite Automaton, FA&#xff09;到正规文法&#xff08;Regular Grammar&#xff09;转换器&#xff0c;它配备了一个直观且完整的图形用户界面&#xff0c;使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...

Xen Server服务器释放磁盘空间

disk.sh #!/bin/bashcd /run/sr-mount/e54f0646-ae11-0457-b64f-eba4673b824c # 全部虚拟机物理磁盘文件存储 a$(ls -l | awk {print $NF} | cut -d. -f1) # 使用中的虚拟机物理磁盘文件 b$(xe vm-disk-list --multiple | grep uuid | awk {print $NF})printf "%s\n"…...

springboot整合VUE之在线教育管理系统简介

可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生&#xff0c;小白用户&#xff0c;想学习知识的 有点基础&#xff0c;想要通过项…...

使用Spring AI和MCP协议构建图片搜索服务

目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式&#xff08;本地调用&#xff09; SSE模式&#xff08;远程调用&#xff09; 4. 注册工具提…...

MFC 抛体运动模拟:常见问题解决与界面美化

在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...