golang errors 取 错误 信息_Golang 单元测试:有哪些误区和实践?

60b59109b67945696190d5e2f356401b.png

背景

测试是保证代码质量的有效手段,而单元测试是程序模块儿的最小化验证。单元测试的重要性是不言而喻的。相对手工测试,单元测试具有自动化执行、可自动回归,效率较高的特点。对于问题的发现效率,单测的也相对较高。在开发阶段编写单测 case ,daily push daily test,并通过单测的成功率、覆盖率来衡量代码的质量,能有效保证项目的整体质量。

bb2e840abcc72e67a5a6b3758fb0187a.png

单测准则

什么是好的单测?阿里巴巴的《Java 开发手册》(点击下载)中描述了好的单测的特征:

  • A(Automatic,自动化):单元测试应该是全自动执行的,并且非交互式的。
  • I:(Independent,独立性):为了保证单元测试稳定可靠且便于维护,单元测试用例之间决不能互相调用,也不能依赖执行的先后次序。
  • R:(Repeatable,可重复):单元测试通常会被放到持续集成中,每次有代码check in时单元测试都会被执行。如果单测对外部环境(网络、服务、中间件等)有依赖,容易导致持续集成机制的不可用。

单测应该是可重复执行的,对外部的依赖、环境的变化要通过 mock 或其他手段屏蔽掉。

在 On the architecture for unit testing[1]中对好的单测有以下描述:

  • 简短,只有一个测试目的
  • 简单,数据构造、清理都很简单
  • 快速,执行函数秒级执行
  • 标准,遵守严格的约定(准备测试上下文,执行关键操作,验证结果)

单测的误区

  • 没有断言。没有断言的单测是没有灵魂的。如果只是 print 出结果,单测是没有意义的。
  • 不接入持续集成。单测不应该是本地的 run once ,而应该接入到研发的整个流程中,合并代码,发布上线都应该触发单测执行,并且可以重复执行。
  • 粒度过大。单测粒度应该尽量小,不应该包含过多计算逻辑,尽量只有输入,输出和断言。

很多人不愿意写单测,是因为项目依赖很多,各个函数之间各种调用,不知道如何在一个隔离的测试环境下进行测试。

在实践中我们调研了几种隔离(mock)的手段。下面进行逐一介绍。

单测实践

本次实践的工程项目是一个 http(基于 gin 的http 框架) 的服务。以入口的 controller 层的函数为被测函数,介绍下对它的单测过程。下面的函数的作用是根据工号输出该用户下的代码仓库的 CodeReview 数据。

可以看到这个函数作为入口层还是比较简单的,只是做了一个参数校验后调用下游并将结果透出。

func ListRepoCrAggregateMetrics(c *gin.Context) {    workNo := c.Query("work_no")    if workNo == "" {        c.JSON(http.StatusOK, errors.BuildRsp(errors.ErrorWarpper(errors.ErrParamError.ErrorCode, "work no miss"), nil))        return    }    crCtx := code_review.NewCrCtx(c)    rsp, err := crCtx.ListRepoCrAggregateMetrics(workNo)    if err != nil {        c.JSON(http.StatusOK, errors.BuildRsp(errors.ErrorWarpper(errors.ErrDbQueryError.ErrorCode, err.Error()), rsp))        return    }    c.JSON(http.StatusOK, errors.BuildRsp(errors.ErrSuccess, rsp))}

它的结果大致如下:

{  "data": {    "total": 10,    "code_review": [      {        "repo": {          "project_id": 1,          "repo_url": "test"        },        "metrics": {          "code_review_rate": 0.0977918,          "thousand_comment_count": 0,          "self_submit_code_review_rate": 0,          "average_merge_cost": 30462.584,          "average_accept_cost": 30388.75        }      }    ]  },  "errorCode": 0,  "errorMsg": "成功"}

针对这个函数测试,我们预期覆盖以下场景:

  • workNo 为空时报错。
  • workNo 不为空时范围 ,下游调用成功,repos cr 聚合数据。
  • workNo 不为空,下游失败,返回报错信息。

方案一:不 mock 下游, mock 依赖存储 (不建议)

这种方式是通过配置文件,将依赖的存储都连接到本地(比如 sqlite , redis)。这种方式下游没有 mock 而是会继续调用。

