【Golang】基于录制,自动生成go test接口自动化用例

目录

背景

框架

ginkgo初始化

抓包&运行脚本

目录说明

∮./business

∮./conf

∮./utils

∮./testcase

testcase 用例目录结构规则

¶示例

实现思路

解析Har数据

定义结构体

解析到json

转换请求数据

转换请求

转换请求参数

写业务请求数据

写gotest测试用例数据

初始化写入suit文件

格式化测试文件

install生成的业务请求目录

格式化响应断言

可能遇到的问题

完整代码

详细代码如下,注释已经给得比较清晰:

 资料获取方法


背景

之前写过一篇博客,介绍怎么用Python通过解析抓包数据,完成自动化用例的编写。最近这段时间在使用go test,所以就在想能不能也使用代码来生成自动化用例,快速提升测试用例覆盖率。说干就干。

框架

首先介绍一下我们使用的测框架:

信息安装备注
GO版本go1.12.9 darwin/amd64
测试框架ginkgogo get -u github.com/onsi/ginkgo/ginkgo
断言库testify/assertgo get github.com/stretchr/testify官方配套的断言库是gomega

ginkgo初始化

  • 初始化: cd path/to/package/you/want/to/test && ginkgo bootstrap
  • 创建示例用例:ginkgo generate (需要手动添加测试用例)
  • 运行测试: go testor ginkgo

注:-v加上参数可打印运行信息

抓包&运行脚本

  • 使用抓包工具(如Charles)抓包,把数据包导出为har格式,保存在当前目录下
    • 如何安装抓包工具在本文就不赘述了,抓包,过滤出想要的数据,导出,保存的格式注意选择为har

  • 根据实际情况修改全局变量信息,如bizBaseFolder、serverName、userFile等
  • 使用go run gentest.go运行脚本即可

目录说明

然后我们一起来了解一下我们的目录结构定义。

∮./business

业务封装,封装具体的请求及测试数据

∮./conf

配置信息及接口请求参数初始化封装

∮./utils

公共函数封装

∮./testcase

接口测试用例目录

testcase 用例目录结构规则

基本原则: 根据项目、模块、接口功能逐级区分,建议最多3层目录层级

¶示例
  1. 软件测试论坛项目组/论坛项目/帖子模块/创建帖子接口:
    • CN_TestBBS/bbs/post/post_test.go
  2. 基础账号项目/首页项目/白名单接口:
    • CN_account/homepage/whitelist_test.go

实现思路

按照har文件的JSON结构定义对应的结构体,然后解析数据,生成请求数据,生成断言数据,初始化测试套suite,格式化代码,初始化包引用信息。

解析Har数据

