Parcourir la source

test(app-game): 添加土地操作相关测试用例

- 新增播种、施肥、收获等功能的测试用例
- 创建对应的测试类和测试方法
- 编写测试环境配置文件
- 实现请求创建和响应断言的通用方法
notfff il y a 7 mois
Parent
commit
907b1b4567

+ 47 - 0
UCore/DcatAdmin/Metrics/Examples/LinkA.php

@@ -0,0 +1,47 @@
+<?php
+
+namespace UCore\DcatAdmin\Metrics\Examples;
+
+use Dcat\Admin\Widgets\Box;
+use Dcat\Admin\Widgets\Metrics\Card;
+use Dcat\Admin\Widgets\Markdown;
+
+class LinkA extends Box
+{
+
+    private $link;
+    public function __construct($title = '链接', $link = '')
+    {
+        $this->title($title);
+        $this->link =$link;
+        $content = $this->link2content($link);
+
+        parent::__construct($title, $content);
+    }
+
+    public function link2content($link)
+    {
+
+        return "<a href='$link' >{$this->title}</a>";
+    }
+
+    public function newopen()
+    {
+        $link = $this->link;
+        $this->content = "<a href='$link' target=\"_blank\" >{$this->title}</a>";
+        return $this;
+    }
+
+    /**
+     * 设置链接
+     * @param $link
+     * @return $this
+     */
+    public function setLink($link)
+    {
+        $content = $this->link2content($link);
+        $this->content($content);
+
+        return $this;
+    }
+}

+ 181 - 0
app/Module/AppGame/Tests/.env.testing

@@ -0,0 +1,181 @@
+# 测试环境配置文件
+# 独立于代码的环境变量配置
+
+# 测试服务器配置
+UNITTEST_URL=http://localhost:8000
+UNITTEST_TIMEOUT=30
+
+# 测试用户配置
+TEST_USER_ID=1
+TEST_USER_PASSWORD=test123456
+TEST_USER_USERNAME=test_user
+TEST_USER_EMAIL=test@example.com
+
+# 测试数据库配置(如果需要独立的测试数据库)
+TEST_DB_CONNECTION=mysql
+TEST_DB_HOST=127.0.0.1
+TEST_DB_PORT=3306
+TEST_DB_DATABASE=kku_test
+TEST_DB_USERNAME=root
+TEST_DB_PASSWORD=
+
+# 灾害去除测试配置
+# 除虫测试
+TEST_PESTICIDE_LAND_ID=1
+TEST_PESTICIDE_ITEM_ID=101
+TEST_PESTICIDE_SUCCESS_RATE=80
+
+# 除草测试
+TEST_WEEDICIDE_LAND_ID=2
+TEST_WEEDICIDE_ITEM_ID=102
+TEST_WEEDICIDE_SUCCESS_RATE=75
+
+# 浇水测试
+TEST_WATERING_LAND_ID=3
+TEST_WATERING_ITEM_ID=103
+TEST_WATERING_SUCCESS_RATE=90
+
+# 测试物品配置
+TEST_PESTICIDE_ITEM_ID=101
+TEST_PESTICIDE_ITEM_NAME=高效除虫剂
+
+TEST_WEEDICIDE_ITEM_ID=102
+TEST_WEEDICIDE_ITEM_NAME=强力除草剂
+
+TEST_WATERING_ITEM_ID=103
+TEST_WATERING_ITEM_NAME=自动洒水器
+
+TEST_INVALID_ITEM_ID=999
+TEST_INVALID_ITEM_NAME=无效物品
+
+# 测试土地配置
+TEST_PEST_LAND_ID=1
+TEST_PEST_LAND_USER_ID=1
+TEST_PEST_LAND_DISASTER_TYPE=2
+
+TEST_WEED_LAND_ID=2
+TEST_WEED_LAND_USER_ID=1
+TEST_WEED_LAND_DISASTER_TYPE=3
+
+TEST_DROUGHT_LAND_ID=3
+TEST_DROUGHT_LAND_USER_ID=1
+TEST_DROUGHT_LAND_DISASTER_TYPE=1
+
+TEST_NORMAL_LAND_ID=4
+TEST_NORMAL_LAND_USER_ID=1
+TEST_NORMAL_LAND_DISASTER_TYPE=0
+
+TEST_OTHER_USER_LAND_ID=5
+TEST_OTHER_USER_LAND_USER_ID=2
+TEST_OTHER_USER_LAND_DISASTER_TYPE=2
+
+# 土地操作测试配置
+TEST_SOW_LAND_ID=10
+TEST_SOW_SEED_ID=201
+TEST_SOW_SEED_NAME=测试种子
+
+TEST_HARVEST_LAND_ID=11
+TEST_HARVEST_CROP_ID=301
+
+TEST_FERTILIZER_LAND_ID=12
+TEST_FERTILIZER_ITEM_ID=401
+TEST_FERTILIZER_ITEM_NAME=有机肥料
+
+TEST_REMOVE_CROP_LAND_ID=13
+TEST_REMOVE_TOOL_ID=501
+
+# 用户操作测试配置
+TEST_USER_NICKNAME=测试用户
+TEST_USER_AVATAR=default_avatar.png
+TEST_USER_OLD_PASSWORD=old_password
+TEST_USER_NEW_PASSWORD=new_password
+
+# 宠物测试配置
+TEST_PET_ID=1001
+TEST_PET_NAME=测试宠物
+TEST_PET_FOOD_ID=601
+TEST_PET_SKILL_ID=701
+
+# 物品测试配置
+TEST_CRAFT_RECIPE_ID=801
+TEST_CRAFT_MATERIAL_IDS=901,902,903
+
+TEST_BOX_ITEM_ID=1001
+TEST_DISMANTLE_ITEM_ID=1101
+
+# 商店测试配置
+TEST_SHOP_ITEM_ID=1201
+TEST_SHOP_ITEM_PRICE=100
+TEST_SHOP_CURRENCY_TYPE=1
+
+# 任务测试配置
+TEST_TASK_ID=1301
+TEST_TASK_TYPE=1
+TEST_TASK_REWARD_ID=1401
+
+# 好友测试配置
+TEST_FRIEND_USER_ID=2001
+TEST_FRIEND_USERNAME=friend_user
+TEST_FRIEND_APPLY_ID=2101
+
+# 房屋测试配置
+TEST_HOUSE_LEVEL=1
+TEST_HOUSE_UPGRADE_COST=1000
+
+# 神像测试配置
+TEST_GOD_TYPE=1
+TEST_GOD_BLESSING_ID=3001
+
+# 测试性能配置
+TEST_PERFORMANCE_REQUEST_COUNT=10
+TEST_PERFORMANCE_MAX_RESPONSE_TIME=5000
+
+# 测试并发配置
+TEST_CONCURRENT_REQUEST_COUNT=5
+TEST_CONCURRENT_MAX_WAIT_TIME=10
+
+# 测试概率配置
+TEST_PROBABILITY_ATTEMPTS=20
+TEST_PROBABILITY_ERROR_MARGIN=30
+
+# 测试日志配置
+TEST_LOG_LEVEL=debug
+TEST_LOG_ENABLED=true
+TEST_LOG_FILE=storage/logs/test.log
+
+# 测试缓存配置
+TEST_CACHE_ENABLED=false
+TEST_CACHE_TTL=300
+
+# 测试队列配置
+TEST_QUEUE_CONNECTION=sync
+TEST_QUEUE_ENABLED=false
+
+# 测试邮件配置
+TEST_MAIL_ENABLED=false
+TEST_MAIL_DRIVER=log
+
+# 测试短信配置
+TEST_SMS_ENABLED=false
+TEST_SMS_DRIVER=log
+
+# 测试文件上传配置
+TEST_UPLOAD_ENABLED=false
+TEST_UPLOAD_DISK=local
+TEST_UPLOAD_MAX_SIZE=1024
+
+# 测试API配置
+TEST_API_VERSION=v1
+TEST_API_RATE_LIMIT=1000
+TEST_API_TIMEOUT=30
+
+# 测试安全配置
+TEST_SECURITY_ENABLED=true
+TEST_SECURITY_TOKEN_TTL=3600
+TEST_SECURITY_MAX_ATTEMPTS=5
+
+# 测试调试配置
+TEST_DEBUG_ENABLED=true
+TEST_DEBUG_DUMP_REQUESTS=true
+TEST_DEBUG_DUMP_RESPONSES=true
+TEST_DEBUG_MEASURE_TIME=true

