添加一个测试

本文翻译自《Add a test》。

现在你已经将代码放到了一个稳定的位置(顺便说一句,做得很好),接下来添加一个测试。在开发期间测试你的代码,可以发现更改代码造成的Bug。在本文中,你将为Hello函数添加一个测试。

注意:本主题是从《创建一个Go模块》开始的多部分教程的一部分。

Go对单元测试的内置支持使你可以轻松地进行测试。具体来说,使用命名约定、Go的testinggo test命令,你可以快速编写和进行测试。

1 在greetings目录中,创建一个名为greetings_test.go的文件。

以_test.go结尾的文件名告诉go test命令该文件包含测试函数。

2 在greetings_test.go中,粘贴以下代码并保存文件。

package greetings

import (
    "testing"
    "regexp"
)

// TestHelloName函数使用一个名字name调用greetings.Hello函数,检查返回值是否有效。
func TestHelloName(t *testing.T) {
    name := "Gladys"
    want := regexp.MustCompile(`\b`+name+`\b`)
    msg, err := Hello("Gladys")
    if !want.MatchString(msg) || err != nil {
        t.Fatalf(`Hello("Gladys") = %q, %v, want match for %#q, nil`, msg, err, want)
    }
}

// TestHelloEmpty函数传入空字符串调用greetings.Hello函数,检查是否会发生错误。
func TestHelloEmpty(t *testing.T) {
    msg, err := Hello("")
    if msg != "" || err == nil {
        t.Fatalf(`Hello("") = %q, %v, want "", error`, msg, err)
    }
}

在此代码中,你:

  • 在与被测代码相同的包中实现测试函数。
  • 创建两个测试函数来测试greetings.Hello函数。测试函数的名字的形式为TestName,其中Name表示有关特定测试的信息。此外,测试函数将指针指向testing包的testing.T类型作为参数。你可以使用此类型的方法在测试中进行报告和记录各种信息。
  • 实现两个测试函数:

TestHelloName函数调用Hello函数,传递一个name值,应该能够返回有效的响应消息。如果返回一个错误或一个意外消息(不包含你传入的name值),你可以使用t参数的Fatalf方法将消息打印到控制台并结束执行。

TestHelloEmpty函数使用空字符串调用Hello函数。此测试旨在确认你的错误处理是否有效。如果返回非空字符串或没有错误,则使用t参数的Fatalf方法将消息打印到控制台并结束执行。

3 在greetings目录下命令行运行go test命令执行测试。

go test命令执行测试文件(名称以_test.go结尾)中的测试函数(名称以Test开头)。你可以添加-v标志以获得所有测试及其结果的详细输出。

本测试应该可以通过。

$ go test
PASS
ok      example.com/greetings   0.364s

$ go test -v
=== RUN   TestHelloName
--- PASS: TestHelloName (0.00s)
=== RUN   TestHelloEmpty
--- PASS: TestHelloEmpty (0.00s)
PASS
ok      example.com/greetings   0.372s

4 破坏greetings.Hello函数以查看失败的测试结果。

测试TestHelloName函数检查你传递name参数给Hello函的返回值。要查看失败的测试结果,请更改greetings.Hello函数,使返回值中不再包含name参数的值。

在greetings/greetings.go中,粘贴以下代码代替Hello函数。请注意,突出显示的行更改该函数返回的值,就好像name参数被意外删除一样。

// Hello函数返回对指定名字的人员的一句问候语。
func Hello(name string) (string, error) {
    // 如果name参数的值为空字符串,返回一个错误信息。
    if name == "" {
        return name, errors.New("empty name")
    }
    // 使用一个随机格式创建一个消息。
    // message := fmt.Sprintf(randomFormat(), name)
    message := fmt.Sprint(randomFormat())
    return message, nil
}

5 在greetings目录下命令行运行go test命令执行测试。

这次,在没有-v标志的情况下运行go test。输出将仅包含失败测试的结果,这在你有大量测试时很有用。测试TestHelloName函数应该会失败——测试TestHelloEmpty函数仍然会通过。

$ go test
--- FAIL: TestHelloName (0.00s)
    greetings_test.go:15: Hello("Gladys") = "Hail, %v! Well met!", <nil>, want match for `\bGladys\b`, nil
FAIL
exit status 1
FAIL    example.com/greetings   0.182s

在下一个(也是最后一个)主题中,你将了解如何编译和安装代码以在本地运行它。

为多个人返回问候语

本文翻译自《Return greetings for multiple people》。

在你对模块代码所做的最后更改中,你将添加代码,在一个请求中获取对多个人的问候语。换句话说,你将处理多个值输入,然后对应地输出多个值。为此,你需要将一组名字传递给一个可以为每个名字返回一句问候语的函数。

注意:本主题是从《创建一个Go模块》开始的多部分教程的一部分。

但有一个问题。将Hello函数的参数从单个名字name更改为一组名字将会改变函数的签名。如果你已经发布了example.com/greeting模块,并且用户已经编写了调用Hello函数的代码,那么这种更改将破坏他们的程序。

在这种情况下,更好的选择是编写一个具有不同名称的新函数。新函数将采用多个参数。这保留了旧功能以实现向后兼容性。

1 在greetings/greetings.go中,更改你的代码,使其如下所示。

package greetings

import (
    "errors"
    "fmt"
    "math/rand"
    "time"
)

// Hello函数为给定名字的人返回一句问候语。
func Hello(name string) (string, error) {
    // 如果name参数的值为空字符串,那么返回一条错误信息。
    if name == "" {
        return name, errors.New("empty name")
    }
    // 使用一个随机格式创建一条消息。
    message := fmt.Sprintf(randomFormat(), name)
    return message, nil
}

// Hellos函数返回一个映射map,用来关联每个给出名字的人和发给他的一句问候语消息。
func Hellos(names []string) (map[string]string, error) {
    // 一个映射map,用来关联名字和消息。
    messages := make(map[string]string)
    // 循环遍历接收到的names切片,为其中每个名字调用Hello函数获取一条消息。
    for _, name := range names {
        message, err := Hello(name)
        if err != nil {
            return nil, err
        }
        //在这个映射map中,关联名字和对应的消息。
        messages[name] = message
    }
    return messages, nil
}

