聊一聊go的单元测试

文章目录

    • 概要
    • 一、测试框架
        • 1.1、testing
        • 1.2、stretchr/testify
        • 1.3、smartystreets/goconvey
        • 1.4、cweill/gotests
    • 二、打桩和mock
        • 2.1、打桩
        • 2.2、mock
            • 2.2.1、mockgen
    • 三、基准测试和模糊测试
        • 3.1、基准测试
        • 3.2、模糊测试
    • 四、总结
        • 4.1、小结
        • 4.2、其他
        • 4.3、参考资料

概要

软件测试是一个很宏大的概念,单元测试、集成测试、系统测试、黑盒测试、白盒测试、功能测试、性能测试、基准测试、压力测试等等都是软件测试下面的一种子概念。作为一名开发者,我们并不要理解每一种测试概念,但基准测试和单元测试在软件编写过程中还是必须的,特别是单元测试。

单元测试是指对软件中的最小可测试单元进行检查和验证。至于单元测试的范围并没有一个明确定义,可以是一个函数、方法、类、功能模块或子系统,但在编写时单元测试内容往往是函数或方法。

我们之所以要做单元测试,主要是因为几点:

  1. 满足可测试性的代码在设计上大概率是良好的(比如函数功能不会包罗万象,导致一个函数上百行代码的地狱场景),从而以较低的成本驱动开发者实现软件编写的高质量;
  2. 保证软件在迭代过程中质量的稳定性,即一个函数进行优化或功能变化,单元测试可以保证变更后问题及时发现及时解决;
  3. 有利于后续集成测试、系统测试的稳定推进,想一想开发者把一个不充分自测的软件交付到测试人员手上,是多么可怕的事情,问题进行合理的左移是非常有必要的。

一、测试框架

要想快速的写出优雅的单元测试就必须要了解Go语言相关的框架,以下是说明:

框架简介
testing内置测试库,用于单元测试、基准测试、模糊测试
cweill/gotests表驱动的测试方式,支持基于模板生成单测,在goland,vs code中都有集成,可以直接使用,提高了单测书写效率
stretchr/testify具体断言机制( testify/require 和 testify/assert),大大简化单元测试的写法,可以替代 t.Fatalf 和 t.Errorf,而且代码实现更为简短、优雅
smartystreets/goconvey提供了丰富的断言机制,相比stretchr/testify,可以使单测看起来更加优雅,并支持断言嵌套

以一个下面的函数对上述框架使用进行说明

func div(a, b int) int {return a / b
}
func add(a, b int) int {return a + b
}
1.1、testing
func TestDiv(t *testing.T) {res := div(10, 2)if res != 1 {t.Error("test div is err")}
}
[root@pigfu ~]# go test -v -run TestDiv
=== RUN   TestDivhelper_test.go:33: test div is err
--- FAIL: TestDiv (0.00s)
FAIL
exit status 1
FAIL    app/test        0.348s
1.2、stretchr/testify
func TestDiv(t *testing.T) {res := div(10, 2)require.Equal(t, 1, res, "test div")
}
[root@pigfu ~]# go test -v -run TestDiv
=== RUN   TestDivhelper_test.go:38:                                                   Error Trace:    F:/WWW/GO/ThinkGin/test/helper_test.go:38Error:          Not equal:                               expected: 1                              actual  : 5                              Test:           TestDiv                                 Messages:       test div is err                                 
--- FAIL: TestDiv (0.00s)                                               
FAIL                                                                     
exit status 1
FAIL    app/test        0.566s

可以看到,编码上更简洁了,友好得写出if xxx { t.Error() } 并且输出更加优美。

ps:注意assert与require区别:require在测试失败以后,所有测试都会停止执行,assert不会

