手把手教你写go单元测试

这篇具有很好参考价值的文章主要介绍了手把手教你写go单元测试。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

什么是单元测试

​ 在 Go 语言中,单元测试是一种测试方法,用于验证代码的某个独立单元是否按预期功能,它的目的是确保代码的每个组成部分都在独立测试的情况下运行正常。

​ 在我们对项目新增一个新功能时,最好就要养成写单元测试的好习惯,这样可以有助于提高我们代码的质量、可维护性和可靠性。

​ 在 Go 中,单元测试的约定是使用标准库中的 testing 包。测试文件通常以 _test.go 为后缀,然后我们使用 go test ... 配合一些参数去进行测试,Go 测试工具会自动识别并运行这些文件中那点测试样例。

go test 的两种模式
1. 本地模式:执行当前目录下的所有测试用例

​ go test

2. 列表模式:输入一个或多个目录,执行这些目录下的测试用例

​ go test xx/xx

怎么写单元测试

​ 首先,要写单元测试,那么肯定需要一个功能函数。这里我们借用一下之前文章内存缓存系统中使用到的一个功能函数 ParseSize ,它的功能是将用户的输入内存大小,转换为字节数和对应的字符串表示形式,其中还会涉及到一些输入不合法的处理,

​ 本文讲的是如何写单元测试,这里 ParseSize 的源码就直接给大家了,如下:

package util

import (
    "regexp"
    "strconv"
    "strings"
    "time"
)

const (
    B = 1 << (iota * 10)
    KB
    MB
    GB
    TB
    PB
)

const defaultNum = 100

func ParseSize(size string) (int64, string) {
    time.Sleep(time.Nanosecond * 500)

    re, _ := regexp.Compile("[0-9]+")
    unit := string(re.ReplaceAll([]byte(size), []byte("")))
    num, _ := strconv.ParseInt(strings.Replace(size, unit, "", 1), 10, 64)
    unit = strings.ToUpper(unit)

    var byteNum int64 = 0
    switch unit {
    case "B":
        byteNum = num
    case "KB":
        byteNum = num * KB
    case "MB":
        byteNum = num * MB
    case "GB":
        byteNum = num * GB
    case "TB":
        byteNum = num * TB
    case "PB":
        byteNum = num * PB
    default:
        num = 0
    }

    if num == 0 {
        num = 100
        byteNum = num * MB
        unit = "MB"
    }

    sizeStr := strconv.FormatInt(num, 10) + unit
    return byteNum, sizeStr
}

 在项目根目录下创建 util 目录,然后创建 util.go 文件,将上面的代码粘贴进去就行了。

​ 强调一点,上面的 ParseSize 函数的开头,我加了一个睡眠函数,是因为我们的 ParseSize 函数的处理逻辑比较简单,怕执行太快,进行测试时显示时间为 0 ,所以加了个睡眠延迟一点时间,模拟一些比较耗时功能函数。

准备工作

​ 同样,我们先在 util 包下创建 util_test.go 文件。在写单元测试的时候,我们通常有两种方法,一种是在测试函数里面构建匿名结构体来组织数据,另一种就是在提前构建数据。前者就是将构建数据的逻辑写在测试函数里,这里不多做介绍,我们要着重讲的是第二种。

​ 为了方便,我们先定义一个结构体,并将其实例化,用于存放我们的数据:

// 所有的测试用例放在这里头
var commTestData []commStruct

type commStruct struct {
    Group         string // 所属类别
    SizeStr       string // 输入大小
    ExpectSize    int64  // 预期输出大小
    ExpectSizeStr string // 预期输出大小字符串类型
}
  • Group:这个是用于子测试时分类的依据,关于子测试后面会提到,这里先不理会。
  • SizeStr:是对应于我们的 ParseSize 功能函数的输入
  • ExpectSize、ExpectSizeStr:对应于我们的 ParseSize 功能函数的输出