定义结构体
Log struct {version stringcreator stringEntries []struct {startedDateTime stringtime            stringRequest         struct {...
解析到json
func UnpackHar(har []byte) (logs *Har) {err := json.Unmarshal(har, &logs)if err != nil {fmt.Println(err)}return
}

转换请求数据

转换请求
转换请求参数

GET

// 格式化请求参数为标准请求string
getReqParam := make(map[string]interface{}, 1)
if len(v.Request.QueryString) > 0 {for _, query := range v.Request.QueryString {getReqParam[query.Name] = query.Value}
}
// 获取postReq数据
postReqParamStr := v.Request.PostData.Textif v.Request.Method == "GET" {paramstr = genGetParam(InterfaceName, getReqParam)
}
func genGetParam(interfaceName string, param map[string]interface{}) (formatParam string) {// 对于请求参数的value值为 数组if len(param) > 0 {for k, v := range param {switch vv := v.(type) {case []interface{}:fmt.Sprintf(k, "is an array:", vv)temp, _ := json.Marshal(param)formatParam = fmt.Sprintf("%sParam = `%s`", interfaceName, fmt.Sprintf("%v", string(temp)))returndefault:// fmt.Println(k, "is of a type didn't handle")}}}temp, _ := json.Marshal(param)formatParam = fmt.Sprintf(`%sParam = map[string]interface{} %s`, interfaceName, fmt.Sprintf("%v", string(temp)))return
}

POST

postReqParamStr := v.Request.PostData.Textif v.Request.Method == "POST" {paramstr = genPostParam(InterfaceName, postReqParamStr)
}
func genPostParam(interfaceName string, postReqParamStr string) (formatParam string) {// formatParam = fmt.Sprintf(`%sParam = map[string]interface{} %s`, interfaceName, param)// fmt.Sprintf("%v", string(temp))postReqParam := make(map[string]interface{}, 1)if len(postReqParamStr) > 0 {// 判断第一个字符是否为{}, 做传递数据为数组[]的兼容if []rune(postReqParamStr)[0] == '{' {var x interface{}err := json.Unmarshal([]byte(postReqParamStr), &x)if err != nil {fmt.Println("err", err)}postReqParam = x.(map[string]interface{})// fmt.Println(postReqParam)// 判断value中是否存在数组for k, v := range postReqParam {switch vv := v.(type) {// switch vv := v.(type) {case []interface{}:fmt.Sprintf(k, "is an array:", vv)// param[k] = fmt.Sprintf("`%s`", vv)temp, _ := json.Marshal(postReqParam)formatParam = fmt.Sprintf("%sParam = `%s`", interfaceName, fmt.Sprintf("%v", string(temp)))paramType = "string"returndefault:formatParam = genGetParam(interfaceName, postReqParam)// fmt.Println(k, "is of a type didn't handle")}}// 如果为数组,做如下处理} else {var y []interface{}err := json.Unmarshal([]byte(postReqParamStr), &y)if err != nil {fmt.Println("err", err)}postReqParam = y[0].(map[string]interface{})temp, _ := json.Marshal(postReqParam)// 声明请求类型paramType = "[]map[string]interface{}"formatParam = fmt.Sprintf(`%sParam =[]map[string]interface{}{%s}`, interfaceName, string(temp))// 无法使用 判断类型 Param := utils.MapDeepCopy(Hebinz123.XlppcPlaylistApiV1RemarkDelParam)}}// temp, _ := json.Marshal(param)// formatParam = fmt.Sprintf(`%sParam = map[string]interface{} %s`, interfaceName, fmt.Sprintf("%v", string(temp)))return
}

写业务请求数据

写gotest测试用例数据

格式化请求参数为标准请求string。

初始化写入suit文件

这里有一个注意点,Test后紧接的数据必须是大写。

格式化测试文件

使用goimports库初始化导入数据包。

install生成的业务请求目录

使用go install目录生成导入业务请求目录

格式化响应断言

使用类型判断格式化接口返回数据为标准断言string。

可能遇到的问题

  • 初始化读取文件的存储buf的size和其实际大小不一致时,json 解析出错“invalid character '\x00' after top-level value”
  • go install 执行失败,导致测试用例无法找到其依赖包
  • get请求,post请求参数在har文件中的存储方式不一致,获取数据的方式差别很大
  • 域名及接口命名规则不一致,-.等等风格不一致
  • 测试suite 紧接Test后方的字符需为大写的字母,否则服务无法被发现,所以需要做大小写转换

完整代码

详细代码如下,注释已经给得比较清晰:

package mainimport ("encoding/base64""encoding/json""fmt""os""os/exec""path/filepath""strings"
)var (baseDomain         = "test.bbs.com"    // 测试域名,用于切割出请求路径bizBaseFolder      = "business/CN_bbs" //业务请求目录testCaseBaseFolder = "testcase/CN_bbs" // 测试用例目录serverName         = "cinecismGo"      // 服务名paramType          = ""
)func main() {userFile := "20190917-cinecismgo.har" // 抓包文件地址fl, err := os.Open(userFile)if err != nil {fmt.Println(userFile, err)return}defer fl.Close()// 读取har数据fileInfo, err := fl.Stat()buf := make([]byte, fileInfo.Size()) // “invalid character '\x00' after top-level value”fl.Read(buf)data := UnpackHar(buf)for _, v := range data.Log.Entries {// 每一个循环初始化请求参数类型paramType = "map[string]interface{}"paramstr := ""// 初始化 请求path,生成标准请求接口名称pathStr, path := initPath(v.Request.URL)InterfaceName := formatInterfaceName(pathStr)// 格式化请求参数为标准请求stringgetReqParam := make(map[string]interface{}, 1)if len(v.Request.QueryString) > 0 {for _, query := range v.Request.QueryString {getReqParam[query.Name] = query.Value}}// 获取postReq数据postReqParamStr := v.Request.PostData.Textif v.Request.Method == "GET" {paramstr = genGetParam(InterfaceName, getReqParam)}if v.Request.Method == "POST" {paramstr = genPostParam(InterfaceName, postReqParamStr)}// 格式化接口返回数据为标准断言stringtext, _ := base64.StdEncoding.DecodeString(v.Response.Content.Text)responseAssertStr := initAssert(text)// 创建业务请求文件、测试用例文件run(serverName, path, InterfaceName, v.Request.Method, responseAssertStr, paramstr)// 【待补充】handle Headers数据// fmt.Println(initHeaders(data))}
}func initAssert(text []byte) (responseAssertStr string) {if len(text) > 0 {var Response interface{}err := json.Unmarshal(text, &Response)if err != nil {fmt.Println("err", err)}responseMap := Response.(map[string]interface{})res := []string{}for k, v := range responseMap {switch vv := v.(type) {case string:// fmt.Println(k, "is string", vv)res = append(res, fmt.Sprintf("%s, _ := js.Get(\"%s\").String() \n assert.Equal(%s, `%v`)", k, k, k, string(vv)))case int64:// fmt.Println(k, "is int", vv)res = append(res, fmt.Sprintf("%s, _ := js.Get(\"%s\").Int() \n assert.Equal(%s, %v)", k, k, k, string(vv)))case float64:// fmt.Println(k, "is float64", vv)res = append(res, fmt.Sprintf("%s, _ := js.Get(\"%s\").Int() \n assert.Equal(%s, %v)", k, k, k, vv))case bool:// fmt.Println(k, "is bool", vv)res = append(res, fmt.Sprintf("%s, _ := js.Get(\"%s\").Bool() \n assert.Equal(%s, %v)", k, k, k, vv))case []interface{}:// fmt.Println(k, "is an array:", vv)res = append(res, fmt.Sprintf("// Key【%s】的子层级的value值未生成断言,系多层级数组数据,具体值如下:", k))res = append(res, fmt.Sprintf("// %v ", vv))case map[string]interface{}:// fmt.Println(k, "is an map:", vv)temp, _ := json.Marshal(vv)res = append(res, fmt.Sprintf("// Key【%s】的子层级value值未生成断言,系多层级Map数据,具体值如下:", k))res = append(res, fmt.Sprintf("// %v ", string(temp)))default:// fmt.Println(k, "is of a type didn't handle", vv)}responseAssertStr = strings.Join(res, "\n")}}return
}func initPath(URL string) (pathStr, path string) {pathStr = strings.Split(URL, baseDomain)[1]if strings.Contains(pathStr, "?") {pathStr = strings.Split(pathStr, "?")[0]path = strings.Split(pathStr, "?")[0]} else {path = pathStr}if strings.Contains(pathStr, ".") {pathStr = strings.Replace(pathStr, ".", "/", 10)pathStr = strings.Replace(pathStr, "-", "/", 10)}// fmt.Println(path)// fmt.Println("pathStr", pathStr)return
}func run(serverName, path, InterfaceName, method, responseAssertStr string, Param string) {// 初始化测试文件InterfaceFilepath := filepath.Join(bizBaseFolder, serverName)Testcasefilepath := filepath.Join(testCaseBaseFolder, serverName)InterfaceFileame := InterfaceName + ".go"Testcasefilename := InterfaceName + "_test.go"// 创建并写入标准请求信息file, err := createFile(InterfaceFilepath, InterfaceFileame)if err != nil {fmt.Println("createInterfaceFile", err)}writeParam(file, serverName, []string{Param})writeReq(file, InterfaceName, path, method)defer file.Close()// 创建并写入测试用例信息file1, err := createFile(Testcasefilepath, Testcasefilename)if err != nil {fmt.Println("createTestcasefile", err)}// 写入suit文件initTestsuit(serverName)// 写入测试用例writeTestcase(file1, serverName, InterfaceName, responseAssertStr)defer file1.Close()// 格式化测试文件exec.Command("goimports", "-w", InterfaceFilepath).Run()exec.Command("goimports", "-w", Testcasefilepath).Run()// 导入InterfaceFilepathexec.Command("go", "install", InterfaceFilepath).Run()
}func initHeaders(har *Har) map[string]string {var headers = make(map[string]string)// fmt.Println(len(har.Log.Entries[0].Request.Headers))for _, v := range har.Log.Entries[0].Request.Headers {headers[v.Name] = v.Value}return headers
}func createFile(filepaths, filename string) (file *os.File, err error) {os.MkdirAll(filepaths, 0777)file, err = os.Create(filepath.Join(filepaths, filename))return
}func createInterfaceFile(path, filename string) (file *os.File, err error) {filename = filename + ".go"filepath := bizBaseFolder + "/" + path + "/"os.MkdirAll(filepath, 0777)file, err = os.Create(filepath + filename)return
}func createTestcasefile(path, filename string) (file *os.File, err error) {filename = filename + "_test.go"filepath := testCaseBaseFolder + "/" + path + "/"os.MkdirAll(filepath, 0777)file, err = os.Create(filepath + filename)return
}func initTestsuit(serverName string) {filename := serverName + "_suite_test.go"filepath := testCaseBaseFolder + "/" + serverName + "/"os.MkdirAll(filepath, 0777)file, err := os.Create(filepath + filename)if err != nil {fmt.Println("initTestsuit Error", err)}// Testsuite后的 首字母需大写,否则suite无法正常检索到testcasefile.WriteString(fmt.Sprintf(`package %s_testimport ("testing". "github.com/onsi/ginkgo". "github.com/onsi/gomega")func Test%s(t *testing.T) {RegisterFailHandler(Fail)RunSpecs(t, "%s Suite")}`, serverName, Capitalize(serverName), serverName))
}func writeTestcase(file *os.File, serverName, InterfaceName, responseAssertStr string) {// 接口引入路径 【服务名称.接口名称】interfaceImportPath := serverName + "." + InterfaceName// 接口标准请求参数 【接口名称Param】paramImportPath := interfaceImportPath + "Param"// 接口标准请求参数拷贝,请求参数为非标准【map[string]interface{}】类型时,该参数为空tempParamStr := ""// 是否使用mapDeepCopy,请求参数为非标准【map[string]interface{}】类型时 使用mapDeepCopy := ""if paramType != "map[string]interface{}" {tempParamStr = paramImportPath}if paramType == "map[string]interface{}" {tempParamStr = "Param"mapDeepCopy = fmt.Sprintf(`Param := utils.MapDeepCopy(%s)`, paramImportPath)}// fmt.Println("---------------->", paramType)file.WriteString(fmt.Sprintf("package %s_test\n\n", serverName))file.WriteString(`import . "github.com/onsi/ginkgo"`)file.WriteString("\n\n")file.WriteString(fmt.Sprintf(`var _ = Describe("%s", func() {headers := common.EntireHeaderParamassert := assert.New(GinkgoT())BeforeEach(func() {By("begin test")})JustBeforeEach(func() {By("just say start")})AfterEach(func() {By("end test")})Context("%s", func() {It("正常%s", func() {%sret, resp, _ := %s(%s, headers)assert.Equal(ret.StatusCode, 200)js, errs := simplejson.NewJson(resp)if errs != nil {panic(errs)}%s})})})`, serverName, InterfaceName, InterfaceName, mapDeepCopy, interfaceImportPath, tempParamStr, responseAssertStr))
}func writeParam(file *os.File, serverName string, params []string) {file.WriteString(fmt.Sprintf("package %s", serverName))file.WriteString("\n\n\n")file.WriteString("var (")for _, param := range params {file.WriteString(param)}file.WriteString(")")file.WriteString("\n\n\n")
}func writeReq(file *os.File, InterfaceName, path, method string) {file.WriteString(fmt.Sprintf(`func %s(param %s, header map[string]string) (ret gorequest.Response, content []byte, result string) {path := "%s"url := CN_bbs.TESTSERVERDOMAIN + pathret, content = common.Common%s(url, param, header)fmt.Println(ret.Request.URL)// js, _ := simplejson.NewJson([]byte(content))//result, _ = js.Get("result").String()return}`, InterfaceName, paramType, path, method))
}func genGetParam(interfaceName string, param map[string]interface{}) (formatParam string) {// 对于请求参数的value值为 数组if len(param) > 0 {for k, v := range param {switch vv := v.(type) {case []interface{}:fmt.Sprintf(k, "is an array:", vv)temp, _ := json.Marshal(param)// 如果是数组格式,直接当作字符串处理(map[]interface{}格式无法表示该类型参数)formatParam = fmt.Sprintf("%sParam = `%s`", interfaceName, fmt.Sprintf("%v", string(temp)))returndefault:// fmt.Println(k, "is of a type didn't handle")}}}temp, _ := json.Marshal(param)formatParam = fmt.Sprintf(`%sParam = map[string]interface{} %s`, interfaceName, fmt.Sprintf("%v", string(temp)))return
}func genPostParam(interfaceName string, postReqParamStr string) (formatParam string) {// formatParam = fmt.Sprintf(`%sParam = map[string]interface{} %s`, interfaceName, param)// fmt.Sprintf("%v", string(temp))postReqParam := make(map[string]interface{}, 1)if len(postReqParamStr) > 0 {// 判断第一个字符是否为{}, 做传递数据为数组[]的兼容if []rune(postReqParamStr)[0] == '{' {var x interface{}err := json.Unmarshal([]byte(postReqParamStr), &x)if err != nil {fmt.Println("err", err)}postReqParam = x.(map[string]interface{})// fmt.Println(postReqParam)// 判断value中是否存在数组for k, v := range postReqParam {switch vv := v.(type) {// switch vv := v.(type) {case []interface{}:fmt.Sprintf(k, "is an array:", vv)// param[k] = fmt.Sprintf("`%s`", vv)temp, _ := json.Marshal(postReqParam)formatParam = fmt.Sprintf("%sParam = `%s`", interfaceName, fmt.Sprintf("%v", string(temp)))paramType = "string"returndefault:formatParam = genGetParam(interfaceName, postReqParam)// fmt.Println(k, "is of a type didn't handle")}}// 如果为数组,做如下处理} else {var y []interface{}err := json.Unmarshal([]byte(postReqParamStr), &y)if err != nil {fmt.Println("err", err)}postReqParam = y[0].(map[string]interface{})temp, _ := json.Marshal(postReqParam)// 声明请求类型paramType = "[]map[string]interface{}"formatParam = fmt.Sprintf(`%sParam =[]map[string]interface{}{%s}`, interfaceName, string(temp))// 无法使用 判断类型 Param := utils.MapDeepCopy(Hebinz123.CNppcPlaylistApiV1RemarkDelParam)}}// temp, _ := json.Marshal(param)// formatParam = fmt.Sprintf(`%sParam = map[string]interface{} %s`, interfaceName, fmt.Sprintf("%v", string(temp)))return
}func formatInterfaceName(path string) (InterfaceName string) {paths := strings.Split(path, "/")for k, v := range paths {paths[k] = Capitalize(v)}InterfaceName = strings.Join(paths, "")return
}// Capitalize 字符首字母大写
func Capitalize(str string) string {var upperStr stringvv := []rune(str)for i := 0; i < len(vv); i++ {if i == 0 {if vv[i] >= 97 && vv[i] <= 122 { // 判断是否是小写字母vv[i] -= 32 // string的码表相差32位upperStr += string(vv[i])} else {fmt.Println("Not begins with lowercase letter,")return str}} else {upperStr += string(vv[i])}}return upperStr
}// Har Logs 解析
type Har struct {Log struct {version stringcreator stringEntries []struct {startedDateTime stringtime            stringRequest         struct {Method      stringURL         stringhttpVersion stringCookies     []stringHeaders     []struct {Name  stringValue string}QueryString []struct {Name  stringValue string}PostData struct {MimeType stringText     string}headersSize int32bodySize    int32}Response struct {_charlesStatus stringStatus         int32StatusText     stringhttpVersion    stringcookies        []stringHeaders        []struct {Name  stringValue string}Content struct {size     int32mimeType stringText     stringEncoding string}redirectURL stringheadersSize intbodySize    int}serverIPAddress stringcache           map[string]stringtimings         map[string]int32}}
}// UnpackHar 解析 har
func UnpackHar(har []byte) (logs *Har) {err := json.Unmarshal(har, &logs)if err != nil {fmt.Println(err)}return
}

文中可能存在描述不正确,欢迎大神们指正补充!

感谢阅读,如果觉得对你有帮助,就在右下角点个赞吧,感谢!

合抱之木,生于毫末;九层之台,起于累土;千里之行,始于足下。


 资料获取方法

【留言777】

各位想获取源码等教程资料的朋友请点赞 + 评论 + 收藏,三连!

三连之后我会在评论区挨个私信发给你们~

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Abaqus 导出单元刚度矩阵和全局刚度矩阵

Abaqus 导出单元刚度矩阵和全局刚度矩阵 首次创建&#xff1a;2023.7.29 最后更新&#xff1a;2023.7.29 如有什么改进的地方&#xff0c;欢迎大家讨论&#xff01; 详细情况请查阅&#xff1a;Abaqus Analysis User’s Guide 一、Abaqus 导出单元刚度矩阵 1.生成单元刚度矩阵…

【MySQL】数据类型

上期我们在建表的时候用到了很多数据类型&#xff0c;下面就来详细讲解这些数据类型 目录 一、数据类型分类 二、对于一些数据类型的分析 2.1 数值类型 2.1.1 有符号和无符号类型 2.1.2 bit类型 2.1.3 浮点型 2.1.3.1 float 2.1.3.2 decimal 2.2 字符串类型 2.2.1 ch…

数据结构--基础知识

数据结构是什么&#xff1f; 数据结构是计算机科学中研究数据组织、存储和管理的方法和原则。它涉及存储和操作数据的方式&#xff0c;以便能够高效地使用和访问数据。 相关内容 基本组成 数组&#xff08;Array&#xff09;&#xff1a;数组是一种线性数据结构&#xff0c;…

数据管理基础知识

数据管理原则 数据管理与其他形式的资产管理的共同特征&#xff0c;涉及了解组织拥有哪些数据以及可以使用这些数据完成哪些工作&#xff0c;然后确定如何最好的使用数据资产来实现组织目标与其他流程一样&#xff0c;他必须平衡战略和运营需求&#xff0c;通过遵循一套原则&a…

【Linux】揭秘:提升dd命令效率的秘密武器!

红帽RHCE试听课程&#xff1a;如何快速实现对服务器密码爆破&#xff1f;https://mp.weixin.qq.com/s/JUpf8G86jvnNwvKLUfWcLQ 红帽RHCE试听课程&#xff1a;linux系统下&#xff0c;用这个命令可以提高60%的工作效率https://mp.weixin.qq.com/s/pZVjMI1PLJzrA8hoPzkgMA 大家好…

机器学习深度学习——Dropout

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位即将上大四&#xff0c;正专攻机器学习的保研er &#x1f30c;上期文章&#xff1a;机器学习&&深度学习——权重衰减 &#x1f4da;订阅专栏&#xff1a;机器学习&&深度学习 希望文章对你们有所帮助 Drop…

使用火山云搜索ESCloud服务构建图文检索应用(以文搜图/以图搜图)

图文检索在生活中具有广泛的应用&#xff0c;常见的图片检索包括基于文本内容搜索和基于图片内容搜索。用户通过输入文字描述或上传图片就可以在海量的图片库中快速找到同款或者相似图片&#xff0c;这种搜索方式被广泛应用于电商、广告、设计以及搜索引擎等热门领域。 本文基…

TCP三次握手四次挥手

一、TCP三次握手四次挥手 1.三次握手&#xff1a; 第一次握手&#xff1a;客户端发送syn包(seqx)到服务器&#xff0c;并进入SYN_SEND(发送)状态&#xff0c;等待服务器确认&#xff1b; 第二次握手&#xff1a;服务器收到syn包&#xff0c;必须确认客户的SYN&#xff08;ac…

redis五种数据类型介绍

、string&#xff08;字符串&#xff09; 它师最基本的类型&#xff0c;可以理解为Memcached一模一样的类型&#xff0c;一个key对应一个value。 注意&#xff1a;一个键最大能存储 512MB。 特性&#xff1a;可以包含任何数据,比如jpg图片或者序列化的对象,一个键最大能存储512…

计划管理与项目管理:有何区别?

简而言之&#xff0c;是的。尽管它们经常互换使用并对全局产生影响&#xff0c;但它们是完全不同的。 在本文中&#xff0c;我们将了解计划和项目管理之间的差异&#xff0c;提供每个示例&#xff0c;并向您展示如何使计划和项目管理工作更有效地实现您的业务目标。 计划管理与…

程序员面试金典17.*

文章目录 17.01 不用加号的加法17.04 消失的数字17.05字母与数字17.06 2出现的次数17.07 婴儿名字17.08 马戏团人塔17.09 第k个数17.10 主要元素17.11 单词距离17.12 BiNode17.13 恢复空格&#xff08;未做&#xff0c;字典树dp&#xff09;17.14 最小K个数17.15 最长单词17.16…

python爬虫-加速乐cookie混淆解析实例小记

注意&#xff01;&#xff01;&#xff01;&#xff01;某XX网站逆向实例仅作为学习案例&#xff0c;禁止其他个人以及团体做谋利用途&#xff01;&#xff01;&#xff01; 第一步&#xff1a;抓包工具第一次请求页面&#xff0c;得到响应。本次我使用的fiddle进行抓包&#…

网易云音乐扫码登录

简介 尚硅谷的网易云音乐项目无法登录&#xff0c;因为目前网易修改了接口使用手机号和密码登录的话需要先通过认证才可以&#xff0c;所以目前无法使用手机号登录&#xff0c;只能使用二维码登录&#xff0c;接下来我就教大家如何使用 二维码进行登录 实现步骤 1.获取nodejs接…

【1.4】Java微服务:服务注册和调用(Eureka和Ribbon实现)

✅作者简介&#xff1a;大家好&#xff0c;我是 Meteors., 向往着更加简洁高效的代码写法与编程方式&#xff0c;持续分享Java技术内容。 &#x1f34e;个人主页&#xff1a;Meteors.的博客 &#x1f49e;当前专栏&#xff1a; 微服务 ✨特色专栏&#xff1a; 知识分享 &#x…

排序八卦炉之选择、堆排

文章目录 1.选择排序1.1代码实现1.2复杂度 2.堆排序2.1代码实现2.2复杂度 1.选择排序 1.1代码实现 // 当数据趋于有序或随机(可能部分有序) 插排更有优势 O(N)~O(N^2) //选择排序&#xff1a;O&#xff08;N^2&#xff09; O(N^2)~O(N^2) void …

【shell】获取ping的时延数据并分析网络情况及常用命令学习

文章目录 获取ping的时延数据并分析网络情况|、||、&、&&辨析teetailkillall 获取ping的时延数据并分析网络情况 网络情况经常让我们头疼&#xff0c;每次都需要手动在终端ping太麻烦了&#xff0c;不如写个脚本ping并将数据带上时间戳存入文件&#xff0c;然后也…

如何克服看到别人优于自己而感到的焦虑和迷茫?

文章目录 每日一句正能量前言简述自己的感受怎么做如何调整自己的心态后记 每日一句正能量 行动是至于恐惧的良药&#xff0c;而犹豫、拖延&#xff0c;将不断滋养恐惧。 前言 虽然清楚知识需要靠时间沉淀&#xff0c;但在看到自己做不出来的题别人会做&#xff0c;自己写不出的…

软件测试缺陷报告

缺陷报告是描述软件缺陷现象和重现步骤地集合。软件缺陷报告Software Bug Report&#xff08;SBR&#xff09;或软件问题报告Software Problem Report&#xff08;SPR&#xff09; 作用&#xff1a;缺陷报告是软件测试人员的工作成果之一&#xff0c;体现软件测试的价值缺陷报…

Pytorch深度学习-----神经网络之线性层用法

系列文章目录 PyTorch深度学习——Anaconda和PyTorch安装 Pytorch深度学习-----数据模块Dataset类 Pytorch深度学习------TensorBoard的使用 Pytorch深度学习------Torchvision中Transforms的使用&#xff08;ToTensor&#xff0c;Normalize&#xff0c;Resize &#xff0c;Co…

<van-empty description=““ /> 滚动条bug

使用 <van-empty description"" /> 时&#xff0c;图片出现了个滚动条&#xff0c;图片可以上下滑动。 代码如下&#xff1a; <block wx:if"{{courseList.length < 0}}"><van-empty description"" /> </block> <…