// 初始化随机数种子
func init() {
    rand.Seed(time.Now().UnixNano())
}

// randomFormat函数返回多个问候语消息中随机选择的其中一个。
func randomFormat() string {
    // 一个消息格式切片。
    formats := []string{
        "Hi, %v. Welcome!",
        "Great to see you, %v!",
        "Hail, %v! Well met!",
    }

    // 返回一个随机选择的消息格式。
    return formats[rand.Intn(len(formats))]
}
  • 添加一个Hellos函数,其参数是一组名字而不是单个名字。此外,你将其返回类型之一从字符串更改为映射,以便你可以返回名字到问候语消息的映射。
  • 让新的Hellos函数调用现有的Hello函数。这有助于减少重复,同时保留这两个函数。
  • 创建一个消息messages映射,将每个接收到的名字(作为关键字)与生成的消息(作为值)相关联。在Go中,使用以下语法初始化映射:make(map[key-type]value-type)。让Hellos函数将此映射返回给调用者。有关映射类型map的更多信息,请参阅Go博客上的实践Go map
  • 遍历你的函数收到的名字,检查每个名字是否具有非空值,然后将消息与每个名字相关联。在此for循环中,range返回两个值:循环中当前条目的索引和条目值的一个副本。你不需要索引,因此你使用Go空白标识符(一个下划线)来忽略它。有关更多信息,请参阅Effective Go中的空白标识符

2 在你的hello/hello.go的代码调用中,传递一个名字切片,然后打印输出你获得的名字/消息映射的内容。

package main

import (
    "fmt"
    "log"

    "example.com/greetings"
)

func main() {
    log.SetPrefix("greetings: ")
log.SetFlags(0)

// 一个名字切片
names := []string{"Gladys", "Samantha", "Darrin"}

	// 为names请求响应的问候语消息
    messages, err := greetings.Hellos(names)
    if err != nil {
        log.Fatal(err)
}

fmt.Println(messages)
}

通过这些更改,你可以:

  • 创建一个names切片类型变量,包含三个名字。
  • names变量作为参数传递给Hellos函数。

3 在命令行中,切换到包含hello/hello.go的目录,然后使用go un运行并确认代码是否有效

输出应该是将名字与消息关联起来的映射的字符串表示,如下所示:

$ go run .
map[Darrin:Hail, Darrin! Well met! Gladys:Hi, Gladys. Welcome! Samantha:Hail, Samantha! Well met!]

本主题介绍了表示名/值对的映射类型。它还引入了通过为模块中的新功能或更改功能来实现新功能并保持向后兼容性的思想。有关向后兼容性的详细信息,请参阅保持模块兼容

接下来,你将使用Go内置的功能为代码创建一个单元测试

随机返回一句问候语

本文翻译自《Return a random greeting》。

在本节中,你将更改你的代码,以便它不会每次都返回同一句问候语,而是返回预定义的几句问候语之一。

注意:本主题是从《创建一个Go模块》开始的多部分教程的一部分。

为此,你将使用Go切片(slice)。切片类似于数组,不同之处在于它的大小会随着你添加和删除元素条目而动态变化。切片是Go中最有用的类型之一。

你将添加一个小切片以包含三句问候语,然后让你的代码随机返回其中一句。有关切片的更多信息,请参阅Go博客中的Go切片

1 在greetings/greetings.go中,更改你的代码,使其如下所示。

package greetings

import (
    "errors"
    "fmt"
    "math/rand"
    "time"
)

// Hello函数对给出名字name的人员返回一句问候语。
func Hello(name string) (string, error) {
    // 如果没有给出名字,返回一条错误消息。
    if name == "" {
        return name, errors.New("empty name")
    }
    // 使用一个随机格式创建一条消息。
    message := fmt.Sprintf(randomFormat(), name)
    return message, nil
}

// 使用时间戳初始化随机数种子。
func init() {
    rand.Seed(time.Now().UnixNano())
}

// randomFormat函数返回一组问候语中的一句。返回的问候语是随机选择的。func randomFormat() string {
    // 一个存储消息格式的切片。
    formats := []string{
        "Hi, %v. Welcome!",
        "Great to see you, %v!",
        "Hail, %v! Well met!",
    }

    // 通过为消息格式切片指定随机索引,返回随机选择的消息格式。
    return formats[rand.Intn(len(formats))]
}

在此代码中,你:

  • 添加一个randomFormat函数,该函数返回随机选择的问候语消息格式。请注意,randomFormat以小写字母开头,使其只能在其自身包中的被访问(换句话说,它不会被导出给其他包调用)。
  • randomFormat函数中声明了包含三种消息格式的切片。声明切片时,在方括号中省略其大小,如下所示:[]string。这告诉Go,切片下的数组大小可以动态更改。
  • 使用math/rand生成一个随机数,用于从切片中选择一个条目。
  • 添加一个init函数以使用当前时间戳为rand包生成随机数种子。Go在程序启动时,在全局变量初始化之后,自动执行init函数。有关init函数的更多信息,请参阅Effective Go
  • Hello函数中,调用randomFormat函数来获取你将返回的消息的格式,然后使用该格式和name参数的值一起创建一句问候语消息。
  • 像以前一样返回消息(或错误)。

2 在hello/hello.go中,更改你的代码,使其如下所示。

你将Gladys的名字(或其他名字,如果你愿意)作为参数添加到hello.go中的Hello函数调用。

package main

import (
    "fmt"
    "log"

    "example.com/greetings"
)

func main() {
    // 设置内置的日志记录器的属性,包括设置日志条目的前缀,设置标志0以不输出日志条目的时间、源文件名称和行号。
    log.SetPrefix("greetings: ")
    log.SetFlags(0)

    // 请求一句问候语信息。
    message, err := greetings.Hello("Gladys")
    // 如果返回一个错误,将其打印到控制台并退出程序。
    if err != nil {
        log.Fatal(err)
    }

    // 如果没有返回错误,将返回的消息打印到控制台。
    fmt.Println(message)
}