+ 270 - 0
app/Module/AppGame/Tests/Land/FertilizerHandlerTest.php

@@ -0,0 +1,270 @@
+<?php
+
+namespace App\Module\AppGame\Tests\Land;
+
+use App\Module\AppGame\Tests\TestConfig;
+use App\Module\AppGame\Tests\TestEnvironment;
+use Google\Protobuf\Internal\Message;
+use Uraus\Kku\Request;
+use Uraus\Kku\Request\RequestLandFertilizer;
+use Uraus\Kku\Common\RESPONSE_CODE;
+
+/**
+ * 施肥Handler E2E测试
+ * 
+ * 测试施肥功能的各种场景,包括成功、失败和边界情况
+ */
+class FertilizerHandlerTest extends DisasterRemovalBaseTest
+{
+    /**
+     * 当前测试配置
+     */
+    private array $currentTestConfig;
+
+    /**
+     * 测试施肥成功场景
+     */
+    public function testFertilizerSuccess()
+    {
+        $this->dumpTestStart('施肥成功测试');
+        
+        $config = TestEnvironment::getFertilizerConfig();
+        $this->dumpTestConfig($config);
+        $this->currentTestConfig = $config;
+
+        $response = $this->executeTestRequest();
+        $this->assertSuccessResponse($response, '施肥成功');
+        
+        $this->dumpTestEnd('施肥成功测试');
+    }
+
+    /**
+     * 测试使用无效物品施肥
+     */
+    public function testFertilizerWithInvalidItem()
+    {
+        $this->dumpTestStart('无效物品施肥测试');
+        
+        $config = TestEnvironment::getFertilizerConfig();
+        $config['item_id'] = TestEnvironment::get('TEST_INVALID_ITEM_ID', 999);
+        $this->dumpTestConfig($config);
+        $this->currentTestConfig = $config;
+
+        $response = $this->executeTestRequest();
+        $this->assertFailureResponse($response, '不是肥料物品');
+        
+        $this->dumpTestEnd('无效物品施肥测试');
+    }
+
+    /**
+     * 测试对其他用户土地施肥
+     */
+    public function testFertilizerOnOtherUserLand()
+    {
+        $this->dumpTestStart('其他用户土地施肥测试');
+        
+        $config = TestEnvironment::getFertilizerConfig();
+        $config['land_id'] = TestEnvironment::get('TEST_OTHER_USER_LAND_ID', 5);
+        $this->dumpTestConfig($config);
+        $this->currentTestConfig = $config;
+
+        $response = $this->executeTestRequest();
+        $this->assertFailureResponse($response, '土地不存在或不属于当前用户');
+        
+        $this->dumpTestEnd('其他用户土地施肥测试');
+    }
+
+    /**
+     * 测试施肥性能
+     */
+    public function testFertilizerPerformance()
+    {
+        $this->dumpTestStart('施肥性能测试');
+        
+        $config = TestEnvironment::getFertilizerConfig();
+        $performanceConfig = TestEnvironment::getPerformanceConfig();
+        $requestCount = $performanceConfig['request_count'];
+        $maxResponseTime = $performanceConfig['max_response_time'];
+        
+        $this->dumpTestConfig([
+            'fertilizer_config' => $config,
+            'request_count' => $requestCount,
+            'max_response_time' => $maxResponseTime
+        ]);
+        
+        $startTime = microtime(true);
+        
+        for ($i = 1; $i <= $requestCount; $i++) {
+            $requestStartTime = microtime(true);
+            $this->currentTestConfig = $config;
+            
+            $response = $this->executeTestRequest();
+            
+            $requestEndTime = microtime(true);
+            $requestDuration = ($requestEndTime - $requestStartTime) * 1000;
+            
+            $this->assertLessThan($maxResponseTime, $requestDuration, 
+                "第 {$i} 个请求响应时间 {$requestDuration}ms 超过限制 {$maxResponseTime}ms");
+                
+            if (TestEnvironment::isDebugMode()) {
+                dump("第 {$i} 个请求耗时: {$requestDuration}ms,响应码: " . $response->getCode());
+            }
+        }
+        
+        $totalTime = (microtime(true) - $startTime) * 1000;
+        $avgTime = $totalTime / $requestCount;
+        
+        if (TestEnvironment::isDebugMode()) {
+            dump("性能测试结果:", [
+                'total_requests' => $requestCount,
+                'total_time' => $totalTime . 'ms',
+                'average_time' => $avgTime . 'ms',
+                'max_allowed_time' => $maxResponseTime . 'ms'
+            ]);
+        }
+        
+        $this->dumpTestEnd('施肥性能测试');
+    }
+
+    /**
+     * 测试重复施肥
+     */
+    public function testRepeatedFertilizer()
+    {
+        $this->dumpTestStart('重复施肥测试');
+        
+        $config = TestEnvironment::getFertilizerConfig();
+        $this->currentTestConfig = $config;
+        
+        // 第一次施肥
+        $response1 = $this->executeTestRequest();
+        $this->dumpResponse($response1, '第一次施肥响应');
+        
+        // 第二次施肥(可能失败,因为已经施过肥)
+        $response2 = $this->executeTestRequest();
+        $this->dumpResponse($response2, '第二次施肥响应');
+        
+        // 验证至少有一次成功
+        $hasSuccess = ($response1->getCode() === RESPONSE_CODE::OK) || 
+                     ($response2->getCode() === RESPONSE_CODE::OK);
+        $this->assertTrue($hasSuccess, '至少应有一次施肥成功');
+        
+        $this->dumpTestEnd('重复施肥测试');
+    }
+
+    /**
+     * 测试所有预定义场景
+     */
+    public function testAllFertilizerScenarios()
+    {
+        $this->dumpTestStart('所有施肥场景测试');
+        
+        // 测试成功场景
+        $successScenarios = TestConfig::getTestScenario('success_scenarios', 'fertilizer_success');
+        if (!empty($successScenarios)) {
+            if (TestEnvironment::isDebugMode()) {
+                dump('测试成功场景:', $successScenarios);
+            }
+            $this->currentTestConfig = [
+                'land_id' => $successScenarios['land_id'],
+                'item_id' => $successScenarios['item_id']
+            ];
+            
+            $response = $this->executeTestRequest();
+            $this->assertSuccessResponse($response, '施肥成功');
+        }
+        
+        $this->dumpTestEnd('所有施肥场景测试');
+    }
+
+    /**
+     * 创建施肥请求
+     */
+    public function create_request_protobuf(): Message
+    {
+        $request = new Request();
+        $fertilizerRequest = new RequestLandFertilizer();
+        
+        // 使用当前测试配置或默认配置
+        $config = $this->currentTestConfig ?? TestEnvironment::getFertilizerConfig();
+        
+        $fertilizerRequest->setLandId($config['land_id']);
+        $fertilizerRequest->setUserItemId($config['item_id']);
+        
+        $request->setLandFertilizer($fertilizerRequest);
+        
+        return $this->createBaseRequest($request);
+    }
+
+    /**
+     * 测试并发施肥请求
+     */
+    public function testConcurrentFertilizerRequests()
+    {
+        $this->dumpTestStart('并发施肥请求测试');
+        
+        $config = TestEnvironment::getFertilizerConfig();
+        $concurrentConfig = TestEnvironment::getConcurrentConfig();
+        $requestCount = $concurrentConfig['request_count'];
+        
+        if (TestEnvironment::isDebugMode()) {
+            dump("开始并发测试,发送 {$requestCount} 个同时请求");
+        }
+        
+        $responses = [];
+        $this->currentTestConfig = $config;
+        
+        // 模拟并发请求(在测试环境中顺序执行)
+        for ($i = 0; $i < $requestCount; $i++) {
+            $responses[] = $this->executeTestRequest();
+        }
+        
+        // 验证响应
+        $successCount = 0;
+        foreach ($responses as $index => $response) {
+            if (TestEnvironment::isDebugMode()) {
+                dump("第 " . ($index + 1) . " 个响应: " . $response->serializeToJsonString());
+            }
+            if ($response->getCode() === RESPONSE_CODE::OK) {
+                $successCount++;
+            }
+        }
+        
+        // 至少应该有一个成功(第一个请求)
+        $this->assertGreaterThan(0, $successCount, '并发请求中至少应有一个成功');
+        
+        if (TestEnvironment::isDebugMode()) {
+            dump("并发测试结果: {$successCount}/{$requestCount} 个请求成功");
+        }
+        
+        $this->dumpTestEnd('并发施肥请求测试');
+    }
+
+    /**
+     * 测试施肥参数验证
+     */
+    public function testFertilizerParameterValidation()
+    {
+        $this->dumpTestStart('施肥参数验证测试');
+        
+        // 测试无效土地ID
+        $this->currentTestConfig = [
+            'land_id' => -1,
+            'item_id' => TestEnvironment::get('TEST_FERTILIZER_ITEM_ID', 401)
+        ];
+        
+        $response = $this->executeTestRequest();
+        $this->assertFailureResponse($response);
+        
+        // 测试无效物品ID
+        $this->currentTestConfig = [
+            'land_id' => TestEnvironment::get('TEST_FERTILIZER_LAND_ID', 12),
+            'item_id' => -1
+        ];
+        
+        $response = $this->executeTestRequest();
+        $this->assertFailureResponse($response);
+        
+        $this->dumpTestEnd('施肥参数验证测试');
+    }
+}