var db *gorm.DBfunc getMetricsRepo() *model.MetricsRepo {    repo := model.MetricsRepo{        ProjectID:     2,        RepoPath:      "/",        FileCount:     5,        CodeLineCount: 76,        OwnerWorkNo:   "999999",    }    return &repo}func getTeam() *model.Teams {    team := model.Teams{        WorkNo: "999999",    }    return &team}func init() {    db, err := gorm.Open("sqlite3", "test.db")    if err != nil {        os.Exit(-1)    }    db.Debug()    db.DropTableIfExists(model.MetricsRepo{})    db.DropTableIfExists(model.Teams{})    db.CreateTable(model.MetricsRepo{})    db.CreateTable(model.Teams{})    db.FirstOrCreate(getMetricsRepo())    db.FirstOrCreate(getTeam())}type RepoMetrics struct {    CodeReviewRate           float32 `json:"code_review_rate"`                ThousandCommentCount     uint    `json:"thousand_comment_count"`           SelfSubmitCodeReviewRate float32 `json:"self_submit_code_review_rate"` }type RepoCodeReview struct {    Repo        repo.Repo   `json:"repo"`    RepoMetrics RepoMetrics `json:"metrics"`}type RepoCrMetricsRsp struct {    Total          int               `json:"total"`    RepoCodeReview []*RepoCodeReview `json:"code_review"`}func TestListRepoCrAggregateMetrics(t *testing.T) {    w := httptest.NewRecorder()    _, engine := gin.CreateTestContext(w)    engine.GET("/api/test/code_review/repo", ListRepoCrAggregateMetrics)    req, _ := http.NewRequest("GET", "/api/test/code_review/repo?work_no=999999", nil)    engine.ServeHTTP(w, req)    assert.Equal(t, w.Code, 200)    var v map[string]RepoCrMetricsRsp    json.Unmarshal(w.Body.Bytes(), &v)    assert.EqualValues(t, 1, v["data"].Total)    assert.EqualValues(t, 2, v["data"].RepoCodeReview[0].Repo.ProjectID)    assert.EqualValues(t, 0, v["data"].RepoCodeReview[0].RepoMetrics.CodeReviewRate)}

上面的代码,我们没有对被测代码做改动。但是在运行 go test 进行测试时,需要指定配置到测试配置。被测项目是通过环境变量设置的。

RDSC_CONF=$sourcepath/test/data/config.yml go test -v -cover=true -coverprofile=$sourcepath/cover/cover.cover ./...
  • 初始化测试环境,清空DB数据,写入被测数据。
  • 执行测试方法。
  • 断言测试结果。

方案二:下游通过 interface 被 mock(推荐)

gomock[2] 是 Golang 官方提供的 Go 语言 mock 框架。它能够很好的和 Go testing 模块儿结合,也能用于其他的测试环境中。Gomock 包括依赖库 gomock 和接口生成工具 mockgen 两部分,gomock 用于完成桩对象的管理, mockgen 用于生成对应的 mock 文件。