3 在命令行的hello目录中,运行hello.go以确认代码有效。多次运行它,注意问候语发生了变化。

$ go run .
Great to see you, Gladys!

$ go run .
Hi, Gladys. Welcome!

$ go run .
Hail, Gladys! Well met!

接下来,你将使用一个切片来问候多个人

返回并处理一个错误

本文翻译自《Return and handle an error》。

能处理错误是可靠代码的基本特征。在本节中,你将添加一些代码以从greetings模块返回一个错误,然后在调用方处理它。

注意:本主题是从《创建一个Go模块》开始的多部分教程的一部分。

1 在greetings/greetings.go中,添加下面突出显示的代码。

如果你不知道该问候谁,回复一句问候语就没有意义。如果name为空,则向调用者返回一个错误。将以下代码复制到greetings.go并保存文件。

package greetings

import (
    "errors"
    "fmt"
)

// Hello函数向给出名字的人返回一句问候语。
func Hello(name string) (string, error) {
    // 如果名字没有给出,就返回一个错误信息。
    if name == "" {
        return "", errors.New("empty name")
    }

    // 如果给出了名字,就返回一句嵌入了该名字的问候语。
    // in a greeting message.
    message := fmt.Sprintf("Hi, %v. Welcome!", name)
    return message, nil
}

在此代码中,你:

  • 更改了Hello函数的代码,使其返回两个值:一个字符串和一个错误。你的调用者将检查第二个值以查看是否发生了错误。(任何Go函数都可以返回多个值。有关更多信息,请参阅Effective Go。)
  • 添加一个if语句来检查无效请求(name为空字符串),如果请求无效则返回一个错误。errors.New函数返回一个错误,其中包含错误信息。
  • 在成功返回中添加nil(表示没有错误)作为第二个值。这样,调用者就可以看到函数成功返回了。

2 在你的hello/hello.go文件中,处理Hello函数返回的错误以及非错误值。 将以下代码粘贴到hello.go中。

package main

import (
    "fmt"
    "log"

    "example.com/greetings"
)

func main() {
    // 设置预定义的日志记录器的属性,包括设置日志条目的前缀,设置标志0以禁用打印时间、源文件和行号。
    log.SetPrefix("greetings: ")
    log.SetFlags(0)

    // 请求一句问候语。
    message, err := greetings.Hello("")
    // 如果返回错误,将其打印到控制台并退出程序。
    if err != nil {
        log.Fatal(err)
    }

    // 如果没有返回错误,将返回的消息打印到控制台。
    fmt.Println(message)
}
  • 配置log以在其日志消息的开头打印命令名称 (“greetings: “),不带时间戳或源文件信息。
  • Hello函数的两个返回值(包括错误)分配给变量。
  • Hello函数参数从具体的名称更改为空字符串,以便你可以尝试运行错误处理代码。
  • 查找非零值错误。在这种情况下继续下去是没有意义的。
  • 使用标准库log包中的函数输出错误信息。如果出现错误,则使用log包的Fatal函数打印错误信息并停止程序。

3 在hello目录的命令行中,运行hello.go以确认代码有效。

现在你传递的是一个空名称,你将收到一个错误。

$ go run .
greetings: empty name
exit status 1

这是Go中的常见的错误处理方式:将错误作为值返回,以便调用者可以检查它。

接下来,你将使用Go切片随机返回选择的一句问候语

从另一个模块调用你的函数

本文翻译自《Call your code from another module》。

上一节中,你创建了一个greetings模块。在本节中,你将编写代码来调用刚刚编写的模块中的Hello函数。你将编写可作为应用程序执行的代码,并调用greetings模块中的代码。

注意:本主题是从《创建一个Go模块》开始的多部分教程的一部分。

1 为你的Go模块源代码创建一个hello目录。这是你将编写调用者的地方。

创建此目录后,你应该在目录层次结构中的同一级别拥有hello目录和greetings目录,如下所示:

<home>/
 |-- greetings/
 |-- hello/

例如,如果你的命令提示符位于greetings目录中,你可以使用以下命令:

cd ..
mkdir hello
cd hello

2 为你将要编写的代码启用依赖项跟踪。

要为你的代码启用依赖项跟踪,请运行go mod init命令,指定你的代码所在模块的名称。

出于本教程的目的,使用example.com/hello作为模块路径。

$ go mod init example.com/hello
go: creating new go.mod: module example.com/hello

3 在你的文本编辑器中,在hello目录中,创建一个用于编写代码的文件,并将其命名为hello.go。

4 编写代码调用Hello函数,然后打印函数的返回值。 为此,将以下代码粘贴到hello.go中。

package main

import (
    "fmt"

    "example.com/greetings"
)

func main() {
    // 获取一句问候语并打印输出它。
    message := greetings.Hello("Gladys")
    fmt.Println(message)
}

在此代码中,你:

  • 声明一个main包。在Go中,作为应用程序执行的代码必须在main包中。
  • 导入两个包:example.com/greetingsfmt。这使你的代码可以访问这些包中的函数。导入example.com/greetings(你之前创建的模块中包含的包)可以让你访问Hello函数。你还导入fmt,具有处理输入和输出文本的功能(例如将文本打印到控制台)。
  • 通过调用greetings包的Hello函数获取一句问候语。

5 编辑example.com/hello模块以使用本地example.com/greetings模块。

对于生产环境,你将从代码仓库中发布example.com/greetings模块(具有反映其发布位置的模块路径),Go工具可以在其中找到它并进行下载。现在,因为你还没有发布该模块,所以你需要调整example.com/hello模块,以便它可以在你的本地文件系统上找到example.com/greetings代码。

为此,请使用go mod edit命令编辑example.com/hello模块,将Go工具从其模块路径(模块不在的位置)重定向到本地目录(它所在的位置)。