+ 334 - 0
app/Module/AppGame/Tests/Land/HarvestHandlerTest.php

@@ -0,0 +1,334 @@
+<?php
+
+namespace App\Module\AppGame\Tests\Land;
+
+use App\Module\AppGame\Tests\TestConfig;
+use App\Module\AppGame\Tests\TestEnvironment;
+use Google\Protobuf\Internal\Message;
+use Uraus\Kku\Request;
+use Uraus\Kku\Request\RequestLandHarvest;
+use Uraus\Kku\Common\RESPONSE_CODE;
+
+/**
+ * 收获Handler E2E测试
+ * 
+ * 测试收获功能的各种场景,包括成功、失败和边界情况
+ */
+class HarvestHandlerTest extends DisasterRemovalBaseTest
+{
+    /**
+     * 当前测试配置
+     */
+    private array $currentTestConfig;
+
+    /**
+     * 测试收获成功场景
+     */
+    public function testHarvestSuccess()
+    {
+        $this->dumpTestStart('收获成功测试');
+        
+        $config = TestEnvironment::getHarvestConfig();
+        $this->dumpTestConfig($config);
+        $this->currentTestConfig = $config;
+
+        $response = $this->executeTestRequest();
+        $this->assertSuccessResponse($response, '收获成功');
+        
+        $this->dumpTestEnd('收获成功测试');
+    }
+
+    /**
+     * 测试收获未成熟作物
+     */
+    public function testHarvestImmatureCrop()
+    {
+        $this->dumpTestStart('收获未成熟作物测试');
+        
+        $config = TestEnvironment::getHarvestConfig();
+        // 使用一个未成熟的作物土地
+        $config['land_id'] = TestEnvironment::get('TEST_IMMATURE_CROP_LAND_ID', 20);
+        $this->dumpTestConfig($config);
+        $this->currentTestConfig = $config;
+
+        $response = $this->executeTestRequest();
+        $this->assertFailureResponse($response, '作物未成熟');
+        
+        $this->dumpTestEnd('收获未成熟作物测试');
+    }
+
+    /**
+     * 测试收获其他用户土地
+     */
+    public function testHarvestOtherUserLand()
+    {
+        $this->dumpTestStart('收获其他用户土地测试');
+        
+        $config = TestEnvironment::getHarvestConfig();
+        $config['land_id'] = TestEnvironment::get('TEST_OTHER_USER_LAND_ID', 5);
+        $this->dumpTestConfig($config);
+        $this->currentTestConfig = $config;
+
+        $response = $this->executeTestRequest();
+        $this->assertFailureResponse($response, '土地不存在或不属于当前用户');
+        
+        $this->dumpTestEnd('收获其他用户土地测试');
+    }
+
+    /**
+     * 测试收获空土地
+     */
+    public function testHarvestEmptyLand()
+    {
+        $this->dumpTestStart('收获空土地测试');
+        
+        $config = TestEnvironment::getHarvestConfig();
+        $config['land_id'] = TestEnvironment::get('TEST_EMPTY_LAND_ID', 30);
+        $this->dumpTestConfig($config);
+        $this->currentTestConfig = $config;
+
+        $response = $this->executeTestRequest();
+        $this->assertFailureResponse($response, '土地上没有作物');
+        
+        $this->dumpTestEnd('收获空土地测试');
+    }
+
+    /**
+     * 测试收获性能
+     */
+    public function testHarvestPerformance()
+    {
+        $this->dumpTestStart('收获性能测试');
+        
+        $config = TestEnvironment::getHarvestConfig();
+        $performanceConfig = TestEnvironment::getPerformanceConfig();
+        $requestCount = $performanceConfig['request_count'];
+        $maxResponseTime = $performanceConfig['max_response_time'];
+        
+        $this->dumpTestConfig([
+            'harvest_config' => $config,
+            'request_count' => $requestCount,
+            'max_response_time' => $maxResponseTime
+        ]);
+        
+        $startTime = microtime(true);
+        $responseTimes = [];
+        
+        for ($i = 1; $i <= $requestCount; $i++) {
+            $requestStartTime = microtime(true);
+            $this->currentTestConfig = $config;
+            
+            $response = $this->executeTestRequest();
+            
+            $requestEndTime = microtime(true);
+            $requestDuration = ($requestEndTime - $requestStartTime) * 1000;
+            $responseTimes[] = $requestDuration;
+            
+            $this->assertLessThan($maxResponseTime, $requestDuration, 
+                "第 {$i} 个请求响应时间 {$requestDuration}ms 超过限制 {$maxResponseTime}ms");
+                
+            if (TestEnvironment::isDebugMode()) {
+                dump("第 {$i} 个请求耗时: {$requestDuration}ms,响应码: " . $response->getCode());
+            }
+        }
+        
+        $totalTime = (microtime(true) - $startTime) * 1000;
+        $avgTime = array_sum($responseTimes) / count($responseTimes);
+        $maxTime = max($responseTimes);
+        $minTime = min($responseTimes);
+        
+        if (TestEnvironment::isDebugMode()) {
+            dump("收获性能测试结果:", [
+                'total_requests' => $requestCount,
+                'total_time' => $totalTime . 'ms',
+                'average_time' => $avgTime . 'ms',
+                'max_time' => $maxTime . 'ms',
+                'min_time' => $minTime . 'ms',
+                'max_allowed_time' => $maxResponseTime . 'ms'
+            ]);
+        }
+        
+        $this->dumpTestEnd('收获性能测试');
+    }
+
+    /**
+     * 测试重复收获
+     */
+    public function testRepeatedHarvest()
+    {
+        $this->dumpTestStart('重复收获测试');
+        
+        $config = TestEnvironment::getHarvestConfig();
+        $this->currentTestConfig = $config;
+        
+        // 第一次收获
+        $response1 = $this->executeTestRequest();
+        $this->dumpResponse($response1, '第一次收获响应');
+        
+        // 第二次收获(应该失败,因为已经收获过了)
+        $response2 = $this->executeTestRequest();
+        $this->dumpResponse($response2, '第二次收获响应');
+        
+        // 验证第一次成功,第二次失败
+        if ($response1->getCode() === RESPONSE_CODE::OK) {
+            $this->assertNotEquals(RESPONSE_CODE::OK, $response2->getCode(), 
+                '第二次收获应该失败,因为作物已被收获');
+        }
+        
+        $this->dumpTestEnd('重复收获测试');
+    }
+
+    /**
+     * 测试批量收获
+     */
+    public function testBatchHarvest()
+    {
+        $this->dumpTestStart('批量收获测试');
+        
+        $landIds = [
+            TestEnvironment::get('TEST_HARVEST_LAND_ID', 11),
+            TestEnvironment::get('TEST_HARVEST_LAND_ID_2', 21),
+            TestEnvironment::get('TEST_HARVEST_LAND_ID_3', 22)
+        ];
+        
+        $successCount = 0;
+        $failureCount = 0;
+        
+        foreach ($landIds as $index => $landId) {
+            $this->currentTestConfig = [
+                'land_id' => $landId
+            ];
+            
+            $response = $this->executeTestRequest();
+            
+            if ($response->getCode() === RESPONSE_CODE::OK) {
+                $successCount++;
+                if (TestEnvironment::isDebugMode()) {
+                    dump("土地 {$landId} 收获成功");
+                }
+            } else {
+                $failureCount++;
+                if (TestEnvironment::isDebugMode()) {
+                    dump("土地 {$landId} 收获失败: " . $response->getMsg());
+                }
+            }
+        }
+        
+        if (TestEnvironment::isDebugMode()) {
+            dump("批量收获结果:", [
+                'total_lands' => count($landIds),
+                'success_count' => $successCount,
+                'failure_count' => $failureCount
+            ]);
+        }
+        
+        // 至少应该有一个成功
+        $this->assertGreaterThan(0, $successCount, '批量收获中至少应有一个成功');
+        
+        $this->dumpTestEnd('批量收获测试');
+    }
+
+    /**
+     * 测试收获奖励验证
+     */
+    public function testHarvestRewardValidation()
+    {
+        $this->dumpTestStart('收获奖励验证测试');
+        
+        $config = TestEnvironment::getHarvestConfig();
+        $this->currentTestConfig = $config;
+        
+        $response = $this->executeTestRequest();
+        
+        if ($response->getCode() === RESPONSE_CODE::OK) {
+            // 验证响应中包含奖励信息
+            $responseJson = $response->serializeToJsonString();
+            $responseData = json_decode($responseJson, true);
+            
+            // 检查是否有奖励数据
+            if (isset($responseData['reward'])) {
+                $this->assertNotEmpty($responseData['reward'], '收获成功应该包含奖励信息');
+                if (TestEnvironment::isDebugMode()) {
+                    dump('收获奖励:', $responseData['reward']);
+                }
+            }
+            
+            // 检查是否有扣除数据(如工具消耗)
+            if (isset($responseData['deduct'])) {
+                if (TestEnvironment::isDebugMode()) {
+                    dump('收获消耗:', $responseData['deduct']);
+                }
+            }
+        }
+        
+        $this->dumpTestEnd('收获奖励验证测试');
+    }
+
+    /**
+     * 创建收获请求
+     */
+    public function create_request_protobuf(): Message
+    {
+        $request = new Request();
+        $harvestRequest = new RequestLandHarvest();
+        
+        // 使用当前测试配置或默认配置
+        $config = $this->currentTestConfig ?? TestEnvironment::getHarvestConfig();
+        
+        $harvestRequest->setLandId($config['land_id']);
+        
+        $request->setLandHarvest($harvestRequest);
+        
+        return $this->createBaseRequest($request);
+    }
+
+    /**
+     * 测试收获参数验证
+     */
+    public function testHarvestParameterValidation()
+    {
+        $this->dumpTestStart('收获参数验证测试');
+        
+        // 测试无效土地ID
+        $this->currentTestConfig = [
+            'land_id' => -1
+        ];
+        
+        $response = $this->executeTestRequest();
+        $this->assertFailureResponse($response);
+        
+        // 测试不存在的土地ID
+        $this->currentTestConfig = [
+            'land_id' => 99999
+        ];
+        
+        $response = $this->executeTestRequest();
+        $this->assertFailureResponse($response, '土地不存在');
+        
+        $this->dumpTestEnd('收获参数验证测试');
+    }
+
+    /**
+     * 测试所有预定义场景
+     */
+    public function testAllHarvestScenarios()
+    {
+        $this->dumpTestStart('所有收获场景测试');
+        
+        // 测试成功场景
+        $successScenarios = TestConfig::getTestScenario('success_scenarios', 'harvest_success');
+        if (!empty($successScenarios)) {
+            if (TestEnvironment::isDebugMode()) {
+                dump('测试成功场景:', $successScenarios);
+            }
+            $this->currentTestConfig = [
+                'land_id' => $successScenarios['land_id']
+            ];
+            
+            $response = $this->executeTestRequest();
+            $this->assertSuccessResponse($response, '收获成功');
+        }
+        
+        $this->dumpTestEnd('所有收获场景测试');
+    }
+}

