浏览代码

test(P1): 新增3个测试类覆盖动态消费者基类与子类

- AbstractDynamicMqttConsumerTest: 4个测试覆盖 deepCopyMap(null/深拷贝/嵌套/List)
- MqttDynamicConsumerTest: 6个测试覆盖 fetchTopics + insertredis
- MqttChargeStationConsumerTest: 4个测试覆盖 fetchTopics(配置/null/空白/trim)
- 测试总数 65→79,JaCoCo覆盖率 18%→25%
mqy20260511
humanleft 4 天前
父节点
当前提交
28470869a3

+ 105
- 0
iot-platform/src/test/java/com/iot/platform/mqtt/AbstractDynamicMqttConsumerTest.java 查看文件

@@ -0,0 +1,105 @@
1
+package com.iot.platform.mqtt;
2
+
3
+import com.iot.platform.config.IotProperties;
4
+import org.junit.jupiter.api.BeforeEach;
5
+import org.junit.jupiter.api.DisplayName;
6
+import org.junit.jupiter.api.Test;
7
+import org.junit.jupiter.api.extension.ExtendWith;
8
+import org.mockito.Mock;
9
+import org.mockito.junit.jupiter.MockitoExtension;
10
+
11
+import java.util.*;
12
+import java.util.concurrent.ExecutorService;
13
+import java.util.concurrent.Executors;
14
+import java.util.concurrent.ScheduledExecutorService;
15
+
16
+import static org.assertj.core.api.Assertions.assertThat;
17
+
18
+@ExtendWith(MockitoExtension.class)
19
+class AbstractDynamicMqttConsumerTest {
20
+
21
+    @Mock
22
+    private IotProperties iotProperties;
23
+
24
+    private TestDynamicConsumer consumer;
25
+
26
+    @BeforeEach
27
+    void setUp() {
28
+        ScheduledExecutorService coreExecutor = Executors.newSingleThreadScheduledExecutor();
29
+        ExecutorService writeExecutor = Executors.newSingleThreadExecutor();
30
+        consumer = new TestDynamicConsumer(iotProperties, coreExecutor, writeExecutor);
31
+    }
32
+
33
+    @Test
34
+    @DisplayName("deepCopyMap: null 输入应返回空 Map")
35
+    void deepCopyMap_nullInput_returnsEmptyMap() {
36
+        Map<String, Object> result = consumer.deepCopyMap(null);
37
+        assertThat(result).isNotNull().isEmpty();
38
+    }
39
+
40
+    @Test
41
+    @DisplayName("deepCopyMap: 应返回深拷贝,修改副本不影响原图")
42
+    void deepCopyMap_returnsDeepCopy() {
43
+        Map<String, Object> original = new HashMap<>();
44
+        original.put("key1", "value1");
45
+        original.put("key2", 42);
46
+
47
+        Map<String, Object> copy = consumer.deepCopyMap(original);
48
+
49
+        assertThat(copy).containsExactlyInAnyOrderEntriesOf(original);
50
+        copy.put("key1", "modified");
51
+        assertThat(original.get("key1")).isEqualTo("value1");
52
+    }
53
+
54
+    @Test
55
+    @DisplayName("deepCopyMap: 应处理嵌套 Map")
56
+    void deepCopyMap_nestedMap_returnsDeepCopy() {
57
+        Map<String, Object> inner = new HashMap<>();
58
+        inner.put("innerKey", "innerValue");
59
+
60
+        Map<String, Object> original = new HashMap<>();
61
+        original.put("nested", inner);
62
+
63
+        Map<String, Object> copy = consumer.deepCopyMap(original);
64
+
65
+        assertThat(copy).containsKey("nested");
66
+        @SuppressWarnings("unchecked")
67
+        Map<String, Object> copiedInner = (Map<String, Object>) copy.get("nested");
68
+        copiedInner.put("innerKey", "modified");
69
+        assertThat(inner.get("innerKey")).isEqualTo("innerValue");
70
+    }
71
+
72
+    @Test
73
+    @DisplayName("deepCopyMap: 应处理包含 List 的 Map")
74
+    void deepCopyMap_withList_returnsDeepCopy() {
75
+        Map<String, Object> original = new HashMap<>();
76
+        original.put("list", Arrays.asList("a", "b", "c"));
77
+
78
+        Map<String, Object> copy = consumer.deepCopyMap(original);
79
+
80
+        assertThat(copy).containsKey("list");
81
+        @SuppressWarnings("unchecked")
82
+        List<String> copiedList = (List<String>) copy.get("list");
83
+        assertThat(copiedList).containsExactly("a", "b", "c");
84
+    }
85
+
86
+    /**
87
+     * 用于测试的匿名实现
88
+     */
89
+    static class TestDynamicConsumer extends AbstractDynamicMqttConsumer {
90
+        TestDynamicConsumer(IotProperties iotProperties,
91
+                            ScheduledExecutorService coreExecutor,
92
+                            ExecutorService writeExecutor) {
93
+            super(iotProperties, coreExecutor, writeExecutor);
94
+        }
95
+
96
+        @Override
97
+        protected List<String> fetchTopics() {
98
+            return Collections.emptyList();
99
+        }
100
+
101
+        @Override
102
+        protected void processMessage(String content, String topic) {
103
+        }
104
+    }
105
+}

