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

news2024/9/21 4:23:48

本文以笔者当前使用的自动化测试项目为例,浅谈分层设计的思路,不涉及到具体的代码细节和某个框架的实现原理,重点关注在分层前后的使用对比,可能会以一些伪代码为例来说明举例。

接口测试三要素

  • 参数构造

  • 发起请求,获取响应

  • 校验结果

一、原始状态

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

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

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

    # 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.mallCode    createCommodityParams["input"]["skuList"][0]["price"] = random.randint(1,10)    createCategoryParams["input"]["categoryName"] = "autoTestCategory" + str(time.time())    createCouponParams。。。    createPublicityParams。。。    publishCommodityParams。。。    publishPublicityParams。。。       # 2、发起请求,获取响应     # 创建商品并获取商品code    createCommodityRes = api.getUrl("testApi.create.commodity").post.params(createCommodityParams)    commodityCode = createCommodityRes["commodityCode"]     # 创建分类并获取分类code    createCategoryRes = api.getUrl("testApi.create.category").post.params(createCategoryParams)    categoryCode = createCategoryRes["categoryCode"]     # 创建优惠券并获取优惠券code    createCouponRes = api.getUrl("testApi.create.coupon").post.params(createCouponParams)    couponCode = createCouponRes["couponCode"]     # 创建活动并关联商品,绑定优惠券,设置分类    createPublicityParams["input"]["commodityCode"] = commodityCode    createPublicityParams["input"]["categoryCode"] = categoryCode    createPublicityParams["input"]["couponCode"] = couponCode    createPublicityRes = 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.mallCode        createCommodityParams["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 response                 def createCategory(createCategoryParams):          ...                 def createCoupon(createCouponParams):          ...                 ......                  class ApiParamsBuild:         def createCommodityParamsBuild(createCommodityParams):          createCommodityParams["input"]["title"] = "autoTest" + str(time.time())          createCommodityParams["input"]["mallCode"] = self.mallCode          createCommodityParams["input"]["skuList"][0]["price"] = random.randint(1,10)          return createCommodityParams                 def 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、发起请求,获取响应         # 创建商品并获取商品code        createCommodityRes = ApiObject().createCommodity(createCommodityParams)        commodityCode = createCommodityRes["commodityCode"]         # 创建分类并获取分类code        createCategoryRes = ApiObject().createCategory(createCategoryParams)        categoryCode = createCategoryRes["categoryCode"]         # 创建优惠券并获取优惠券code        createCouponRes = ApiObject().createCoupon(createCouponParams)        couponCode = createCouponRes["couponCode"]         # 创建活动并关联商品,绑定优惠券,设置分类        createPublicityParams["input"]["commodityCode"] = commodityCode        createPublicityParams["input"]["categoryCode"] = categoryCode        createPublicityParams["input"]["couponCode"] = couponCode        createPublicityRes = ApiObject().createPublicity(createPublicityParams)                # 结果校验(断言)        assert.equal(createPublicityRes["code"], 0)        assert.equal(createPublicityRes["publicityName"], createPublicityParams["publicityName"])        。。。
复制代码

可以看到,现在接口请求的url、method、通用入参处理等已经不会在用例中体现了,接下来继续封装caseService层。

caseService:

我们将多接口的场景步骤进行封装

        class CaseService:         def createPublicityByCategory(params):           # 创建商品并获取商品code          createCommodityRes = ApiObject().createCommodity(createCommodityParams)          commodityCode = createCommodityRes["commodityCode"]           # 创建分类并获取分类code          createCategoryRes = ApiObject().createCategory(createCategoryParams)          categoryCode = createCategoryRes["categoryCode"]           # 创建优惠券并获取优惠券code          createCouponRes = ApiObject().createCoupon(createCouponParams)          couponCode = createCouponRes["couponCode"]           # 创建活动并关联商品,绑定优惠券,设置分类          createPublicityParams["input"]["commodityCode"] = commodityCode          createPublicityParams["input"]["categoryCode"] = categoryCode          createPublicityParams["input"]["couponCode"] = couponCode          createPublicityRes = 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].price
    value: 1  - keyPath: $.input.skuList[0].retailPrice    value: 6
复制代码

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

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

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

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

 

 6、Base

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

在我们实际的自动化开发过程中,有很多前置条件或公共方法,比如登录处理,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
复制代码

以上,期待与各位同学一起交流探讨。

最后: 下方这份完整的软件测试视频学习教程已经整理上传完成,朋友们如果需要可以自行免费领取【保证100%免费】

 

 这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!

软件测试面试文档

我们学习必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有字节大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。

 面试文档获取方式:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/536324.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

NA、商业和分销市场通盘布局,华为“四大角色”浮出水面

作者 | 曾响铃 文 | 响铃说 数字经济深化发展,谋求数字化转型的行业、企业客户,越来越渴望满足自身需求的产品或解决方案。 这给从事转型服务的ICT厂商们提出了新的挑战。 在客户面前,拥有核心技术与支持能力的企业,与进击数字…

yolov1

1、对precision(精确度)和recall(召回度)的理解 1、TP TN FP FN的概念 TP(True Positives)意思就是被分为了正样本,而且分对了。 TN(True Negatives)意思就是被分为了负…

原装美国Agilent安捷伦34970A数据采集仪

Agilent安捷伦34970A数据采集仪 3槽主机,内置GPIB和RS232接口 6 1/2 位(22 位)内部 DMM,每秒扫描多达 250 个通道(选件 001 不可用) 8个开关和控制插件模块可供选择 内置信号调理可测量热电偶、RTD 和热敏电…

linux基础(IO)中