1.3、smartystreets/goconvey
func TestDiv(t *testing.T) {res := div(10, 2)Convey("test div", t, func() {So(res, ShouldEqual, 1)})
}
[root@pigfu ~]# go test -v -run TestDiv
=== RUN   TestDivtest div xFailures:* F:/WWW/GO/ThinkGin/test/helper_test.goLine 44:Expected: '1'Actual:   '5'(Should be equal)1 total assertion--- FAIL: TestDiv (0.00s)
FAIL
exit status 1
FAIL    app/test        0.233s

输出结果相比stretchr/testify更加优美,并且支持断言嵌套。

func TestAll(t *testing.T) {Convey("test div", t, func() {So(div(10, 2), ShouldEqual, 5)Convey("test add", func() {So(add(5, 6), ShouldEqual, 11)})})
}

ps:注意同一个根Convey在第一个断言失败后,其他的测试都会停止执行

func TestAll(t *testing.T) {Convey("test div", t, func() {So(div(10, 2), ShouldEqual, 1)So(add(5, 6), ShouldEqual, 11)Convey("test add", func() {So(add(5, 6), ShouldEqual, 11)})})
}
[root@pigfu ~]# go test -v -run TestAll
=== RUN   TestAlltest div xFailures:* F:/WWW/GO/ThinkGin/test/helper_test.goLine 49:Expected: '1'Actual:   '5'(Should be equal)1 total assertion--- FAIL: TestAll (0.00s)
FAIL
exit status 1
FAIL    app/test        0.293s

可以看到test add并没有被执行,可以采用如下方式避免

func TestAll(t *testing.T) {Convey("test div", t, func() {So(div(10, 2), ShouldEqual, 1)})Convey("test add", t, func() {So(add(5, 6), ShouldEqual, 11)})
}
1.4、cweill/gotests

这个工具可以与前三个做配合,自动生成表驱动的测试代码,其太goland中是有被集成的,鼠标点一下就可以了,非常方便,当然了,也可以命令行执行 gotests -h
goland使用如下:
如下图,鼠标右键点击要单测的函数,可以看到三个选项,依次是只测试这个方法、测试整个文件中方法、测试整个包中的方法,一般选择测试function
goland test
生成代码如下:

func Test_add(t *testing.T) {type args struct {a intb int}tests := []struct {name stringargs argswant int}{// TODO: Add test cases.}for _, tt := range tests {t.Run(tt.name, func(t *testing.T) {if got := add(tt.args.a, tt.args.b); got != tt.want {t.Errorf("add() = %v, want %v", got, tt.want)}})}
}

可以看到只需要填充测试数据即可。默认是基于内置测试库生成的,我们也可以基于stretchr/testify或smartystreets/goconvey定义自己的模板。

命令行如下:

[root@pigfu ~]# gotests -h
Usage of D:\gopath\bin\gotests.exe:-all  #对指定文件中的函数和方法都生成测试代码generate tests for all functions and methods-excl string        #要排除的函数或方法regexp. generate tests for functions and methods that don't match. Takes precedence over -only, -exported, and -all-exported #为导出的函数和方法生成go测试,优先级高于 -only和-allgenerate tests for exported functions and methods. Takes precedence over -only and -all-i    print test inputs in error messages #在错误消息中打印测试输入-nosubtests #禁用子测试生成。仅适用于Go 1.7+disable generating tests using the Go 1.7 subtests feature-only string  #指定要单测的函数或方法regexp. generate tests for functions and methods that match only. Takes precedence over -all-template_dir string    #指定模板目录optional. Path to a directory containing custom test code templates-w    write output to (test) files instead of stdout   #指定生成代码的文件,默认stdout

例如:gotests -only div -w herper_test.go helper.go,其他指令自行探索

二、打桩和mock

我们在编写单元测试的时候,如果有第三方依赖怎么办?比如当创建订单的时候,需要写数据库。为了解决这种场景,可以使用打桩或mock的方式,其本质就是能指定依赖方的输入输出,可以理解为提前插入的固定数据,如此,流程就能正常跑起来。

主要试用场景如下:

  • 依赖的服务返回不确定的结果,如获取当前时间;
  • 依赖的服务返回状态中有的难以重建或复现,比如模拟网络错误;
  • 依赖的服务搭建环境代价高,速度慢,需要一定的成本,比如数据库,web服务,RPC服务;
  • 依赖的服务行为多变。
2.1、打桩

打桩简单地来说就是对一些代码片段(全局变量,函数,方法)进行替换。
这种方式主要有两个库prashantv/gostub和agiledragon/gomonkey。前者并不友好,详见本文,这里主要以后者举例:

const DateLayout = "2006-01-02"func StartToday() int64 {date := time.Now().Format(DateLayout)t, _ := time.ParseInLocation(DateLayout, date, time.Local)return t.UnixMilli()
}
func TestStartToday(t *testing.T) {patch := gomonkey.ApplyFunc(time.Now, func() time.Time {return time.Date(2023, 12, 20, 20, 32, 11, 0, time.Local)})//替换time.Now函数defer patch.Reset()//结束后重置time.Now函数Convey("StartToday", t, func() {So(StartToday(), ShouldEqual, 1703001600000)})
}
[root@pigfu ~]# go test -v -run TestStartToday
=== RUN   TestStartTodaystart .1 total assertion--- PASS: TestStartToday (0.03s)
PASS
ok      app/test        0.369s

可以看到 time.Now函数被替换成了 func() time.Time {return time.Date(2023, 12, 20, 20, 32, 11, 0, time.Local)}函数,时间被固定下来了,我们就可以得心应手的写单元测试代码了。

除了ApplyFunc,还有ApplyMethod、ApplyGlobalVar、ApplyFuncSeq等接口,可以自行探索。

2.2、mock

mock通过替换接口来实现对强依赖的处理。
这种方式主要有两个库vektra/mockery和golang/mock。前者是基于stretchr/testify/mock实现的,本文不做过多描述,详见本文。后者是Golang 官方开发维护的测试框架,实现了较为完整的基于 interface 的 Mock 功能,能够与Golang 内置的 testing 包良好集成,也能用于其它的测试环境中。GoMock 测试框架包含了 gomock包和mockgen 工具两部分,其中 gomock 包完成对桩对象生命周期的管理,mockgen 工具用来基于定义的 interface 生成对应的 Mock 类源文件,用来辅助生成测试代码。

二者对比有:

  • stretchr/testify/mock 对应 golang/mock/gomock;
  • vektra/mockery 对应 golang/mock/mockgen 。
2.2.1、mockgen

mockgen 有两种操作模式:源文件模式和反射模式。其命令如下

mockgen 工具支持的选项如下:
-source:指定接口的源文件。-destinatio:mock类代码的输出文件,如果没有设置本选项,代码将被输出到标准输出。-destination选项输入太长,因此推荐使用重定向符号>将输出到标准输出的内容重定向到某个文件,并且mock类代码的输出文件的路径必须是绝对路径。-packag:指定 mock 类源文件的包名,如果没有设置本选项,则包名由 mock_ 和输入文件的包名级联而成。-aux_fi:附加文件列表用于解析嵌套定义在不同文件中的 interface,指定元素列表以逗号分隔,元素形式为foo=bar/baz.go,其中bar/baz.go是源文件,foo是-source选项指定的源文件用到的包名。-build_flags:传递给 build 工具的参数。-imports:依赖的需要 import 的包,在生成的源代码中应该使用的一个显式导入列表,指定为一个以逗号分隔的元素列表,形式为foo=bar/baz,其中bar/baz是被导入的包,foo是生成的源代码中包使用的标识符。-mock_names:自定义生成 mock 文件的列表,使用逗号分割。如 IPay=MockPay,IMan=MockMan。IPay、IMan为接口,MockPay,MockMan为相应的 mock结构体名称 。-self_package:生成代码的完整包导入路径,这个标志的目的是通过尝试包含它自己的包来防止生成代码中的导入死循环。如果 mock 的包被设置为它的一个输入(通常是主输入),并且输出是 stdio,因此 mockgen 无法检测到最终的输出包,就会发生这种情况,设置这个标志将告诉 mockgen 要排除哪个导入。-copyright_file:用于向生成的源代码中添加版权头的版权文件。-debug_parser:只打印解析器结果。-exec_only:(反射模式)如果设置,执行反射程序。-prog_only:(反射模式)只生成反射程序,将其写入stdout并退出。-write_package_comment:如果为true,编写包文档注释(godoc),默认为true。

示例(假设有一个支付接口,依赖第三方的http服务):

type IPay interface {Pay(id string, money float64) errorRefund(id string, money float64) errorQueryPayResult(id string) (float64, error)
}type WxPay struct {Url       stringAppKey    stringAppSecret string
}func (pay *WxPay) sign() string {//签名 sign(AppKey,AppSecret)return "sign result"
}
func (pay *WxPay) Pay(id string, money float64) error {//简单的示例代码,着重强调走了http,依赖第三方服务b, err := json.Marshal(map[string]any{"opr": "pay", "id": id, "money": money})if err != nil {return err}rsp, err := http.Post(pay.Url, "application/json", bytes.NewReader(b))defer func() {_ = rsp.Body.Close()}()return err
}
func (pay *WxPay) Refund(id string, money float64) error {//简单的示例代码,着重强调走了http,依赖第三方服务b, err := json.Marshal(map[string]any{"opr": "refund", "id": id, "money": money})if err != nil {return err}rsp, err := http.Post(pay.Url, "application/json", bytes.NewReader(b))defer func() {_ = rsp.Body.Close()}()return err
}
func (pay *WxPay) QueryPayResult(id string) (float64, error) {//简单的示例代码,着重强调走了http,依赖第三方服务b, err := json.Marshal(map[string]any{"opr": "query_pay_result", "id": id})if err != nil {return 0, err}rsp, err := http.Post(pay.Url, "application/json", bytes.NewReader(b))if err != nil {return 0, err}defer func() {_ = rsp.Body.Close()}()body, err := io.ReadAll(rsp.Body)if err != nil {return 0, err}rspMap := make(map[string]any)err = json.Unmarshal(body, &rspMap)if err != nil {return 0, err}return rspMap["money"].(float64), err
}func GetPayResult(id string, pay IPay) (float64, error) {//业务代码...money, err := pay.QueryPayResult(id)//业务代码...return money, err
}

现在我们要对GetPayResult函数写单元测试,可以这样做:
使用mockgen生成一个对IPay接口mock的结构体,mockgen -destination=./mocks/mock_pay.go -package=mocks -source mock.go -mock_names IPay=MockPay
./mocks/mock_pay.go文件内容如下:

// Code generated by MockGen. DO NOT EDIT.
// Source: mock.go// Package mocks is a generated GoMock package.
package mocksimport (reflect "reflect"gomock "github.com/golang/mock/gomock"
)
// MockPay is a mock of IPay interface.
type MockPay struct {ctrl     *gomock.Controllerrecorder *MockPayMockRecorder
}// MockPayMockRecorder is the mock recorder for MockPay.
type MockPayMockRecorder struct {mock *MockPay
}// NewMockPay creates a new mock instance.
func NewMockPay(ctrl *gomock.Controller) *MockPay {mock := &MockPay{ctrl: ctrl}mock.recorder = &MockPayMockRecorder{mock}return mock
}// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockPay) EXPECT() *MockPayMockRecorder {return m.recorder
}// Pay mocks base method.
func (m *MockPay) Pay(id string, money float64) error {m.ctrl.T.Helper()ret := m.ctrl.Call(m, "Pay", id, money)ret0, _ := ret[0].(error)return ret0
}// Pay indicates an expected call of Pay.
func (mr *MockPayMockRecorder) Pay(id, money interface{}) *gomock.Call {mr.mock.ctrl.T.Helper()return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Pay", reflect.TypeOf((*MockPay)(nil).Pay), id, money)
}// QueryPayResult mocks base method.
func (m *MockPay) QueryPayResult(id string) (float64, error) {m.ctrl.T.Helper()ret := m.ctrl.Call(m, "QueryPayResult", id)ret0, _ := ret[0].(float64)ret1, _ := ret[1].(error)return ret0, ret1
}// QueryPayResult indicates an expected call of QueryPayResult.
func (mr *MockPayMockRecorder) QueryPayResult(id interface{}) *gomock.Call {mr.mock.ctrl.T.Helper()return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueryPayResult", reflect.TypeOf((*MockPay)(nil).QueryPayResult), id)
}// Refund mocks base method.
func (m *MockPay) Refund(id string, money float64) error {m.ctrl.T.Helper()ret := m.ctrl.Call(m, "Refund", id, money)ret0, _ := ret[0].(error)return ret0
}// Refund indicates an expected call of Refund.
func (mr *MockPayMockRecorder) Refund(id, money interface{}) *gomock.Call {mr.mock.ctrl.T.Helper()return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Refund", reflect.TypeOf((*MockPay)(nil).Refund), id, money)
}