+ 86
- 0
iot-platform/src/test/java/com/iot/platform/mqtt/MqttChargeStationConsumerTest.java 查看文件

@@ -0,0 +1,86 @@
1
+package com.iot.platform.mqtt;
2
+
3
+import com.iot.platform.config.IotProperties;
4
+import com.iot.platform.service.TDengineService;
5
+import org.junit.jupiter.api.BeforeEach;
6
+import org.junit.jupiter.api.DisplayName;
7
+import org.junit.jupiter.api.Test;
8
+import org.junit.jupiter.api.extension.ExtendWith;
9
+import org.mockito.Mock;
10
+import org.mockito.junit.jupiter.MockitoExtension;
11
+
12
+import java.util.Arrays;
13
+import java.util.Collections;
14
+import java.util.List;
15
+import java.util.concurrent.ExecutorService;
16
+import java.util.concurrent.Executors;
17
+import java.util.concurrent.ScheduledExecutorService;
18
+
19
+import static org.assertj.core.api.Assertions.assertThat;
20
+import static org.mockito.Mockito.*;
21
+
22
+@ExtendWith(MockitoExtension.class)
23
+class MqttChargeStationConsumerTest {
24
+
25
+    @Mock
26
+    private IotProperties iotProperties;
27
+
28
+    @Mock
29
+    private IotProperties.Mqtt mqtt;
30
+
31
+    @Mock
32
+    private TDengineService tdengineService;
33
+
34
+    private MqttChargeStationConsumer consumer;
35
+
36
+    @BeforeEach
37
+    void setUp() {
38
+        ScheduledExecutorService coreExecutor = Executors.newSingleThreadScheduledExecutor();
39
+        ExecutorService writeExecutor = Executors.newSingleThreadExecutor();
40
+        consumer = new MqttChargeStationConsumer(iotProperties, coreExecutor, writeExecutor, tdengineService);
41
+    }
42
+
43
+    @Test
44
+    @DisplayName("fetchTopics: 应返回配置的 chargeStationTopic")
45
+    void fetchTopics_withConfiguredTopic_returnsTopicList() {
46
+        when(iotProperties.getMqtt()).thenReturn(mqtt);
47
+        when(mqtt.getChargeStationTopic()).thenReturn("station/ChargeStation/device/+/post/json");
48
+
49
+        List<String> result = consumer.fetchTopics();
50
+
51
+        assertThat(result).containsExactly("station/ChargeStation/device/+/post/json");
52
+    }
53
+
54
+    @Test
55
+    @DisplayName("fetchTopics: topic 为 null 时应返回空列表")
56
+    void fetchTopics_nullTopic_returnsEmptyList() {
57
+        when(iotProperties.getMqtt()).thenReturn(mqtt);
58
+        when(mqtt.getChargeStationTopic()).thenReturn(null);
59
+
60
+        List<String> result = consumer.fetchTopics();
61
+
62
+        assertThat(result).isEmpty();
63
+    }
64
+
65
+    @Test
66
+    @DisplayName("fetchTopics: topic 为空字符串时应返回空列表")
67
+    void fetchTopics_emptyTopic_returnsEmptyList() {
68
+        when(iotProperties.getMqtt()).thenReturn(mqtt);
69
+        when(mqtt.getChargeStationTopic()).thenReturn("   ");
70
+
71
+        List<String> result = consumer.fetchTopics();
72
+
73
+        assertThat(result).isEmpty();
74
+    }
75
+
76
+    @Test
77
+    @DisplayName("fetchTopics: topic 应被 trim 处理")
78
+    void fetchTopics_trimmedTopic_returnsTrimmedList() {
79
+        when(iotProperties.getMqtt()).thenReturn(mqtt);
80
+        when(mqtt.getChargeStationTopic()).thenReturn("  station/test  ");
81
+
82
+        List<String> result = consumer.fetchTopics();
83
+
84
+        assertThat(result).containsExactly("station/test");
85
+    }
86
+}

