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

接口自动化测试分层设计与实践总结

🍅 视频学习:文末有免费的配套视频可观看

🍅 关注公众号:互联网杂货铺,回复1 ,免费获取软件测试全套资料,资料在手,涨薪更快

接口测试三要素

  • 参数构造

  • 发起请求,获取响应

  • 校验结果

一、原始状态

当我们的用例没有进行分层设计的时候,只能算是一个“苗条式”的脚本。以一个后台创建商品活动的场景为例,大概流程是这样的(默认已经是登录状态下):

创建商品-创建分类-创建优惠券-创建活动

要进行接口测试的话,按照接口测试的三要素来进行,具体的效果如下:

 # 1、参数构造createCommodityParams = {"input": {"title": "活动商品","subtitle": "","brand": "","categoryLevel1Code": "12","categoryLevel2Code": "1312","categoryLevel3Code": "131211","detail": [{"uri": "ecommerce/1118d9.jpg","type": 0}],"installInfo": {"installType": 1,"installFee": null},"pictureList": [{"uri": "ecommerce/222.jpg","main": true}],"postageInfo": {"postageType": 2,"postageFee": 1,"postageId": null},"sellerDefinedCode": "","publish": 1,"skuList": [{"skuCode": "","externalSkuCode": "","price": 1,"retailPrice": 6,"stock": 100,"weight": 0,"suggestPrice": 0,"skuAttrValueList": [{"attrCode": "COLOR","attrName": "颜色","attrValue": "绿色","attrValueId": "1001"}]}],"jumpSwitch":false,"recommendCommodityCodeList": [],"recommendFittingCodeList": [],"mallCode": "8h4xxx"}}createCategoryParams = {......}createCouponParams = {......}createPublicityParams = {......}publishCommodityParams = {......}publishPublicityParams = {......}createCommodityParams["input"]["title"] = "autoTest" + str(time.time())createCommodityParams["input"]["mallCode"] = self.mallCodecreateCommodityParams["input"]["skuList"][0]["price"] = random.randint(1,10)createCategoryParams["input"]["categoryName"] = "autoTestCategory" + str(time.time())createCouponParams。。。createPublicityParams。。。publishCommodityParams。。。publishPublicityParams。。。# 2、发起请求,获取响应# 创建商品并获取商品codecreateCommodityRes = api.getUrl("testApi.create.commodity").post.params(createCommodityParams)commodityCode = createCommodityRes["commodityCode"]# 创建分类并获取分类codecreateCategoryRes = api.getUrl("testApi.create.category").post.params(createCategoryParams)categoryCode = createCategoryRes["categoryCode"]# 创建优惠券并获取优惠券codecreateCouponRes = api.getUrl("testApi.create.coupon").post.params(createCouponParams)couponCode = createCouponRes["couponCode"]# 创建活动并关联商品,绑定优惠券,设置分类createPublicityParams["input"]["commodityCode"] = commodityCodecreatePublicityParams["input"]["categoryCode"] = categoryCodecreatePublicityParams["input"]["couponCode"] = couponCodecreatePublicityRes = api.getUrl("testApi.create.publicity").post.params(createPublicityParams)# 结果校验(断言)assert.equal(createPublicityRes["code"], 0)assert.equal(createPublicityRes["publicityName"], createPublicityParams["publicityName"])。。。

按照上面的写法,对于单个脚本的调式来说或许可以,但是一旦用例的数量和复杂程度积累起来后,其维护成本将是巨大的,或者可以说不具备可维护性。

弊端说明

  • 可读性差,所有的处理都放在一起,代码量大,不简洁直观

  • 灵活性差,参数写死在脚本,适用用例范围小

  • 复用性差,如果其他用例需要同样或类似的步骤,需要重新写一份

  • 维护性差,如果接口有任何改动,那么所有涉及到此接口的脚本都需要一一修改

例如:随着用例场景的增加,就可能会出现下面这种情况

