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

1 回复

更多关于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运行都重新下载所有依赖。

回到顶部