重构RocketMQ集成测试,优化用例结构与健壮性 - 拆分Producer/Consumer集成测试类,简化结构 - 统一用例为[Fact],补充[DisplayName]标注 - 新增SkipIfUnavailable及TCP连通性检测 - 优化配置获取,提升代码一致性与可维护性大石头 authored at 2026-05-14 23:13:02
diff --git a/XUnitTestRocketMQ/Consumers/ConsumerTests.cs b/XUnitTestRocketMQ/Consumers/ConsumerTests.cs
index fc5a286..08bb9dd 100644
--- a/XUnitTestRocketMQ/Consumers/ConsumerTests.cs
+++ b/XUnitTestRocketMQ/Consumers/ConsumerTests.cs
@@ -13,7 +13,9 @@ namespace XUnitTest.Consumers;
public class ConsumerTests
{
private static Consumer _consumer;
+
[Fact]
+ [System.ComponentModel.DisplayName("Consumer集成测试_拉取消息不抛异常")]
public static void ConsumeTest()
{
var set = BasicTest.GetConfig();
diff --git a/XUnitTestRocketMQ/Integration/BasicTest.cs b/XUnitTestRocketMQ/Integration/BasicTest.cs
index 9617bcb..7c4a359 100644
--- a/XUnitTestRocketMQ/Integration/BasicTest.cs
+++ b/XUnitTestRocketMQ/Integration/BasicTest.cs
@@ -1,4 +1,6 @@
-using NewLife.Log;
+using System;
+using System.Net.Sockets;
+using NewLife.Log;
using NewLife.RocketMQ;
using Xunit;
@@ -11,6 +13,8 @@ namespace XUnitTest.Integration;
public class BasicTest
{
private static MqSetting _config;
+
+ /// <summary>获取 RocketMQ 配置</summary>
public static MqSetting GetConfig()
{
if (_config != null) return _config;
@@ -31,4 +35,37 @@ public class BasicTest
return _config = set;
}
}
+
+ /// <summary>若 NameServer 不可达则跳过当前测试。适合不使用 RocketMqFixture 的集成测试方法。</summary>
+ public static void SkipIfUnavailable()
+ {
+ var addr = Environment.GetEnvironmentVariable("ROCKETMQ_NAMESERVER");
+ if (String.IsNullOrEmpty(addr)) addr = GetConfig().NameServer;
+
+ Skip.If(!IsReachable(addr),
+ $"无法连接 RocketMQ NameServer [{addr}],跳过集成测试。\n" +
+ "请检查 Config/RocketMQ.xml 配置或通过 ROCKETMQ_NAMESERVER 环境变量指定地址。\n" +
+ "启动本机 RocketMQ:dotnet run --file scripts/RocketMqSetup.cs");
+ }
+
+ /// <summary>TCP 连通性检测</summary>
+ /// <param name="address">格式 host:port</param>
+ /// <returns>可达返回 true</returns>
+ public static Boolean IsReachable(String? address)
+ {
+ if (String.IsNullOrEmpty(address)) return false;
+ try
+ {
+ var parts = address.Split(':');
+ var host = parts[0];
+ var port = parts.Length > 1 ? Int32.Parse(parts[1]) : 9876;
+ using var tcp = new TcpClient();
+ tcp.Connect(host, port);
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
}
diff --git a/XUnitTestRocketMQ/Integration/RocketMqIntegrationTests.cs b/XUnitTestRocketMQ/Integration/RocketMqIntegrationTests.cs
index d1d9eb1..7b9a6c3 100644
--- a/XUnitTestRocketMQ/Integration/RocketMqIntegrationTests.cs
+++ b/XUnitTestRocketMQ/Integration/RocketMqIntegrationTests.cs
@@ -19,12 +19,10 @@ namespace XUnitTest.Integration;
[Collection("RocketMQ")]
public class ProducerIntegrationTests(RocketMqFixture fixture) : IClassFixture<RocketMqFixture>
{
- [SkippableFact]
+ [Fact]
[DisplayName("发送普通消息_返回SendOK")]
public async Task PublishMessage_ReturnsSuccess()
{
- fixture.SkipIfUnavailable();
-
using var producer = new Producer
{
Topic = "integration-test-topic",
@@ -37,12 +35,10 @@ public class ProducerIntegrationTests(RocketMqFixture fixture) : IClassFixture<R
Assert.NotEmpty(result.MsgId);
}
- [SkippableFact]
+ [Fact]
[DisplayName("发送带属性的消息_属性正常保存")]
public async Task PublishMessageWithProperties_PropertiesPreserved()
{
- fixture.SkipIfUnavailable();
-
using var producer = new Producer
{
Topic = "integration-test-topic",
@@ -62,12 +58,10 @@ public class ProducerIntegrationTests(RocketMqFixture fixture) : IClassFixture<R
Assert.Equal(SendStatus.SendOK, result.Status);
}
- [SkippableFact]
+ [Fact]
[DisplayName("并发发送多条消息_全部成功")]
public async Task PublishMessagesParallel_AllSucceed()
{
- fixture.SkipIfUnavailable();
-
using var producer = new Producer
{
Topic = "integration-test-topic",
@@ -95,12 +89,10 @@ public class ProducerIntegrationTests(RocketMqFixture fixture) : IClassFixture<R
[Collection("RocketMQ")]
public class ConsumerIntegrationTests(RocketMqFixture fixture) : IClassFixture<RocketMqFixture>
{
- [SkippableFact]
+ [Fact]
[DisplayName("先发再消费_能收到消息")]
public async Task ProduceThenConsume_MessageReceived()
{
- fixture.SkipIfUnavailable();
-
const String topic = "integration-consume-topic";
const String content = "Hello Consumer";
diff --git a/XUnitTestRocketMQ/Producers/ProducerTests.cs b/XUnitTestRocketMQ/Producers/ProducerTests.cs
index 1c7b256..7e419ca 100644
--- a/XUnitTestRocketMQ/Producers/ProducerTests.cs
+++ b/XUnitTestRocketMQ/Producers/ProducerTests.cs
@@ -8,6 +8,7 @@ namespace XUnitTest.Producers;
public class ProducerTests
{
[Fact]
+ [System.ComponentModel.DisplayName("Producer_创建主题_返回队列数")]
public void CreateTopic()
{
var set = BasicTest.GetConfig();
@@ -29,6 +30,7 @@ public class ProducerTests
}
[Fact]
+ [System.ComponentModel.DisplayName("Producer_发送消息_不抛异常")]
public static void ProduceTest()
{
var set = BasicTest.GetConfig();
diff --git a/XUnitTestRocketMQ/Producers/RequestReplyTests.cs b/XUnitTestRocketMQ/Producers/RequestReplyTests.cs
index 901285c..5e33b51 100644
--- a/XUnitTestRocketMQ/Producers/RequestReplyTests.cs
+++ b/XUnitTestRocketMQ/Producers/RequestReplyTests.cs
@@ -13,6 +13,7 @@ namespace XUnitTest.Producers;
public class RequestReplyTests
{
[Fact]
+ [System.ComponentModel.DisplayName("RequestReply_同步请求_收到回复")]
public void RequestSyncTest()
{
var set = BasicTest.GetConfig();
@@ -72,6 +73,7 @@ public class RequestReplyTests
}
[Fact]
+ [System.ComponentModel.DisplayName("RequestReply_异步请求_收到回复")]
public async Task RequestAsyncTest()
{
var set = BasicTest.GetConfig();