在图像处理测试中,直接比较字节确实不可靠,因为PNG编码器可能产生不同的字节输出(时间戳、压缩参数等)。以下是几种实用的测试方法:
1. 像素级比较(推荐)
比较图像的实际像素数据,忽略元数据:
import (
"image"
"testing"
_ "image/png"
"os"
)
func TestImagePixels(t *testing.T) {
// 生成测试图像
got := generateChessBoard()
// 加载预期图像
wantFile, err := os.Open("testdata/expected.png")
if err != nil {
t.Fatalf("无法打开预期图像: %v", err)
}
defer wantFile.Close()
want, _, err := image.Decode(wantFile)
if err != nil {
t.Fatalf("无法解码预期图像: %v", err)
}
// 比较尺寸
if got.Bounds() != want.Bounds() {
t.Errorf("图像尺寸不匹配: got %v, want %v", got.Bounds(), want.Bounds())
}
// 比较每个像素
bounds := got.Bounds()
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
for x := bounds.Min.X; x < bounds.Max.X; x++ {
gR, gG, gB, gA := got.At(x, y).RGBA()
wR, wG, wB, wA := want.At(x, y).RGBA()
if gR != wR || gG != wG || gB != wB || gA != wA {
t.Errorf("像素不匹配 at (%d, %d): got (%d,%d,%d,%d), want (%d,%d,%d,%d)",
x, y, gR, gG, gB, gA, wR, wG, wB, wA)
return
}
}
}
}
2. 使用go-cmp进行灵活比较
import (
"testing"
"github.com/google/go-cmp/cmp"
"image/color"
)
func TestImageWithGoCmp(t *testing.T) {
got := generateChessBoard()
want := loadExpectedImage()
// 自定义比较选项
opts := []cmp.Option{
cmp.Comparer(func(x, y color.Color) bool {
xr, xg, xb, xa := x.RGBA()
yr, yg, yb, ya := y.RGBA()
return xr == yr && xg == yg && xb == yb && xa == ya
}),
cmp.AllowUnexported(image.RGBA{}),
}
if diff := cmp.Diff(want, got, opts...); diff != "" {
t.Errorf("图像不匹配 (-want +got):\n%s", diff)
}
}
3. 哈希比较(快速但可能误判)
import (
"crypto/md5"
"image"
"testing"
)
func TestImageHash(t *testing.T) {
got := generateChessBoard()
want := loadExpectedImage()
// 计算像素数据的哈希
gotHash := hashImage(got)
wantHash := hashImage(want)
if gotHash != wantHash {
t.Errorf("图像哈希不匹配: got %x, want %x", gotHash, wantHash)
}
}
func hashImage(img image.Image) [16]byte {
bounds := img.Bounds()
hash := md5.New()
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
for x := bounds.Min.X; x < bounds.Max.X; x++ {
r, g, b, a := img.At(x, y).RGBA()
hash.Write([]byte{
byte(r >> 8), byte(r & 0xFF),
byte(g >> 8), byte(g & 0xFF),
byte(b >> 8), byte(b & 0xFF),
byte(a >> 8), byte(a & 0xFF),
})
}
}
var result [16]byte
copy(result[:], hash.Sum(nil))
return result
}
4. 针对国际象棋棋盘的特定测试
func TestChessBoardSpecific(t *testing.T) {
board := generateChessBoard()
bounds := board.Bounds()
// 测试棋盘格颜色
// 假设棋盘是8x8,每个格子50x50像素
cellSize := 50
// 测试a1应该是深色格子
a1Color := board.At(cellSize/2, bounds.Max.Y-cellSize/2)
expectedDark := color.RGBA{181, 136, 99, 255} // 典型深色格子
if !colorsEqual(a1Color, expectedDark) {
t.Errorf("a1格子颜色错误: got %v, want %v", a1Color, expectedDark)
}
// 测试h8应该是深色格子
h8Color := board.At(bounds.Max.X-cellSize/2, cellSize/2)
if !colorsEqual(h8Color, expectedDark) {
t.Errorf("h8格子颜色错误: got %v, want %v", h8Color, expectedDark)
}
}
func colorsEqual(c1, c2 color.Color) bool {
r1, g1, b1, a1 := c1.RGBA()
r2, g2, b2, a2 := c2.RGBA()
return r1 == r2 && g1 == g2 && b1 == b2 && a1 == a2
}
5. 黄金文件测试模式
// testdata/golden_test.go
package chessimager_test
import (
"flag"
"image/png"
"os"
"path/filepath"
"testing"
)
var update = flag.Bool("update", false, "更新黄金文件")
func TestGoldenImages(t *testing.T) {
testCases := []struct {
name string
fen string
filename string
}{
{"起始位置", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR", "start_position.png"},
{"王车易位后", "r3k2r/pppppppp/8/8/8/8/PPPPPPPP/R3K2R", "castled.png"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// 生成图像
img := generateBoardFromFEN(tc.fen)
goldenPath := filepath.Join("testdata", tc.filename)
if *update {
// 更新黄金文件
f, err := os.Create(goldenPath)
if err != nil {
t.Fatal(err)
}
defer f.Close()
png.Encode(f, img)
return
}
// 与黄金文件比较
compareWithGolden(t, img, goldenPath)
})
}
}
对于你的国际象棋图像包,建议结合使用像素级比较和特定领域测试。像素比较确保视觉一致性,特定测试验证棋盘布局的正确性。使用-update标志可以方便地更新测试数据。