20. 自动化测试与CI/CD
2026/4/14大约 4 分钟
自动化测试与CI/CD
手动测试——每次改完代码自己跑一遍游戏看看有没有 bug——在小项目里还行,项目一大就扛不住了。本章讲怎么用自动化工具代替手动测试,以及如何让电脑自动帮你构建和发布游戏。
为什么要自动化测试
| 场景 | 手动测试 | 自动化测试 |
|---|---|---|
| 改了一行代码 | 把整个游戏跑一遍(10分钟) | 点一下按钮(30秒) |
| 修复一个 bug | 不确定有没有改坏其他地方 | 测试全部跑一遍,立刻知道 |
| 发布新版本 | 手动导出每个平台(20分钟) | 一键自动导出所有平台 |
打个比方
手动测试就像每次出门前都把所有灯开关按一遍确认没坏。自动化测试就像装了一个智能系统,哪个灯坏了自动报警。
Godot 的测试框架:GUT
GUT(Godot Unit Test)是 Godot 社区最流行的测试框架。
安装 GUT
- 从 GUT 的 GitHub Releases 下载最新版本
- 解压到项目目录
addons/gut/ - 重新打开 Godot 编辑器
创建测试场景
路径:项目根目录 → 新建 tests/test_suite.tscn
tests/
├── test_suite.tscn ← 测试入口场景
├── test_player.gd ← 玩家相关测试
└── test_inventory.gd ← 背包相关测试单元测试
单元测试就是测试一个最小的功能单元——一个函数、一个方法。
C
using Godot;
using GUT;
// 玩家生命值测试
[Test]
public void PlayerStartsWithFullHp()
{
var player = new Player();
AssertEqual(player.Hp, 100, "初始生命值应该是100");
}
[Test]
public void PlayerTakesDamage()
{
var player = new Player();
player.TakeDamage(30);
AssertEqual(player.Hp, 70, "受到30点伤害后生命值应该是70");
}
[Test]
public void PlayerDiesWhenHpReachesZero()
{
var player = new Player();
player.TakeDamage(100);
AssertTrue(player.IsDead, "生命值归零后应该死亡");
}
[Test]
public void PlayerCannotHaveNegativeHp()
{
var player = new Player();
player.TakeDamage(200);
AssertGreaterOrEqual(player.Hp, 0, "生命值不能为负数");
}GDScript
extends GutTest
# 玩家生命值测试
func test_player_starts_with_full_hp():
var player = Player.new()
assert_eq(player.hp, 100, "初始生命值应该是100")
player.queue_free()
func test_player_takes_damage():
var player = Player.new()
player.take_damage(30)
assert_eq(player.hp, 70, "受到30点伤害后生命值应该是70")
player.queue_free()
func test_player_dies_when_hp_reaches_zero():
var player = Player.new()
player.take_damage(100)
assert_true(player.is_dead, "生命值归零后应该死亡")
player.queue_free()
func test_player_cannot_have_negative_hp():
var player = Player.new()
player.take_damage(200)
assert_ge(player.hp, 0, "生命值不能为负数")
player.queue_free()集成测试
集成测试测试多个系统之间的配合。
C
[Test]
public void CoinPickupIncreasesScore()
{
// 创建玩家和金币
var player = new Player();
var coin = new Coin();
// 模拟碰撞
coin.OnBodyEntered(player);
AssertEqual(player.Score, 10, "拾取金币后分数应该+10");
AssertEqual(coin.Visible, false, "金币拾取后应该消失");
}
[Test]
public void DoorOpensWithCorrectKey()
{
var player = new Player();
var door = new Door();
var key = new KeyItem();
player.AddItem(key);
player.UseItem(key, door);
AssertTrue(door.IsOpen, "使用正确的钥匙后门应该打开");
}GDScript
func test_coin_pickup_increases_score():
var player = Player.new()
var coin = Coin.new()
# 模拟碰撞
coin._on_body_entered(player)
assert_eq(player.score, 10, "拾取金币后分数应该+10")
assert_eq(coin.visible, false, "金币拾取后应该消失")
player.queue_free()
coin.queue_free()
func test_door_opens_with_correct_key():
var player = Player.new()
var door = Door.new()
var key = KeyItem.new()
player.add_item(key)
player.use_item(key, door)
assert_true(door.is_open, "使用正确的钥匙后门应该打开")
player.queue_free()
door.queue_free()
key.queue_free()运行测试
在 Godot 编辑器中:
- 打开
tests/test_suite.tscn - 按 F5 运行
- GUT 会自动运行所有测试并显示结果
命令行运行:
# 在项目根目录执行
godot --headless --script addons/gut/gut_cmdln.gdGitHub Actions 自动构建
用 GitHub Actions 可以在每次提交代码时自动构建游戏,不用手动操作。
创建构建配置文件
路径:.github/workflows/build.yml
name: Build Game
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Download Godot
uses: chickensoft-games/godot-action@v1
with:
version: 4.3.0
export-templates: true
- name: Build Windows
run: godot --headless --export-release "Windows Desktop" build/game.exe
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: windows-build
path: build/
build-web:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Download Godot
uses: chickensoft-games/godot-action@v1
with:
version: 4.3.0
export-templates: true
- name: Build Web
run: godot --headless --export-release "HTML5" build/index.html
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: web-build
path: build/测试最佳实践
| 原则 | 说明 |
|---|---|
| 测试用户行为,不是实现细节 | 测试"玩家拾取金币后分数+10",不是"调用 add_score 函数" |
| 每个测试只测一件事 | 一个测试函数只验证一个功能点 |
| 测试名称要说清楚期望结果 | test_player_dies_when_hp_zero 比 test_player 好 |
| 不需要追求 100% 覆盖率 | 优先测试核心玩法逻辑,UI 细节可以手动测 |
不要过度测试
- 不需要测试 Godot 内置功能(MoveAndSlide、碰撞检测等)
- 不需要测试纯展示性的代码(UI 布局、动画播放)
- 把时间花在测试你的游戏逻辑上(计分、战斗、背包、存档)
测试的目的是让你改代码时有安全感,不是追求覆盖率数字。
下一章
自动化测试讲完了。回顾完整进阶篇内容,请查看 推荐学习路线。