按照原始的模式,我们就需要些3个脚本文件分别来描述着3个场景,并且创建商品_API创建分类_API创建优惠券_API在场景1,2,3中均出现了;上架商品_API在场景2,3中均出现。由此我们完全可以预见到,当几百上千的用例场景出现后,这种形式是没有维护性可言的。

二、进化历程

因此我们依照着痛点,以最开始的原始状态为例,对用例进行分层改造,来看看进化后的状态。

1、API 定义层

我们编程的时候会将一些重复的代码进行封装使用,那么这里依然可以借用这种思想,我们将 API 的定义单独抽离,单独定义。

我们期望的效果是这样的:

提前将API的定义放在一层,供用例场景引用,这样当接口有任何修改时,我们只需要修改API definition层即可。

实例演示

对应着上面的demo,我们就是需要做如下抽离:

class APIDefinition:
‘’’
创建商品API定义
createCommodityParams: 创建商品接口入参
return:创建商品接口响应结果
‘’’
def createCommodityRequest(createCommodityParams):
return api.getUrl(“testApi.create.commodity”).post.params(createCommodityParams)

     '''创建分类API定义createCategoryParams: 创建分类接口入参return:创建分类接口响应结果''' def createCategoryRequest(createCategoryParams)return api.getUrl("testApi.create.category").post.params(createCategoryParams)# 创建优惠券接口定义def createCouponRequest(createCouponParams)return api.getUrl("testApi.create.coupon").post.params(createCouponParams)# 创建活动接口定义def createPublicityRequest(createPublicityParams)return api.getUrl("testApi.create.publicity").post.params(createPublicityParams)# ...其余省略

2、Service 层

上面我们已经将接口的定义抽离出来,解决了 API 重复定义的问题,但是再继续分析会发现有一个问题依然没有解决,就是场景的复用性.

再看刚才的图:

3个场景中都有重复的步骤,类似创建商品创建分类创建优惠券这些,并且这些步骤都是一个个API的组合,一个步骤对应一个API,在各个步骤之间还会有数据的处理与传递,为了解决这些问题,将对场景再次做抽离,这里我称之为 service 层。

这一层之所以叫做service(服务)层,是因为它的作用是用来提供测试用例所需要的各种“服务”,好比参数构建、接口请求、数据处理、测试步骤。

用下图先来看分层的目标:

我们希望将常用的测试场景步骤封装至service层中,供用例场景调用,增加复用性,也可以理解为测试用例的前置处理;

但是这里还是有一点小问题,就是service层的东西太多太杂,有些场景步骤可能只适用于我当前的项目用例,在实际的工作中,各个系统间是相互依赖的,前台APP的测试很大可能就依赖后台创建作为前置条件

好比我在APP端只要商品和分类,可能只想创建商品和分类,并不想创建优惠券,这个时候service层就没有适用的场景步骤供调用,那么我就需要根据自己的需要重新封装;可是对于很多单接口的前置数据处理又是一致的,比如:

     createCommodityParams["input"]["title"] = "autoTest" + str(time.time())createCommodityParams["input"]["mallCode"] = self.mallCodecreateCommodityParams["input"]["skuList"][0]["price"] = random.randint(1,10)createCategoryParams["input"]["categoryName"] = "autoTestCategory" + str(time.time())createCouponParams。。。createPublicityParams。。。publishCommodityParams。。。publishPublicityParams。。。

重新封装的话还要再处理这一步,就有点麻烦且不符合我们的复用性设计了,因此我们对service层再细化为3层,分别为:

apiObject

单接口的预处理层,这一层主要作用是单接口入参的构造,接口的请求与响应值返回

  • 每个接口请求不依赖与业务步骤,都是单接口的请求;

  • 此外一些简单固定的入参构建也直接放在这里处理,比如随机的商品名,title等,和具体业务流程无关,针对所有调用此接口的场景均适用

caseService

多接口的预处理层,这一层主要是测试步骤(teststep)或场景的有序集合。

  • 用例所需要的步骤,通过每一个请求进行组合,每一个步骤都对应着一个API请求,这些步骤会组成一个个场景,各个场景之间可以互相调用组成新的场景,以适应不同的测试用例需求。

  • 场景封装好以后可以供不同的测试用例调用,除了当前项目的用例,其他业务线需要的话也可从此caseService中选择调用,提高复用性的同时也避免了用例相互依赖的问题。