​ 在单元测试中,也有一个 func TestMain(m *testing.M)入口函数,功能和用法于平时我们使用的 main 类似。我们可以在这里面为单元测试做一些准备工作,但需要注意的是:如果我们没有写 TestMain 函数,那么测试工具会直接调用我们的测试函数,但如果我们写了 TestMain 函数,就需要在 TestMain 中通过 m.Run() 显示地调用测试用例:

// 测试用例的入口函数:可以为测试做一些准备工作
func TestMain(m *testing.M) {
    initCommonData()
    m.Run() // 执行测试用例
}

func initCommonData() {
    commTestData = []commStruct{
        {"B", "1b", B, "1B"},
        {"B", "100b", 100 * B, "100B"},
        {"KB", "1kb", KB, "1KB"},
        {"KB", "100KB", 100 * KB, "100KB"},
        {"MB", "1Mb", MB, "1MB"},
        {"GB", "10Gb", 10 * GB, "10GB"},
        {"TB", "1tb", TB, "1TB"},
        {"PB", "10PB", 10 * PB, "10PB"},
        {"unknown", "1G", 100 * MB, "100MB"},
    }
}

上面我们通过 TestMain 函数,提前构建好了测试所需要的数据,避免在不同的测试函数中重复构建测试用例。

功能测试

​ 功能测试是一种验证代码是否按照规范和需求进行工作的测试,它关注于测试单个函数或方法的功能是否正确,以确保其符合预期的行为。

​ 根据它的定义,我们就大概知道该怎么写我们的功能测试了。首先功能测试的函数签名是这样的 func TestFunctionName(t *testing.T)。我们直接在函数里面写逻辑即可,因为有很多组测试样例,所以我们肯定要用 for 循环将所有的样例拿出来,然后一一进行验证,验证的过程就是将该样例的输入拿出来执行一遍功能函数,然后将结果与我们的样例预期结果进行比对即可,如下:

// 功能测试
func TestParseSize(t *testing.T) {
    testData := commTestData
    for _, data := range testData {
        size, sizeStr := ParseSize(data.SizeStr)
        if size != data.ExpectSize || sizeStr != data.ExpectSizeStr {
            t.Errorf("测试结果不符合预期:%+v", data)
        }
    }
}

​ 这样我们就写好了一个具备基本功能的功能测试代码了。我们可以通过命令 go test -v 去执行,输出如下:

$  go test -v
=== RUN   TestParseSize
--- PASS: TestParseSize (0.14s)
PASS
ok      main/util       0.178s

​ 我们一起来看看这个输出:

  1. === RUN TestParseSize:表示正在运行名为 TestParseSize 的测试函数。
  2. --- PASS: TestParseSize (0.14s):表示测试函数 TestParseSize 成功通过,用时 0.14 秒。PASS 表示测试通过,FAIL 则表示测试失败。
  3. PASS:表示整个测试过程中没有发现错误,所有的测试函数都成功通过。
  4. ok main/util 0.178s:表示测试包 main/util 成功通过,总用时为 0.178 秒。

​ 下面我们再来看看功能测试的子测试。

​ 功能测试的子测试,又可以叫做并发测试,我们可以利用它来加快测试的效率。我们下面以测试样例中的单位,即 group 字段来将测试样例分个组:

testData := make(map[string][]commStruct)
for _, item := range commTestData {
    group := item.Group
    _, ok := testData[group]
    if !ok {
        testData[group] = make([]commStruct, 0)
    }
    testData[group] = append(testData[group], item)
}

​ 有了数据,其实我们的子测试,就相当于对不同组别分别去进行测试。

​ 所以首先要用一个 for 循环拿出不同组别的数据,去分别运行,然后在每个组别运行时,去拿出对应组别的数据去做验证即可,代码如下:

func TestParseSizeSub(t *testing.T) {
    if testing.Short() {
        t.Skip("跳过测试用例 TestParseSizeSub")
    }

    // 按照 group 分个组
    testData := make(map[string][]commStruct)
    for _, item := range commTestData {
        group := item.Group
        _, ok := testData[group]
        if !ok {
            testData[group] = make([]commStruct, 0)
        }
        testData[group] = append(testData[group], item)
    }

    // 分组去测试 测试数据
    for k, _ := range testData {
        t.Run(k, func(t *testing.T) {
            // 下面的子测试样例就会去并行执行:通过睡眠可以看出效果
            t.Parallel()
            for _, data := range testData[k] {
                size, sizeStr := ParseSize(data.SizeStr)
                if size != data.ExpectSize || sizeStr != data.ExpectSizeStr {
                    t.Errorf("测试结果不符合预期:%+v", data)
                }
            }
        })
    }
}

​ 细心的小伙伴一定看到了上面有两个点是我们没讲的:

  1. if testing.Short() 这个是做什么的呢?还记得我们上面介绍参数的时候说过吗,这个参数是用来避免一些不必要的测试的,所以如果我们的测试不需要,就可以使用 short 参数跳过这个子测试。
  2. t.Parallel() 这个就是我们子测试并行测试的关键了,只有加了这行代码,我们的子测试才能进行并行测试。

​ 下面带大家看看t.Parallel() 是不是真的有效果,我们在子测试代码中加入一个睡眠时间,先把 t.Parallel() 注释掉:

for k, _ := range testData {
    t.Run(k, func(t *testing.T) {
        //t.Parallel()
        for _, data := range testData[k] {
            time.Sleep(time.Second)
            size, sizeStr := ParseSize(data.SizeStr)
            if size != data.ExpectSize || sizeStr != data.ExpectSizeStr {
                t.Errorf("测试结果不符合预期:%+v", data)
            }
        }
    })
}

​ 然后执行命令 go test -v,可以观察到子测试的样例每隔一秒才执行一次,最终耗时 9.367 秒。

$  go test -v
=== RUN   TestParseSize
--- PASS: TestParseSize (0.10s)
=== RUN   TestParseSizeSub
=== RUN   TestParseSizeSub/KB
=== RUN   TestParseSizeSub/MB
=== RUN   TestParseSizeSub/GB
=== RUN   TestParseSizeSub/TB
=== RUN   TestParseSizeSub/PB
=== RUN   TestParseSizeSub/unknown
=== RUN   TestParseSizeSub/B
--- PASS: TestParseSizeSub (9.22s)            
    --- PASS: TestParseSizeSub/KB (2.05s)     
    --- PASS: TestParseSizeSub/MB (1.02s)     
    --- PASS: TestParseSizeSub/GB (1.02s)     
    --- PASS: TestParseSizeSub/TB (1.03s)     
    --- PASS: TestParseSizeSub/PB (1.03s)     
    --- PASS: TestParseSizeSub/unknown (1.03s)
    --- PASS: TestParseSizeSub/B (2.04s)      
PASS
ok      main/util       9.367s

​ 我们再把 t.Parallel() 的注释去掉,再执行 go test -v 观察一下:

$  go test -v
=== RUN   TestParseSize
--- PASS: TestParseSize (0.14s)
=== RUN   TestParseSizeSub
=== RUN   TestParseSizeSub/unknown
=== PAUSE TestParseSizeSub/unknown
=== RUN   TestParseSizeSub/B
=== PAUSE TestParseSizeSub/B
=== RUN   TestParseSizeSub/KB
=== PAUSE TestParseSizeSub/KB
=== RUN   TestParseSizeSub/MB
=== PAUSE TestParseSizeSub/MB
=== RUN   TestParseSizeSub/GB
=== PAUSE TestParseSizeSub/GB
=== RUN   TestParseSizeSub/TB
=== PAUSE TestParseSizeSub/TB
=== RUN   TestParseSizeSub/PB
=== PAUSE TestParseSizeSub/PB
=== CONT  TestParseSizeSub/unknown
=== CONT  TestParseSizeSub/GB
=== CONT  TestParseSizeSub/PB
=== CONT  TestParseSizeSub/TB
=== CONT  TestParseSizeSub/KB
=== CONT  TestParseSizeSub/MB
=== CONT  TestParseSizeSub/B
--- PASS: TestParseSizeSub (0.00s)
    --- PASS: TestParseSizeSub/TB (1.03s)
    --- PASS: TestParseSizeSub/B (1.03s)
    --- PASS: TestParseSizeSub/MB (1.03s)
    --- PASS: TestParseSizeSub/PB (1.03s)
    --- PASS: TestParseSizeSub/KB (1.03s)
    --- PASS: TestParseSizeSub/unknown (1.03s)
    --- PASS: TestParseSizeSub/GB (1.03s)