type Foo interface {  Bar(x int) int}func SUT(f Foo) { // ...}ctrl := gomock.NewController(t)  // Assert that Bar() is invoked.  defer ctrl.Finish()  //mockgen -source=foo.g  m := NewMockFoo(ctrl)  // Asserts that the first and only call to Bar() is passed 99.  // Anything else will fail.  m.    EXPECT().    Bar(gomock.Eq(99)).    Return(101)SUT(m)

上面的例子,接口 Foo 被 mock。回到我们的项目,在我们上面的被测代码中是通过内部声明对象进行调用的。使用 gomock 需要修改代码,把依赖通过参数暴露出来,然后初始化时。下面是修改后的被测函数:

type RepoCrCRController struct {    c     *gin.Context    crCtx code_review.CrCtxInterface}func NewRepoCrCRController(ctx *gin.Context, cr code_review.CrCtxInterface) *TeamCRController {    return &TeamCRController{c: ctx, crCtx: cr}}func (ctrl *RepoCrCRController)ListRepoCrAggregateMetrics(c *gin.Context) {    workNo := c.Query("work_no")    if workNo == "" {        c.JSON(http.StatusOK, errors.BuildRsp(errors.ErrorWarpper(errors.ErrParamError.ErrorCode, "员工工号信息错误"), nil))        return    }    rsp, err := ctrl.crCtx.ListRepoCrAggregateMetrics(workNo)    if err != nil {        c.JSON(http.StatusOK, errors.BuildRsp(errors.ErrorWarpper(errors.ErrDbQueryError.ErrorCode, err.Error()), rsp))        return    }    c.JSON(http.StatusOK, errors.BuildRsp(errors.ErrSuccess, rsp))}

这样通过 gomock 生成 mock 接口可以进行测试了:

func TestListRepoCrAggregateMetrics(t *testing.T) {     ctrl := gomock.NewController(t)    defer ctrl.Finish()    m := mock.NewMockCrCtxInterface(ctrl)    resp := &code_review.RepoCrMetricsRsp{    }    m.EXPECT().ListRepoCrAggregateMetrics("999999").Return(resp, nil)    w := httptest.NewRecorder()    ctx, engine := gin.CreateTestContext(w)    repoCtrl := NewRepoCrCRController(ctx, m)    engine.GET("/api/test/code_review/repo", repoCtrl.ListRepoCrAggregateMetrics)    req, _ := http.NewRequest("GET", "/api/test/code_review/repo?work_no=999999", nil)    engine.ServeHTTP(w, req)    assert.Equal(t, w.Code, 200)    got := gin.H{}    json.NewDecoder(w.Body).Decode(&got)    assert.EqualValues(t, got["errorCode"], 0)}

方案三:通过 monkey patch 方式 mock 下游 (推荐)

在上面的例子中,我们需要修改代码来实现 interface 的mock,对于对象成员函数,无法进行 mock。monkey patch 通过运行时对底层指针内容修改的方式,实现对 instance method 的 mock (注意,这里要求 instance 的 method 必须是可以暴露的)。用 monkey 方式测试如下:

func TestListRepoCrAggregateMetrics(t *testing.T) {    w := httptest.NewRecorder()    _, engine := gin.CreateTestContext(w)    engine.GET("/api/test/code_review/repo", ListRepoCrAggregateMetrics)    var crCtx *code_review.CrCtx    repoRet := code_review.RepoCrMetricsRsp{    }    monkey.PatchInstanceMethod(reflect.TypeOf(crCtx), "ListRepoCrAggregateMetrics",        func(ctx *code_review.CrCtx, workNo string) (*code_review.RepoCrMetricsRsp, error) {            if workNo == "999999" {                repoRet.Total = 0                repoRet.RepoCodeReview = []*code_review.RepoCodeReview{}            }            return &repoRet, nil        })    req, _ := http.NewRequest("GET", "/api/test/code_review/repo?work_no=999999", nil)    engine.ServeHTTP(w, req)    assert.Equal(t, w.Code, 200)    var v map[string]code_review.RepoCrMetricsRsp    json.Unmarshal(w.Body.Bytes(), &v)    assert.EqualValues(t, 0, v["data"].Total)    assert.Len(t, v["data"].RepoCodeReview, 0)}

存储层 mock

Go-sqlmock 可以针对接口 sql/driver[3] 进行 mock。它可以不用真实的 db ,而模拟 sql driver 行为,实现强大的底层数据测试。下面是我们采用 table driven[4] 写法来进行数据相关测试的例子。

package storeimport (    "database/sql/driver"    "github.com/DATA-DOG/go-sqlmock"    "github.com/gin-gonic/gin"    "github.com/jinzhu/gorm"    "github.com/stretchr/testify/assert"    "net/http/httptest"    "testing")type RepoCommitAndCRCountMetric struct {    ProjectID                 uint `json:"project_id"`    RepoCommitCount           uint `json:"repo_commit_count"`    RepoCodeReviewCommitCount uint `json:"repo_code_review_commit_count"`}var (    w      = httptest.NewRecorder()    ctx, _ = gin.CreateTestContext(w)    ret    = []RepoCommitAndCRCountMetric{})func TestCrStore_FindColumnValues1(t *testing.T) {    type fields struct {        g  *gin.Context        db func() *gorm.DB    }    type args struct {        table      string        column     string        whereAndOr []SqlFilter        group      string        out        interface{}    }    tests := []struct {        name      string        fields    fields        args      args        wantErr   bool        checkFunc func()    }{        {            name: "whereAndOr is null",            fields: fields{                db: func() *gorm.DB {                    sqlDb, mock, _ := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))                    rs1 := sqlmock.NewRows([]string{"project_id", "repo_commit_count", "repo_code_review_commit_count"}).FromCSVString("1, 2, 3")                    mock.ExpectQuery("SELECT project_id, sum(commit_count) as repo_commit_count, sum(code_review_commit_count) as repo_code_review_commit_count FROM `metrics_repo_cr` GROUP BY project_id").WillReturnRows(rs1)                    gdb, _ := gorm.Open("mysql", sqlDb)                    gdb.Debug()                    return gdb                },            },            args: args{                table:      "metrics_repo_cr",                column:     "project_id, sum(commit_count) as repo_commit_count, sum(code_review_commit_count) as repo_code_review_commit_count",                whereAndOr: []SqlFilter{},                group:      "project_id",                out:        &ret,            },            checkFunc: func() {                assert.EqualValues(t, 1, ret[0].ProjectID, "project id should be 1")                assert.EqualValues(t, 2, ret[0].RepoCommitCount, "RepoCommitCount id should be 2")                assert.EqualValues(t, 3, ret[0].RepoCodeReviewCommitCount, "RepoCodeReviewCommitCount should be 3")            },        },        {            name: "whereAndOr is not null",            fields: fields{                db: func() *gorm.DB {                    sqlDb, mock, _ := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))                    rs1 := sqlmock.NewRows([]string{"project_id", "repo_commit_count", "repo_code_review_commit_count"}).FromCSVString("1, 2, 3")                    mock.ExpectQuery("SELECT project_id, sum(commit_count) as repo_commit_count, sum(code_review_commit_count) as repo_code_review_commit_count FROM `metrics_repo_cr` WHERE (metrics_repo_cr.project_id in (?)) GROUP BY project_id").                        WithArgs(driver.Value(1)).WillReturnRows(rs1)                    gdb, _ := gorm.Open("mysql", sqlDb)                    gdb.Debug()                    return gdb                },            },            args: args{                table:  "metrics_repo_cr",                column: "project_id, sum(commit_count) as repo_commit_count, sum(code_review_commit_count) as repo_code_review_commit_count",                whereAndOr: []SqlFilter{                    {                        Condition: SQLWHERE,                        Query:     "metrics_repo_cr.project_id in (?)",                        Arg:       []uint{1},                    },                },                group: "project_id",                out:   &ret,            },            checkFunc: func() {                assert.EqualValues(t, 1, ret[0].ProjectID, "project id should be 1")                assert.EqualValues(t, 2, ret[0].RepoCommitCount, "RepoCommitCount id should be 2")                assert.EqualValues(t, 3, ret[0].RepoCodeReviewCommitCount, "RepoCodeReviewCommitCount should be 3")            },        },        {            name: "group is null",            fields: fields{                db: func() *gorm.DB {                    sqlDb, mock, _ := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))                    rs1 := sqlmock.NewRows([]string{"project_id", "repo_commit_count", "repo_code_review_commit_count"}).FromCSVString("1, 2, 3")                    mock.ExpectQuery("SELECT project_id, sum(commit_count) as repo_commit_count, sum(code_review_commit_count) as repo_code_review_commit_count FROM `metrics_repo_cr` WHERE (metrics_repo_cr.project_id in (?))").                        WithArgs(driver.Value(1)).WillReturnRows(rs1)                    gdb, _ := gorm.Open("mysql", sqlDb)                    gdb.Debug()                    return gdb                },            },            args: args{                table:  "metrics_repo_cr",                column: "project_id, sum(commit_count) as repo_commit_count, sum(code_review_commit_count) as repo_code_review_commit_count",                whereAndOr: []SqlFilter{                    {                        Condition: SQLWHERE,                        Query:     "metrics_repo_cr.project_id in (?)",                        Arg:       []uint{1},                    },                },                group: "",                out:   &ret,            },            checkFunc: func() {                assert.EqualValues(t, 1, ret[0].ProjectID, "project id should be 1")                assert.EqualValues(t, 2, ret[0].RepoCommitCount, "RepoCommitCount id should be 2")                assert.EqualValues(t, 3, ret[0].RepoCodeReviewCommitCount, "RepoCodeReviewCommitCount should be 3")            },        },    }    for _, tt := range tests {        t.Run(tt.name, func(t *testing.T) {            cs := &CrStore{                g: ctx,            }            db = tt.fields.db()            if err := cs.FindColumnValues(tt.args.table, tt.args.column, tt.args.whereAndOr, tt.args.group, tt.args.out); (err != nil) != tt.wantErr {                t.Errorf("FindColumnValues() error = %v, wantErr %v", err, tt.wantErr)            }            tt.checkFunc()        })    }}