5.1 从hello目录中的命令行提示符运行以下命令:

$ go mod edit -replace example.com/greetings=../greetings

该命令指定example.com/greetings应替换为../greetings以定位依赖项。运行该命令后,hello目录中的go.mod文件应包含一个replace指令

module example.com/hello

go 1.16

replace example.com/greetings => ../greetings

5.2 从hello目录中的命令行提示符运行go mod tidy命令以同步example.com/hello模块的依赖项,添加代码所需但尚未在模块中跟踪的依赖项。

$ go mod tidy
go: found example.com/greetings in example.com/greetings v0.0.0-00010101000000-000000000000

命令完成后,example.com/hello模块的go.mod文件应该如下所示:

module example.com/hello

go 1.16

replace example.com/greetings => ../greetings

require example.com/greetings v0.0.0-00010101000000-000000000000

该命令在greetings目录中找到本地代码,然后添加require指令以指定example.com/hello需求example.com/greetings。当你在hello.go中导入greetings包时,你就创建了这个依赖项。

模块路径后面的数字是一个伪版本号(pseudo-version number)——一个生成的数字,用来代替语义版本号(该模块目前还没有)。

要引用已发布的模块,go.mod文件通常会省略replace指令并使用末尾带有标签版本号的require指令。

require example.com/greetings v1.1.0

有关版本号的更多信息,请参阅模块版本编号

6 在hello目录中的命令行提示符下,运行你的代码以确认它是否有效。

$ go run .
Hi, Gladys. Welcome!

恭喜!你已经编写了两个功能模块。

在下一主题中,你将添加一些错误处理

教程:创建一个 Go 模块

本文翻译自《Tutorial: Create a Go module》。

目录

先决条件

创建一个其他人可以使用的模块

这是介绍Go语言的一些基本特性的教程的第一部分。如果你刚刚开始使用Go,请务必查看教程:Go入门,其中介绍了go命令、Go模块和非常简单的Go代码。

在本教程中,你将创建两个模块。第一个是旨在由其他库或应用程序导入的库。第二个是将调用第一个模块的应用程序。

本教程的顺序包括七个简短的主题,每个主题都说明了该语言的不同部分。

1 创建一个模块——编写一个小模块,其中包含可以被另一个模块调用的函数。

2 从另一个模块调用你的代码——导入并使用你的新模块。

3 返回并处理一个错误——添加简单的错误处理程序。

4 返回一个随机问候语——处理切片中的数据(Go的动态大小数组)。

5 回复多个人的问候语——在一个映射(map)中存储键/值对。

6 添加一个测试——使用Go内置的单元测试功能来测试你的代码。

7 编译和安装这个应用程序——在本地编译和安装你的代码。

注意:有关其他教程,请参阅教程

先决条件

  • 一些编程经验。此处的代码非常简单,但有助于了解有关函数、循环和数组的一些知识。
  • 一个编辑代码的工具。你拥有的任何文本编辑器都可以正常工作。大多数文本编辑器都对 Go 有很好的支持。最受欢迎的是VSCode(免费)、GoLand(付费)和Vim(免费)。
  • 一个命令行终端。Go在Linux和Mac的任何终端以及Windows中的PowerShell或 cmd上都能很好地工作。

创建一个其他人可以使用的模块

首先创建一个Go模块。在一个模块中,一组离散且有用的功能集合构成一个或多个相关包。例如,你可以创建一个模块,其中的包具有进行财务分析的功能,以便其他编写财务应用程序的人可以使用你的工作。有关开发模块的更多信息,请参阅开发和发布模块

Go代码被分组到包中,包又被分组到模块中。你的模块指定运行代码所需的依赖项,包括Go版本和它需要的其他模块集。

当你在模块中添加或改进功能时,你发布了该模块的新版本。编写调用模块中函数的代码的开发人员可以导入模块的更新后的包并可以在将其投入生产使用之前使用新版本进行测试。

1 打开命令行提示符并cd到你的家目录。

在Linux或Mac上:

cd

在Windows上:

cd %HOMEPATH%

2 为你的Go模块源代码创建一个greetings目录。

例如,在你的家目录使用以下命令:

mkdir greetings
cd greetings

3 使用go mod init命令启动你的模块。

运行go mod init命令,并传给它你的模块路径作为参数——在这里,使用example.com/greetings。如果你发布一个模块,这必须是该模块可以被Go工具下载的路径的一部分。另一部分路径将是你的代码存储库的URL。

有关使用模块路径命名模块的更多信息,请参阅管理依赖项

$ go mod init example.com/greetings
go: creating new go.mod: module example.com/greetings

go mod init命令创建一个go.mod文件来跟踪代码的依赖项。到目前为止,该文件仅包含你的模块名称和你的代码支持的Go版本。但是当你添加依赖项时,go.mod文件将列出你的代码所依赖的版本。这使构建可重现,并让你直接控制要使用的模块及其版本。

4 在你的文本编辑器中,创建一个用于编写代码的文件,并将其命名为greetings.go。

5 将以下代码粘贴到你的greetings.go文件中并保存该文件。

package greetings

import "fmt"

// Hello函数返回对名字由name参数给出的人的一句问候语。
func Hello(name string) string {
    // 返回一句问候语,把name参数的值嵌入其中。
    message := fmt.Sprintf("Hi, %v. Welcome!", name)
    return message
}

这是你的模块的第一个代码块。它向任何请求Hello函数的调用者返回一句问候语。你将在下一步中编写调用此函数的代码。

在此代码中,你:

  • 声明一个greetings包来收集相关的功能。
  • 实现一个Hello函数来返回一句问候语。

该函数接受一个name参数,其类型为字符串(string)类型。该函数还返回一个字符串。在Go中,一个名字以大写字母开头的函数可以被不在同一个包中的函数调用。这在Go中被称为导出名称(Exported names)。有关导出名称的更多信息,请参阅Go教程中的导出名称

  • 声明一个message变量来保存你的问候语。

在Go中,:=运算符是一种在一行中声明和初始化变量的快捷方式(Go使用右侧的值来确定变量的类型)。从长远来看,你可能会这样写:

var message string
message = fmt.Sprintf("Hi, %v. Welcome!", name)
  • 使用fmt包的Sprintf函数创建一句问候语。第一个参数是格式字符串,Sprintfname参数的值替换为%v格式的谓词。插入name参数的值即可完成这个问候语文本。
  • 将格式化后的问候语文本返回给调用者。

在下一步中,你将从另一个模块调用此函数

管理依赖项

本文翻译自《Managing dependencies》。

目录

使用和管理依赖项的工作流程

管理模块依赖项

查找和导入有用的包

在代码中启用依赖项跟踪

给一个模块命名

添加一个依赖项

获取一个指定版本的依赖项

发现可用的更新

升级或降级一个依赖项

同步你代码的依赖项

开发和测试未发布的模块代码

       需求某个本地目录中的模块代码

       需求来自你自己的代码仓库分支的外部模块代码

使用代码仓库标识符获取特定的某个提交(commit)

移除一个依赖项指定一个模块代理服务器

当你的代码使用外部包时,这些包(作为模块分发)成为依赖项。随着时间的推移,你可能需要升级或更换它们。Go提供了依赖项管理工具,可帮助你在合并外部依赖项时确保Go应用程序的安全。

本主题描述如何执行任务来管理你在代码中采用的依赖项。你可以使用Go工具执行其中的大部分操作。本主题还描述了如何执行一些你可能会觉得有用的其他依赖相关的任务。

另请参阅

  • 如果你不熟悉将依赖项作为模块来使用,请查看入门教程以获取简要介绍。
  • 使用go命令管理依赖项有助于确保你对依赖项的需求保持一致,并且go.mod文件的内容有效。有关命令的参考,请参阅go命令。你还可以通过键入go help 命令名从命令行获得帮助,例如go help mod tidy
  • 用于更改依赖项的Go命令编辑你的go.mod文件。有关该文件内容的更多信息,请参阅go.mod文件参考
  • 让你的编辑器或IDE能够感知Go模块可以使管理它们的工作变得更容易。有关支持Go的编辑器的更多信息,请参阅编辑器插件和IDE
  • 本主题不描述如何开发、发布和模块的版本控制供其他人使用。有关更多信息,请参阅开发和发布模块

使用和管理依赖项的工作流程

你可以通过Go工具获取和使用有用的包。在pkg.go.dev上,你可以搜索你可能觉得有用的包,然后使用go命令将这些包导入到你自己的代码中以调用它们的功。

下面列出了最常见的依赖管理步骤。有关每个步骤的更多信息,请参阅本主题中的对应小节。

1 在pkg.go.dev找到有用的包

2 在代码中导入所需的包

3 将你的代码添加到模块中以进行依赖跟踪(如果它不在模块中的话)。请参阅启用依赖项跟踪

4 添加外部包作为依赖项,以便你可以管理它们。

5 随着时间的推移,根据需要升级或降级依赖项的版本

管理模块依赖项

在Go中,你将依赖项作为模块来管理,在代码中导入这些模块的包。该过程得到以下支持:

  • 用于发布模块和检索其代码的去中心化系统。开发人员使他们的模块可供其他开发人员从代码仓库中使用,并使用版本号发布。开发人员使用版本号在代码仓库中发布他们的模块,供其他开发人员获取并使用。
  • 一个包搜索引擎和文档浏览器(pkg.go.dev),你可以在其中找到模块。请参阅查找和导入有用的包
  • 模块版本编号规约可帮助你了解模块的稳定性和向后兼容性。请参阅模块版本编号
  • 使你更容易管理依赖项的Go工具,包括获取模块的源代码、升级等。有关更多信息,请参阅本主题中的各个小节。

查找和导入有用的包

你可以搜索pkg.go.dev来查找包含可能有用的函数的包。

找到要在代码中使用的包后,在页面顶部找到包路径,然后单击复制路径按钮将路径复制到剪贴板。在你自己的代码中,将路径粘贴到import语句中,如下例所示:

import "rsc.io/quote"

在代码导入包后,启用依赖项跟踪并获取要编译的包代码。有关更多信息,请参阅在代码中启用依赖项跟踪添加依一个赖项

在代码中启用依赖项跟踪

要跟踪和管理你添加的依赖项,你首先要将代码放入其自己的模块中。这会在源代码树的根目录下创建一个go.mod文件。你添加的依赖项将列在该文件中。 要将你的代码添加到它自己的模块中,请使用go mod init命令。例如,从命令行切换到代码的根目录,然后按以下示例运行命令:

$ go mod init example/mymodule

go mod init命令的参数是模块的模块路径。如果可能,模块路径应该是源代码的存储库的位置。

如果一开始你不知道模块的最终存储库位置,请使用一个安全的替代品。这可以是你拥有的域名或你控制的其他名称(如公司名称),以及模块名称或源代码目录的路径。有关详细信息,请参见给一模块个命名

当你使用Go工具管理依赖关系时,这些工具会更新go.mod文件,以便它维护你当前的依赖项列表。

添加依赖项时,Go工具也会创建一个go.sum文件,其中包含你所依赖的模块的校验和。Go使用该文件来验证下载的模块文件的完整性,尤其是对其他项目开发人员而言。

把go.mod文件和go.sum文件和代码一起放在代码存储库中。

有关更多信息,请参阅go.mod参考手册

给一个模块命名

当你运行go mod init以创建用于跟踪依赖项的模块时,你指定一个模块路径作为模块的名称。模块路径是模块中的包的导入路径的前缀。请务必指定一个不会与其他模块的模块路径有冲突的模块路径。

模块路径至少要指出有关其来源的信息,例如公司名称或作者名称或所有者姓名。但是模块路径最好也能描述模块是什么或做什么。 模块路径通常采用以下形式:

<prefix>/<descriptive-text>