现在就引入./mocks/mock_pay.go文件,就可以写单测了。

//go:generate go test -v -run TestMock2
func TestGetPayResult(t *testing.T) {mc := gomock.NewController(t)defer mc.Finish()entry := mocks.NewMockPay(mc)entry.EXPECT().QueryPayResult("123423454345").Return(100.01, nil)Convey("start", t, func() {money, _ := GetPayResult("123423454345", entry)So(money, ShouldEqual, 100.11)})
}

三、基准测试和模糊测试

go test 命令如下:

-convey-json                                                                                                                         When true, emits results in JSON blocks. Default: 'false'                                                                      -convey-silent                                                                                                                       When true, all output from GoConvey is suppressed.                                                                             -convey-story                                                                                                                        When true, emits story output, otherwise emits dot output. When not provided, this flag mirrors the value of the '-test.v' flag-test.bench regexp    run only benchmarks matching regexp                                                                                            -test.benchmem  print memory allocations for benchmarks-test.benchtime d  #基准测试多久(3s)或多少次(300x)run each benchmark for duration d (default 1s)-test.blockprofile filewrite a goroutine blocking profile to file-test.blockprofilerate rateset blocking profile rate (see runtime.SetBlockProfileRate) (default 1)-test.count n  #基准测试多少轮run tests and benchmarks n times (default 1)-test.coverprofile filewrite a coverage profile to file-test.cpu listcomma-separated list of cpu counts to run each test with-test.cpuprofile filewrite a cpu profile to file-test.failfastdo not start new tests after the first test failure-test.fuzz regexprun the fuzz test matching regexp-test.fuzzcachedir stringdirectory where interesting fuzzing inputs are stored (for use only by cmd/go)-test.fuzzminimizetime valuetime to spend minimizing a value after finding a failing input (default 1m0s)-test.fuzztime valuetime to spend fuzzing; default is to run indefinitely-test.fuzzworkercoordinate with the parent process to fuzz random values (for use only by cmd/go)-test.gocoverdir stringwrite coverage intermediate files to this directory-test.list regexplist tests, examples, and benchmarks matching regexp then exit-test.memprofile filewrite an allocation profile to file-test.memprofilerate rateset memory allocation profiling rate (see runtime.MemProfileRate)-test.mutexprofile stringrun at most n tests in parallel (default 4)-test.run regexprun only tests and examples matching regexp-test.shortrun smaller test suite to save time-test.shuffle stringrandomize the execution order of tests and benchmarks (default "off")-test.skip regexpdo not list or run tests matching regexp-test.testlogfile filewrite test action log to file (for use only by cmd/go)-test.timeout dpanic test binary after duration d (default 0, timeout disabled)-test.trace filewrite an execution trace to file-test.v    #列出详情verbose: print additional output
3.1、基准测试