持续集成

Aone (阿里内部项目协作管理平台)提供了类似 travis-ci[5] 的功能:测试服务[6]。我们可以通过创建单测类型的任务或者直接使用实验室进行单测集成。

# 执行测试命令mkdir -p $sourcepath/coverRDSC_CONF=$sourcepath/config/config.yaml go test -v -cover=true -coverprofile=$sourcepath/cover/cover.cover ./...ret=$?; if [[ $ret -ne 0 && $ret -ne 1 ]]; then exit $ret; fi

增量覆盖率可以通过 gocov/gocov-xml 转换成 xml 报告,然后通过 diff_cover 输出增量报告:

cp $sourcepath/cover/cover.cover /root/cover/cover.coverpip install diff-cover==2.6.1gocov convert cover/cover.cover | gocov-xml > coverage.xmlcd $sourcepathdiff-cover $sourcepath/coverage.xml --compare-branch=remotes/origin/develop > diff.out

设置触发的集成阶段:

706142136345cccc32e7fa6e3ad038c6.png

参考资料

[1]https://thomasvilhena.com/2020/04/on-the-architecture-for-unit-testing
[2]https://github.com/golang/mock
[3]https://godoc.org/database/sql/driver
[4]https://github.com/golang/go/wiki/TableDrivenTests
[5]https://travis-ci.org/
[6]https://help.aliyun.com/document_detail/64021.html