util

这一层主要放置针对当前业务的接口需要处理的数据

  • 在实际编写测试步骤时,可能部分接口的参数是通过其他接口获取后经过处理才可以使用,或是修改数据格式,或是修改字段名称,亦或是某些 value 的加解密处理等。

细化分层后,各层的职责便更加清晰明确,具体如下图:

实例演示

apiObject:

  class ApiObject:def createCommodity(createCommodityParams):inputParams = ApiParamsBuild().createCommodityParamsBuild(createCommodityParams)response = APIDefinition().createCommodityRequest(inputParams)return responsedef createCategory(createCategoryParams):...def createCoupon(createCouponParams):.........class ApiParamsBuild:def createCommodityParamsBuild(createCommodityParams):createCommodityParams["input"]["title"] = "autoTest" + str(time.time())createCommodityParams["input"]["mallCode"] = self.mallCodecreateCommodityParams["input"]["skuList"][0]["price"] = random.randint(1,10)return createCommodityParamsdef createCategoryParamsBuild(createCategoryParams):...def createCouponParamsBuild(createCouponParams):.........

到此,我们来看看原始的用例经过目前封装后的模样:

1、参数构造

    createCommodityParams = {"input": {"title": "活动商品","subtitle": "","brand": "","categoryLevel1Code": "12","categoryLevel2Code": "1312","categoryLevel3Code": "131211","detail": [{"uri": "ecommerce/1118d9.jpg","type": 0}],"installInfo": {"installType": 1,"installFee": null},"pictureList": [{"uri": "ecommerce/222.jpg","main": true}],"postageInfo": {"postageType": 2,"postageFee": 1,"postageId": null},"sellerDefinedCode": "","publish": 1,"skuList": [{"skuCode": "","externalSkuCode": "","price": 1,"retailPrice": 6,"stock": 100,"weight": 0,"suggestPrice": 0,"skuAttrValueList": [{"attrCode": "COLOR","attrName": "颜色","attrValue": "绿色","attrValueId": "1001"}]}],"jumpSwitch":false,"recommendCommodityCodeList": [],"recommendFittingCodeList": [],"mallCode": "8h4xxx"}}createCategoryParams = {......}createCouponParams = {......}createPublicityParams = {......}publishCommodityParams = {......}publishPublicityParams = {......}# 2、发起请求,获取响应# 创建商品并获取商品codecreateCommodityRes = ApiObject().createCommodity(createCommodityParams)commodityCode = createCommodityRes["commodityCode"]# 创建分类并获取分类codecreateCategoryRes = ApiObject().createCategory(createCategoryParams)categoryCode = createCategoryRes["categoryCode"]# 创建优惠券并获取优惠券codecreateCouponRes = ApiObject().createCoupon(createCouponParams)couponCode = createCouponRes["couponCode"]# 创建活动并关联商品,绑定优惠券,设置分类createPublicityParams["input"]["commodityCode"] = commodityCodecreatePublicityParams["input"]["categoryCode"] = categoryCodecreatePublicityParams["input"]["couponCode"] = couponCodecreatePublicityRes = ApiObject().createPublicity(createPublicityParams)# 结果校验(断言)assert.equal(createPublicityRes["code"], 0)assert.equal(createPublicityRes["publicityName"], createPublicityParams["publicityName"])。。。