基准测试我们常常来测试一个函数的时间,CPU,内存的使用,来衡量该函数性能是否满足要求

func BinarySearch(n int, f func(int) int) int {i, j := 0, n-1for i <= j {m := int(uint(i+j) >> 1)flag := f(m)if flag == 0 {return m}if flag < 0 {i = m + 1} else {j = m - 1}}return -1
}
func BenchmarkBinarySearch(b *testing.B) {data := []struct {Key string//...}{{Key: "key1"}, {Key: "key2"}, {Key: "key"}, {Key: "key4"}, {Key: "key5"}, {Key: "key6"}, {Key: "key7"}, {Key: "key8"}, {Key: "key9"}, {Key: "key10"},{Key: "key11"}, {Key: "key12"}, {Key: "key13"}, {Key: "key14"}, {Key: "key15"}}for i := 0; i < b.N; i++ {BinarySearch(len(data), func(i int) int {return strings.Compare("key", data[i].Key)})}
}
[root@pigfu ~]# go test -v -bench=BenchmarkBinarySearch -run=none -benchtime=200x #运行200次
goos: linux
goarch: amd64
pkg: app/test
cpu: Intel(R) Core(TM) i5-7500 CPU @ 3.40GHz
BenchmarkBinarySearch
BenchmarkBinarySearch-4              200               137.5 ns/op
PASS
ok      app/test        0.246s
3.2、模糊测试

模糊测试在Go1.18彻底加入go的testing库中,之前是一个github项目dvyukov/go-fuzz。

func FuzzDiv(f *testing.F) {testcases := []struct {a, b int}{{10, 2},{5, 3},{-6, 3},{-6, -3},}for _, v := range testcases {f.Add(v.a, v.b)}f.Fuzz(func(t *testing.T, a, b int) {fmt.Println(a, b)div(a, b)})
}

或者没有预置参数,这种情况下,不指定时间会无休止的运行,直至panic,比如当b=0的时候。

func FuzzDiv(f *testing.F) {f.Fuzz(func(t *testing.T, a, b int) {div(a, b)})
}

go test -v -fuzz=^FuzzDiv$ -fuzztime=5s

四、总结

4.1、小结

本文总结了Go语言常用的多种测试框架,它们在不同的场景具有不同的应用。个人认为无论什么时候都可以用Convey+So的组合优雅地实现测试用例嵌套和断言,而testify适合最基本的测试(少许测试用例)。gomonkey可以实现对全局变量,函数,方法的替换,gomock作为官方mock库,可以对接口进行很好的替换。因此推荐goconvey、goconvey+gomonkey、goconvey+gomock、goconvey+gomonkey+gomock,这四种组合基本可以覆盖99%的单测场景。

4.2、其他

DATA-DOG/go-sqlmock实现了database/sql/driver,帮助我们做到无数据库测试,符合 TDD 工作流。所有基于 go 标准库database/sql/driver的 orm 框架也都支持,比如 gorm。
alicebob/miniredis是一个实现 Redis Server 的包,专门用于 Go 的单元测试,目前支持 Redis6 的几乎所有开发会用到的命令。
golang官方库net/http/httptest可以解决http的三方依赖。
GRPC 生成的 client stub 都是 Interface,所以可以使用gomock来解决对其的依赖。

4.3、参考资料

1]:Go测试库
2]:Go单元测试:stub与mock
3]:mockery v2的介绍和使用
4]:GoMock框架使用指南
5]:Go语言工具包之gomock

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

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