+ 376 - 0
app/Module/AppGame/Tests/Land/SowHandlerTest.php

@@ -0,0 +1,376 @@
+<?php
+
+namespace App\Module\AppGame\Tests\Land;
+
+use App\Module\AppGame\Tests\TestConfig;
+use App\Module\AppGame\Tests\TestEnvironment;
+use Google\Protobuf\Internal\Message;
+use Uraus\Kku\Request;
+use Uraus\Kku\Request\RequestLandSow;
+use Uraus\Kku\Common\RESPONSE_CODE;
+
+/**
+ * 播种Handler E2E测试
+ * 
+ * 测试播种功能的各种场景,包括成功、失败和边界情况
+ */
+class SowHandlerTest extends DisasterRemovalBaseTest
+{
+    /**
+     * 当前测试配置
+     */
+    private array $currentTestConfig;
+
+    /**
+     * 测试播种成功场景
+     */
+    public function testSowSuccess()
+    {
+        $this->dumpTestStart('播种成功测试');
+        
+        $config = TestEnvironment::getSowConfig();
+        $this->dumpTestConfig($config);
+        $this->currentTestConfig = $config;
+
+        $response = $this->executeTestRequest();
+        $this->assertSuccessResponse($response, '播种成功');
+        
+        $this->dumpTestEnd('播种成功测试');
+    }
+
+    /**
+     * 测试使用无效种子播种
+     */
+    public function testSowWithInvalidSeed()
+    {
+        $this->dumpTestStart('无效种子播种测试');
+        
+        $config = TestEnvironment::getSowConfig();
+        $config['seed_id'] = TestEnvironment::get('TEST_INVALID_ITEM_ID', 999);
+        $this->dumpTestConfig($config);
+        $this->currentTestConfig = $config;
+
+        $response = $this->executeTestRequest();
+        $this->assertFailureResponse($response, '不是种子物品');
+        
+        $this->dumpTestEnd('无效种子播种测试');
+    }
+
+    /**
+     * 测试在其他用户土地播种
+     */
+    public function testSowOnOtherUserLand()
+    {
+        $this->dumpTestStart('其他用户土地播种测试');
+        
+        $config = TestEnvironment::getSowConfig();
+        $config['land_id'] = TestEnvironment::get('TEST_OTHER_USER_LAND_ID', 5);
+        $this->dumpTestConfig($config);
+        $this->currentTestConfig = $config;
+
+        $response = $this->executeTestRequest();
+        $this->assertFailureResponse($response, '土地不存在或不属于当前用户');
+        
+        $this->dumpTestEnd('其他用户土地播种测试');
+    }
+
+    /**
+     * 测试在已有作物的土地播种
+     */
+    public function testSowOnOccupiedLand()
+    {
+        $this->dumpTestStart('已占用土地播种测试');
+        
+        $config = TestEnvironment::getSowConfig();
+        $config['land_id'] = TestEnvironment::get('TEST_OCCUPIED_LAND_ID', 40);
+        $this->dumpTestConfig($config);
+        $this->currentTestConfig = $config;
+
+        $response = $this->executeTestRequest();
+        $this->assertFailureResponse($response, '土地已有作物');
+        
+        $this->dumpTestEnd('已占用土地播种测试');
+    }
+
+    /**
+     * 测试播种性能
+     */
+    public function testSowPerformance()
+    {
+        $this->dumpTestStart('播种性能测试');
+        
+        $config = TestEnvironment::getSowConfig();
+        $performanceConfig = TestEnvironment::getPerformanceConfig();
+        $requestCount = $performanceConfig['request_count'];
+        $maxResponseTime = $performanceConfig['max_response_time'];
+        
+        $this->dumpTestConfig([
+            'sow_config' => $config,
+            'request_count' => $requestCount,
+            'max_response_time' => $maxResponseTime
+        ]);
+        
+        $startTime = microtime(true);
+        $responseTimes = [];
+        
+        for ($i = 1; $i <= $requestCount; $i++) {
+            $requestStartTime = microtime(true);
+            // 使用不同的土地ID避免冲突
+            $testConfig = $config;
+            $testConfig['land_id'] = $config['land_id'] + $i;
+            $this->currentTestConfig = $testConfig;
+            
+            $response = $this->executeTestRequest();
+            
+            $requestEndTime = microtime(true);
+            $requestDuration = ($requestEndTime - $requestStartTime) * 1000;
+            $responseTimes[] = $requestDuration;
+            
+            $this->assertLessThan($maxResponseTime, $requestDuration, 
+                "第 {$i} 个请求响应时间 {$requestDuration}ms 超过限制 {$maxResponseTime}ms");
+                
+            if (TestEnvironment::isDebugMode()) {
+                dump("第 {$i} 个请求耗时: {$requestDuration}ms,响应码: " . $response->getCode());
+            }
+        }
+        
+        $totalTime = (microtime(true) - $startTime) * 1000;
+        $avgTime = array_sum($responseTimes) / count($responseTimes);
+        $maxTime = max($responseTimes);
+        $minTime = min($responseTimes);
+        
+        if (TestEnvironment::isDebugMode()) {
+            dump("播种性能测试结果:", [
+                'total_requests' => $requestCount,
+                'total_time' => $totalTime . 'ms',
+                'average_time' => $avgTime . 'ms',
+                'max_time' => $maxTime . 'ms',
+                'min_time' => $minTime . 'ms',
+                'max_allowed_time' => $maxResponseTime . 'ms'
+            ]);
+        }
+        
+        $this->dumpTestEnd('播种性能测试');
+    }
+
+    /**
+     * 测试重复播种
+     */
+    public function testRepeatedSow()
+    {
+        $this->dumpTestStart('重复播种测试');
+        
+        $config = TestEnvironment::getSowConfig();
+        $this->currentTestConfig = $config;
+        
+        // 第一次播种
+        $response1 = $this->executeTestRequest();
+        $this->dumpResponse($response1, '第一次播种响应');
+        
+        // 第二次播种(应该失败,因为土地已有作物)
+        $response2 = $this->executeTestRequest();
+        $this->dumpResponse($response2, '第二次播种响应');
+        
+        // 验证第一次成功,第二次失败
+        if ($response1->getCode() === RESPONSE_CODE::OK) {
+            $this->assertNotEquals(RESPONSE_CODE::OK, $response2->getCode(), 
+                '第二次播种应该失败,因为土地已有作物');
+        }
+        
+        $this->dumpTestEnd('重复播种测试');
+    }
+
+    /**
+     * 测试批量播种
+     */
+    public function testBatchSow()
+    {
+        $this->dumpTestStart('批量播种测试');
+        
+        $baseConfig = TestEnvironment::getSowConfig();
+        $landIds = [
+            TestEnvironment::get('TEST_SOW_LAND_ID', 10),
+            TestEnvironment::get('TEST_SOW_LAND_ID_2', 50),
+            TestEnvironment::get('TEST_SOW_LAND_ID_3', 51)
+        ];
+        
+        $successCount = 0;
+        $failureCount = 0;
+        
+        foreach ($landIds as $index => $landId) {
+            $this->currentTestConfig = [
+                'land_id' => $landId,
+                'seed_id' => $baseConfig['seed_id']
+            ];
+            
+            $response = $this->executeTestRequest();
+            
+            if ($response->getCode() === RESPONSE_CODE::OK) {
+                $successCount++;
+                if (TestEnvironment::isDebugMode()) {
+                    dump("土地 {$landId} 播种成功");
+                }
+            } else {
+                $failureCount++;
+                if (TestEnvironment::isDebugMode()) {
+                    dump("土地 {$landId} 播种失败: " . $response->getMsg());
+                }
+            }
+        }
+        
+        if (TestEnvironment::isDebugMode()) {
+            dump("批量播种结果:", [
+                'total_lands' => count($landIds),
+                'success_count' => $successCount,
+                'failure_count' => $failureCount
+            ]);
+        }
+        
+        // 至少应该有一个成功
+        $this->assertGreaterThan(0, $successCount, '批量播种中至少应有一个成功');
+        
+        $this->dumpTestEnd('批量播种测试');
+    }
+
+    /**
+     * 测试不同种子类型播种
+     */
+    public function testDifferentSeedTypes()
+    {
+        $this->dumpTestStart('不同种子类型播种测试');
+        
+        $seedIds = [
+            TestEnvironment::get('TEST_SOW_SEED_ID', 201),
+            TestEnvironment::get('TEST_SOW_SEED_ID_2', 202),
+            TestEnvironment::get('TEST_SOW_SEED_ID_3', 203)
+        ];
+        
+        $baseLandId = TestEnvironment::get('TEST_SOW_LAND_ID', 10);
+        
+        foreach ($seedIds as $index => $seedId) {
+            $this->currentTestConfig = [
+                'land_id' => $baseLandId + $index + 10, // 使用不同的土地
+                'seed_id' => $seedId
+            ];
+            
+            $response = $this->executeTestRequest();
+            
+            if (TestEnvironment::isDebugMode()) {
+                dump("种子 {$seedId} 播种结果: " . $response->getCode() . " - " . $response->getMsg());
+            }
+            
+            // 记录结果但不强制要求成功(因为种子可能不存在)
+            $this->assertInstanceOf(\Uraus\Kku\Response::class, $response);
+        }
+        
+        $this->dumpTestEnd('不同种子类型播种测试');
+    }
+
+    /**
+     * 测试播种资源消耗验证
+     */
+    public function testSowResourceConsumption()
+    {
+        $this->dumpTestStart('播种资源消耗验证测试');
+        
+        $config = TestEnvironment::getSowConfig();
+        $this->currentTestConfig = $config;
+        
+        $response = $this->executeTestRequest();
+        
+        if ($response->getCode() === RESPONSE_CODE::OK) {
+            // 验证响应中包含消耗信息
+            $responseJson = $response->serializeToJsonString();
+            $responseData = json_decode($responseJson, true);
+            
+            // 检查是否有扣除数据(种子消耗)
+            if (isset($responseData['deduct'])) {
+                $this->assertNotEmpty($responseData['deduct'], '播种成功应该包含资源消耗信息');
+                if (TestEnvironment::isDebugMode()) {
+                    dump('播种消耗:', $responseData['deduct']);
+                }
+            }
+            
+            // 检查是否有最新数据更新
+            if (isset($responseData['last_data'])) {
+                if (TestEnvironment::isDebugMode()) {
+                    dump('数据更新:', $responseData['last_data']);
+                }
+            }
+        }
+        
+        $this->dumpTestEnd('播种资源消耗验证测试');
+    }
+
+    /**
+     * 创建播种请求
+     */
+    public function create_request_protobuf(): Message
+    {
+        $request = new Request();
+        $sowRequest = new RequestLandSow();
+        
+        // 使用当前测试配置或默认配置
+        $config = $this->currentTestConfig ?? TestEnvironment::getSowConfig();
+        
+        $sowRequest->setLandId($config['land_id']);
+        $sowRequest->setUserItemId($config['seed_id']);
+        
+        $request->setLandSow($sowRequest);
+        
+        return $this->createBaseRequest($request);
+    }
+
+    /**
+     * 测试播种参数验证
+     */
+    public function testSowParameterValidation()
+    {
+        $this->dumpTestStart('播种参数验证测试');
+        
+        // 测试无效土地ID
+        $this->currentTestConfig = [
+            'land_id' => -1,
+            'seed_id' => TestEnvironment::get('TEST_SOW_SEED_ID', 201)
+        ];
+        
+        $response = $this->executeTestRequest();
+        $this->assertFailureResponse($response);
+        
+        // 测试无效种子ID
+        $this->currentTestConfig = [
+            'land_id' => TestEnvironment::get('TEST_SOW_LAND_ID', 10),
+            'seed_id' => -1
+        ];
+        
+        $response = $this->executeTestRequest();
+        $this->assertFailureResponse($response);
+        
+        $this->dumpTestEnd('播种参数验证测试');
+    }
+
+    /**
+     * 测试所有预定义场景
+     */
+    public function testAllSowScenarios()
+    {
+        $this->dumpTestStart('所有播种场景测试');
+        
+        // 测试成功场景
+        $successScenarios = TestConfig::getTestScenario('success_scenarios', 'sow_success');
+        if (!empty($successScenarios)) {
+            if (TestEnvironment::isDebugMode()) {
+                dump('测试成功场景:', $successScenarios);
+            }
+            $this->currentTestConfig = [
+                'land_id' => $successScenarios['land_id'],
+                'seed_id' => $successScenarios['seed_id']
+            ];
+            
+            $response = $this->executeTestRequest();
+            $this->assertSuccessResponse($response, '播种成功');
+        }
+        
+        $this->dumpTestEnd('所有播种场景测试');
+    }
+}