前缀prefix通常是部分描述模块的字符串,例如描述模块来源的字符串。这可能是:

  • 可以让Go工具找到模块源代码的存储库的位置(如果你要发布模块,则需要模块源代码的存储库)。例如它可能是github.com/<project-name>/。如果你认为可以发布模块供其他人使用,请使用此最佳实践。有关发布的详细信息,请参阅开发和发布模块
  • 一个你掌控的名字。如果你不使用存储库名称,请确保选择一个你确信不会被其他人使用的前缀。一个很好的选择是你公司的名字。避免使用widgets、utilities或app等常用术语。

对于描述性文本descriptive-text,最好选择项目名称。请记住,包的名称才是主要描述功能信息的。模块路径为这些包的名称创建了一个命名空间。

保留的模块路径前缀

Go保证不会在包名中使用以下字符串。

  • test–你可以使用test作为模块的模块路径前缀,该模块的代码旨在本地测试另一个模块中的功能。对作为测试的一部分创建的模块使用test路径前缀。例如,你的测试本身可能会运行go mod init test,然后以某种特定方式安装该模块,以便使用Go源代码分析工具进行测试。
  • example–在某些Go文档中用作模块路径前缀,例如在教程中创建的模块只是为了跟踪依赖项。

请注意,当示例可能是一个已发布的模块时,Go文档也会使用example.com来演示。

添加一个依赖项

从已发布的模块导入包后,可以使用go get命令将该模块添加为依赖项进行管理。

该命令执行以下操作:

  • 如果有必要,它会在你的go.mod文件中为命令行上给出的包的模块添加require指令。require指令跟踪所依赖的模块的最低版本。请参阅go.mod参考手册以了解更多信息。
  • 如果需要,它会下载模块源代码,以便你可以编译依赖于它们的包。它可以从proxy.golang.org等模块代理或直接从版本控制存储库下载模块。模块源代码缓存在本地。你可以设置Go工具下载模块的位置。有关更多信息,请参阅指定模块代理服务器

下面描述几个例子。

  • 要在模块中添加包的所有依赖项,请运行如下命令(“.”指的是当前目录中的包):$ go get .
  • 要添加特定的依赖项,请将其模块路径指定为该命令的参数:$ go get example.com/theirmodule

该命令还验证它下载的每个模块。这确保它在模块发布后没有发生改变。如果模块在发布后发生了变化——例如,开发人员更改了提交(commit)的内容——Go工具将显示一个安全错误。此身份验证检查可保护你免受可能已被篡改的模块的侵害。

获取一个指定版本的依赖项

你可以通过在go get命令中指定版本号来获取依赖模块的特定版本。该命令更新go.mod文件中的require指令(当然你也可以手动更新)。

你可能想要那样做,如果:

  • 你想要获得模块的指定预发布版本以进行试用。
  • 你发现你当前使用的版本不适合你,因此你想获得一个你知道的可以依赖的版本。
  • 你想要升级或降级你已经依赖的模块。

以下是使用go get命令的示例:

  • 要获得特定编号的版本,请在模块路径后附加一个@符号,后跟你想要的版本编号:$ go get example.com/[email protected]
  • 要获取最新版本,请在模块路径后附加@latest:$ go get example.com/theirmodule@latest

以下go.mod文件的require指令示例(有关更多信息,请参阅go.mod参考手册)说明了如何需求特定版本号:

require example.com/theirmodule v1.3.4

发现可用的更新

你可以检查当前模块中是否已经使用了更新版本的依赖项。使用go list命令显示模块的依赖项列表,以及该模块可用的最新版本。发现可用的升级后,你可以使用你的代码进行试用,以决定是否升级到新版本。

有关go list命令的更多信息,请参阅go list -m

这里有几个例子。

  • 列出当前模块依赖的所有模块,以及每个模块可用的最新版本:$ go list -m -u all
  • 显示特定模块最新可用的版本:$ go list -m -u example.com/theirmodule

升级或降级一个依赖项

你可以通过使用Go工具发现可用版本来升级或降级依赖的模块,然后将不同的版本添加为依赖项。

1 要发现新版本,请使用go list命令,如发现可用更新小节所述。

2 要将特定版本添加为依赖项,请使用获取一个指定版本的依赖项小节所述的go get命令。

同步你代码的依赖项

你可以管理所有导入的包对应的依赖项,同时删除不再使用的依赖项。

当你一直在更改代码和依赖项时,这可能很有用,可能会创建一个你管理的依赖项和下载模块的集合,这些模块不再与代码中导入的包依赖的模块集合相匹配。

要保持依赖项集合整洁,请使用go mod tidy命令。对于代码中导入的包集,此命令编辑你的go.mod文件以添加必需但缺少的模块。它还会删除不提供任何相关包的未使用的模块。

该命令除了一个标志-v外没有其他参数,它打印有关已删除模块的信息。

$ go mod tidy

开发和测试未发布的模块的代码

你可以指定你的代码使用可能还未发布的依赖项模块。这些模块的代码可能在它们各自的存储库中,在这些存储库的分支中,或者在当前依赖它们的模块所在的本地硬盘里。

你可能希望在以下情况下执行此操作:

  • 你想对外部模块的代码进行自己的更改,例如在分叉和/或克隆它之后。例如,你可能想要修复该模块,然后将其作为一个拉取请求(pull request)发送给模块的开发人员。
  • 你正在构建一个新模块并且尚未发布它,因此go get命令访问不到它所在的存储库。

需求本地目录中的某个模块的代码

你可以指定所需模块的代码与依赖它的代码位于同一本地硬盘里。当你处于以下情况时,你可能会发现这很有用:

  • 开发自己的独立模块并希望使用当前模块进行测试。
  • 修复外部模块中的问题或向外部模块添加功能,并希望使用当前模块进行测试。(请注意,你还可以从你自己的存储库分支中获取外部模块。有关更多信息,请参阅下文的需求来自你自己的代码仓库分支的外部模块代码。)

要让Go命令使用模块代码的本地副本,请在go.mod文件中使用replace指令替换require指令中给出的模块路径。有关指令的更多信息,请参阅go.mod参考手册