来源:阿里云开发者社区

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

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

相关文章

节能prru进入深度休眠节能_2021-2024年中国工业节能行业深度调研及投资前景预测报告...

近年来,随着供给侧结构性改革不断深化,工业经济发展势头良好,转型升级步伐进一步加快,生产稳定增长、结构持续优化、效益明显提高、新动能加速成长。随着中国工业节能的政策推力逐渐加大,工业节能市场迅速发展&#xf…

多布局怎么搭建_关键词SEO优化怎么做?具体包括哪些方面?

在互联网的活动中,网站优化是企业们提升网站排名的常用方法之一,这也是进行线上获客的重要手段,超过75%的中小企业都非常认可这种营销方式。但是,想要做好关键词的优化,网站关键词定位、关键词布局、关键词密度等等都是…

使用特征_R语言-使用caret包实现特征选择:递归特征消除(RFE)算法

在caret(short for classification and regression training)包中有多个函数可以实现特征选择,总的分为封装法和过滤法。封装法,将特征选择过程与训练过程融合在一起,以模型的预测能力作为特征选择的衡量标准。封装法可…

7 centos 查看程序文件数量_「动手打造家庭媒体网络平台」安装篇-centos搭建DLNA媒体服务...

大家好,我是路程lucky,热爱开发、设计、学习、生活、爱捣鼓的web前后端工程师~本文的重点是在之前文章提到创建的centos基础上继续搭建DLNA媒体服务。由于centos的系统是从零开始初步安装完成,我们在安装媒体服务时,不可避免出现各…

python 逻辑回归准确率是1_python数据分析(三)——逻辑回归之学生成绩预测

Python数据分析项目——学生成绩预测一.数据源阿里云天池公开数据集:学生成绩预测数据集https://tianchi.aliyun.com/dataset/dataDetail?dataId6785特征(Features)介绍:Gender: 性别Nationality: 国籍PlaceofBirth:出生地StageI…

向量数量积公式_多法 | 向量数量积相关最值问题的常见处理方法

近日QQ群更新的部分内容如下高中11大类86个易错点全梳理(185页Word)恒成立涉及10大类40小类题型梳理(100页Word)高中数学10大专题100个考点配例题全梳理2020高考真题分类汇编理科(14讲Word)2019年江苏高考数学题根系列(50份Word)2020届江苏高三上期中期末分类汇编(12讲Word)202…

kettle增加字段报错_【实战】使用 Kettle 工具将 mysql 数据增量导入到 MongoDB 中

每一个成功人士的背后,必定曾经做出过勇敢而又孤独的决定。放弃不难,但坚持很酷~最近有一个将 mysql 数据导入到 MongoDB 中的需求,打算使用 Kettle 工具实现。本文章记录了数据导入从 0 到 1 的过程,最终实现了每秒钟快速导入约 …

增量同步_使用Kettle工具进行增量数据同步

增量同步的方式有很多种,我使用的是: 快照表 触发器需求:当主库库表发生增删改时,从库库表与主库库表数据保持一致。环境:1、Mysql2、kettle 7.1思路:1、在主库中,将需要同步的库表新建快照表,…

mysql in优化_MySQL的一次优化记录 (IN子查询和索引优化)

