Golang测试结合GitHub Actions运行缓慢(耗时问题)
Golang测试结合GitHub Actions运行缓慢(耗时问题)
大家好,这是我第一次使用 Go 实现 CICD 工作流(之前一直用 Node)。go test 花费了非常多的时间,即使它在我的本地机器上只运行了大约 7 秒。
我已经尝试了以下方法:
- npm run test
- 使用我自己的
affected脚本 - 在项目工作目录内运行
go test ./...
我尝试的所有方法测试耗时都几乎相同。
这是因为 monorepo 的设置吗?我看过一些 youtube 视频,他们的测试都是立即运行并退出的。
这是我的项目实际的 CI。
编辑
补充信息:我的 tests 只是虚拟测试,整个项目中没有实现任何 go routine。另外,我的 repo 是公开的,可以通过上面的链接查看。
示例
package handlers
import "testing"
func TestIndexRequestHandler(t *testing.T) {
t.Logf("Running minimal example test")
if false {
t.Errorf("This should not fail")
}
}
更多关于Golang测试结合GitHub Actions运行缓慢(耗时问题)的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于Golang测试结合GitHub Actions运行缓慢(耗时问题)的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
从你的GitHub Actions日志来看,测试执行本身确实很快(约7秒),但整个Go模块的初始化和依赖下载消耗了大量时间。这是GitHub Actions环境中Go测试的常见问题。
主要瓶颈在于每次CI运行时都需要重新下载所有依赖并构建模块缓存。以下是优化方案:
1. 使用Go模块缓存(关键优化)
在你的workflow中添加Go模块缓存:
# .github/workflows/test.yml
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.21'
- name: Cache Go modules
uses: actions/cache@v3
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Download dependencies
run: go mod download
- name: Run tests
run: go test ./... -v
2. 并行化测试执行
对于monorepo结构,并行运行测试可以显著减少时间:
- name: Run tests in parallel
run: |
go test ./... -p 4 -count=1
或者使用专门的测试运行器:
// 创建并行测试脚本
// cmd/test/main.go
package main
import (
"flag"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
)
func main() {
workers := flag.Int("workers", 4, "number of parallel workers")
flag.Parse()
var dirs []string
filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
if strings.Contains(path, "_test.go") {
dir := filepath.Dir(path)
dirs = append(dirs, dir)
}
return nil
})
// 去重
uniqueDirs := make(map[string]bool)
for _, dir := range dirs {
uniqueDirs[dir] = true
}
var wg sync.WaitGroup
semaphore := make(chan struct{}, *workers)
for dir := range uniqueDirs {
wg.Add(1)
semaphore <- struct{}{}
go func(d string) {
defer wg.Done()
defer func() { <-semaphore }()
cmd := exec.Command("go", "test", d, "-v")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Run()
}(dir)
}
wg.Wait()
}
3. 使用测试缓存和增量构建
- name: Run tests with caching
run: |
# 启用测试结果缓存
go test ./... -count=1 -json > test-results.json
# 或者使用go test的缓存功能
export GOCACHE=/tmp/gocache
export GOMODCACHE=/tmp/gomodcache
go test ./... -race -coverprofile=coverage.out
4. 针对monorepo的优化策略
如果你的monorepo包含多个模块,可以只测试变更的部分:
// scripts/test-affected/main.go
package main
import (
"bufio"
"fmt"
"os"
"os/exec"
"strings"
)
func main() {
// 获取变更的文件
cmd := exec.Command("git", "diff", "--name-only", "HEAD~1")
output, _ := cmd.Output()
changedFiles := strings.Split(string(output), "\n")
testDirs := make(map[string]bool)
for _, file := range changedFiles {
if strings.HasSuffix(file, ".go") {
dir := file[:strings.LastIndex(file, "/")]
if _, err := os.Stat(dir + "/go.mod"); err == nil {
testDirs[dir] = true
}
}
}
// 运行受影响模块的测试
for dir := range testDirs {
fmt.Printf("Testing %s...\n", dir)
cmd := exec.Command("go", "test", "./...")
cmd.Dir = dir
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Run()
}
}
5. 完整优化后的workflow示例
name: Optimized Go Tests
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
env:
GO_VERSION: '1.21'
GOCACHE: /tmp/gocache
GOMODCACHE: /tmp/gomodcache
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- uses: actions/setup-go@v4
with:
go-version: ${{ env.GO_VERSION }}
cache: true
cache-dependency-path: go.sum
- name: Cache Go modules
uses: actions/cache@v3
with:
path: |
~/.cache/go-build
~/go/pkg/mod
/tmp/gocache
/tmp/gomodcache
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
- name: Pre-warm module cache
run: |
go mod download
go mod verify
- name: Run tests with parallelism
run: |
# 根据CPU核心数设置并行度
go test ./... -p $(nproc) -count=1 -timeout=5m
# 或者运行特定包
# go test ./handlers ./utils ./models -v
- name: Upload test results
if: always()
uses: actions/upload-artifact@v3
with:
name: test-results
path: |
coverage.out
test-results.json
这些优化应该能将你的测试时间从几分钟减少到接近本地运行的7秒左右。关键点在于缓存Go模块和构建结果,避免每次CI运行都重新下载所有依赖。

