上篇講到 Protobuf 的編譯與序列化與反序列化,加上前面幾期的內容,都以說明居多,有些人可能已經等待不及,想知道實際運行起來是什麼樣子。
這一期就稍微跳快一點,來測試一下我們請 AI 助手做出來的系統。
同時也會正式地公佈我們 GitHub 的 repo,讓有心想自己動手調試或者覺得我講得太慢或者幫我 debug 的人,都可以直接看程式碼自行研讀。
這次會用到兩個 Python 的測試框架:Unittest 跟 Pytest 。不過並非本次主題且限於篇幅,就不細講。
以下引用其他人的文章,請各位自行跳轉研讀:
鐵人賽: 單元測試框架
鐵人賽: Pytest系列介紹
簡單說呢,Unittest是古董級的產品。Pytest是比較現代化、多功能的,也建議大家真的要用以Pytest為主。
在閱讀這篇文章的同時,你們也可以同步去 GitHub 下載,然後在本地操作。
下載後,你可以獲得什麼呢?
GitHub 的 repo位置如下: 如果看到其他的東西,就自行解讀吧,哈哈哈。
https://github.com/scotthsiao/sample_gaming_sut
首先,啓動服務端
(venv) PS C:\Users\Scott\PycharmProjects\sample_gaming_sut> python .\scripts\start_server.py
2025-09-14 06:46:32,479 - src.tornado_game_server - INFO - Starting Tornado game server on localhost:8767
2025-09-14 06:46:32,503 - src.tornado_game_server - INFO - Tornado game server started successfully
簡單解析服務端過程的日誌
接著啟動客戶端的 Demo 模式
(venv) PS C:\Users\Scott\PycharmProjects\sample_gaming_sut> python .\scripts\start_client.py --demo
INFO:src.game_client:Connected to ws://localhost:8767
INFO:src.game_client:Login successful! User ID: 1, Balance: 1000000
INFO:src.game_client:Joined room 1 with 1 players
INFO:src.game_client:Jackpot pool: 0
=== Demo Game Session ===
INFO:src.game_client:Starting game session
INFO:src.game_client:Bet placed! Bet ID: 94fe7e1f-cdc7-420e-810b-d5dbb186c250, Remaining balance: 999900
INFO:src.game_client:Server created round ID: 3d7c8abc-590b-4dad-96ad-15d8331fa2bb
INFO:src.game_client:Bet placed! Bet ID: 4481d707-6b43-4ca7-afa5-57fb19559db9, Remaining balance: 999850
INFO:src.game_client:Bet placed! Bet ID: fbc436ab-615b-4fa6-8bb8-86e4e50c0076, Remaining balance: 999825
INFO:src.game_client:Betting phase completed successfully
INFO:src.game_client:Game session completed:
INFO:src.game_client: Dice result: 3
INFO:src.game_client: Total winnings: 600
INFO:src.game_client: New balance: 1000425
INFO:src.game_client: Jackpot pool: 1
INFO:src.game_client: Bet on 3: $100 - WON (payout: $600)
INFO:src.game_client: Bet on 6: $50 - LOST (payout: $0)
INFO:src.game_client: Bet on 1: $25 - LOST (payout: $0)
=== Demo Completed Successfully ===
INFO:src.game_client:Disconnected from server
(venv) PS C:\Users\Scott\PycharmProjects\sample_gaming_sut> python .\scripts\start_client.py --demo
INFO:src.game_client:Connected to ws://localhost:8767
INFO:src.game_client:Login successful! User ID: 1, Balance: 1000425
INFO:src.game_client:Joined room 1 with 1 players
INFO:src.game_client:Jackpot pool: 1
=== Demo Game Session ===
INFO:src.game_client:Starting game session
INFO:src.game_client:Bet placed! Bet ID: 05dfac74-0b2e-4500-99a9-f8823376cf9e, Remaining balance: 1000325
INFO:src.game_client:Server created round ID: da89d310-0ce3-44d1-ac80-9af0e3133bca
INFO:src.game_client:Bet placed! Bet ID: 23d6bdcd-ad9d-4970-b2ec-caceb322e57f, Remaining balance: 1000275
INFO:src.game_client:Bet placed! Bet ID: 8cf0cbd1-80c1-4840-b9e3-fbf0fe6fa966, Remaining balance: 1000250
INFO:src.game_client:Betting phase completed successfully
INFO:src.game_client:Game session completed:
INFO:src.game_client: Dice result: 2
INFO:src.game_client: Total winnings: 0
INFO:src.game_client: New balance: 1000250
INFO:src.game_client: Jackpot pool: 2
INFO:src.game_client: Bet on 3: $100 - LOST (payout: $0)
INFO:src.game_client: Bet on 6: $50 - LOST (payout: $0)
INFO:src.game_client: Bet on 1: $25 - LOST (payout: $0)
=== Demo Completed Successfully ===
INFO:src.game_client:Disconnected from server
(venv) PS C:\Users\Scott\PycharmProjects\sample_gaming_sut> python .\scripts\start_client.py --demo
INFO:src.game_client:Connected to ws://localhost:8767
INFO:src.game_client:Login successful! User ID: 1, Balance: 1000250
INFO:src.game_client:Joined room 1 with 1 players
INFO:src.game_client:Jackpot pool: 2
=== Demo Game Session ===
INFO:src.game_client:Starting game session
INFO:src.game_client:Bet placed! Bet ID: 0e0985d7-da8c-40c1-b925-d05e3e767bb3, Remaining balance: 1000150
INFO:src.game_client:Server created round ID: af0806d0-1f2d-4fca-8bff-2dc15c347cd6
INFO:src.game_client:Bet placed! Bet ID: b46144f7-e12f-47eb-8736-aa86d982b05f, Remaining balance: 1000100
INFO:src.game_client:Bet placed! Bet ID: 12eb8b47-2352-41e2-8fbe-2ca14804fc8c, Remaining balance: 1000075
INFO:src.game_client:Betting phase completed successfully
INFO:src.game_client:Game session completed:
INFO:src.game_client: Dice result: 6
INFO:src.game_client: Total winnings: 300
INFO:src.game_client: New balance: 1000375
INFO:src.game_client: Jackpot pool: 3
INFO:src.game_client: Bet on 3: $100 - LOST (payout: $0)
INFO:src.game_client: Bet on 6: $50 - WON (payout: $300)
INFO:src.game_client: Bet on 1: $25 - LOST (payout: $0)
=== Demo Completed Successfully ===
INFO:src.game_client:Disconnected from server
(venv) PS C:\Users\Scott\PycharmProjects\sample_gaming_sut>
簡單解析客戶端過程的日誌
(venv) PS C:\Users\Scott\PycharmProjects\sample_gaming_sut> python.exe .\tests\test_game_system.py
Running comprehensive test suite for gaming system...
test_create_user (__main__.TestUser.test_create_user)
Test user creation with password hashing ... ok
test_session_token_generation (__main__.TestUser.test_session_token_generation)
Test session token generation and validation ... ok
test_player_management (__main__.TestRoom.test_player_management)
Test adding and removing players ... ok
test_room_creation (__main__.TestRoom.test_room_creation)
Test room creation and basic operations ... ok
test_bet_management (__main__.TestGameRound.test_bet_management)
Test adding bets to a round ... ok
test_result_calculation (__main__.TestGameRound.test_result_calculation)
Test calculating round results ... ok
test_round_creation (__main__.TestGameRound.test_round_creation)
Test game round creation ... ok
test_calculate_results_winning_bet (__main__.TestGameEngine.test_calculate_results_winning_bet)
Test result calculation with winning bet ... 2025-09-21 17:00:35,764 - src.game_engine - INFO - User 1 placed bet d76cc5a3-ba78-4315-8577-4fb6434a9038 for 100 on dice face 3
2025-09-21 17:00:35,764 - src.game_engine - INFO - User 1 finished betting for round 6f6ae8dc-e25a-4865-89bd-67d8c93f2e2a
2025-09-21 17:00:35,765 - src.game_engine - INFO - User 1 round 6f6ae8dc-e25a-4865-89bd-67d8c93f2e2a results: dice=3, winnings=600
ok
test_get_user_snapshot (__main__.TestGameEngine.test_get_user_snapshot)
Test getting user snapshot ... 2025-09-21 17:00:37,767 - src.game_engine - INFO - User 1 placed bet 27d7c380-a69d-4ba6-bc79-05a09d3deb7d for 100 on dice face 3
ok
test_place_bet_insufficient_balance (__main__.TestGameEngine.test_place_bet_insufficient_balance)
Test bet placement with insufficient balance ... ok
test_place_bet_invalid_amount (__main__.TestGameEngine.test_place_bet_invalid_amount)
Test bet placement with invalid bet amount ... ok
test_place_bet_invalid_dice_face (__main__.TestGameEngine.test_place_bet_invalid_dice_face)
Test bet placement with invalid dice face ... ok
test_place_bet_success (__main__.TestGameEngine.test_place_bet_success)
Test successful bet placement ... 2025-09-21 17:00:45,044 - src.game_engine - INFO - User 1 placed bet 037221f5-ee68-4251-ab93-1769ed4351d9 for 100 on dice face 3
ok
test_room_management (__main__.TestGameState.test_room_management)
Test room join/leave functionality ... ok
test_user_authentication (__main__.TestGameState.test_user_authentication)
Test user authentication ... 2025-09-21 17:00:48,663 - src.models - INFO - User testuser1 authenticated successfully, session token generated
2025-09-21 17:00:49,164 - src.models - WARNING - Authentication failed for username: testuser1
ok
test_bet_placement_request (__main__.TestProtocolBuffers.test_bet_placement_request)
Test BetPlacementRequest message ... ok
test_login_request_serialization (__main__.TestProtocolBuffers.test_login_request_serialization)
Test LoginRequest message serialization ... ok
test_reckon_result_response (__main__.TestProtocolBuffers.test_reckon_result_response)
Test ReckonResultResponse message with bet results ... ok
test_bet_validation_errors (__main__.TestErrorHandling.test_bet_validation_errors)
Test bet validation error scenarios ... ok
test_invalid_user_operations (__main__.TestErrorHandling.test_invalid_user_operations)
Test operations with invalid user IDs ... ok
test_concurrent_bet_placement (__main__.TestConcurrency.test_concurrent_bet_placement)
Test placing bets concurrently ... 2025-09-21 17:00:55,408 - src.game_engine - INFO - User 1 placed bet 3dde0682-c342-4884-a3ee-dc5c38dc31dc for 100 on dice face 3
2025-09-21 17:00:55,408 - src.game_engine - INFO - User 2 placed bet 0f3756d1-17b5-496b-a8dc-72fb604ec0fb for 100 on dice face 3
2025-09-21 17:00:55,409 - src.game_engine - INFO - User 3 placed bet 856a5429-6b74-4fe0-a24c-4c01ae432578 for 100 on dice face 3
2025-09-21 17:00:55,409 - src.game_engine - INFO - User 4 placed bet 200e4b6c-3374-484c-aab2-6f3fdc43a992 for 100 on dice face 3
2025-09-21 17:00:55,409 - src.game_engine - INFO - User 5 placed bet 82d51581-cfec-45c7-88bf-d4d166124ffb for 100 on dice face 3
ok
test_tornado_server_creation (__main__.TestTornadoGameServer.test_tornado_server_creation)
Test Tornado server can be created ... ok
test_tornado_server_custom_config (__main__.TestTornadoGameServer.test_tornado_server_custom_config)
Test Tornado server with custom configuration ... ok
test_tornado_server_has_game_engine_access (__main__.TestTornadoServerIntegration.test_tornado_server_has_game_engine_access)
Test that Tornado server properly integrates with game engine ... ok
test_tornado_server_payout_calculation_integration (__main__.TestTornadoServerIntegration.test_tornado_server_payout_calculation_integration)
Test complete payout calculation flow through game engine ... 2025-09-21 17:00:57,996 - src.game_engine - INFO - User 1 placed bet 4fcdeb1a-c675-426c-abfe-a0632a701868 for 100 on dice face 3
2025-09-21 17:00:57,996 - src.game_engine - INFO - User 1 finished betting for round c2c968f7-27bf-4680-abbb-0cbdaf657cd8
2025-09-21 17:00:57,997 - src.game_engine - INFO - User 1 round c2c968f7-27bf-4680-abbb-0cbdaf657cd8 results: dice=3, winnings=600
ok
test_tornado_server_payout_fix (__main__.TestTornadoServerPayoutFix.test_tornado_server_payout_fix)
Test that Tornado server has correct payout calculation ... ok
test_client_server_communication (__main__.TestIntegration.test_client_server_communication)
Test basic client-server communication ... 2025-09-21 17:00:59,270 - src.game_server - INFO - Starting game server on localhost:8766
2025-09-21 17:00:59,292 - websockets.server - INFO - server listening on [::1]:8766
2025-09-21 17:00:59,292 - websockets.server - INFO - server listening on 127.0.0.1:8766
2025-09-21 17:00:59,293 - src.game_server - INFO - Game server started successfully
🎮 Game server running on ws://localhost:8766
Press Ctrl+C to stop the server
2025-09-21 17:00:59,782 - websockets.server - INFO - connection open
2025-09-21 17:00:59,782 - src.game_server - INFO - New client connected: ('::1', 55673, 0, 0)
2025-09-21 17:00:59,782 - src.game_client - INFO - Connected to ws://localhost:8766
2025-09-21 17:01:00,008 - src.models - INFO - User testuser1 authenticated successfully, session token generated
2025-09-21 17:01:00,009 - src.game_server - INFO - User testuser1 logged in successfully
2025-09-21 17:01:00,010 - src.game_client - INFO - Login successful! User ID: 1, Balance: 1000000
2025-09-21 17:01:00,010 - src.game_server - INFO - User testuser1 joined room 1
2025-09-21 17:01:00,011 - src.game_client - INFO - Joined room 1 with 1 players
2025-09-21 17:01:00,011 - src.game_client - INFO - Jackpot pool: 0
2025-09-21 17:01:00,012 - src.game_client - INFO - Disconnected from server
2025-09-21 17:01:00,012 - src.game_server - INFO - Client ('::1', 55673, 0, 0) disconnected normally
2025-09-21 17:01:00,013 - src.game_server - INFO - Server task cancelled
ok
test_complete_game_flow (__main__.TestIntegration.test_complete_game_flow)
Test a complete game flow from login to results ... 2025-09-21 17:01:01,254 - src.game_server - INFO - Starting game server on localhost:8766
2025-09-21 17:01:01,256 - websockets.server - INFO - server listening on [::1]:8766
2025-09-21 17:01:01,257 - websockets.server - INFO - server listening on 127.0.0.1:8766
2025-09-21 17:01:01,257 - src.game_server - INFO - Game server started successfully
🎮 Game server running on ws://localhost:8766
Press Ctrl+C to stop the server
2025-09-21 17:01:01,768 - websockets.server - INFO - connection open
2025-09-21 17:01:01,768 - src.game_server - INFO - New client connected: ('::1', 55676, 0, 0)
2025-09-21 17:01:01,768 - src.game_client - INFO - Connected to ws://localhost:8766
2025-09-21 17:01:02,018 - src.models - INFO - User testuser1 authenticated successfully, session token generated
2025-09-21 17:01:02,018 - src.game_server - INFO - User testuser1 logged in successfully
2025-09-21 17:01:02,018 - src.game_client - INFO - Login successful! User ID: 1, Balance: 1000000
2025-09-21 17:01:02,019 - src.game_server - INFO - User testuser1 joined room 1
2025-09-21 17:01:02,019 - src.game_client - INFO - Joined room 1 with 1 players
2025-09-21 17:01:02,020 - src.game_client - INFO - Jackpot pool: 0
2025-09-21 17:01:02,020 - src.game_client - INFO - Starting game session
2025-09-21 17:01:02,020 - src.game_engine - INFO - User 1 placed bet 58e7a854-8d7b-4583-b4d5-66515fe9b53a for 100 on dice face 3
2025-09-21 17:01:02,021 - src.game_client - INFO - Bet placed! Bet ID: 58e7a854-8d7b-4583-b4d5-66515fe9b53a, Remaining balance: 999900
2025-09-21 17:01:02,021 - src.game_client - INFO - Server created round ID: 2a1854c2-ece8-40b3-a96c-c2998b8019e8
2025-09-21 17:01:02,021 - src.game_engine - INFO - User 1 placed bet 3ac6cfef-8765-4ea0-b0bd-175bf80e378b for 50 on dice face 6
2025-09-21 17:01:02,022 - src.game_client - INFO - Bet placed! Bet ID: 3ac6cfef-8765-4ea0-b0bd-175bf80e378b, Remaining balance: 999850
2025-09-21 17:01:02,022 - src.game_engine - INFO - User 1 finished betting for round 2a1854c2-ece8-40b3-a96c-c2998b8019e8
2025-09-21 17:01:02,022 - src.game_client - INFO - Betting phase completed successfully
2025-09-21 17:01:02,023 - src.game_engine - INFO - User 1 round 2a1854c2-ece8-40b3-a96c-c2998b8019e8 results: dice=3, winnings=600
2025-09-21 17:01:02,023 - src.game_client - INFO - Game session completed:
2025-09-21 17:01:02,024 - src.game_client - INFO - Dice result: 3
2025-09-21 17:01:02,024 - src.game_client - INFO - Total winnings: 600
2025-09-21 17:01:02,024 - src.game_client - INFO - New balance: 1000450
2025-09-21 17:01:02,024 - src.game_client - INFO - Jackpot pool: 1
2025-09-21 17:01:02,024 - src.game_client - INFO - Bet on 3: $100 - WON (payout: $600)
2025-09-21 17:01:02,025 - src.game_client - INFO - Bet on 6: $50 - LOST (payout: $0)
2025-09-21 17:01:02,026 - src.game_client - INFO - Disconnected from server
2025-09-21 17:01:02,026 - src.game_server - INFO - Client ('::1', 55676, 0, 0) disconnected normally
2025-09-21 17:01:02,027 - src.game_server - INFO - Server task cancelled
ok
----------------------------------------------------------------------
Ran 28 tests in 29.331s
OK
🎉 All tests passed!
(venv) PS C:\Users\Scott\PycharmProjects\sample_gaming_sut>
(venv) PS C:\Users\Scott\PycharmProjects\sample_gaming_sut> pytest tests/test_game_system_pytest.py -v
========================================================================================= test session starts ==========================================================================================
platform win32 -- Python 3.12.10, pytest-8.4.2, pluggy-1.6.0 -- C:\Users\Scott\PycharmProjects\sample_gaming_sut\venv\Scripts\python.exe
cachedir: .pytest_cache
rootdir: C:\Users\Scott\PycharmProjects\sample_gaming_sut
plugins: asyncio-1.1.0
asyncio: mode=Mode.STRICT, asyncio_default_fixture_loop_scope=None, asyncio_default_test_loop_scope=function
collected 23 items
tests/test_game_system_pytest.py::test_create_user PASSED [ 4%]
tests/test_game_system_pytest.py::test_session_token_generation PASSED [ 8%]
tests/test_game_system_pytest.py::test_room_add_player PASSED [ 13%]
tests/test_game_system_pytest.py::test_room_remove_player PASSED [ 17%]
tests/test_game_system_pytest.py::test_game_round_creation PASSED [ 21%]
tests/test_game_system_pytest.py::test_game_round_add_bet PASSED [ 26%]
tests/test_game_system_pytest.py::test_game_round_calculate_results PASSED [ 30%]
tests/test_game_system_pytest.py::test_place_bet_success PASSED [ 34%]
tests/test_game_system_pytest.py::test_place_bet_insufficient_balance PASSED [ 39%]
tests/test_game_system_pytest.py::test_place_bet_invalid_dice_face PASSED [ 43%]
tests/test_game_system_pytest.py::test_place_bet_invalid_amount PASSED [ 47%]
tests/test_game_system_pytest.py::test_calculate_results_winning_bet PASSED [ 52%]
tests/test_game_system_pytest.py::test_get_user_snapshot PASSED [ 56%]
tests/test_game_system_pytest.py::test_authenticate_user PASSED [ 60%]
tests/test_game_system_pytest.py::test_join_room PASSED [ 65%]
tests/test_game_system_pytest.py::test_create_game_round PASSED [ 69%]
tests/test_game_system_pytest.py::test_error_codes PASSED [ 73%]
tests/test_game_system_pytest.py::test_protocol_messages PASSED [ 78%]
tests/test_game_system_pytest.py::TestTornadoGameServer::test_tornado_server_creation PASSED [ 82%]
tests/test_game_system_pytest.py::TestTornadoGameServer::test_tornado_server_custom_config PASSED [ 86%]
tests/test_game_system_pytest.py::TestTornadoServerIntegration::test_tornado_server_has_game_engine_access PASSED [ 91%]
tests/test_game_system_pytest.py::TestTornadoServerIntegration::test_tornado_server_payout_calculation_integration PASSED [ 95%]
tests/test_game_system_pytest.py::test_tornado_server_payout_fix PASSED [100%]
========================================================================================= 23 passed in 17.98s ==========================================================================================
(venv) PS C:\Users\Scott\PycharmProjects\sample_gaming_sut>
這篇不做過多的技術說明,只是想借由不同的測試方法表達一個觀念:就是自己的程式自己要做完完整測試,所以我們用了 unittest 跟 pytest 兩個來做交叉驗證。
細節不一一贅述,反本程式碼都已公開,想知道細節的,可以去看看原始碼是怎麼做的。
花了十篇左右的篇幅在介紹我們的技術設計說明跟待測系統。
下一章會做一個小小總結,然後正式地進入 Robot Framework 的主題。