相关文章

前端入门(二)Vue2到Vue3

文章目录 Vue简介Vue的特点Hello, Vue Vue基本语法模板语法数据绑定&#xff08;v-bind、v-model&#xff09;el与data的两种写法 数据代理实现原理Object.defineProperty()数据代理 事件处理&#xff08;v-on:click / click&#xff09;事件修饰符键盘事件&#xff08;略&…

万户OA upload任意文件上传漏洞复现

0x01 产品简介 万户OA ezoffice是万户网络协同办公产品多年来一直将主要精力致力于中高端市场的一款OA协同办公软件产品&#xff0c;统一的基础管理平台&#xff0c;实现用户数据统一管理、权限统一分配、身份统一认证。统一规划门户网站群和协同办公平台&#xff0c;将外网信息…

OSG文字-显示汉字 (1)

OSG文字 适当的文字信息对于显示场景信息是非常重要的。在 OSG中&#xff0c;osgText 提供了向场景中添加文字的强大功能&#xff0c;由于有第三方插件 FreeType 的支持&#xff0c;它完全支持TrueType字体。 读者可能对 FreeType和TrueType还不太了解&#xff0c;下面进行具体…

在windows Server安装Let‘s Encrypt的SSL证书

1、到官网&#xff08;https://certbot.eff.org/instructions?wswebproduct&oswindows&#xff09;下载 certbot客户端。 2、安装客户端&#xff08;全部默认安装即可&#xff09; 3、暂停IIS中的网站 开始菜单中找到并运行“Certbot”&#xff0c;输入指令&#xff1a; …

