iT邦幫忙

2025 iThome 鐵人賽

DAY 3
0
自我挑戰組

Robot Framework 與 Websocket 協議測試系列 第 10

簡易的伺服器跟客戶端聯調

  • 分享至 

  • xImage
  •  

前言

上篇講到 Protobuf 的編譯與序列化與反序列化,加上前面幾期的內容,都以說明居多,有些人可能已經等待不及,想知道實際運行起來是什麼樣子。

這一期就稍微跳快一點,來測試一下我們請 AI 助手做出來的系統。
同時也會正式地公佈我們 GitHub 的 repo,讓有心想自己動手調試或者覺得我講得太慢或者幫我 debug 的人,都可以直接看程式碼自行研讀。

Python 測試框架 Unittest & Pytest

這次會用到兩個 Python 的測試框架:Unittest 跟 Pytest 。不過並非本次主題且限於篇幅,就不細講。
以下引用其他人的文章,請各位自行跳轉研讀:
鐵人賽: 單元測試框架
鐵人賽: Pytest系列介紹
簡單說呢,Unittest是古董級的產品。Pytest是比較現代化、多功能的,也建議大家真的要用以Pytest為主。

自己抓下來玩!

在閱讀這篇文章的同時,你們也可以同步去 GitHub 下載,然後在本地操作。
下載後,你可以獲得什麼呢?

  1. 簡單的由 Python 寫的博弈遊戲 demo,可以用來練習 Websocket 跟 Protobuf 的使用
  2. 帶有一包 Robot Framework 範例檔的驗證腳本
  3. 以及一堆機器人生成的英文文件。

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

簡單解析服務端過程的日誌

  1. 服務器啟動後,顯示基本資訊,就是把自己跑在 "ws://localhost:8767" 這個 URL
  2. 並顯示啟動成功的訊息

接著啟動客戶端的 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>

簡單解析客戶端過程的日誌

  1. Connected to ws://localhost:8767: 連接上服務端 URL ws://localhost:8767
  2. Login successful! User ID: 1, Balance: 1000000: 登入成功,使用者代號為 1,初始餘額為 1,000,000
  3. Joined room 1 with 1 players: 進入遊戲房間成功,且只有一個人在這間房
  4. Starting game session: 遊戲開始, 可以下注了
  5. Bet placed! Bet ID: 94fe7e1f-cdc7-420e-810b-d5dbb186c250, Remaining balance: 999900: 客戶端下注成功,餘額 999,900
  6. Betting phase completed successfully: 玩家端送出下注完畢,服務器確認下注完成
  7. Game session completed: 開獎與派彩
  8. 重複上述步驟 1-7 直到玩完三局

玩法2: 使用 Unittest 驗證服務端功能

(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>

玩法3: 使用 Pytest 驗證服務端功能

(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 的主題。


上一篇
Protobuf Python 端的編譯與序列化/反序列化/編解碼處理
系列文
Robot Framework 與 Websocket 協議測試10
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言