+ 138
- 0
iot-platform/src/test/java/com/iot/platform/mqtt/MqttDynamicConsumerTest.java 查看文件

@@ -0,0 +1,138 @@
1
+package com.iot.platform.mqtt;
2
+
3
+import com.iot.platform.config.IotProperties;
4
+import com.iot.platform.domain.SysController;
5
+import com.iot.platform.service.SysControllerService;
6
+import com.iot.platform.service.TDengineService;
7
+import org.junit.jupiter.api.BeforeEach;
8
+import org.junit.jupiter.api.DisplayName;
9
+import org.junit.jupiter.api.Test;
10
+import org.junit.jupiter.api.extension.ExtendWith;
11
+import org.mockito.Mock;
12
+import org.mockito.junit.jupiter.MockitoExtension;
13
+import org.springframework.data.redis.core.HashOperations;
14
+import org.springframework.data.redis.core.SetOperations;
15
+import org.springframework.data.redis.core.StringRedisTemplate;
16
+
17
+import java.util.*;
18
+import java.util.concurrent.ExecutorService;
19
+import java.util.concurrent.Executors;
20
+import java.util.concurrent.ScheduledExecutorService;
21
+import java.util.concurrent.TimeUnit;
22
+
23
+import static org.assertj.core.api.Assertions.assertThat;
24
+import static org.mockito.ArgumentMatchers.*;
25
+import static org.mockito.Mockito.*;
26
+
27
+@ExtendWith(MockitoExtension.class)
28
+class MqttDynamicConsumerTest {
29
+
30
+    @Mock
31
+    private IotProperties iotProperties;
32
+
33
+    @Mock
34
+    private SysControllerService sysControllerService;
35
+
36
+    @Mock
37
+    private TDengineService tdengineService;
38
+
39
+    @Mock
40
+    private StringRedisTemplate stringRedisTemplate;
41
+
42
+    @Mock
43
+    private HashOperations<String, Object, Object> hashOperations;
44
+
45
+    @Mock
46
+    private SetOperations<String, String> setOperations;
47
+
48
+    private MqttDynamicConsumer consumer;
49
+
50
+    @BeforeEach
51
+    void setUp() {
52
+        ScheduledExecutorService coreExecutor = Executors.newSingleThreadScheduledExecutor();
53
+        ExecutorService writeExecutor = Executors.newSingleThreadExecutor();
54
+        consumer = new MqttDynamicConsumer(iotProperties, coreExecutor, writeExecutor,
55
+                sysControllerService, tdengineService, stringRedisTemplate);
56
+    }
57
+
58
+    @Test
59
+    @DisplayName("fetchTopics 应返回 sysControllerService.selectall() 的结果")
60
+    void fetchTopics_delegatesToService() {
61
+        List<String> expected = Arrays.asList("topic/1", "topic/2");
62
+        when(sysControllerService.selectall()).thenReturn(expected);
63
+
64
+        List<String> result = consumer.fetchTopics();
65
+
66
+        assertThat(result).isEqualTo(expected);
67
+        verify(sysControllerService).selectall();
68
+    }
69
+
70
+    @Test
71
+    @DisplayName("fetchTopics 应处理 null 返回值")
72
+    void fetchTopics_nullReturn_returnsNull() {
73
+        when(sysControllerService.selectall()).thenReturn(null);
74
+
75
+        List<String> result = consumer.fetchTopics();
76
+
77
+        assertThat(result).isNull();
78
+    }
79
+
80
+    @Test
81
+    @DisplayName("insertredis: 应写入 Redis hash 和 active devices set")
82
+    void insertredis_validData_writesToRedis() throws Exception {
83
+        when(stringRedisTemplate.opsForHash()).thenReturn(hashOperations);
84
+        when(stringRedisTemplate.opsForSet()).thenReturn(setOperations);
85
+
86
+        SysController controller = new SysController();
87
+        controller.setName("temperature");
88
+        when(sysControllerService.selectcontrollerpath("ctrl1/temperature")).thenReturn(controller);
89
+
90
+        Map<String, Object> weather = new HashMap<>();
91
+        weather.put("timestamp", "1234567890");
92
+        weather.put("device_id", "dev001");
93
+        Map<String, Object> metricData = new HashMap<>();
94
+        metricData.put("val", "25.5");
95
+        weather.put("temperature", metricData);
96
+
97
+        consumer.insertredis(weather, "ctrl1/temperature");
98
+
99
+        verify(hashOperations).putAll(eq("DSB:ctrl1:temperature"), anyMap());
100
+        verify(stringRedisTemplate).expire("DSB:ctrl1:temperature", 2, TimeUnit.HOURS);
101
+        verify(setOperations).add("DSB:active:devices", "DSB:ctrl1:temperature");
102
+        verify(stringRedisTemplate).expire("DSB:active:devices", 2, TimeUnit.HOURS);
103
+    }
104
+
105
+    @Test
106
+    @DisplayName("insertredis: topic 格式无效时应直接返回")
107
+    void insertredis_invalidTopic_returnsSilently() throws Exception {
108
+        consumer.insertredis(new HashMap<>(), "invalidtopic");
109
+
110
+        verifyNoInteractions(stringRedisTemplate);
111
+    }
112
+
113
+    @Test
114
+    @DisplayName("insertredis: controller 未找到时应直接返回")
115
+    void insertredis_controllerNotFound_returnsSilently() throws Exception {
116
+        when(sysControllerService.selectcontrollerpath("ctrl1/missing")).thenReturn(null);
117
+
118
+        Map<String, Object> weather = new HashMap<>();
119
+        consumer.insertredis(weather, "ctrl1/missing");
120
+
121
+        verifyNoInteractions(stringRedisTemplate);
122
+    }
123
+
124
+    @Test
125
+    @DisplayName("insertredis: metricData 为 null 时应直接返回")
126
+    void insertredis_nullMetricData_returnsSilently() throws Exception {
127
+        SysController controller = new SysController();
128
+        controller.setName("temperature");
129
+        when(sysControllerService.selectcontrollerpath("ctrl1/temperature")).thenReturn(controller);
130
+
131
+        Map<String, Object> weather = new HashMap<>();
132
+        weather.put("temperature", null);
133
+
134
+        consumer.insertredis(weather, "ctrl1/temperature");
135
+
136
+        verifyNoInteractions(stringRedisTemplate);
137
+    }
138
+}

正在加载...
取消
保存