+ 318 - 0
app/Module/AppGame/Tests/TestEnvironment.php

@@ -0,0 +1,318 @@
+<?php
+
+namespace App\Module\AppGame\Tests;
+
+/**
+ * 测试环境配置加载器
+ * 
+ * 独立于代码的环境变量管理,从.env.testing文件加载配置
+ */
+class TestEnvironment
+{
+    /**
+     * 环境变量缓存
+     */
+    private static array $envCache = [];
+
+    /**
+     * 是否已加载环境变量
+     */
+    private static bool $loaded = false;
+
+    /**
+     * 加载测试环境变量
+     */
+    public static function load(): void
+    {
+        if (self::$loaded) {
+            return;
+        }
+
+        $envFile = __DIR__ . '/.env.testing';
+        
+        if (!file_exists($envFile)) {
+            throw new \RuntimeException("测试环境配置文件不存在: {$envFile}");
+        }
+
+        $lines = file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
+        
+        foreach ($lines as $line) {
+            $line = trim($line);
+            
+            // 跳过注释行
+            if (empty($line) || strpos($line, '#') === 0) {
+                continue;
+            }
+            
+            // 解析环境变量
+            if (strpos($line, '=') !== false) {
+                list($key, $value) = explode('=', $line, 2);
+                $key = trim($key);
+                $value = trim($value);
+                
+                // 移除引号
+                if (preg_match('/^"(.*)"$/', $value, $matches)) {
+                    $value = $matches[1];
+                } elseif (preg_match("/^'(.*)'$/", $value, $matches)) {
+                    $value = $matches[1];
+                }
+                
+                self::$envCache[$key] = $value;
+                
+                // 同时设置到系统环境变量
+                if (!getenv($key)) {
+                    putenv("{$key}={$value}");
+                }
+            }
+        }
+        
+        self::$loaded = true;
+    }
+
+    /**
+     * 获取环境变量值
+     */
+    public static function get(string $key, $default = null)
+    {
+        self::load();
+        
+        // 优先从系统环境变量获取
+        $value = getenv($key);
+        if ($value !== false) {
+            return self::parseValue($value);
+        }
+        
+        // 从缓存获取
+        if (isset(self::$envCache[$key])) {
+            return self::parseValue(self::$envCache[$key]);
+        }
+        
+        return $default;
+    }
+
+    /**
+     * 解析环境变量值
+     */
+    private static function parseValue($value)
+    {
+        if ($value === 'true' || $value === 'TRUE') {
+            return true;
+        }
+        
+        if ($value === 'false' || $value === 'FALSE') {
+            return false;
+        }
+        
+        if ($value === 'null' || $value === 'NULL') {
+            return null;
+        }
+        
+        if (is_numeric($value)) {
+            return strpos($value, '.') !== false ? (float)$value : (int)$value;
+        }
+        
+        return $value;
+    }
+
+    /**
+     * 获取测试服务器URL
+     */
+    public static function getTestUrl(): string
+    {
+        return self::get('UNITTEST_URL', 'http://localhost:8000');
+    }
+
+    /**
+     * 获取测试用户配置
+     */
+    public static function getTestUser(): array
+    {
+        return [
+            'user_id' => self::get('TEST_USER_ID', 1),
+            'password' => self::get('TEST_USER_PASSWORD', 'test123456'),
+            'username' => self::get('TEST_USER_USERNAME', 'test_user'),
+            'email' => self::get('TEST_USER_EMAIL', 'test@example.com'),
+        ];
+    }
+
+    /**
+     * 获取除虫测试配置
+     */
+    public static function getPesticideConfig(): array
+    {
+        return [
+            'land_id' => self::get('TEST_PESTICIDE_LAND_ID', 1),
+            'item_id' => self::get('TEST_PESTICIDE_ITEM_ID', 101),
+            'success_rate' => self::get('TEST_PESTICIDE_SUCCESS_RATE', 80),
+        ];
+    }
+
+    /**
+     * 获取除草测试配置
+     */
+    public static function getWeedicideConfig(): array
+    {
+        return [
+            'land_id' => self::get('TEST_WEEDICIDE_LAND_ID', 2),
+            'item_id' => self::get('TEST_WEEDICIDE_ITEM_ID', 102),
+            'success_rate' => self::get('TEST_WEEDICIDE_SUCCESS_RATE', 75),
+        ];
+    }
+
+    /**
+     * 获取浇水测试配置
+     */
+    public static function getWateringConfig(): array
+    {
+        return [
+            'land_id' => self::get('TEST_WATERING_LAND_ID', 3),
+            'item_id' => self::get('TEST_WATERING_ITEM_ID', 103),
+            'success_rate' => self::get('TEST_WATERING_SUCCESS_RATE', 90),
+        ];
+    }
+
+    /**
+     * 获取播种测试配置
+     */
+    public static function getSowConfig(): array
+    {
+        return [
+            'land_id' => self::get('TEST_SOW_LAND_ID', 10),
+            'seed_id' => self::get('TEST_SOW_SEED_ID', 201),
+            'seed_name' => self::get('TEST_SOW_SEED_NAME', '测试种子'),
+        ];
+    }
+
+    /**
+     * 获取收获测试配置
+     */
+    public static function getHarvestConfig(): array
+    {
+        return [
+            'land_id' => self::get('TEST_HARVEST_LAND_ID', 11),
+            'crop_id' => self::get('TEST_HARVEST_CROP_ID', 301),
+        ];
+    }
+
+    /**
+     * 获取施肥测试配置
+     */
+    public static function getFertilizerConfig(): array
+    {
+        return [
+            'land_id' => self::get('TEST_FERTILIZER_LAND_ID', 12),
+            'item_id' => self::get('TEST_FERTILIZER_ITEM_ID', 401),
+            'item_name' => self::get('TEST_FERTILIZER_ITEM_NAME', '有机肥料'),
+        ];
+    }
+
+    /**
+     * 获取宠物测试配置
+     */
+    public static function getPetConfig(): array
+    {
+        return [
+            'pet_id' => self::get('TEST_PET_ID', 1001),
+            'pet_name' => self::get('TEST_PET_NAME', '测试宠物'),
+            'food_id' => self::get('TEST_PET_FOOD_ID', 601),
+            'skill_id' => self::get('TEST_PET_SKILL_ID', 701),
+        ];
+    }
+
+    /**
+     * 获取商店测试配置
+     */
+    public static function getShopConfig(): array
+    {
+        return [
+            'item_id' => self::get('TEST_SHOP_ITEM_ID', 1201),
+            'price' => self::get('TEST_SHOP_ITEM_PRICE', 100),
+            'currency_type' => self::get('TEST_SHOP_CURRENCY_TYPE', 1),
+        ];
+    }
+
+    /**
+     * 获取好友测试配置
+     */
+    public static function getFriendConfig(): array
+    {
+        return [
+            'friend_user_id' => self::get('TEST_FRIEND_USER_ID', 2001),
+            'friend_username' => self::get('TEST_FRIEND_USERNAME', 'friend_user'),
+            'apply_id' => self::get('TEST_FRIEND_APPLY_ID', 2101),
+        ];
+    }
+
+    /**
+     * 获取性能测试配置
+     */
+    public static function getPerformanceConfig(): array
+    {
+        return [
+            'request_count' => self::get('TEST_PERFORMANCE_REQUEST_COUNT', 10),
+            'max_response_time' => self::get('TEST_PERFORMANCE_MAX_RESPONSE_TIME', 5000),
+        ];
+    }
+
+    /**
+     * 获取并发测试配置
+     */
+    public static function getConcurrentConfig(): array
+    {
+        return [
+            'request_count' => self::get('TEST_CONCURRENT_REQUEST_COUNT', 5),
+            'max_wait_time' => self::get('TEST_CONCURRENT_MAX_WAIT_TIME', 10),
+        ];
+    }
+
+    /**
+     * 获取概率测试配置
+     */
+    public static function getProbabilityConfig(): array
+    {
+        return [
+            'attempts' => self::get('TEST_PROBABILITY_ATTEMPTS', 20),
+            'error_margin' => self::get('TEST_PROBABILITY_ERROR_MARGIN', 30),
+        ];
+    }
+
+    /**
+     * 获取调试配置
+     */
+    public static function getDebugConfig(): array
+    {
+        return [
+            'enabled' => self::get('TEST_DEBUG_ENABLED', true),
+            'dump_requests' => self::get('TEST_DEBUG_DUMP_REQUESTS', true),
+            'dump_responses' => self::get('TEST_DEBUG_DUMP_RESPONSES', true),
+            'measure_time' => self::get('TEST_DEBUG_MEASURE_TIME', true),
+        ];
+    }
+
+    /**
+     * 检查是否为调试模式
+     */
+    public static function isDebugMode(): bool
+    {
+        return self::get('TEST_DEBUG_ENABLED', true);
+    }
+
+    /**
+     * 获取所有环境变量
+     */
+    public static function getAll(): array
+    {
+        self::load();
+        return self::$envCache;
+    }
+
+    /**
+     * 重新加载环境变量
+     */
+    public static function reload(): void
+    {
+        self::$loaded = false;
+        self::$envCache = [];
+        self::load();
+    }
+}

