返回教程正文

配套源码

test_protocol.py

app/tests/test_protocol.py
ros_gateway Brick:在 App Lab 中建立可靠的 WebSocket 通道app/tests/test_protocol.py
Python151 行
  1. # SPDX-License-Identifier: MIT
  2. import importlib.util
  3. import json
  4. from pathlib import Path
  5. import unittest
  6. PROTOCOL_PATH = (
  7. Path(__file__).resolve().parents[1]
  8. / "bricks"
  9. / "ros_gateway"
  10. / "protocol.py"
  11. )
  12. SPEC = importlib.util.spec_from_file_location("ros_gateway_protocol", PROTOCOL_PATH)
  13. protocol = importlib.util.module_from_spec(SPEC)
  14. SPEC.loader.exec_module(protocol)
  15. class ProtocolTests(unittest.TestCase):
  16. """ROS Gateway JSON 协议单元测试。"""
  17. def make_cmd(self, **overrides):
  18. """
  19. @description : 创建可按字段覆盖的标准 cmd_vel 测试消息
  20. @param overrides : 需要覆盖的消息字段
  21. @return : cmd_vel 消息字典
  22. """
  23. message = {
  24. "version": 1,
  25. "type": "cmd_vel",
  26. "seq": 1,
  27. "timestamp_ms": 1_750_000_000_000,
  28. "vx": 0.2,
  29. "vy": 0.0,
  30. "wz": 0.3,
  31. }
  32. message.update(overrides)
  33. return message
  34. def test_decode_valid_json(self):
  35. """
  36. @description : 验证合法 JSON 能被解析
  37. @param : 无参数
  38. @return : 无返回值
  39. """
  40. message = protocol.decode_message(json.dumps(self.make_cmd()))
  41. self.assertEqual(message["type"], "cmd_vel")
  42. def test_decode_rejects_non_object(self):
  43. """
  44. @description : 验证数组根节点会被拒绝
  45. @param : 无参数
  46. @return : 无返回值
  47. """
  48. with self.assertRaisesRegex(protocol.ProtocolError, "root must be an object"):
  49. protocol.decode_message("[]")
  50. def test_sequence_must_increase(self):
  51. """
  52. @description : 验证重复或倒退序号会被拒绝
  53. @param : 无参数
  54. @return : 无返回值
  55. """
  56. with self.assertRaises(protocol.ProtocolError) as context:
  57. protocol.validate_sequence(self.make_cmd(seq=4), 4)
  58. self.assertEqual(context.exception.code, "non_monotonic_seq")
  59. def test_cmd_vel_accepts_valid_values(self):
  60. """
  61. @description : 验证合法速度指令通过并转换为浮点数
  62. @param : 无参数
  63. @return : 无返回值
  64. """
  65. command = protocol.validate_cmd_vel(
  66. self.make_cmd(),
  67. "ROS_TELEOP",
  68. {"vx": 0.8, "vy": 0.8, "wz": 1.5},
  69. 300,
  70. current_timestamp_ms=1_750_000_000_100,
  71. )
  72. self.assertEqual(command["vx"], 0.2)
  73. def test_cmd_vel_rejects_overspeed(self):
  74. """
  75. @description : 验证超限速度被拒绝
  76. @param : 无参数
  77. @return : 无返回值
  78. """
  79. with self.assertRaises(protocol.ProtocolError) as context:
  80. protocol.validate_cmd_vel(
  81. self.make_cmd(vx=0.81),
  82. "ROS_TELEOP",
  83. {"vx": 0.8, "vy": 0.8, "wz": 1.5},
  84. 300,
  85. current_timestamp_ms=1_750_000_000_100,
  86. )
  87. self.assertEqual(context.exception.code, "out_of_range")
  88. def test_cmd_vel_rejects_stale_timestamp(self):
  89. """
  90. @description : 验证过期速度指令被拒绝
  91. @param : 无参数
  92. @return : 无返回值
  93. """
  94. with self.assertRaises(protocol.ProtocolError) as context:
  95. protocol.validate_cmd_vel(
  96. self.make_cmd(),
  97. "ROS_TELEOP",
  98. {"vx": 0.8, "vy": 0.8, "wz": 1.5},
  99. 300,
  100. current_timestamp_ms=1_750_000_000_301,
  101. )
  102. self.assertEqual(context.exception.code, "stale_command")
  103. def test_cmd_vel_rejects_boolean_number(self):
  104. """
  105. @description : 验证 JSON 布尔值不能冒充数值字段
  106. @param : 无参数
  107. @return : 无返回值
  108. """
  109. with self.assertRaises(protocol.ProtocolError) as context:
  110. protocol.validate_cmd_vel(
  111. self.make_cmd(vx=True),
  112. "ROS_TELEOP",
  113. {"vx": 0.8, "vy": 0.8, "wz": 1.5},
  114. 300,
  115. current_timestamp_ms=1_750_000_000_100,
  116. )
  117. self.assertEqual(context.exception.code, "invalid_field")
  118. def test_nonzero_velocity_requires_teleop(self):
  119. """
  120. @description : 验证非遥控模式拒绝非零速度
  121. @param : 无参数
  122. @return : 无返回值
  123. """
  124. with self.assertRaises(protocol.ProtocolError) as context:
  125. protocol.validate_cmd_vel(
  126. self.make_cmd(),
  127. "IDLE",
  128. {"vx": 0.8, "vy": 0.8, "wz": 1.5},
  129. 300,
  130. current_timestamp_ms=1_750_000_000_100,
  131. )
  132. self.assertEqual(context.exception.code, "mode_denied")
  133. if __name__ == "__main__":
  134. unittest.main(verbosity=2)