可以看到,现在接口请求的url、method、通用入参处理等已经不会在用例中体现了,接下来继续封装caseService层。caseService:我们将多接口的场景步骤进行封装
```    class CaseService:def createPublicityByCategory(params):# 创建商品并获取商品codecreateCommodityRes = ApiObject().createCommodity(createCommodityParams)commodityCode = createCommodityRes["commodityCode"]# 创建分类并获取分类codecreateCategoryRes = ApiObject().createCategory(createCategoryParams)categoryCode = createCategoryRes["categoryCode"]# 创建优惠券并获取优惠券codecreateCouponRes = ApiObject().createCoupon(createCouponParams)couponCode = createCouponRes["couponCode"]# 创建活动并关联商品,绑定优惠券,设置分类createPublicityParams["input"]["commodityCode"] = commodityCodecreatePublicityParams["input"]["categoryCode"] = categoryCodecreatePublicityParams["input"]["couponCode"] = couponCodecreatePublicityRes = ApiObject().createPublicity(createPublicityParams)return createPublicityRes......

这时体现在用例中的表现就如下层testcase层所示.

3、testcase 层

我们想要的是一个清晰明了,“一劳永逸”的自动化测试用例,就像我们的手工测试用例一样,我们的前置条件可以复用,我们入参可以任意修改,但测试步骤都是固定不变的(前提可能是产品没有偷偷改需求~)。

这一层其实是对应的testsuite(测试用例集),是测试用例的无序集合。其中各个用例之间应该是相互独立,互不干扰,不存在依赖关系,每个用例都可以单独运行。

最终我们期望自动化用例的维护过程中达到的效果如下:
testcase 层:

   # 1、参数构造createCommodityParams = {"input": {"title": "活动商品","subtitle": "","brand": "","categoryLevel1Code": "12","categoryLevel2Code": "1312","categoryLevel3Code": "131211","detail": [{"uri": "ecommerce/1118d9.jpg","type": 0}],"installInfo": {"installType": 1,"installFee": null},"pictureList": [{"uri": "ecommerce/222.jpg","main": true}],"postageInfo": {"postageType": 2,"postageFee": 1,"postageId": null},"sellerDefinedCode": "","publish": 1,"skuList": [{"skuCode": "","externalSkuCode": "","price": 1,"retailPrice": 6,"stock": 100,"weight": 0,"suggestPrice": 0,"skuAttrValueList": [{"attrCode": "COLOR","attrName": "颜色","attrValue": "绿色","attrValueId": "1001"}]}],"jumpSwitch":false,"recommendCommodityCodeList": [],"recommendFittingCodeList": [],"mallCode": "8h4xxx"}}createCategoryParams = {......}createCouponParams = {......}createPublicityParams = {......}publishCommodityParams = {......}publishPublicityParams = {......}# 2、发起请求,获取响应createPublicityRes = CaseService().createPublicityByCategory(createCommodityParams,createCategoryParams,createCouponParams...)# 结果校验(断言)assert.equal(createPublicityRes["code"], 0)assert.equal(createPublicityRes["publicityName"], createPublicityParams["publicityName"])。。。

可以看到,这时涉及到用例场景步骤的代码已经非常少了,并且完全独立,与框架、其他用例等均无耦合。

到这里我们再看用例,会发现一点,测试数据依然冗长,那么下面就开始对测试数据进行参数化和数据驱动的处理。

4、testdata

此层用来管理测试数据,作为参数化场景的数据驱动。

参数化: 所谓参数化,简单来说就是将入参利用变量的形式传入,不要将参数写死,增加灵活性,好比搜索商品的接口,不同的关键字和搜索范围作为入参,就会得到不同的搜索结果。上面的例子中其实已经是参数化了。

数据驱动:对于参数,我们可以将其放入一个文件中,可以存放多个入参,形成一个参数列表的形式,然后从中读取参数传入接口即可。常见做数据驱动的有 JSON、CSV、YAML 等。

实例演示

我们以CSV为例,不特别依照某个框架,通常测试框架都具备参数化的功能。

将所需要的入参放入test.csv文件中:

  createCommodityParams,createCategoryParams,...{"input": {"title": "活动商品","subtitle": "","brand": "","categoryLevel1Code": "12","categoryLevel2Code": "1312","categoryLevel3Code": "131211","detail": [{"uri": "ecommerce/1118d9.jpg","type": 0}],"installInfo": {"installType": 1,"installFee": null},"pictureList": [{"uri": "ecommerce/222.jpg","main": true}],"postageInfo": {"postageType": 2,"postageFee": 1,"postageId": null},"sellerDefinedCode": "","publish": 1,"skuList": [{"skuCode": "","externalSkuCode": "","price": 1,"retailPrice": 6,"stock": 100,"weight": 0,"suggestPrice": 0,"skuAttrValueList": [{"attrCode": "COLOR","attrName": "颜色","attrValue": "绿色","attrValueId": "1001"}]}],"jumpSwitch":false,"recommendCommodityCodeList": [],"recommendFittingCodeList": [],"mallCode": "8h4xxx"}},...

然后再回到用例层,利用框架参数化的功能对数据进行读取

    # 1、参数构造@parametrize(params = readCsv("test.csv"))# 2、发起请求,获取响应createPublicityRes = CaseService().createPublicityByCategory(params)# 结果校验(断言)assert.equal(createPublicityRes["code"], 0)assert.equal(createPublicityRes["publicityName"], createPublicityParams["publicityName"])。。。

注:这里的测试数据,不仅仅局限于接口的请求参数,既然做数据驱动,那么断言也可以维护在此,以减少用例层的代码冗余。

5、rawData

这一层是存放接口原始入参的地方。

某些接口的入参可能很多,其中很多参数值又可能是固定不变的,构建入参的时候我们只想对"变"的值进行动态的维护,而不维护的值就使用原始参数中的默认值,以此减少工作量(emmm…可能也就是CV大法的量吧~)

再者就是数据驱动的数据文件中只维护需要修改的参数,使数据文件更简洁,可阅读性更强。

实例演示:

这种利用原始参数(rawData)的方法我们称之为模板化,实际工作中有多种方式可实现,例如jsonpath、Mustache或者自己根据需求实现方法,本文重点在介绍分层设计,所以就不具体演示模板化技术的细节了,仅说明设计此层的作用。

以实例中的入参createCommodityParams为例,未用模板化技术前,我们要在CSV里面维护完整的入参:

 createCommodityParams,createCategoryParams,...{"input": {"title": "活动商品","subtitle": "","brand": "","categoryLevel1Code": "12","categoryLevel2Code": "1312","categoryLevel3Code": "131211","detail": [{"uri": "ecommerce/1118d9.jpg","type": 0}],"installInfo": {"installType": 1,"installFee": null},"pictureList": [{"uri": "ecommerce/222.jpg","main": true}],"postageInfo": {"postageType": 2,"postageFee": 1,"postageId": null},"sellerDefinedCode": "","publish": 1,"skuList": [{"skuCode": "","externalSkuCode": "","price": 1,"retailPrice": 6,"stock": 100,"weight": 0,"suggestPrice": 0,"skuAttrValueList": [{"attrCode": "COLOR","attrName": "颜色","attrValue": "绿色","attrValueId": "1001"}]}],"jumpSwitch":false,"recommendCommodityCodeList": [],"recommendFittingCodeList": [],"mallCode": "8h4xxx"}},...

但是实际上,我们可能仅仅需要修改维护其中某个或某几个字段(例如只想维护商品价格),其余的使用默认值即可,使用模板化技术后可能在CSV中就是这样的表现:

createCommodityParams,createCategoryParams,...{"input": {"skuList": [{"price": 1,"retailPrice": 6}},...

或者这样

- keyPath: $.input.skuList[0].pricevalue: 1- keyPath: $.input.skuList[0].retailPricevalue: 6

亦或使用Mustache,将需要修改的value进行参数化{{value}}。

我们可以看到,这样处理后的数据驱动的文件就变得简洁清晰的许多,当一个文件中维护了多个用例且入参字段很多时,这样维护起来就可以清晰的看出每个数据对应的用例的作用了;

price就是为了测试价格的,stock就是为了测试库存的,publish就是为了测试上下架的等等。

注: 当然,此层的使用视实际情况而定,有可能这个接口的参数本身就没多少,那么直接全量使用就行,或者你就是觉得数据量哪怕再大我都能分得清楚,看的明白,不用也rawData是可以的~

6、Base

此层主要放置我们需要处理的公共前置条件和一些自动化公共方法,也可以理解为公共的config和util。

在我们实际的自动化开发过程中,有很多前置条件或公共方法,比如登录处理,log 处理,断言方法或一些数据处理;

使用过程中所有的service和testcase层都会继承此类,这样这些公共方法和前置条件便可直接通用;在各个业务线之间也可保持一致性。

三、完结

最后,我们来看下整体分层后的目录结构总览:

 └─apiautotest└─project└─rawData(原始参数)├─testRawData.json└─service(用例服务)└─apiObject(单接口预处理,单接口入参的构造,接口的请求与响应值返回)├─testApiObject.py└─caseService(多接口预处理,测试步骤(teststep)或场景的有序集合)├─testCaseService.py└─util(工具类)├─util.py└─testcase(测试用例)└─testDataDriven(测试数据驱动)├─testData.csv├─testcase.py(测试用例集)└─testBase.py(测试基类,初始化和公共方法) └─platformapi(Api定义)├─testApiDefinition.py

同时,在这我为大家准备了一份软件测试视频教程(含面试、接口、自动化、性能测试等),就在下方,需要的可以直接去观看,也可以直接【点击文末小卡片免费领取资料文档】

自动化测试从菜鸟到高手【2024最新完整版】,从基础到项目实战(Python自动化测试保姆级教程)

相关文章:

接口自动化测试分层设计与实践总结

🍅 视频学习:文末有免费的配套视频可观看 🍅 关注公众号:互联网杂货铺,回复1 ,免费获取软件测试全套资料,资料在手,涨薪更快 接口测试三要素: 参数构造 发起请求&#x…...

集合(下)Map集合的使用

文章目录 前言一、Map接口二、Map接口的实现类 1.HashMap类2.TreeMap类总结 前言 Map集合没有继承Collection接口,不能像List集合和Set集合那样直接使用Collection接口的方法。Map集合其自身通过以key到value的映射关系实现的集合,也有相应的许多方法。类…...

AAPT: error: resource android:attr/dialogCornerRadius not found.

ERROR:D:\android.gradle\caches\transforms-3\b3b98118f65da38d0ad9da84cfc70a72\transformed\appcompat-1.0.0\res\values-v28\values-v28.xml:5:5-8:13: AAPT: error: resource android:attr/dialogCornerRadius not found. 请帮我看看这个错误是什么意思。我改如何做。 这个…...

数字功放VS模拟功放,选择适合你的音频解决方案

数字功放和模拟功放是音频系统中常用的两种功放技术,适用于不同的音频应用,都具有各自的优势和特点。本文将为您详细介绍数字功放和模拟功放的差异,并帮助您找到适合自己的音频解决方案。 1、数字功放是一种利用数字信号处理技术的功放。它将…...

5.88 BCC工具之tcpsynbl.py解读

一,工具简介 tcpsynbl工具以直方图的形式显示SYN到达时的TCP SYN积压大小。这可以让我们了解应用程序距离达到积压限制并丢弃SYN(导致SYN重传产生性能问题)还有多远。 TCP SYN 数据包则通常用于启动 TCP 三次握手过程的第一次握手。 二,代码示例 #!/usr/bin/env python…...

GVRP实现vlan的自动创建和注册

拓扑图 资源已上传 流程 第一、每台交换机的全局及端口下使能GVRP功能 第二、配置交换机的二层连通性,交换机某些端口配置Trunk端口,并允许相应的vlan通过 第三、在交换机4和5配置静态vlan100,然后查看1和3交换机是否有vlan100的定义&…...

Oracle VM VirtualBox修改磁盘大小

一、 修改虚拟机磁盘大小 先把虚拟机停掉。再增加磁盘大小。 路径中有空格的用""包起来。 D:\Program Files\Oracle\VirtualBox>VBoxManage.exe modifyhd "D:\Program Files\VirtualBox VMs\mycentos\mycentos.vdi" --resize 30000 0%...10%...20%...3…...

【嵌入式硬件】步进电机

1.步进电机简介 1.1步进电机基本原理 步进电机的英文是stepping motor。step的中文意思是行走、迈步。所以仅从字面上我们就可以得知,步进电机就是一步一步移动的电动机。说的官方一点儿,步进电机是一种将电脉冲信号转换成相应角位移或者线位移的电动机(直线电机)。下图为…...

FlyControls 是 THREE.js 中用于实现飞行控制的类,它用于控制摄像机在三维空间中的飞行。

demo演示地址 FlyControls 是 THREE.js 中用于实现飞行控制的类,它用于控制摄像机在三维空间中的飞行。 入参: object:摄像机对象,即要控制的摄像机。domElement:用于接收用户输入事件的 HTML 元素,通常…...

【Java程序设计】【C00366】基于(JavaWeb)Springboot的纹理生产图片系统(有论文)

TOC 博主介绍:java高级开发,从事互联网行业六年,已经做了六年的毕业设计程序开发,开发过上千套毕业设计程序,博客中有上百套程序可供参考,欢迎共同交流学习。 项目简介 项目获取 🍅文末点击卡片…...

编译原理Lab. 1 初代编译器实验说明和要求

目录 Lab. 1 初代编译器实验说明和要求一、初代编译器功能描述二、初代编译器文法要求三、初代编译器测试样例四、初代编译器提交要求五、初代编译器实验测试框架说明 代码与思路 Lab. 1 初代编译器实验说明和要求 一、初代编译器功能描述 初代编译器将 C 语言顺序语句序列翻…...

python判断工作日,节假日

一、概述 需要判断一个日期是否为工作日,节假日。 找到一个现成的插件,蛮好用的。 插件介绍 https://pypi.org/project/chinesecalendar/ 判断某年某月某一天是不是工作日/节假日。 支持 2004年 至 2020年,包括 2020年 的春节延长。 兼容…...

练习4-权重衰减(李沐函数简要解析)

环境:练习1的环境 代码详解 0.导入库 import torch from torch import nn from d2l import torch as d2l1.初始化数据 这里初始化出train_iter test_iter 可以查一下之前的获取Fashion数据集后的数据格式与此对应 n_train, n_test, num_inputs, batch_size 20, 100, 200, …...

websocket 中 request-line 中的URI编码问题

首先,request-line组成如下: Request-Line Method SP Request-URI SP HTTP-Version CRLF 在 rfc6455 规范的 5.1.2 Request-URI 中,有这样的描述: The Request-URI is transmitted in the format specified in section 3.2.1. …...

为何ChatGPT日耗电超50万度?

看新闻说,ChatGPT每天的耗电量是50万度,国内每个家庭日均的耗电量不到10度,ChatGPT耗电相当于国内5万个家庭用量。 网上流传,英伟达创始人黄仁勋说:“AI的尽头是光伏和储能”,大佬的眼光就是毒辣&#xff…...

__init__.py 的作用

在 Python 中,包含一个名为 __ init __.py 的文件的目录被称为一个包(package)。 __ init __.py 文件的作用有以下几点: 指示包含该文件的目录是一个 Python 包:当 Python 导入一个包时,会查找该包所在目录…...

Redis到底是多线程还是单线程?

Redis6.0之前:是单线程模式。 Redis6.0之后:Redis的IO线程是多线程,worker线程是单线程。 Redis6.0之前:单线程 Redis6.0之后:Redis的IO线程是多线程,worker线程是单线程。...

JAVA 100道题(18)

18.实现一个除法运算的方法,能够处理被除数为零的情况,并抛出异常。 在Java中,你可以创建一个除法运算的方法,该方法接受两个整数作为参数,分别代表被除数和除数。如果被除数为零,你可以抛出一个自定义的异…...

【C++】每日一题 137 只出现一次的数字

给你一个整数数组 nums &#xff0c;除某个元素仅出现 一次 外&#xff0c;其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。 你必须设计并实现线性时间复杂度的算法且使用常数级空间来解决此问题。 #include <vector>int singleNumber(std::vecto…...

RAG进阶笔记:RAG进阶

1 查询/索引部分 1.1 层次索引 创建两个索引——一个由摘要组成&#xff0c;另一个由文档块组成分两步进行搜索&#xff1a;首先通过摘要过滤出相关文档&#xff0c;接着只在这个相关群体内进行搜索 1.2 假设性问题 让LLM为每个块生成一个假设性问题&#xff0c;并将这些问…...

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周&#xff0c;有很多同学在写期末Java web作业时&#xff0c;运行tomcat出现乱码问题&#xff0c;经过多次解决与研究&#xff0c;我做了如下整理&#xff1a; 原因&#xff1a; IDEA本身编码与tomcat的编码与Windows编码不同导致&#xff0c;Windows 系统控制台…...

ABAP设计模式之---“简单设计原则(Simple Design)”

“Simple Design”&#xff08;简单设计&#xff09;是软件开发中的一个重要理念&#xff0c;倡导以最简单的方式实现软件功能&#xff0c;以确保代码清晰易懂、易维护&#xff0c;并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计&#xff0c;遵循“让事情保…...

基于TurtleBot3在Gazebo地图实现机器人远程控制

1. TurtleBot3环境配置 # 下载TurtleBot3核心包 mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src git clone -b noetic-devel https://github.com/ROBOTIS-GIT/turtlebot3.git git clone -b noetic https://github.com/ROBOTIS-GIT/turtlebot3_msgs.git git clone -b noetic-dev…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

如何更改默认 Crontab 编辑器 ?

在 Linux 领域中&#xff0c;crontab 是您可能经常遇到的一个术语。这个实用程序在类 unix 操作系统上可用&#xff0c;用于调度在预定义时间和间隔自动执行的任务。这对管理员和高级用户非常有益&#xff0c;允许他们自动执行各种系统任务。 编辑 Crontab 文件通常使用文本编…...

Bean 作用域有哪些?如何答出技术深度?

导语&#xff1a; Spring 面试绕不开 Bean 的作用域问题&#xff0c;这是面试官考察候选人对 Spring 框架理解深度的常见方式。本文将围绕“Spring 中的 Bean 作用域”展开&#xff0c;结合典型面试题及实战场景&#xff0c;帮你厘清重点&#xff0c;打破模板式回答&#xff0c…...

毫米波雷达基础理论(3D+4D)

3D、4D毫米波雷达基础知识及厂商选型 PreView : https://mp.weixin.qq.com/s/bQkju4r6med7I3TBGJI_bQ 1. FMCW毫米波雷达基础知识 主要参考博文&#xff1a; 一文入门汽车毫米波雷达基本原理 &#xff1a;https://mp.weixin.qq.com/s/_EN7A5lKcz2Eh8dLnjE19w 毫米波雷达基础…...

破解路内监管盲区:免布线低位视频桩重塑停车管理新标准

城市路内停车管理常因行道树遮挡、高位设备盲区等问题&#xff0c;导致车牌识别率低、逃费率高&#xff0c;传统模式在复杂路段束手无策。免布线低位视频桩凭借超低视角部署与智能算法&#xff0c;正成为破局关键。该设备安装于车位侧方0.5-0.7米高度&#xff0c;直接规避树枝遮…...

深入浅出Diffusion模型:从原理到实践的全方位教程

I. 引言&#xff1a;生成式AI的黎明 – Diffusion模型是什么&#xff1f; 近年来&#xff0c;生成式人工智能&#xff08;Generative AI&#xff09;领域取得了爆炸性的进展&#xff0c;模型能够根据简单的文本提示创作出逼真的图像、连贯的文本&#xff0c;乃至更多令人惊叹的…...

2025-05-08-deepseek本地化部署

title: 2025-05-08-deepseek 本地化部署 tags: 深度学习 程序开发 2025-05-08-deepseek 本地化部署 参考博客 本地部署 DeepSeek&#xff1a;小白也能轻松搞定&#xff01; 如何给本地部署的 DeepSeek 投喂数据&#xff0c;让他更懂你 [实验目的]&#xff1a;理解系统架构与原…...