+ 136 - 0
app/Module/AppGame/Tests/test_runner.php

@@ -0,0 +1,136 @@
+<?php
+
+/**
+ * 简单的测试运行器
+ * 用于快速验证测试配置和环境
+ */
+
+require_once __DIR__ . '/../../../../vendor/autoload.php';
+
+use App\Module\AppGame\Tests\TestEnvironment;
+use App\Module\AppGame\Tests\TestConfig;
+
+echo "=== 测试环境验证 ===\n";
+
+try {
+    // 1. 加载环境变量
+    echo "1. 加载测试环境变量...\n";
+    TestEnvironment::load();
+    echo "✅ 环境变量加载成功\n";
+
+    // 2. 验证基本配置
+    echo "\n2. 验证基本配置...\n";
+    $testUrl = TestEnvironment::getTestUrl();
+    echo "测试URL: {$testUrl}\n";
+
+    $testUser = TestEnvironment::getTestUser();
+    echo "测试用户: " . json_encode($testUser, JSON_UNESCAPED_UNICODE) . "\n";
+
+    // 3. 验证灾害去除配置
+    echo "\n3. 验证灾害去除配置...\n";
+
+    $pesticideConfig = TestConfig::getPesticideTestConfig();
+    echo "除虫配置: " . json_encode($pesticideConfig, JSON_UNESCAPED_UNICODE) . "\n";
+
+    $weedicideConfig = TestConfig::getWeedicideTestConfig();
+    echo "除草配置: " . json_encode($weedicideConfig, JSON_UNESCAPED_UNICODE) . "\n";
+
+    $wateringConfig = TestConfig::getWateringTestConfig();
+    echo "浇水配置: " . json_encode($wateringConfig, JSON_UNESCAPED_UNICODE) . "\n";
+
+    // 4. 验证测试物品配置
+    echo "\n4. 验证测试物品配置...\n";
+
+    $pesticideItem = TestConfig::getTestItem('pesticide');
+    echo "除虫剂: " . json_encode($pesticideItem, JSON_UNESCAPED_UNICODE) . "\n";
+
+    $weedicideItem = TestConfig::getTestItem('weedicide');
+    echo "除草剂: " . json_encode($weedicideItem, JSON_UNESCAPED_UNICODE) . "\n";
+
+    $wateringItem = TestConfig::getTestItem('watering_tool');
+    echo "浇水道具: " . json_encode($wateringItem, JSON_UNESCAPED_UNICODE) . "\n";
+
+    // 5. 验证测试土地配置
+    echo "\n5. 验证测试土地配置...\n";
+
+    $pestLand = TestConfig::getTestLand('pest_land');
+    echo "虫害土地: " . json_encode($pestLand, JSON_UNESCAPED_UNICODE) . "\n";
+
+    $weedLand = TestConfig::getTestLand('weed_land');
+    echo "杂草土地: " . json_encode($weedLand, JSON_UNESCAPED_UNICODE) . "\n";
+
+    $droughtLand = TestConfig::getTestLand('drought_land');
+    echo "干旱土地: " . json_encode($droughtLand, JSON_UNESCAPED_UNICODE) . "\n";
+
+    // 6. 验证测试场景配置
+    echo "\n6. 验证测试场景配置...\n";
+
+    $pesticideSuccess = TestConfig::getTestScenario('success_scenarios', 'pesticide_success');
+    echo "除虫成功场景: " . json_encode($pesticideSuccess, JSON_UNESCAPED_UNICODE) . "\n";
+
+    $invalidItem = TestConfig::getTestScenario('failure_scenarios', 'invalid_item');
+    echo "无效物品场景: " . json_encode($invalidItem, JSON_UNESCAPED_UNICODE) . "\n";
+
+    // 7. 验证调试配置
+    echo "\n7. 验证调试配置...\n";
+    $debugConfig = TestEnvironment::getDebugConfig();
+    echo "调试配置: " . json_encode($debugConfig, JSON_UNESCAPED_UNICODE) . "\n";
+
+    $isDebugMode = TestEnvironment::isDebugMode();
+    echo "调试模式: " . ($isDebugMode ? '开启' : '关闭') . "\n";
+
+    // 8. 验证性能配置
+    echo "\n8. 验证性能配置...\n";
+    $performanceConfig = TestEnvironment::getPerformanceConfig();
+    echo "性能配置: " . json_encode($performanceConfig, JSON_UNESCAPED_UNICODE) . "\n";
+
+    $concurrentConfig = TestEnvironment::getConcurrentConfig();
+    echo "并发配置: " . json_encode($concurrentConfig, JSON_UNESCAPED_UNICODE) . "\n";
+
+    $probabilityConfig = TestEnvironment::getProbabilityConfig();
+    echo "概率配置: " . json_encode($probabilityConfig, JSON_UNESCAPED_UNICODE) . "\n";
+
+    // 9. 测试网络连接
+    echo "\n9. 测试网络连接...\n";
+    $ch = curl_init();
+    curl_setopt($ch, CURLOPT_URL, $testUrl);
+    curl_setopt($ch, CURLOPT_TIMEOUT, 5);
+    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+    curl_setopt($ch, CURLOPT_NOBODY, true);
+    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
+
+    $result = curl_exec($ch);
+    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+    $error = curl_error($ch);
+    curl_close($ch);
+
+    if ($result !== false && $httpCode > 0) {
+        echo "✅ 服务器连接正常 (HTTP {$httpCode})\n";
+    } else {
+        echo "❌ 服务器连接失败: {$error}\n";
+    }
+
+    echo "\n=== 环境验证完成 ===\n";
+    echo "✅ 所有配置验证通过,可以开始运行测试\n";
+
+} catch (Exception $e) {
+    echo "❌ 环境验证失败: " . $e->getMessage() . "\n";
+    echo "错误详情: " . $e->getTraceAsString() . "\n";
+    exit(1);
+}
+
+// 10. 显示使用说明
+echo "\n=== 使用说明 ===\n";
+echo "环境验证完成,您可以使用以下命令运行测试:\n\n";
+echo "# 运行所有测试\n";
+echo "./app/Module/AppGame/Tests/run_tests.sh all\n\n";
+echo "# 运行特定测试\n";
+echo "./app/Module/AppGame/Tests/run_tests.sh pesticide\n";
+echo "./app/Module/AppGame/Tests/run_tests.sh weedicide\n";
+echo "./app/Module/AppGame/Tests/run_tests.sh watering\n\n";
+echo "# 使用Laravel测试命令\n";
+echo "php artisan test app/Module/AppGame/Tests/Land/PesticideHandlerTest.php\n\n";
+echo "# 准备测试数据\n";
+echo "php app/Module/AppGame/Tests/prepare_test_data.php prepare\n\n";
+echo "# 清理测试数据\n";
+echo "php app/Module/AppGame/Tests/prepare_test_data.php cleanup\n\n";