新一代车载以太网传输技术研讨会(AEM)顺利圆满举行

时间定格在2023年11月17日&#xff0c;新一代车载以太网传输技术研讨会在东莞国际会展中心举行。来自相关的的企业几百家。当然&#xff0c;深圳维信仪器作为主办方&#xff08;AEM线束测试仪中国区总平台&#xff09;举优质的线束测试设备&#xff0c;不论是手持线束测试&…

苹果(Apple)公司的新产品开发流程(一)

目录 简介 ANPP CSDN学院推荐 作者简介 简介 苹果这家企业给人的长期印象就是颠覆和创新。 而流程跟创新似乎是完全不搭边的两个平行线&#xff1a; 流程是一个做事的标准&#xff0c;定义了权力的边界&#xff0c;对应人员按章办事&#xff1b;而创新的主旋律是发散&am…

实时监控电脑屏幕的软件丨同时查看12台电脑屏幕

Hello 大家好 又见面啦 今天给大家推荐两款比较实用的监控电脑使用情况、屏幕的软件&#xff01; 软件一 实时性能监控 从软件名就可以看出来&#xff0c;这是一款电脑性能监测工具。它可以实时监测内存、CPU、磁盘占用情况&#xff0c;也能一键结束进程&#xff0c;给电脑提…

【zabbix监控三】zabbix之部署代理服务器

一、部署代理服务器 分布式监控的作用&#xff1a; 分担server的几种压力解决多机房之间的网络延时问题 1、搭建proxy主机 1.1 关闭防火墙&#xff0c;修改主机名 systemctl disbale --now firewalld setenforce 0 hostnamectl set-hostname zbx-proxy su1.2 设置zabbix下…

BP神经网络原理与如何实现BP神经网络

本文部分图文来自《老饼讲解-BP神经网络》bp.bbbdata.com 目录 一、BP神经网络的背景生物学原理 二、BP神经网络模型 2.1 BP神经网络的结构 2.2 BP神经网络的激活函数 三、BP神经网络的误差函数 四、BP神经网络的训练 4.1 BP神经网络的训练流程 4.2 BP神经网络的训练流…

【运维篇】5.6 Redis server 主从复制配置

文章目录 0. 前言1. 配置方式步骤1: 准备硬件和网络步骤2: 安装Redis步骤3: 配置主服务器的Redis步骤4: 配置从服务器的Redis步骤5: 测试复制功能步骤6: 监控复制状态 2. 参考文档 0. 前言 在Redis运维篇的第5.6章节中&#xff0c;将讨论Redis服务器的主从复制配置。在开始之前…

如何实现MATLAB与Simulink的数据交互

参考链接&#xff1a;如何实现MATLAB与Simulink的数据交互 MATLAB是一款强大的数学计算软件&#xff0c;Simulink则是一种基于模型的多域仿真平台&#xff0c;常用于工程和科学领域中的系统设计、控制设计和信号处理等方面。MATLAB和Simulink都是MathWorks公司的产品&#xff0…