这两天实习项目遇到一个网页加载巨慢的问题(10多秒),然后定位到是一个MySQL查询特别慢的语句引起的:SELECT *FROM (SELECT DISTINCT t.vc_date, t.c_bankno, t.vc_bankacco, t.vc_moneytype, t.en_totalbala, t.en_usablebala, t1.vc_nameinbank, date_f…

mysql sqlite转换_数据库转换工具(SqliteToMysql)

SqliteToMysql是一款用于SQLITE和MYSQL之间的数据库转换工具。它能够将SQLITE数据转换成MYSQL数据库,让用户自主配置转换条件,满足用户的数据库格式需要。。相关软件软件大小版本说明下载地址SqliteToMysql是一款用于SQLITE和MYSQL之间的数据库转换工具。…

mysql 查看集群状态_MySQL数据库集群正确配置步骤

类型:电子教程大小:8.5M语言:中文 评分:8.3标签:立即下载对MySQL数据库集群进行正确配置的实际操作步骤,以及对其概念的讲述,如果你对其相关的实际操作有兴趣了解的话,以下的文章将会…

oschina mysql limit_MySQL 用 limit 为什么会影响性能?

点击上方“武培轩”,选择“设为星标”技术文章第一时间送达!一,前言首先说明一下MySQL的版本:mysql> select version();-----------| version() |-----------| 5.7.17 |-----------1 row in set (0.00 sec)表结构:m…

在mysql中建立聚簇索引_给我一分钟,让你彻底明白MySQL聚簇索引和非聚簇索引...

推荐阅读:吊打面试官!MySQL灵魂100问,你能答出多少?MySQL的InnoDB索引数据结构是B树,主键索引叶子节点的值存储的就是MySQL的数据行,普通索引的叶子节点的值存储的是主键值,这是了解聚簇索引和非…

根据从日期控件选定的时间以表格形式显示数据_VB项目开发FlexGrid控件使用讲解...

FlexGrid控件使用介绍大家好,在VB开发管理系统中,FlexGrid控件使用是非常普遍的。用FlexGrid ActiveX控件可以在 Visual Basic的窗体中创建一个电子数据表格,也可称之为网格。FlexGrid ActiveX控件可以在网格中显示任何类型的表格式数据&…

mysql爆内存_线上MySQL数据库机器内存爆掉原因分析与解决

本文主要向大家介绍了线上MySQL数据库机器内存爆掉原因分析与解决,通过具体的内容向大家展现,希望对大家学习MySQL数据库有所帮助。现象:阿里金融某业务的MySQL机器的内存每隔几天就会增长,涨上去后,却不下来。累积后内…

mysql atlas更新问题_Atlas几种常见故障解决(不定期更新)

1)使用atlas却发现“读库闲置,框架还是去主库读写数据”配置完atlas之后,发现使用jdbc框架的话,读库和写库各司其职,但是使用mybatis框架之后,就发现框架的读写都去了主库,把读库放置一边,那么这…

如何更改mysql服务名_技术小百科 |【云小课】数据复制服务如何实现对象名映射...

数据复制服务(Data Replication Service,简称DRS)是一种易用、稳定、高效、用于数据库在线迁移和数据库实时同步的云服务。数据复制服务提供了在线迁移、备份迁移、数据同步、数据订阅和多活灾备等多种功能。数据复制服务支持哪些对象名映射数据复制服务的数据同步功…

python 二维强度图_荐 python数据分析matplotlib库使用之二维图形绘制

本篇内容会在后期不定时更新什么是matplotlibmatplotlib是最流行的python底层绘图库,主要做数据可视化图表。为什么要学习matplotlib能将数据进行可视化,更直观的呈现使数据更加客观,更具有说服力二维图绘制matplotlib库的基本使用之折线图导…

python数据爬虫代码_python如何示例爬虫代码

python爬虫代码示例的方法:首先获取浏览器信息,并使用urlencode生成post数据;然后安装pymysql,并存储数据到MySQL即可。python爬虫代码示例的方法:1、urllib和BeautifuSoup获取浏览器信息from urllib import requestre…

kali linux查看网卡_CentOS7.6安装无线网卡驱动|Linux如何安装网卡驱动|Linux如何让配置网卡...

此前提到,Thinkpad E490安装CentOS7.6遇到内核崩溃的问题,解决之后,安装CentOS7.6操作系统成功。安装时发现,系统能够检测到有线网卡,但无法检测到无线网卡,说明CentOS7.6对此无线网卡的支持不足&#xff0…