はじめに
前回の記事の続きです。
テストコード
プロダクトコードに乱数を含むケースがあり、テストが安定しないため、プロダクトコードも一部修正しています。
internal/core/move.go
package core
import (
"math/rand"
"time"
)
type Move struct {
Name string
Power int // 技の威力
Accuracy int // 命中率(0-100%)
Type string // 技の属性(例: 電気、炎など)
randSource *rand.Rand // 乱数生成器を保持
randomFactorFunc func() float64 // ランダムなダメージ倍率を計算する関数
hitCheckFunc func() bool // 命中判定の関数
}
// NewMove creates a new move and initializes the random source.
func NewMove(name string, power, accuracy int, moveType string) *Move {
randSource := rand.New(rand.NewSource(time.Now().UnixNano()))
move := &Move{
Name: name,
Power: power,
Accuracy: accuracy,
Type: moveType,
randSource: randSource,
randomFactorFunc: func() float64 {
// デフォルトのダメージ倍率:0.85~1.0
return 0.85 + randSource.Float64()*(1.0-0.85)
},
hitCheckFunc: func() bool {
// デフォルトの命中判定:Accuracyに基づくランダムな命中判定
return randSource.Intn(100) < accuracy
},
}
return move
}
// CanHit checks if the move hits based on its accuracy.
func (m *Move) CanHit() bool {
return m.hitCheckFunc() // 注入された命中判定関数を使用
}
// CalculateDamage calculates the damage of the move.
func (m *Move) CalculateDamage(attacker *Pokemon, defender *Pokemon) int {
// ダメージ計算式
damage := (attacker.Attack * m.Power) / defender.Defense
// ランダム要素を使用してダメージを計算
randomFactor := m.randomFactorFunc() // 注入された関数を使用
damage = int(float64(damage) * randomFactor)
if damage < 0 {
damage = 0
}
return damage
}
internal/core/pokemon_test.go
package core
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
// ポケモンのHPが正しく減少するかをテスト
func TestPokemonAttack_FixedValues(t *testing.T) {
// Pikachu と Charmander のセットアップ
pikachu := &Pokemon{
Name: "Pikachu",
HP: 100,
Attack: 55,
Defense: 40,
Speed: 90,
Moves: []Move{
*NewMove("Thunderbolt", 90, 95, "Electric"),
},
}
charmander := &Pokemon{
Name: "Charmander",
HP: 100,
Attack: 52,
Defense: 43,
Speed: 65,
Moves: []Move{
*NewMove("Flamethrower", 90, 95, "Fire"),
},
}
// Pikachu の技を取得
move := &pikachu.Moves[0]
// 乱数要素と命中判定を固定化する
move.randomFactorFunc = func() float64 {
return 1.0 // ダメージのランダム倍率を固定(1.0倍)
}
move.hitCheckFunc = func() bool {
return true // 命中判定を固定(常に命中)
}
// Pikachu が Charmander を攻撃
damage, err := pikachu.AttackPokemon(charmander, *move)
// 結果を表示
fmt.Println(pikachu.Name, "技:", move.Name, ", ", damage, "ダメージ!")
// エラーがないことを確認
assert.NoError(t, err)
// ダメージが期待通りか確認
expectedDamage := 115 // 期待する固定ダメージ(例: 計算に基づく値)
assert.Equal(t, expectedDamage, damage, "ダメージが期待値と異なります")
// Charmander のHPが正しく減少しているか確認
expectedHP := 0
assert.Equal(t, expectedHP, charmander.HP, "CharmanderのHPが期待値と異なります")
}
func TestCanHitWithFixedHitCheck(t *testing.T) {
// テスト対象の技を作成
move := NewMove("Thunderbolt", 90, 95, "Electric")
// 命中判定を固定化(常に命中する)
move.hitCheckFunc = func() bool {
return true // 常に命中する
}
// CanHitの結果をテスト(命中するはず)
if !move.CanHit() {
t.Errorf("命中するはずのCanHitがfalseを返しました")
}
// 命中判定を固定化(常にミスする)
move.hitCheckFunc = func() bool {
return false // 常にミスする
}
// CanHitの結果をテスト(ミスするはず)
if move.CanHit() {
t.Errorf("ミスするはずのCanHitがtrueを返しました")
}
}