目录: 1.回顾上一篇的文件系统调用接口 2.返回值文件描述符 3.文件描述符分配规则 ---------------------------------------------------------------------------------------------------------------------------- 1.回顾上一篇的文件系统调用接口 open &…

【数据结构】二叉树进阶题目练习

文章目录 二叉树创建字符串二叉树的分层遍历1二叉树的分层遍历2给定一个二叉树, 找到该树中两个指定节点的最近公共祖先二叉树搜索树转换成排序双向链表二叉树展开为链表根据一棵树的前序遍历与中序遍历构造二叉树根据一棵树的中序遍历与后序遍历构造二叉树二叉树的前序遍历 非…

学爬虫,吃牢饭,卑微前端小丑复制antd的icon图标真的太难啦,我用python几秒扒完

目标需求 最近用reactviteantd写了个后管项目,在菜单管理中,需要用户选择菜单的icon图标。 如下: 而在react中使用antd UI库,每个组件都是需要单独导入的,也就是说,如果我要用到所有icon,我需…

亚马逊云科技进一步加快BMW Group的Analytics步伐

BMW Group和亚马逊云科技于2020年宣布达成全面战略合作。本次合作的目标是通过将数据分析置于决策中心,进一步加快BMW Group的创新步伐。本次合作的一个关键要素是进一步开发BMW Group的云数据中心(CDH)。这是在云端管理全公司数据和数据解决…

windows_exporter 部署

目录 - 配置服务- 配置prometheus - 配置服务 下载地址: https://github.com/prometheus-community/windows_exporter/releases 从github上下载windows_exporter.msi(我下载的是windows_exporter-0.22.0-amd64.msi)cmd命令:msie…

对 API 中敏感数据检测,用这个插件就好了

Postcat 中的 openDLP 插件基于 openDLP 开源项目,针对 Postcat 场景实现了敏感 API 发现功能,通过扫描 API 文档,识别该 API 是否可能是一个涉及敏感数据的 API。 目前内置支持 17 类敏感数据类型,可以通过自定义正则支持更多类型…

2023年安全岗秋招经验分享,纯干货,建议收藏!

需要准备的几个方向 简历自我介绍计算机网络操作系统(操作系统原理,Linux,Windows)数据库算法(Leetcode)编程语言(Python,C,go等)安全知识(很多很杂,建议根据…

python3 爬虫相关学习3:response= requests.get(url)的各种属性

目录 1 requests.get(url) 的各种属性,也就是response的各种属性 2 下面进行测试 2.1 response.text 1.2 response.content.decode() 1.2.1 response.content.decode() 或者 response.content.decode("utf-8") 1.2.2 response.content.decode(…

实验室信息管理系统源码,LIS系统源码

云LIS系统是医院信息管理的重要组成部分之一,系统集申请、采样、核收、计费、检验、审核、发布、质控、查询、耗材控制等检验科工作为一体的网络管理系统。LIS系统不仅是自动接收检验数据,打印检验报告,系统保存检验信息的工具,而…

平抑风电波动的电-氢混合储能容量优化配置(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

22届硕士,去年秋招拿了字节跳动offer,有一说一,不是很难进

自从抖音短视频APP火了之后,起公司字节跳动也逐渐向着大厂靠拢,相信大家都已经对这家公司很熟悉了,尤其是近几年来,对它的认识也在不断刷新,它惊人的发展速度确实让行业内人刮目相看,如今很多年轻人也想要挤…

【工作记录】springsecurity从入门到实战(一)

一、介绍 在web应用开发中,安全无疑是十分重要的,目前最流行的安全框架莫过于shiro和springsecurity了。 以下是二者简单的一个对比: SpringSecurityShiro基本功能完善完善文档完善程度强大强大社区支持度依托于Spring,社区支持强大强大集…

Flutter仿写微信导航栏快速实现页面导航

文章目录 前言使用TabBar实现TabBar介绍TabBar的重要属性说明TabBarView介绍TabBarView的重要属性TabBar总结TabBar实现底部导航的例子 BottomNavigationBar实现BottomNavigationBar介绍BottomNavigationBar实现底部导航栏的例子 总结BottomNavigationBarTabBar根据实际情况选择…

代码随想录算法训练营day43 | 1049. 最后一块石头的重量 II ,494. 目标和,474.一和零,01背包问题总结

代码随想录算法训练营day43 | 1049. 最后一块石头的重量 II ,494. 目标和,474.一和零 1049. 最后一块石头的重量 II解法一:动态规划 494. 目标和解法一:动态规划 474.一和零解法一:动态规划 01背包问题总结 1049. 最后…

商场导航地图系统,商场导航怎么实现?

商场导航怎么实现?在商场里面,手机上的导航往往接收不了信号或者不支持,由于室内不只是平面的,跟室外导航有很大的区别,因此,室内如何快速导航就成了现代化发展的趋势。电子地图作为大家最喜闻乐见的高效应…

交换机欺骗(Switch Spoofing)简介

交换机欺骗是修改设备的 MAC 地址以伪装成授权交换机端口并获得对目标网络的访问权限的过程。由于 MAC 地址对于特定设备是唯一的,因此大多数网络使用这些 MAC 地址来验证设备并与其通信或建立网络连接。通过欺骗授权设备或更简单地说,网络识别为“受信任…

redis_exporter 部署

目录 - 下载地址- 启动vim ./start.shvim ./stop.sh - 配置prometheus - 下载地址 https://github.com/oliver006/redis_exporter/releases - 启动 为了方便辨认 我更改了它的端口,并编了./start.sh和./stop.sh两个脚本方便以后启动,语句如下&#xf…