PASS
ok      main/util       1.210s

​ 会发现子测试几乎是同时打印出来的信息,最终耗时 1.120s,这就验证了 t.Parallel() 的作用,也同时验证了功能测试的子测试的作用。

模糊测试

​ 模糊测试是一种随机生成输入数据并将其提供给函数或程序的测试方法,它可以帮助发现潜在的边界情况和异常输入,以检测代码的鲁棒性。

​ 也就是说,模式测试本质上也是功能测试,只不过模糊测试的输入不再是我们提前构建好的数据,而是测试工具根据我们传入的参数类型去帮我们构建各种输入,以此来检测我们的功能函数在这种随机构造的输入情况下,是否还能照常工作。模糊测试的函数签名是func FuzzFunctionName(f *testing.F) {},如下:

func FuzzParseSize(f *testing.F) {
    // 也就是说,模糊测试,本质上也是一个功能测试。
    // 只是输入的内容不再是 data,而是所谓的 a
    f.Fuzz(func(t *testing.T, a string) {
        size, sizeStr := ParseSize(a)
        if size == 0 || sizeStr == "" {
            t.Errorf("输入异常,导致 parsesize 没拿到正确结果")
        }
    })
}

​ 然后我们可以通过 go test -fuzz FuzzParseSize 命令开启模糊测试,输出如下:

go test -fuzz FuzzParseSize
warning: starting with empty corpus
fuzz: elapsed: 0s, execs: 0 (0/sec), new interesting: 0 (total: 0)
fuzz: elapsed: 3s, execs: 614 (205/sec), new interesting: 7 (total: 7)
fuzz: elapsed: 6s, execs: 4210 (1194/sec), new interesting: 22 (total: 22)
fuzz: elapsed: 9s, execs: 5579 (456/sec), new interesting: 26 (total: 26)
fuzz: elapsed: 12s, execs: 9227 (1221/sec), new interesting: 35 (total: 35)
fuzz: elapsed: 15s, execs: 14480 (1744/sec), new interesting: 44 (total: 44)
fuzz: elapsed: 18s, execs: 16198 (572/sec), new interesting: 49 (total: 49)
......
  1. warning: starting with empty corpus:这是一个警告,表示开始时模糊测试的语料库(corpus)是空的。语料库是用来保存历史模式测试时,出现错误的样例。
  2. elapsed:经过的时间
  3. execs:执行的测试次数(平均每秒执行多少次)
  4. new interesting:新增的随机测试输入个数
  5. total:本次测试的的输入样例个数

​ 运行模糊测试,你会发现根本不会停,只能主动去停止,这也是为什么模糊测试只能同时测试的原因。

​ 还有就是上面提到的预料库,在运行模糊测试时,如果出现了预期之外的错误,那就会将这个样例保存到语料库中,并且在之后每次的模糊测试都会去运行这些出错的样例。语料库也是保存在本地的,会在根目录下生成一个对应的文件去存放。

性能测试

​ 最后我们再来看看性能测试,在进行性能测试之前,我们需要先将 ParseSize 函数中的睡眠函数关掉,避免影响我们的性能测试。因为Sleep() 不仅会让程序睡眠,还会做一些其他处理,会对我们的性能测试产生不小的影响。