数据结构【DS】图的应用

图的连通性问题 最少边数 最多边数 无向图非连通 &#x1d48e;&#x1d7ce; &#x1d48e;&#x1d48f;−&#x1d7d0;∗(&#x1d48f;−&#x1d7cf;)/&#x1d7d0; 无向图连通 &#x1d48e;&#x1d48f;−&#x1d7cf; &#x1d48e;&#x1d48f;∗(&#…

在Sprinng Boot中使用Redis充当缓存

关于我们使用EhCache可以适应很多的应用场景了&#xff0c;但是因为EhCache是进程内的缓存框架&#xff0c;在集群模式下&#xff0c;我们在我们的应用服务器或者云服务器之间的缓存都是独立的。故而在不同的服务器之间的进程会存在缓存不一致的情况&#xff0c;就算我们的EhCa…

word批量图片导出wps office word 图片批量导出

word批量导出图片教程 背景 今天遇到了一个场景&#xff0c;因为word里的图片打开看太模糊了&#xff0c;如果一个一个导出来太麻烦。想批量将word中的图片全部导出 但是&#xff0c;wps导出的时候需要会员 教程开始&#xff1a; 将word保存为 .docx 格式&#xff0c;可以按F1…

Golang 协程、主线程

Go协程、Go主线程 原先的程序没有并发和并行的概念&#xff0c;没有多核的概念&#xff0c;就是一个进程打天下。后面发现这个效率太低了&#xff0c;就搞出了线程&#xff0c;这样极大的发挥CPU的效率&#xff0c;因为硬件总是比软件发展的快。 现在go考虑的是能不能让多核cp…

【数据结构】C语言实现队列

目录 前言 1. 队列 1.1 队列的概念 1.2 队列的结构 2. 队列的实现 2.1 队列的定义 2.2 队列的初始化 2.3 入队 2.4 出队 2.5 获取队头元素 2.6 获取队尾元素 2.7 判断空队列 2.8 队列的销毁 3. 队列完整源码 Queue.h Queue.c &#x1f388;个人主页&#xff1a…

【数据结构】树与二叉树(廿一):树和森林的遍历——先根遍历(递归算法PreOrder、非递归算法NPO)

文章目录 5.1 树的基本概念5.1.1 树的定义5.1.2 森林的定义5.1.3 树的术语 5.2 二叉树5.3 树5.3.1 树的存储结构1. 理论基础2. 典型实例3. Father链接结构4. 儿子链表链接结构5. 左儿子右兄弟链接结构 5.3.2 获取结点的算法5.3.3 树和森林的遍历1. 先根遍历&#xff08;递归&am…

<b><strong>,<i><em>标签的区别

1. b标签和strong标签 b标签&#xff1a;仅仅是UI层面的加粗样式&#xff0c;并不具备HTML语义 strong标签&#xff1a;不仅是在UI层面的加粗样式&#xff0c;具备HTML语义&#xff0c;表示强调 2. i标签和em标签 i 标签&#xff1a;仅仅是UI层面的斜体样式&#xff0c;并不…

HTML5学习系列之实用性标记

HTML5学习系列之实用性标记 前言实用性标记高亮显示进度刻度时间联系信息显示方向换行断点标注 总结 前言 学习记录 实用性标记 高亮显示 mark元素可以进行高亮显示。 <p><mark>我感冒了</mark></p>进度 progress指示某项任务的完成进度。 <p…

(c语言进阶)内存函数

一.memcpy(void* dest,void* src,int num) &#xff0c;操作单位为字节&#xff0c;完成复制且粘贴字符串 1.应用 #include <stdio.h> #include<string.h> int main() {int arr1[] { 1,2,3,4,5,6,7,8,9,10 };int arr2[20] { 0 };memcpy(arr2, arr1, 20);//从…