在下面的go.mod文件示例中,当前模块依赖外部模块example.com/theirmodule,使用不存在的版本号(v0.0.0-unpublished)来确保替换正常工作。replace指令然后用../theirmodule替换原始模块路径,该目录与当前模块目录处于同一级别。

module example.com/mymodule

go 1.16

require example.com/theirmodule v0.0.0-unpublished

replace example.com/theirmodule v0.0.0-unpublished => ../theirmodule

设置require/replace对时,使用go mod editgo get命令确保文件描述的依赖项保持一致:

$ go mod edit -replace=example.com/[email protected]=../theirmodule
$ go get example.com/[email protected]

注意:当你使用replace指令时,Go工具不会验证外部模块,如添加一个依赖项小节所述。

有关版本号的更多信息,请参阅模块版本编号

需求来自你自己的代码仓库分支的外部模块的代码

当你fork一个外部模块的代码仓库时(例如修复模块代码中的问题或添加新功能),你可以让Go工具使用你fork的代码作为模块的源代码。这对于测试你自己的代码的更改很有用。(请注意,你还可以在本地硬盘目录中需求模块代码。有关更多信息,请参阅需求本地目录中的某个模块的代码小节。)

为此,你可以在go.mod文件中使用replace指令,将外部模块的原始模块路径替换为你fork的代码仓库的路径。这会指示Go工具在编译时使用替换路径(fork的代码仓库的路径),同时允许你保持你代码里的import语句里的原始模块路径保持不变。

有关replace指令的更多信息,请参阅go.mod文件参考手册

在下面的go.mod文件示例中,当前模块需求外部模块example.com/theirmodule。然后replace指令使用example.com/myfork/theirmodule替换原始模块路径,这是模块代码仓库的你的一个分支。

module example.com/mymodule

go 1.16

require example.com/theirmodule v1.2.3

replace example.com/theirmodule v1.2.3 => example.com/myfork/theirmodule v1.2.3-fixed

设置require/replace对时,最好使用Go工具命令,以确保与go.mod文件中描述的需求保持一致。使用go list命令查看当前使用的模块的版本。然后使用go mod edit命令用fork的分支替换所需的模块:

$ go list -m example.com/theirmodule
example.com/theirmodule v1.2.3
$ go mod edit -replace=example.com/[email protected]=example.com/myfork/[email protected]

注意:当你使用replace指令时,Go工具不会对外部模块进行身份验证,如添加一个依赖项小节中所述。

有关版本号的更多信息,请参阅模块版本编号

使用代码仓库标识符获取特定的某个提交(commit)

你可以使用go get命令从某个模块的代码仓库中,提取指定某个commit里的未发布(译者注:未加版本号标签)的代码。

为此,你可以使用go get命令,并使用@符号指定你想要的代码。当你使用go get时,该命令将向你的go.mod文件添加一个需求外部模块的require指令,使用基于commit的详细信息的伪版本号。

以下示例提供了一些说明。这些基于一个模块,其源代码位于git存储库中。

  • 要提取指定某个commit里的模块代码,请附加@commithash(译者注:@commit的哈希值)形式:$ go get example.com/theirmodule@4cf76c2
  • 要提取指定某个分支里的模块代码,请附加@branchname(译者注:@分支名称)形式:$ go get example.com/theirmodule@bugfixes

移除一个依赖项

当你的代码不再使用某个模块中的任何包时,你可以停止将该模块作为依赖项进行追踪。

要停止追踪所有未使用的模块,请运行go mod tidy命令。此命令还可以在构建模块的包时添加所需的缺失依赖项。

$ go mod tidy

要删除特定依赖项,请使用go get命令,指定该模块的模块路径并附加@none,如下例所示:

$ go get example.com/theirmodule@none

go get命令还将降级或删除依赖于已删除模块的其他依赖项。

指定一个模块代理服务器

当你使用Go工具处理模块时,这些工具默认从proxy.golang.org(Google运营的公共模块镜像)或直接从模块的代码仓库下载模块。你可以指定Go工具使用另一个代理服务器来下载和验证模块。例如,一些人设置了一个自己的模块代理服务器,以便更好地控制依赖项的使用方式。

要指定另外的模块代理服务器供Go工具使用,请将GOPROXY环境变量设置为一个或多个另外的模块代理服务器的URL。Go工具将按照你指定的顺序尝试每个URL。默认情况下,GOPROXY首先指定一个Google运营的公共的模块代理,然后直接从模块的存储库中下载模块代码(由其模块路径指出):

GOPROXY="https://proxy.golang.org,direct"

有关GOPROXY环境变量(包括其值支持的他行为)的详细信息,请参阅go命令参考手册

你可以将变量设置为其他模块代理服务器的URL,用逗号或管道分隔多个URL。

  • 当你使用逗号时,只有当前URL返回HTTP 404或410时,Go工具才会尝试列表中的下一个URL。GOPROXY="https://proxy.example.com,https://proxy2.example.com"
  • 当你使用管道时,Go工具将尝试列表中的下一个URL,而不考虑HTTP错误代码。GOPROXY="https://proxy.example.com|https://proxy2.example.com"

Go模块经常在版本控制服务器和模块代理上开发和分发,有些服务器和代理在公共互联网上不可用。此时你可以设置GOPRIVATE环境变量来配置go命令,以便从私有源下载和构建模块。

GOPRIVATEGONOPROXY环境变量可以设置为与模块前缀匹配的全局模式列表,与这些模式匹配的模块是私有的,不应该从中代理任何请求。例如:

GOPRIVATE=*.corp.example.com,*.research.example.com

管理模块的源代码

本文翻译自《Managing module source》。

目录

Go工具如何查找你发布的模块

在存储库中组织代码

选择存储库的范围

一个存储库管理一个模块的源代码

一个存储库管理多个模块的源代码

当你开发要发布以供其他人使用的模块时,你可以通过遵循本篇文章中描述的存储库规约来确保其他开发人员更容易地使用你的模块。