​ 待会我们也可以做一个测试,然后进行一个对比。

​ 性能测试写起来条条框框会比较多,它的函数签名是这样的 func BenchmarkFunctionName(b *testing.B) {}我们啥也先不管,先来个 for 循环,然后直接调用我们的 ParseSize 函数:

func BenchmarkParseSize(b *testing.B) {
    for i := 0; i < b.N; i++ {
        ParseSize("1MB")
    }
}

​ 这样,一个简易的性能测试就写完了,我们可以用 go test -bench BenchmarkParseSize,这里先不注释 ParseSize 中的睡眠函数,我们看看效果:

go test -bench BenchmarkParseSize
goos: windows
goarch: amd64
pkg: main/util
cpu: AMD Ryzen 7 4800H with Radeon Graphics
BenchmarkParseSize-16                100          15301008 ns/op
BenchmarkParseSizeSub/B-16           100          14830110 ns/op
BenchmarkParseSizeSub/KB-16          100          15324944 ns/op
BenchmarkParseSizeSub/MB-16          100          15445510 ns/op
BenchmarkParseSizeSub/GB-16          100          14851633 ns/op
BenchmarkParseSizeSub/TB-16          100          15136910 ns/op
BenchmarkParseSizeSub/PB-16          100          15281375 ns/op
BenchmarkParseSizeSub/unknown-16                     100          15188822 ns/op
PASS
ok      main/util       22.495s

​ 再将睡眠函数注释掉,运行同样的命令,看看效果:

go test -bench BenchmarkParseSize                                    
goos: windows
goarch: amd64                                       
pkg: main/util                                      
cpu: AMD Ryzen 7 4800H with Radeon Graphics         
BenchmarkParseSize-16             735984              1603 ns/op
BenchmarkParseSizeSub/B-16        704841              1616 ns/op
BenchmarkParseSizeSub/KB-16       750050              1630 ns/op
BenchmarkParseSizeSub/MB-16       748998              1647 ns/op
BenchmarkParseSizeSub/GB-16       635871              1689 ns/op
BenchmarkParseSizeSub/TB-16       769012              1639 ns/op
BenchmarkParseSizeSub/PB-16       748689              1642 ns/op
BenchmarkParseSizeSub/unknown-16                  770593              1620 ns/op
PASS
ok      main/util       19.901s

​ 我们先来解释一下各个参数代表什么:

  1. goos: windows 和 goarch: amd64:表示你的操作系统和体系结构。
  2. pkg: main/util:表示正在测试的 Go 包的路径。
  3. cpu: AMD Ryzen 7 4800H with Radeon Graphics:表示你的 CPU 信息。
  4. BenchmarkParseSize-16 735984 1603 ns/op:表示运行了 735984 次,平均每次耗时 1603 纳秒
  5. PASS:表示所有的性能测试都通过
  6. ok main/util 19.901s:表示整个测试过程消耗了 19.901 秒。

​ 可以很明显的看到,这里两次测试的平均每次迭代耗时差了很多个数量级,但算上我们的睡眠时间 time.Sleep(time.Nanosecond * 500),也就 500 ns 而已。之所以会这样是因为 time.Sleep 函数的调用对于测试的结果会产生较大的影响,特别是在精度较高的情况,比如我们这里的纳米级别。 time.Sleep 会导致当前 goroutine 挂起,等待指定的时间再继续执行。在测试中,这样的挂起会导致每次迭代的耗时相对较大,从而影响性能测试的结果。

​ 可能会有人好奇,为什么平均时长差了很多,但是总耗时却差不多。因为在 Go 语言的性能测试中,每个子测试的迭代次数数由测试框架自动决定的,它会根据自己执行时间的变化动态调整迭代次数,以保证测试结果的稳定性和可靠性。我们也可以自己使用 -benchtime t 参数来配置自己想要的运行次数和时间。

​ 下面我们再来看看说说性能测试的子测试。

​ 性能测试的子测试,其实没有啥明确的使用场景,我们下面所举的例子,也只是为了写性能测试子测试而写子测试,能够使用的场景也就是需要分组归类去测试的数据,比如 B、KB、MB 等相同单位的一组去测试。

​ 这样做的好处是啥?有人肯定会觉得,可以像功能测试那样做并行测试。

​ 答案是否定的,性能测试的子测试没有并行机制。我个人觉得这样的好处就是,可以指定只执行对应分组的测试用例,比如我们只需要对某一个单位的大小进行特殊处理,就可以只去执行对应分组的测试用例了。

​ 然后我们来看看怎么写,同样的,需要先对我们的测试样例进行分组,然后在用 for 对不同组别的测试样例分别去运行性能测试函数:

func BenchmarkParseSizeSub(b *testing.B) {
    testData := make(map[string][]commStruct)
    for _, item := range commTestData {
        group := item.Group
        _, ok := testData[group]
        if !ok {
            testData[group] = make([]commStruct, 0)
        }
        testData[group] = append(testData[group], item)
    }

    for k, _ := range testData {
        b.Run(k, func(b *testing.B) {
            for i := 0; i < b.N; i++ {
                ParseSize(testData[k][0].SizeStr)
            }
        })
    }
}

​ 上面代码需要知道的一点,就是在每次运行 b.Run() 的时候,for 循环里的测试次数是测试工具自动决定的,我们只需要调用就可以了。

​ 上面就差不多是性能测试的基本写法了,只不过在一些情况下,比如我们在每次测试时需要去进行一下其他的数据准备,如果不进行一些处理,这些准备数据的时间就可能会导致我们的性能测试偏差较大:

for k, _ := range testData {
    b.Run(k, func(b *testing.B) {
        // case1
        preBenchmark()
        for i := 0; i < b.N; i++ {
            // case2
            preBenchmark1()
            ParseSize(testData[k][0].SizeStr)
        }
    })
}

func preBenchmark1() {
    time.Sleep(10 * time.Second)
}

func preBenchmark2() {
    time.Sleep(time.Nanosecond * 500)
}

​ 在上述代码中,我们通过 preBenchmark1 和 preBenchmark2 函数模拟了准备数据等其他操作的耗时,这里就直接告诉大家解决的方法了:

  • 对于 case1:可以在数据准备完成后,使用b.ResetTimer() 重置计时器
  • 对于case2:可以在准备数据前使用 b.StopTimer() 将计时器暂停,然后在准备好数据后,重新启动计时器 b.StartTimer(),这样就可以减小误差。
for k, _ := range testData {
    b.Run(k, func(b *testing.B) {
        // for 循环外,可以通过 b.ResetTimer() 来重置
        preBenchmark1()
        b.ResetTimer()
        for i := 0; i < b.N; i++ {
            // for 循环内,可以通过 b.StopTimer() 和 b.StartTimer() 配合使用,来跳过我们不想统计的耗时操作。迫不得已不要使用,测试速度慢
            b.StopTimer()
            preBenchmark2()
            b.StartTimer()
            ParseSize(testData[k][0].SizeStr)
        }
    })
}

​ 这里强调一点,上面的解决办法也只能减缓误差,并不能真正避免误差。并且如果你要测试上述代码的话,记得加上-benchtime 限制一下执行次数,否则会等很久。文章来源地址https://www.toymoban.com/news/detail-851267.html