本文描述了你在管理模块存储库时可能采取的操作。有关在版本之间进行修订时你将采取的工作流程步骤顺序的信息,请参阅模块发布和版本控制工作流程

此处描述的一些规约在模块中是必需的,而另一些则是最佳实践。本内容假设你熟悉管理依赖项中描述的基本模块使用实践。

Go支持以下用于发布模块的存储库:Git、Subversion、Mercurial、Bazaar和Fossil。

有关模块开发的概述,请参阅开发和发布模块

Go工具如何找到你发布的模块

在Go发布模块和获取代码的分布式系统中,你可以将代码保留在存储库中的同时发布模块。Go工具依赖于存储库路径和存储库标签的命名规则,它们指示模块的名称和版本号。当你的存储库符合这些要求时,你的模块代码可以通过Go工具(例如go get命令)从存储库下载。

当开发人员使用go get命令为其代码导入的包获取源代码时,该命令执行以下操作:

1 go get在Go源代码中的import语句的包路径中识别模块路径。

2 使用从模块路径派生的URL,该命令在模块代理服务器上或直接在其存储库中定位模块源代码。

3 通过将模块的版本号与存储库标签匹配来查找要下载的模块版本的源代码,以发现存储库中的代码。当尚不知道要使用的版本号时,go get将查找最新版本。

4 检索模块源代码并将其下载到开发人员的本地模块缓存。

在存储库中组织代码

通过遵循此处描述的规约,你可以保持简单的维护并改善开发人员对你的模块的使用体验。将模块代码放入存储库通常与使用其他代码一样简单。

下图说明了具有两个包的简单模块的源代码层次结构。

你的首次提交应包括下表中列出的文件:

文件描述
LICENSE模块的许可证。
go.mod描述模块,包括它的模块路径(实际上就是它的名称)和它的依赖关系。有关更多信息,请参阅go.mod参考
模块路径将在module指令中给出,例如:
module example.com/mymodule
有关选择模块路径的更多信息,请参阅管理依赖项
虽然你可以编辑go.mod文件,但你会发现通过go命令进行更改更可靠。
go.sum包含表示模块依赖项的加密哈希值。Go工具使用这些哈希值来验证下载的模块,试图确认下载的模块是可信的。如果此确认失败,Go将显示一个安全性错误。
当没有依赖项时,该文件将为空或不存在。你不应该编辑此文件,除非使用go mod tidy命令删除不需要的条目。
包目录和.go源代码文件目录构成模块中的包,.go文件就是源代码文件。

你可以在命令行创建一个空的存储库,添加将成为首次提交的一部分的文件,并在提交时添加注释。下面是一个使用git的例子:

$ git init
$ git add --all
$ git commit -m "mycode: initial commit"
$ git push

选择存储库的范围

当代码应该独立于其他模块中的代码进行版本控制时,你在模块中发布该代码。

将存储库设计为在其根目录中托管单个模块将有助于简化维护,尤其是随着时间的推移,当你发布新的次版本和补丁版本、创建新的主版本分支等时候。但是,如果你的其他模块的代码依赖它,你可以改为在单个存储库中维护一组模块。

一个存储库管理一个模块的源代码

你可以维护一个包含单个模块源代码的存储库。在此模型中,你将go.mod文件放在存储库的根目录里,存储库的根目录里还有包子目录,包子目录里有Go源代码文件。

这是最简单的方法,随着时间的推移,你的模块可能更易于管理。它可以帮助你避免在模块目录名上附加版本号。

一个存储库管理多个模块的源代码

你可以用单个存储库发布多个模块。例如,你可能在一个存储库中包含多个模块的源代码,但希望分别对这些模块进行版本控制。

作为模块根目录的每个子目录都必须有自己的go.mod文件。

当发布一个模块时,在子目录中管理模块源代码需要更改版本标签的形式。你必须在标签版本号前面加上对应的模块根目录下的子目录的名称。有关版本号的更多信息,请参阅模块版本编号

例如,对于下面的模块example.com/mymodules/module1的v1.2.3版本,你将拥有以下内容:

  • 模块路径:example.com/mymodules/module1
  • 版本标签:module1/v1.2.3
  • 用户导入的包路径:example.com/mymodules/module1/package1
  • 用户require指令中的模块路径:example.com/mymodules/module1 module1/v1.2.3

发布一个模块

本文翻译自《Publishing a module》。

当你想让一个模块可供其他开发人员使用时,你可以发布它,以便Go工具可以看到它。发布模块后,导入其包的开发人员将能够通过运行go get等命令来解析对模块的依赖关系。

注意:发布后不要更改模块的已加标签的版本。对于使用该模块的开发人员,Go工具会根据第一个下载的副本对下载的模块进行身份验证。如果两者不同,Go工具将返回安全错误。不要更改以前发布版本的代码,而是发布一个新版本。

另请参阅

发布步骤

使用以下步骤发布模块。

1 打开命令提示符并切换到本地存储库中的模块的根目录。

2 运行go mod tidy,它会删除模块可能积累的任何不再需要的依赖项。

$ go mod tidy

3 最后一次运行go test ./...以确保一切正常。

这会运行你使用Go测试框架编写的单元测试。

$ go test ./...
ok      example.com/mymodule       0.015s

4 使用git tag命令给项目打新版本号标签。

对于版本号,请使用一个向用户表明此版本中更改性质的数字。有关更多信息,请参阅模块版本编号

$ git commit -m "mymodule: changes for v0.1.0"
$ git tag v0.1.0

5 将新标签推送到源存储库。

$ git push origin v0.1.0

6 通过运行go list命令使模块可用,以提示Go使用有关你正在发布的模块的信息更新其模块索引。

在命令前面加上将GOPROXY环境变量设置为Go代理的语句。这将确保你的请求到达代理。

$ GOPROXY=proxy.golang.org go list -m example.com/[email protected]

对你的模块感兴趣的开发人员从中导入一个包并运行go get命令,就像他们对任何其他模块一样。他们可以针对最新版本运行go get命令,也可以指定特定版本,如下例所示:

$ go get example.com/[email protected]