到了这里,关于手把手教你写go单元测试的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 手把手教你写一个JSON在线解析的前端网站1

    作为一名Android开发,经常要跟后端同事联调接口,那么总避免不了要格式化接口返回值,将其转换为清晰直观高亮的UI样式以及折叠部分内容,方便我们查看定位关键的信息。 一直以来都是打开Google 搜索json格式化,然后选择Google推荐的前三名的网址,比如 bejson网站

    2024年02月08日
    浏览(34)
  • 正则表达式详解(零基础教学,手把手教你写正则)

    本篇文章将从零讲解什么是正则表达式,以及正则表达式的规则、在python中的应用,用通俗易懂的描述方式进行零基础级别的讲解,尽量做到全网最全讲解,力求最高质量文章,欢迎关注!点击目录可直接进行相关位置跳转。 目录: 什么是正则? 为什么需要正则? 元字符

    2023年04月08日
    浏览(35)
  • [Kotlin]手把手教你写一个安卓APP(第一章注册登录)

    开发软件:Android Studio 1.创建项目默认选择Empty Activity                                                                      点击Next  2.生成项目设置包名选择开发语言(这里我用的是kotlin)  在生成项目后我们要做的就是添加需要的配置打开我们的app目录下的 buil

    2023年04月23日
    浏览(71)
  • 【Java】手把手教你写学生信息管理系统(窗口化+MYSQL)

                (本项目使用到了数据库的可视化软件DataGrip,需要同学们自行下载并配置环境) 首先我们需要在DataGrip中建立一个student的框架                                                         然后建立一个studenttable表                   

    2024年02月04日
    浏览(33)
  • 手把手教你写代码——基于控制台的通讯录管理系统(单表)

    本栏目专为入门java学习者设计的一些简单的入门项目 本项目为简单的基于控制台的通讯录管理系统,所需要的环境仅仅为jdk以及mysql(版本不限)!只有一个简单的eclipse软件以及我们的mysql可视化工具(视频使用navicat) 本项目数据库表仅有一个,单表操作,方便学习! 本项

    2024年02月15日
    浏览(35)
  • 【Golang项目实战】手把手教你写一个备忘录程序|附源码——建议收藏

    博主简介: 努力学习的大一在校计算机专业学生,热爱学习和创作。目前在学习和分享:数据结构、Go,Java等相关知识。 博主主页: @是瑶瑶子啦 所属专栏: Go语言核心编程 近期目标: 写好专栏的每一篇文章 前几天瑶瑶子学习了Go语言的基础语法知识,那么今天我们就写个

    2024年02月06日
    浏览(44)
  • FPGA之手把手教你写串口协议解析(STM32与FPGA数据互传)

    最近趁热打铁做了一个关于STM32与FPGA通信并且控制高速DA模块产生不同频率信号的正弦波、方波、三角波和锯齿波的项目,从中收获到了很多东西,也踩了一些雷和坑,将分为几篇文章将整个过程分享出来。 这一次准备分享的是对串口数据的解析和赋值。解析的数据由STM32发

    2024年02月06日
    浏览(33)
  • 数据结构:线性表————顺序表的实现、项目和OJ题目(手把手教你写代码)

    🌈 个人主页: 小新_- 🎈个人座右铭:“成功者不是从不失败的人,而是从不放弃的人!”🎈 🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝 🏆所属专栏:  话说那些与C++的爱恨情仇   欢迎订阅,持续更新中~~~                                           ✨让小新带着你

    2024年04月16日
    浏览(42)
  • 基于STM32F103RCT6之手把手教你写智能家居项目(2)

            上一节我们简述了智能家居项目,实现了点灯的相关代码编写,还有WIFI模块的固件烧录。 连接什么平台:         我们想要远程控制家具的开关和获取家中的状态,少不了一个可以传输数据的云平台。我认为易监控是一个简单好用的云平台。 怎么连接平台:

    2024年02月20日
    浏览(40)
  • 【C语言】手把手教你文件操作

    程序运行时,数据存放在内存中,而当程序退出后,数据也就不复存在。 想做到数据持久化,我们可以把数据存放在硬盘,或者放到数据库里。而在C语言中,利用文件操作,就可以将数据存放在硬盘上。 读写之前应该先打开文件,使用结束之后要关闭文件。 fopen 函数用于打

    2024年02月05日
    浏览(32)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包