A:common 文件测试
This commit is contained in:
1 parent
9cfbc3e458
commit
fdf9a3c762
7 files changed
+2610
No files matched your search
@@ -0,0 +1,204 @@
|
||||
using Common.ExpandException;
|
||||
|
||||
namespace Common.Test;
|
||||
|
||||
/// <summary>
|
||||
/// ExceptionExpand 自定义异常测试
|
||||
/// 展示带错误码和建议的异常处理
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class ExceptionExpandTests
|
||||
{
|
||||
/// <summary>
|
||||
/// 测试:创建带错误码和建议的异常
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Constructor_CreatesExceptionWithCodeAndSuggestion()
|
||||
{
|
||||
// Arrange & Act
|
||||
var exception = new ExceptionExpand(1001L, "Process failed", "Retry with admin rights");
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(1001L, exception.ErrorCode);
|
||||
Assert.AreEqual("Process failed", exception.Message);
|
||||
Assert.AreEqual("Retry with admin rights", exception.Suggestion);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:创建带内部异常的自定义异常
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Constructor_WithInnerException_PreservesInnerException()
|
||||
{
|
||||
// Arrange
|
||||
var innerException = new InvalidOperationException("Inner error");
|
||||
|
||||
// Act
|
||||
var exception = new ExceptionExpand(2001L, "Outer error", "Check configuration", innerException);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(2001L, exception.ErrorCode);
|
||||
Assert.AreEqual("Outer error", exception.Message);
|
||||
Assert.AreEqual("Check configuration", exception.Suggestion);
|
||||
Assert.AreSame(innerException, exception.InnerException);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:ToString 包含所有信息
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void ToString_IncludesAllInformation()
|
||||
{
|
||||
// Arrange
|
||||
var exception = new ExceptionExpand(404L, "Resource not found", "Check the resource ID");
|
||||
|
||||
// Act
|
||||
var message = exception.ToString();
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(message.Contains("404"));
|
||||
Assert.IsTrue(message.Contains("Resource not found"));
|
||||
Assert.IsTrue(message.Contains("Check the resource ID"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:抛出和捕获自定义异常
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void ThrowAndCatch_WorksCorrectly()
|
||||
{
|
||||
// Act & Assert
|
||||
var exception = Assert.ThrowsException<ExceptionExpand>(() =>
|
||||
{
|
||||
throw new ExceptionExpand(500L, "Server error", "Contact support");
|
||||
});
|
||||
|
||||
Assert.AreEqual(500L, exception.ErrorCode);
|
||||
Assert.AreEqual("Server error", exception.Message);
|
||||
Assert.AreEqual("Contact support", exception.Suggestion);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 场景测试:使用预定义的异常枚举
|
||||
/// 演示如何使用 ExampleExceptionEnum 中的预定义异常
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Scenario_UsePredefinedExceptions()
|
||||
{
|
||||
// Act: 抛出预定义异常
|
||||
var exception = Assert.ThrowsException<ExceptionExpand>(() =>
|
||||
{
|
||||
throw ExampleExceptionEnum.ProcessGetException;
|
||||
});
|
||||
|
||||
// Assert: 验证预定义异常的属性
|
||||
Assert.AreEqual(100001L, exception.ErrorCode);
|
||||
Assert.IsTrue(exception.Message.Contains("进程消息获取失败"));
|
||||
Assert.IsTrue(exception.Suggestion.Contains("管理员权限"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 场景测试:在业务逻辑中使用自定义异常
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Scenario_UseInBusinessLogic()
|
||||
{
|
||||
// Act: 执行可能失败的业务操作
|
||||
var exception = Assert.ThrowsException<ExceptionExpand>(() =>
|
||||
{
|
||||
ProcessBusinessOperation(-1);
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(4001L, exception.ErrorCode);
|
||||
Assert.AreEqual("Invalid user ID", exception.Message);
|
||||
Assert.AreEqual("User ID must be positive", exception.Suggestion);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 场景测试:异常链
|
||||
/// 演示如何使用 InnerException 构建异常链
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Scenario_ExceptionChain()
|
||||
{
|
||||
// Arrange: 创建异常链
|
||||
var databaseException = new InvalidOperationException("Database connection failed");
|
||||
var serviceException = new ExceptionExpand(
|
||||
5001L,
|
||||
"Service unavailable",
|
||||
"Check database connection",
|
||||
databaseException
|
||||
);
|
||||
|
||||
// Act: 捕获并检查异常链
|
||||
var exception = Assert.ThrowsException<ExceptionExpand>(() =>
|
||||
{
|
||||
throw serviceException;
|
||||
});
|
||||
|
||||
// Assert: 验证异常链
|
||||
Assert.AreEqual(5001L, exception.ErrorCode);
|
||||
Assert.IsNotNull(exception.InnerException);
|
||||
Assert.IsInstanceOfType(exception.InnerException, typeof(InvalidOperationException));
|
||||
Assert.AreEqual("Database connection failed", exception.InnerException.Message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 场景测试:根据错误码进行不同处理
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Scenario_HandleByErrorCode()
|
||||
{
|
||||
// Arrange: 模拟不同的错误场景
|
||||
var errors = new[]
|
||||
{
|
||||
new ExceptionExpand(400L, "Bad request", "Check input parameters"),
|
||||
new ExceptionExpand(404L, "Not found", "Verify resource ID"),
|
||||
new ExceptionExpand(500L, "Internal error", "Contact support")
|
||||
};
|
||||
|
||||
// Act & Assert: 根据错误码分类处理
|
||||
foreach (var error in errors)
|
||||
{
|
||||
var handled = HandleError(error);
|
||||
|
||||
if (error.ErrorCode == 400L)
|
||||
Assert.AreEqual("Client error", handled);
|
||||
else if (error.ErrorCode == 404L)
|
||||
Assert.AreEqual("Client error", handled);
|
||||
else if (error.ErrorCode == 500L)
|
||||
Assert.AreEqual("Server error", handled);
|
||||
}
|
||||
}
|
||||
|
||||
#region 测试辅助方法
|
||||
|
||||
/// <summary>
|
||||
/// 模拟业务操作
|
||||
/// </summary>
|
||||
private void ProcessBusinessOperation(int userId)
|
||||
{
|
||||
if (userId <= 0)
|
||||
{
|
||||
throw new ExceptionExpand(4001L, "Invalid user ID", "User ID must be positive");
|
||||
}
|
||||
|
||||
// 正常处理逻辑...
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据错误码处理错误
|
||||
/// </summary>
|
||||
private string HandleError(ExceptionExpand exception)
|
||||
{
|
||||
return exception.ErrorCode switch
|
||||
{
|
||||
>= 400L and < 500L => "Client error",
|
||||
>= 500L and < 600L => "Server error",
|
||||
_ => "Unknown error"
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,510 @@
|
||||
using Common.Utils;
|
||||
using System.Net;
|
||||
|
||||
namespace Common.Test;
|
||||
|
||||
/// <summary>
|
||||
/// JsonHelper 测试
|
||||
/// 展示 JSON 序列化和反序列化功能
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class JsonHelperTests
|
||||
{
|
||||
#region 基础序列化测试
|
||||
|
||||
/// <summary>
|
||||
/// 测试:序列化简单对象
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void ToJson_SimpleObject_SerializesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var person = new Person
|
||||
{
|
||||
Name = "John Doe",
|
||||
Age = 30,
|
||||
Email = "john@example.com"
|
||||
};
|
||||
|
||||
// Act
|
||||
var json = JsonHelper.ToJson(person);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(json.Contains("\"name\": \"John Doe\"")); // camelCase
|
||||
Assert.IsTrue(json.Contains("\"age\": 30"));
|
||||
Assert.IsTrue(json.Contains("\"email\": \"john@example.com\""));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:序列化使用 camelCase 命名策略
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void ToJson_UsesCamelCase()
|
||||
{
|
||||
// Arrange
|
||||
var config = new AppConfig
|
||||
{
|
||||
AppName = "MyApp",
|
||||
MaxConnections = 100
|
||||
};
|
||||
|
||||
// Act
|
||||
var json = JsonHelper.ToJson(config);
|
||||
|
||||
// Assert: 属性名应该是 camelCase
|
||||
Assert.IsTrue(json.Contains("\"appName\""));
|
||||
Assert.IsTrue(json.Contains("\"maxConnections\""));
|
||||
Assert.IsFalse(json.Contains("\"AppName\"")); // 不应该是 PascalCase
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:序列化带格式化(缩进)
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void ToJson_WritesIndented()
|
||||
{
|
||||
// Arrange
|
||||
var person = new Person { Name = "Jane", Age = 25 };
|
||||
|
||||
// Act
|
||||
var json = JsonHelper.ToJson(person);
|
||||
|
||||
// Assert: 应该包含换行符(表示有缩进)
|
||||
Assert.IsTrue(json.Contains("\n") || json.Contains("\r\n"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 基础反序列化测试
|
||||
|
||||
/// <summary>
|
||||
/// 测试:反序列化简单对象
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void FromJson_ValidJson_DeserializesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var json = """
|
||||
{
|
||||
"name": "Alice",
|
||||
"age": 28,
|
||||
"email": "alice@example.com"
|
||||
}
|
||||
""";
|
||||
|
||||
// Act
|
||||
var person = JsonHelper.FromJson<Person>(json);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(person);
|
||||
Assert.AreEqual("Alice", person.Name);
|
||||
Assert.AreEqual(28, person.Age);
|
||||
Assert.AreEqual("alice@example.com", person.Email);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:反序列化 camelCase JSON
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void FromJson_CamelCaseJson_DeserializesCorrectly()
|
||||
{
|
||||
// Arrange: JSON 使用 camelCase
|
||||
var json = """
|
||||
{
|
||||
"appName": "TestApp",
|
||||
"maxConnections": 50
|
||||
}
|
||||
""";
|
||||
|
||||
// Act
|
||||
var config = JsonHelper.FromJson<AppConfig>(json);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(config);
|
||||
Assert.AreEqual("TestApp", config.AppName);
|
||||
Assert.AreEqual(50, config.MaxConnections);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 特殊类型处理测试
|
||||
|
||||
/// <summary>
|
||||
/// 测试:序列化和反序列化枚举(使用字符串)
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void ToJson_Enum_SerializesAsString()
|
||||
{
|
||||
// Arrange
|
||||
var data = new DataWithEnum
|
||||
{
|
||||
Status = Status.Active,
|
||||
Priority = Priority.High
|
||||
};
|
||||
|
||||
// Act
|
||||
var json = JsonHelper.ToJson(data);
|
||||
|
||||
// Assert: 枚举应该序列化为字符串(camelCase)
|
||||
Assert.IsTrue(json.Contains("\"active\""));
|
||||
Assert.IsTrue(json.Contains("\"high\""));
|
||||
Assert.IsFalse(json.Contains("0")); // 不应该是数字
|
||||
Assert.IsFalse(json.Contains("1"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:反序列化枚举字符串
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void FromJson_EnumString_DeserializesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var json = """
|
||||
{
|
||||
"status": "active",
|
||||
"priority": "low"
|
||||
}
|
||||
""";
|
||||
|
||||
// Act
|
||||
var data = JsonHelper.FromJson<DataWithEnum>(json);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(data);
|
||||
Assert.AreEqual(Status.Active, data.Status);
|
||||
Assert.AreEqual(Priority.Low, data.Priority);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:序列化 IPAddress
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void ToJson_IPAddress_SerializesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var networkInfo = new NetworkInfo
|
||||
{
|
||||
ServerIp = IPAddress.Parse("192.168.1.1"),
|
||||
ClientIp = IPAddress.Parse("10.0.0.5")
|
||||
};
|
||||
|
||||
// Act
|
||||
var json = JsonHelper.ToJson(networkInfo);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(json.Contains("\"192.168.1.1\""));
|
||||
Assert.IsTrue(json.Contains("\"10.0.0.5\""));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:反序列化 IPAddress
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void FromJson_IPAddress_DeserializesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var json = """
|
||||
{
|
||||
"serverIp": "192.168.1.1",
|
||||
"clientIp": "10.0.0.5"
|
||||
}
|
||||
""";
|
||||
|
||||
// Act
|
||||
var networkInfo = JsonHelper.FromJson<NetworkInfo>(json);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(networkInfo);
|
||||
Assert.AreEqual(IPAddress.Parse("192.168.1.1"), networkInfo.ServerIp);
|
||||
Assert.AreEqual(IPAddress.Parse("10.0.0.5"), networkInfo.ClientIp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:IPv6 地址序列化和反序列化
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void ToJson_IPv6Address_SerializesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var networkInfo = new NetworkInfo
|
||||
{
|
||||
ServerIp = IPAddress.Parse("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),
|
||||
ClientIp = IPAddress.IPv6Loopback
|
||||
};
|
||||
|
||||
// Act
|
||||
var json = JsonHelper.ToJson(networkInfo);
|
||||
var deserialized = JsonHelper.FromJson<NetworkInfo>(json);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(deserialized);
|
||||
Assert.AreEqual(networkInfo.ServerIp, deserialized.ServerIp);
|
||||
Assert.AreEqual(IPAddress.IPv6Loopback, deserialized.ClientIp);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 复杂对象测试
|
||||
|
||||
/// <summary>
|
||||
/// 测试:序列化嵌套对象
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void ToJson_NestedObject_SerializesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var order = new Order
|
||||
{
|
||||
OrderId = "ORD-001",
|
||||
Customer = new Person
|
||||
{
|
||||
Name = "Bob",
|
||||
Age = 35,
|
||||
Email = "bob@example.com"
|
||||
},
|
||||
Items = new List<OrderItem>
|
||||
{
|
||||
new() { ProductName = "Laptop", Price = 999.99, Quantity = 1 },
|
||||
new() { ProductName = "Mouse", Price = 29.99, Quantity = 2 }
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
var json = JsonHelper.ToJson(order);
|
||||
var deserialized = JsonHelper.FromJson<Order>(json);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(deserialized);
|
||||
Assert.AreEqual("ORD-001", deserialized.OrderId);
|
||||
Assert.AreEqual("Bob", deserialized.Customer?.Name);
|
||||
Assert.AreEqual(2, deserialized.Items?.Count);
|
||||
Assert.AreEqual("Laptop", deserialized.Items?[0].ProductName);
|
||||
Assert.AreEqual(999.99, deserialized.Items?[0].Price);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:序列化集合
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void ToJson_Collection_SerializesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var people = new List<Person>
|
||||
{
|
||||
new() { Name = "Alice", Age = 30 },
|
||||
new() { Name = "Bob", Age = 35 },
|
||||
new() { Name = "Charlie", Age = 28 }
|
||||
};
|
||||
|
||||
// Act
|
||||
var json = JsonHelper.ToJson(people);
|
||||
var deserialized = JsonHelper.FromJson<List<Person>>(json);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(deserialized);
|
||||
Assert.AreEqual(3, deserialized.Count);
|
||||
Assert.AreEqual("Alice", deserialized[0].Name);
|
||||
Assert.AreEqual("Bob", deserialized[1].Name);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 实际应用场景测试
|
||||
|
||||
/// <summary>
|
||||
/// 场景测试:配置文件序列化
|
||||
/// 演示如何使用 JsonHelper 读写配置文件
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Scenario_SaveAndLoadConfiguration()
|
||||
{
|
||||
// Arrange: 创建配置对象
|
||||
var originalConfig = new AppConfig
|
||||
{
|
||||
AppName = "MyApplication",
|
||||
MaxConnections = 100,
|
||||
Settings = new Dictionary<string, string>
|
||||
{
|
||||
["Theme"] = "Dark",
|
||||
["Language"] = "en-US"
|
||||
}
|
||||
};
|
||||
|
||||
// Act: 序列化配置
|
||||
var json = JsonHelper.ToJson(originalConfig);
|
||||
|
||||
// 模拟保存到文件后再读取
|
||||
var loadedConfig = JsonHelper.FromJson<AppConfig>(json);
|
||||
|
||||
// Assert: 验证配置正确加载
|
||||
Assert.IsNotNull(loadedConfig);
|
||||
Assert.AreEqual(originalConfig.AppName, loadedConfig.AppName);
|
||||
Assert.AreEqual(originalConfig.MaxConnections, loadedConfig.MaxConnections);
|
||||
Assert.AreEqual("Dark", loadedConfig.Settings?["Theme"]);
|
||||
Assert.AreEqual("en-US", loadedConfig.Settings?["Language"]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 场景测试:API 请求和响应
|
||||
/// 演示如何序列化 API 数据
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Scenario_ApiRequestResponse()
|
||||
{
|
||||
// Arrange: 创建 API 请求
|
||||
var request = new ApiRequest
|
||||
{
|
||||
Endpoint = "/api/users",
|
||||
Method = "POST",
|
||||
Headers = new Dictionary<string, string>
|
||||
{
|
||||
["Content-Type"] = "application/json",
|
||||
["Authorization"] = "Bearer token123"
|
||||
},
|
||||
Body = new Person
|
||||
{
|
||||
Name = "New User",
|
||||
Age = 25,
|
||||
Email = "newuser@example.com"
|
||||
}
|
||||
};
|
||||
|
||||
// Act: 序列化请求
|
||||
var requestJson = JsonHelper.ToJson(request);
|
||||
|
||||
// 模拟 API 调用后收到响应
|
||||
var responseJson = """
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"name": "New User",
|
||||
"age": 25,
|
||||
"email": "newuser@example.com"
|
||||
},
|
||||
"message": "User created successfully"
|
||||
}
|
||||
""";
|
||||
|
||||
var response = JsonHelper.FromJson<ApiResponse<Person>>(responseJson);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(response);
|
||||
Assert.IsTrue(response.Success);
|
||||
Assert.AreEqual("User created successfully", response.Message);
|
||||
Assert.IsNotNull(response.Data);
|
||||
Assert.AreEqual("New User", response.Data.Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 场景测试:网络配置持久化
|
||||
/// 演示如何序列化包含 IPAddress 的复杂对象
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Scenario_NetworkConfigurationPersistence()
|
||||
{
|
||||
// Arrange: 创建网络配置
|
||||
var networkConfig = new NetworkConfig
|
||||
{
|
||||
ServerIp = IPAddress.Parse("192.168.1.100"),
|
||||
DnsServers = new List<IPAddress>
|
||||
{
|
||||
IPAddress.Parse("8.8.8.8"),
|
||||
IPAddress.Parse("8.8.4.4")
|
||||
},
|
||||
Gateway = IPAddress.Parse("192.168.1.1")
|
||||
};
|
||||
|
||||
// Act: 序列化并反序列化
|
||||
var json = JsonHelper.ToJson(networkConfig);
|
||||
var loaded = JsonHelper.FromJson<NetworkConfig>(json);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(loaded);
|
||||
Assert.AreEqual(networkConfig.ServerIp, loaded.ServerIp);
|
||||
Assert.AreEqual(2, loaded.DnsServers?.Count);
|
||||
Assert.AreEqual(IPAddress.Parse("8.8.8.8"), loaded.DnsServers?[0]);
|
||||
Assert.AreEqual(networkConfig.Gateway, loaded.Gateway);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 测试辅助类型
|
||||
|
||||
private class Person
|
||||
{
|
||||
public string Name { get; set; } = "";
|
||||
public int Age { get; set; }
|
||||
public string? Email { get; set; }
|
||||
}
|
||||
|
||||
private class AppConfig
|
||||
{
|
||||
public string AppName { get; set; } = "";
|
||||
public int MaxConnections { get; set; }
|
||||
public Dictionary<string, string>? Settings { get; set; }
|
||||
}
|
||||
|
||||
private enum Status
|
||||
{
|
||||
Inactive = 0,
|
||||
Active = 1,
|
||||
Suspended = 2
|
||||
}
|
||||
|
||||
private enum Priority
|
||||
{
|
||||
Low = 0,
|
||||
Medium = 1,
|
||||
High = 2
|
||||
}
|
||||
|
||||
private class DataWithEnum
|
||||
{
|
||||
public Status Status { get; set; }
|
||||
public Priority Priority { get; set; }
|
||||
}
|
||||
|
||||
private class NetworkInfo
|
||||
{
|
||||
public IPAddress? ServerIp { get; set; }
|
||||
public IPAddress? ClientIp { get; set; }
|
||||
}
|
||||
|
||||
private class Order
|
||||
{
|
||||
public string OrderId { get; set; } = "";
|
||||
public Person? Customer { get; set; }
|
||||
public List<OrderItem>? Items { get; set; }
|
||||
}
|
||||
|
||||
private class OrderItem
|
||||
{
|
||||
public string ProductName { get; set; } = "";
|
||||
public double Price { get; set; }
|
||||
public int Quantity { get; set; }
|
||||
}
|
||||
|
||||
private class ApiRequest
|
||||
{
|
||||
public string Endpoint { get; set; } = "";
|
||||
public string Method { get; set; } = "";
|
||||
public Dictionary<string, string>? Headers { get; set; }
|
||||
public object? Body { get; set; }
|
||||
}
|
||||
|
||||
private class ApiResponse<T>
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public T? Data { get; set; }
|
||||
public string Message { get; set; } = "";
|
||||
}
|
||||
|
||||
private class NetworkConfig
|
||||
{
|
||||
public IPAddress? ServerIp { get; set; }
|
||||
public List<IPAddress>? DnsServers { get; set; }
|
||||
public IPAddress? Gateway { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,880 @@
|
||||
using Common.Results;
|
||||
using Common.ExpandException;
|
||||
|
||||
namespace Common.Test;
|
||||
|
||||
/// <summary>
|
||||
/// Result 模式测试
|
||||
/// 展示函数式错误处理的所有功能
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class ResultTests
|
||||
{
|
||||
#region 基础创建和状态测试
|
||||
|
||||
/// <summary>
|
||||
/// 测试:创建成功的 Result
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Success_CreatesSuccessResult()
|
||||
{
|
||||
// Act: 创建成功的无返回值 Result
|
||||
var result = Result.Success();
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.IsSuccess);
|
||||
Assert.IsFalse(result.IsFailure);
|
||||
Assert.IsNull(result.Exception);
|
||||
Assert.IsNull(result.ErrorMessage);
|
||||
Assert.IsNull(result.ErrorCode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:创建成功的 Result<T>
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Success_WithValue_CreatesSuccessResultWithValue()
|
||||
{
|
||||
// Act: 创建带值的成功 Result
|
||||
var result = Result.Success(42);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.IsSuccess);
|
||||
Assert.AreEqual(42, result.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:创建失败的 Result(使用错误消息)
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Failure_WithMessage_CreatesFailureResult()
|
||||
{
|
||||
// Act
|
||||
var result = Result.Failure<int>("Something went wrong");
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(result.IsSuccess);
|
||||
Assert.IsTrue(result.IsFailure);
|
||||
Assert.AreEqual("Something went wrong", result.ErrorMessage);
|
||||
Assert.IsNull(result.ErrorCode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:创建失败的 Result(使用异常)
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Failure_WithException_CreatesFailureResult()
|
||||
{
|
||||
// Arrange
|
||||
var exception = new InvalidOperationException("Invalid operation");
|
||||
|
||||
// Act
|
||||
var result = Result.Failure<int>(exception);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.IsFailure);
|
||||
Assert.AreSame(exception, result.Exception);
|
||||
Assert.AreEqual("Invalid operation", result.ErrorMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:创建带错误码的失败 Result
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Failure_WithErrorCode_CreatesFailureResultWithCode()
|
||||
{
|
||||
// Act
|
||||
var result = Result.Failure<int>(404L, "Not found");
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.IsFailure);
|
||||
Assert.AreEqual(404L, result.ErrorCode);
|
||||
Assert.AreEqual("Not found", result.ErrorMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:从 ExceptionExpand 创建失败 Result
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Failure_WithExceptionExpand_ExtractsErrorCode()
|
||||
{
|
||||
// Arrange
|
||||
var exception = new ExceptionExpand(1001L, "Custom error", "Try again");
|
||||
|
||||
// Act
|
||||
var result = Result.Failure<int>(exception);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.IsFailure);
|
||||
Assert.AreEqual(1001L, result.ErrorCode);
|
||||
Assert.AreEqual("Custom error", result.ErrorMessage);
|
||||
Assert.AreSame(exception, result.Exception);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Try 模式测试
|
||||
|
||||
/// <summary>
|
||||
/// 测试:Try 捕获成功的操作
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Try_SuccessfulAction_ReturnsSuccess()
|
||||
{
|
||||
// Arrange
|
||||
var executed = false;
|
||||
|
||||
// Act
|
||||
var result = Result.Try(() =>
|
||||
{
|
||||
executed = true;
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.IsSuccess);
|
||||
Assert.IsTrue(executed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:Try 捕获失败的操作
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Try_FailingAction_ReturnsFailure()
|
||||
{
|
||||
// Act
|
||||
var result = Result.Try(() =>
|
||||
{
|
||||
throw new InvalidOperationException("Operation failed");
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.IsFailure);
|
||||
Assert.IsInstanceOfType(result.Exception, typeof(InvalidOperationException));
|
||||
Assert.AreEqual("Operation failed", result.ErrorMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:Try 捕获返回值的成功操作
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Try_SuccessfulFunc_ReturnsSuccessWithValue()
|
||||
{
|
||||
// Act
|
||||
var result = Result.Try(() => int.Parse("123"));
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.IsSuccess);
|
||||
Assert.AreEqual(123, result.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:Try 捕获返回值的失败操作
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Try_FailingFunc_ReturnsFailure()
|
||||
{
|
||||
// Act
|
||||
var result = Result.Try(() => int.Parse("invalid"));
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.IsFailure);
|
||||
Assert.IsInstanceOfType(result.Exception, typeof(FormatException));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:TryAsync 异步操作
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public async Task TryAsync_SuccessfulTask_ReturnsSuccess()
|
||||
{
|
||||
// Act
|
||||
var result = await Result.TryAsync(async () =>
|
||||
{
|
||||
await Task.Delay(10);
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.IsSuccess);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:TryAsync 异步操作返回值
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public async Task TryAsync_SuccessfulTaskWithValue_ReturnsSuccessWithValue()
|
||||
{
|
||||
// Act
|
||||
var result = await Result.TryAsync(async () =>
|
||||
{
|
||||
await Task.Delay(10);
|
||||
return 42;
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.IsSuccess);
|
||||
Assert.AreEqual(42, result.Value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 值提取测试
|
||||
|
||||
/// <summary>
|
||||
/// 测试:GetOrNull 获取引用类型
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetOrNull_SuccessResult_ReturnsValue()
|
||||
{
|
||||
// Arrange
|
||||
var result = Result.Success("test");
|
||||
|
||||
// Act
|
||||
var value = result.GetOrNull();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual("test", value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:GetOrNull 失败返回 null
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetOrNull_FailureResult_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
var result = Result.Failure<string>("error");
|
||||
|
||||
// Act
|
||||
var value = result.GetOrNull();
|
||||
|
||||
// Assert
|
||||
Assert.IsNull(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:GetOrNullable 获取值类型
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetOrNullable_SuccessResult_ReturnsValue()
|
||||
{
|
||||
// Arrange
|
||||
var result = Result.Success(42);
|
||||
|
||||
// Act
|
||||
var value = result.GetOrNullable();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(42, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:GetOrDefault 获取默认值
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetOrDefault_FailureResult_ReturnsDefault()
|
||||
{
|
||||
// Arrange
|
||||
var result = Result.Failure<int>("error");
|
||||
|
||||
// Act
|
||||
var value = result.GetOrDefault(999);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(999, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:GetOrThrow 成功时返回值
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetOrThrow_SuccessResult_ReturnsValue()
|
||||
{
|
||||
// Arrange
|
||||
var result = Result.Success(42);
|
||||
|
||||
// Act
|
||||
var value = result.GetOrThrow();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(42, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:GetOrThrow 失败时抛出异常
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetOrThrow_FailureResult_ThrowsException()
|
||||
{
|
||||
// Arrange
|
||||
var exception = new InvalidOperationException("test error");
|
||||
var result = Result.Failure<int>(exception);
|
||||
|
||||
// Act & Assert
|
||||
var thrown = Assert.ThrowsException<InvalidOperationException>(() =>
|
||||
{
|
||||
result.GetOrThrow();
|
||||
});
|
||||
|
||||
Assert.AreSame(exception, thrown);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:GetOrElse 使用函数提供替代值
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetOrElse_FailureResult_CallsAlternativeProvider()
|
||||
{
|
||||
// Arrange
|
||||
var result = Result.Failure<int>("error");
|
||||
|
||||
// Act
|
||||
var value = result.GetOrElse(() => 100);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(100, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 函数式操作测试
|
||||
|
||||
/// <summary>
|
||||
/// 测试:Map 转换成功的值
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Map_SuccessResult_TransformsValue()
|
||||
{
|
||||
// Arrange
|
||||
var result = Result.Success(10);
|
||||
|
||||
// Act
|
||||
var mapped = result.Map(x => x * 2);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(mapped.IsSuccess);
|
||||
Assert.AreEqual(20, mapped.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:Map 传播失败
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Map_FailureResult_PropagatesFailure()
|
||||
{
|
||||
// Arrange
|
||||
var result = Result.Failure<int>("error");
|
||||
|
||||
// Act
|
||||
var mapped = result.Map(x => x * 2);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(mapped.IsFailure);
|
||||
Assert.AreEqual("error", mapped.ErrorMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:Bind 链式操作
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Bind_SuccessResult_ChainsOperations()
|
||||
{
|
||||
// Arrange
|
||||
var result = Result.Success(10);
|
||||
|
||||
// Act
|
||||
var bound = result
|
||||
.Bind(x => x > 5 ? Result.Success(x * 2) : Result.Failure<int>("Too small"))
|
||||
.Bind(x => Result.Success(x.ToString()));
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(bound.IsSuccess);
|
||||
Assert.AreEqual("20", bound.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:Bind 中断失败链
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Bind_FailureInChain_StopsExecution()
|
||||
{
|
||||
// Arrange
|
||||
var result = Result.Success(3);
|
||||
var secondBindExecuted = false;
|
||||
|
||||
// Act
|
||||
var bound = result
|
||||
.Bind(x => x > 5 ? Result.Success(x * 2) : Result.Failure<int>("Too small"))
|
||||
.Bind(x =>
|
||||
{
|
||||
secondBindExecuted = true;
|
||||
return Result.Success(x * 2);
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(bound.IsFailure);
|
||||
Assert.IsFalse(secondBindExecuted); // 第二个 Bind 不应该执行
|
||||
Assert.AreEqual("Too small", bound.ErrorMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:Where 过滤值
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Where_PredicateSatisfied_ReturnsSuccess()
|
||||
{
|
||||
// Arrange
|
||||
var result = Result.Success(10);
|
||||
|
||||
// Act
|
||||
var filtered = result.Where(x => x > 5, "Value must be greater than 5");
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(filtered.IsSuccess);
|
||||
Assert.AreEqual(10, filtered.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:Where 过滤失败
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Where_PredicateNotSatisfied_ReturnsFailure()
|
||||
{
|
||||
// Arrange
|
||||
var result = Result.Success(3);
|
||||
|
||||
// Act
|
||||
var filtered = result.Where(x => x > 5, "Value must be greater than 5");
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(filtered.IsFailure);
|
||||
Assert.AreEqual("Value must be greater than 5", filtered.ErrorMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:Ensure 验证
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Ensure_ValidationPasses_ReturnsSuccess()
|
||||
{
|
||||
// Arrange
|
||||
var result = Result.Success("test@example.com");
|
||||
|
||||
// Act
|
||||
var ensured = result
|
||||
.Ensure(email => email.Contains("@"), "Invalid email format")
|
||||
.Ensure(email => email.Length > 5, "Email too short");
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(ensured.IsSuccess);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:Ensure 验证失败
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Ensure_ValidationFails_ReturnsFailure()
|
||||
{
|
||||
// Arrange
|
||||
var result = Result.Success("invalid");
|
||||
|
||||
// Act
|
||||
var ensured = result.Ensure(email => email.Contains("@"), "Invalid email format");
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(ensured.IsFailure);
|
||||
Assert.AreEqual("Invalid email format", ensured.ErrorMessage);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 条件执行测试
|
||||
|
||||
/// <summary>
|
||||
/// 测试:OnSuccess 成功时执行
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void OnSuccess_SuccessResult_ExecutesAction()
|
||||
{
|
||||
// Arrange
|
||||
var result = Result.Success(42);
|
||||
var executed = false;
|
||||
var capturedValue = 0;
|
||||
|
||||
// Act
|
||||
result.OnSuccess(value =>
|
||||
{
|
||||
executed = true;
|
||||
capturedValue = value;
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(executed);
|
||||
Assert.AreEqual(42, capturedValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:OnSuccess 失败时不执行
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void OnSuccess_FailureResult_DoesNotExecute()
|
||||
{
|
||||
// Arrange
|
||||
var result = Result.Failure<int>("error");
|
||||
var executed = false;
|
||||
|
||||
// Act
|
||||
result.OnSuccess(value => executed = true);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(executed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:OnFailure 失败时执行
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void OnFailure_FailureResult_ExecutesAction()
|
||||
{
|
||||
// Arrange
|
||||
var result = Result.Failure<int>(404L, "Not found");
|
||||
var executed = false;
|
||||
string? capturedMessage = null;
|
||||
long? capturedCode = null;
|
||||
|
||||
// Act
|
||||
result.OnFailure((ex, msg, code) =>
|
||||
{
|
||||
executed = true;
|
||||
capturedMessage = msg;
|
||||
capturedCode = code;
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(executed);
|
||||
Assert.AreEqual("Not found", capturedMessage);
|
||||
Assert.AreEqual(404L, capturedCode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:Match 模式匹配
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Match_SuccessResult_CallsSuccessHandler()
|
||||
{
|
||||
// Arrange
|
||||
var result = Result.Success(42);
|
||||
|
||||
// Act
|
||||
var message = result.Match(
|
||||
onSuccess: value => $"Success: {value}",
|
||||
onFailure: (ex, msg) => $"Error: {msg}"
|
||||
);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual("Success: 42", message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:Match 失败结果
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Match_FailureResult_CallsFailureHandler()
|
||||
{
|
||||
// Arrange
|
||||
var result = Result.Failure<int>("Something went wrong");
|
||||
|
||||
// Act
|
||||
var message = result.Match(
|
||||
onSuccess: value => $"Success: {value}",
|
||||
onFailure: (ex, msg) => $"Error: {msg}"
|
||||
);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual("Error: Something went wrong", message);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region LINQ 查询语法测试
|
||||
|
||||
/// <summary>
|
||||
/// 测试:使用 LINQ 查询语法组合 Result
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void LinqQuery_CombinesResults()
|
||||
{
|
||||
// Arrange
|
||||
var result1 = Result.Success(5);
|
||||
var result2 = Result.Success(10);
|
||||
|
||||
// Act: 使用 LINQ 查询语法
|
||||
var combined = from x in result1
|
||||
from y in result2
|
||||
where x > 0 && y > 0
|
||||
select x + y;
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(combined.IsSuccess);
|
||||
Assert.AreEqual(15, combined.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:LINQ 查询中的失败传播
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void LinqQuery_PropagatesFailure()
|
||||
{
|
||||
// Arrange
|
||||
var result1 = Result.Success(5);
|
||||
var result2 = Result.Failure<int>("Error in result2");
|
||||
|
||||
// Act
|
||||
var combined = from x in result1
|
||||
from y in result2
|
||||
select x + y;
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(combined.IsFailure);
|
||||
Assert.AreEqual("Error in result2", combined.ErrorMessage);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 错误码相关测试
|
||||
|
||||
/// <summary>
|
||||
/// 测试:HasErrorCode 检查错误码
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void HasErrorCode_MatchingCode_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var result = Result.Failure<int>(404L, "Not found");
|
||||
|
||||
// Act & Assert
|
||||
Assert.IsTrue(result.HasErrorCode(404L));
|
||||
Assert.IsFalse(result.HasErrorCode(500L));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:GetErrorCodeOrDefault 获取错误码
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetErrorCodeOrDefault_ReturnsCodeOrDefault()
|
||||
{
|
||||
// Arrange
|
||||
var failureResult = Result.Failure<int>(404L, "Not found");
|
||||
var successResult = Result.Success(42);
|
||||
|
||||
// Act & Assert
|
||||
Assert.AreEqual(404L, failureResult.GetErrorCodeOrDefault());
|
||||
Assert.AreEqual(-1L, successResult.GetErrorCodeOrDefault());
|
||||
Assert.AreEqual(999L, successResult.GetErrorCodeOrDefault(999L));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:FromExceptionExpand 转换自定义异常
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void FromExceptionExpand_CreatesResultWithErrorCode()
|
||||
{
|
||||
// Arrange
|
||||
var exception = new ExceptionExpand(1001L, "Business error", "Check input");
|
||||
|
||||
// Act
|
||||
var result = ResultExtensions.FromExceptionExpand<string>(exception);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.IsFailure);
|
||||
Assert.AreEqual(1001L, result.ErrorCode);
|
||||
Assert.AreEqual("Business error", result.ErrorMessage);
|
||||
Assert.AreSame(exception, result.Exception);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 异步操作测试
|
||||
|
||||
/// <summary>
|
||||
/// 测试:MapAsync 异步转换
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public async Task MapAsync_TransformsValueAsynchronously()
|
||||
{
|
||||
// Arrange
|
||||
var result = Task.FromResult(Result.Success(10));
|
||||
|
||||
// Act
|
||||
var mapped = await result.MapAsync(async value =>
|
||||
{
|
||||
await Task.Delay(10);
|
||||
return value * 2;
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(mapped.IsSuccess);
|
||||
Assert.AreEqual(20, mapped.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:BindAsync 异步链式操作
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public async Task BindAsync_ChainsAsynchronously()
|
||||
{
|
||||
// Arrange
|
||||
var result = Task.FromResult(Result.Success(10));
|
||||
|
||||
// Act
|
||||
var bound = await result.BindAsync(async value =>
|
||||
{
|
||||
await Task.Delay(10);
|
||||
return value > 5 ? Result.Success(value * 2) : Result.Failure<int>("Too small");
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(bound.IsSuccess);
|
||||
Assert.AreEqual(20, bound.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:DoOnSuccessAsync 异步副作用
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public async Task DoOnSuccessAsync_ExecutesAsyncAction()
|
||||
{
|
||||
// Arrange
|
||||
var result = Task.FromResult(Result.Success(42));
|
||||
var executed = false;
|
||||
|
||||
// Act
|
||||
await result.DoOnSuccessAsync(async value =>
|
||||
{
|
||||
await Task.Delay(10);
|
||||
executed = true;
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(executed);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 实际应用场景测试
|
||||
|
||||
/// <summary>
|
||||
/// 场景测试:验证用户输入
|
||||
/// 演示多步骤验证链
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Scenario_ValidateUserInput()
|
||||
{
|
||||
// Act: 验证有效输入
|
||||
var validResult = ValidateEmail("user@example.com");
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(validResult.IsSuccess);
|
||||
Assert.AreEqual("user@example.com", validResult.Value);
|
||||
|
||||
// Act: 验证无效输入
|
||||
var invalidResult = ValidateEmail("invalid");
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(invalidResult.IsFailure);
|
||||
Assert.AreEqual("Invalid email format", invalidResult.ErrorMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 场景测试:数据库操作链
|
||||
/// 演示如何使用 Result 处理可能失败的操作序列
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Scenario_DatabaseOperationChain()
|
||||
{
|
||||
// Act: 成功的操作链
|
||||
var successResult = FindUserById(1)
|
||||
.Bind(user => UpdateUserName(user, "NewName"))
|
||||
.Bind(user => SaveUser(user))
|
||||
.Map(user => $"User {user.Name} saved successfully");
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(successResult.IsSuccess);
|
||||
Assert.AreEqual("User NewName saved successfully", successResult.Value);
|
||||
|
||||
// Act: 失败的操作链(用户不存在)
|
||||
var notFoundResult = FindUserById(999)
|
||||
.Bind(user => UpdateUserName(user, "NewName"))
|
||||
.Bind(user => SaveUser(user))
|
||||
.Map(user => $"User {user.Name} saved successfully");
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(notFoundResult.IsFailure);
|
||||
Assert.IsTrue(notFoundResult.HasErrorCode(404L));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 场景测试:错误恢复
|
||||
/// 演示使用 OrElse 提供备用逻辑
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Scenario_ErrorRecovery()
|
||||
{
|
||||
// Act: 主操作失败,使用备用操作
|
||||
var result = TryPrimaryOperation()
|
||||
.OrElse(() => TryFallbackOperation())
|
||||
.OrElse(() => Result.Success("Default value"));
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.IsSuccess);
|
||||
Assert.AreEqual("Fallback success", result.Value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 测试辅助方法
|
||||
|
||||
private Result<string> ValidateEmail(string email)
|
||||
{
|
||||
return email.AsResult()
|
||||
.Ensure(e => !string.IsNullOrEmpty(e), "Email cannot be empty")
|
||||
.Ensure(e => e.Contains("@"), "Invalid email format")
|
||||
.Ensure(e => e.Length >= 5, "Email too short");
|
||||
}
|
||||
|
||||
private class User
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = "";
|
||||
}
|
||||
|
||||
private Result<User> FindUserById(int id)
|
||||
{
|
||||
return id == 1
|
||||
? Result.Success(new User { Id = 1, Name = "John" })
|
||||
: Result.Failure<User>(404L, "User not found");
|
||||
}
|
||||
|
||||
private Result<User> UpdateUserName(User user, string newName)
|
||||
{
|
||||
user.Name = newName;
|
||||
return Result.Success(user);
|
||||
}
|
||||
|
||||
private Result<User> SaveUser(User user)
|
||||
{
|
||||
// 模拟保存成功
|
||||
return Result.Success(user);
|
||||
}
|
||||
|
||||
private Result<string> TryPrimaryOperation()
|
||||
{
|
||||
return Result.Failure<string>("Primary operation failed");
|
||||
}
|
||||
|
||||
private Result<string> TryFallbackOperation()
|
||||
{
|
||||
return Result.Success("Fallback success");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,392 @@
|
||||
using Common.ExpandFun;
|
||||
|
||||
namespace Common.Test;
|
||||
|
||||
/// <summary>
|
||||
/// StringExpand 字符串扩展方法测试
|
||||
/// 展示字符串相关的实用扩展方法
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class StringExpandTests
|
||||
{
|
||||
#region IsValidUrl 测试
|
||||
|
||||
/// <summary>
|
||||
/// 测试:有效的 HTTP URL
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void IsValidUrl_ValidHttpUrl_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var url = "http://www.example.com";
|
||||
|
||||
// Act
|
||||
var isValid = url.IsValidUrl();
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(isValid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:有效的 HTTPS URL
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void IsValidUrl_ValidHttpsUrl_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var url = "https://www.example.com/path/to/resource?query=value";
|
||||
|
||||
// Act
|
||||
var isValid = url.IsValidUrl();
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(isValid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:有效的 FTP URL
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void IsValidUrl_ValidFtpUrl_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var url = "ftp://ftp.example.com/file.txt";
|
||||
|
||||
// Act
|
||||
var isValid = url.IsValidUrl();
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(isValid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:无效的 URL
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void IsValidUrl_InvalidUrl_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var invalidUrls = new[]
|
||||
{
|
||||
"not a url",
|
||||
"example.com", // 缺少协议
|
||||
"http://", // 不完整
|
||||
"", // 空字符串
|
||||
"://invalid" // 无效格式
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
foreach (var url in invalidUrls)
|
||||
{
|
||||
Assert.IsFalse(url.IsValidUrl(), $"'{url}' should be invalid");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ToInt 和 TryToInt 测试
|
||||
|
||||
/// <summary>
|
||||
/// 测试:有效的整数字符串转换
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void ToInt_ValidIntegerString_ReturnsInteger()
|
||||
{
|
||||
// Arrange & Act & Assert
|
||||
Assert.AreEqual(123, "123".ToInt());
|
||||
Assert.AreEqual(-456, "-456".ToInt());
|
||||
Assert.AreEqual(0, "0".ToInt());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:无效字符串返回默认值
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void ToInt_InvalidString_ReturnsDefault()
|
||||
{
|
||||
// Arrange & Act & Assert
|
||||
Assert.AreEqual(0, "invalid".ToInt());
|
||||
Assert.AreEqual(0, "12.34".ToInt());
|
||||
Assert.AreEqual(999, "abc".ToInt(999));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:空字符串和 null 返回默认值
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void ToInt_NullOrEmpty_ReturnsDefault()
|
||||
{
|
||||
// Arrange & Act & Assert
|
||||
Assert.AreEqual(0, ((string)null!).ToInt());
|
||||
Assert.AreEqual(0, "".ToInt());
|
||||
Assert.AreEqual(0, " ".ToInt());
|
||||
Assert.AreEqual(100, ((string)null!).ToInt(100));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:TryToInt 成功转换
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TryToInt_ValidString_ReturnsValue()
|
||||
{
|
||||
// Arrange & Act
|
||||
var result = "42".TryToInt();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(result);
|
||||
Assert.AreEqual(42, result.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:TryToInt 转换失败返回 null
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TryToInt_InvalidString_ReturnsNull()
|
||||
{
|
||||
// Arrange & Act
|
||||
var result1 = "invalid".TryToInt();
|
||||
var result2 = ((string)null!).TryToInt();
|
||||
var result3 = "".TryToInt();
|
||||
|
||||
// Assert
|
||||
Assert.IsNull(result1);
|
||||
Assert.IsNull(result2);
|
||||
Assert.IsNull(result3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:整数溢出
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void ToInt_Overflow_ReturnsDefault()
|
||||
{
|
||||
// Arrange: int.MaxValue + 1
|
||||
var overflowString = "2147483648";
|
||||
|
||||
// Act
|
||||
var result = overflowString.ToInt(-1);
|
||||
|
||||
// Assert: 溢出应该返回默认值
|
||||
Assert.AreEqual(-1, result);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ToDouble 和 TryToDouble 测试
|
||||
|
||||
/// <summary>
|
||||
/// 测试:有效的浮点数字符串转换
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void ToDouble_ValidDoubleString_ReturnsDouble()
|
||||
{
|
||||
// Arrange & Act & Assert
|
||||
Assert.AreEqual(123.456, "123.456".ToDouble());
|
||||
Assert.AreEqual(-789.12, "-789.12".ToDouble());
|
||||
Assert.AreEqual(0.0, "0".ToDouble());
|
||||
Assert.AreEqual(3.14, "3.14".ToDouble());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:整数字符串也能转换为 double
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void ToDouble_IntegerString_ReturnsDouble()
|
||||
{
|
||||
// Arrange & Act
|
||||
var result = "42".ToDouble();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(42.0, result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:无效字符串返回默认值
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void ToDouble_InvalidString_ReturnsDefault()
|
||||
{
|
||||
// Arrange & Act & Assert
|
||||
Assert.AreEqual(0.0, "invalid".ToDouble());
|
||||
Assert.AreEqual(999.9, "abc".ToDouble(999.9));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:空字符串和 null 返回默认值
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void ToDouble_NullOrEmpty_ReturnsDefault()
|
||||
{
|
||||
// Arrange & Act & Assert
|
||||
Assert.AreEqual(0.0, ((string)null!).ToDouble());
|
||||
Assert.AreEqual(0.0, "".ToDouble());
|
||||
Assert.AreEqual(0.0, " ".ToDouble());
|
||||
Assert.AreEqual(100.5, ((string)null!).ToDouble(100.5));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:TryToDouble 成功转换
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TryToDouble_ValidString_ReturnsValue()
|
||||
{
|
||||
// Arrange & Act
|
||||
var result = "3.14159".TryToDouble();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(result);
|
||||
Assert.AreEqual(3.14159, result.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:TryToDouble 转换失败返回 null
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TryToDouble_InvalidString_ReturnsNull()
|
||||
{
|
||||
// Arrange & Act
|
||||
var result1 = "invalid".TryToDouble();
|
||||
var result2 = ((string)null!).TryToDouble();
|
||||
var result3 = "".TryToDouble();
|
||||
|
||||
// Assert
|
||||
Assert.IsNull(result1);
|
||||
Assert.IsNull(result2);
|
||||
Assert.IsNull(result3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:科学计数法
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void ToDouble_ScientificNotation_ConvertsCorrectly()
|
||||
{
|
||||
// Arrange & Act & Assert
|
||||
Assert.AreEqual(1.23e5, "1.23e5".ToDouble());
|
||||
Assert.AreEqual(1.23e-5, "1.23e-5".ToDouble());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 实际应用场景测试
|
||||
|
||||
/// <summary>
|
||||
/// 场景测试:解析配置文件值
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Scenario_ParseConfigurationValues()
|
||||
{
|
||||
// Arrange: 模拟从配置文件读取的字符串值
|
||||
var config = new Dictionary<string, string>
|
||||
{
|
||||
["Port"] = "8080",
|
||||
["Timeout"] = "30",
|
||||
["MaxRetries"] = "invalid", // 无效值
|
||||
["Price"] = "99.99",
|
||||
["Discount"] = "0.15"
|
||||
};
|
||||
|
||||
// Act: 解析配置值
|
||||
var port = config["Port"].ToInt(80); // 默认 80
|
||||
var timeout = config["Timeout"].ToInt(60); // 默认 60
|
||||
var maxRetries = config["MaxRetries"].ToInt(3); // 无效值,使用默认 3
|
||||
var price = config["Price"].ToDouble(0.0);
|
||||
var discount = config["Discount"].ToDouble(0.0);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(8080, port);
|
||||
Assert.AreEqual(30, timeout);
|
||||
Assert.AreEqual(3, maxRetries); // 使用了默认值
|
||||
Assert.AreEqual(99.99, price);
|
||||
Assert.AreEqual(0.15, discount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 场景测试:验证用户输入的 URL
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Scenario_ValidateUserInputUrl()
|
||||
{
|
||||
// Arrange: 模拟用户输入
|
||||
var userInputs = new[]
|
||||
{
|
||||
"https://www.google.com",
|
||||
"not a url",
|
||||
"ftp://files.example.com",
|
||||
"example.com"
|
||||
};
|
||||
|
||||
// Act: 验证 URL
|
||||
var validUrls = userInputs.Where(url => url.IsValidUrl()).ToList();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(2, validUrls.Count);
|
||||
Assert.IsTrue(validUrls.Contains("https://www.google.com"));
|
||||
Assert.IsTrue(validUrls.Contains("ftp://files.example.com"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 场景测试:安全地解析用户输入的数字
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Scenario_SafelyParseUserInput()
|
||||
{
|
||||
// Arrange: 模拟用户输入(可能无效)
|
||||
string? userAgeInput = "25";
|
||||
string? userWeightInput = "invalid";
|
||||
string? userHeightInput = null;
|
||||
|
||||
// Act: 安全解析
|
||||
var age = userAgeInput.TryToInt();
|
||||
var weight = userWeightInput.TryToDouble();
|
||||
var height = userHeightInput.TryToDouble();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(age);
|
||||
Assert.AreEqual(25, age.Value);
|
||||
Assert.IsNull(weight); // 无效输入
|
||||
Assert.IsNull(height); // null 输入
|
||||
|
||||
// 使用默认值的版本
|
||||
var ageWithDefault = userAgeInput.ToInt(18);
|
||||
var weightWithDefault = userWeightInput.ToDouble(70.0);
|
||||
var heightWithDefault = userHeightInput.ToDouble(170.0);
|
||||
|
||||
Assert.AreEqual(25, ageWithDefault);
|
||||
Assert.AreEqual(70.0, weightWithDefault); // 使用默认值
|
||||
Assert.AreEqual(170.0, heightWithDefault); // 使用默认值
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 场景测试:处理来自外部 API 的数据
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Scenario_ParseApiResponse()
|
||||
{
|
||||
// Arrange: 模拟 API 返回的字符串数据
|
||||
var apiData = new
|
||||
{
|
||||
UserId = "12345",
|
||||
Score = "95.5",
|
||||
ProfileUrl = "https://api.example.com/users/12345",
|
||||
InvalidUrl = "not-a-url",
|
||||
MissingValue = (string)null!
|
||||
};
|
||||
|
||||
// Act: 解析数据
|
||||
var userId = apiData.UserId.ToInt();
|
||||
var score = apiData.Score.ToDouble();
|
||||
var isValidProfileUrl = apiData.ProfileUrl.IsValidUrl();
|
||||
var isValidInvalidUrl = apiData.InvalidUrl.IsValidUrl();
|
||||
var missingValue = apiData.MissingValue.ToInt(-1);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(12345, userId);
|
||||
Assert.AreEqual(95.5, score);
|
||||
Assert.IsTrue(isValidProfileUrl);
|
||||
Assert.IsFalse(isValidInvalidUrl);
|
||||
Assert.AreEqual(-1, missingValue);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
using Common.Utils;
|
||||
|
||||
namespace Common.Test;
|
||||
|
||||
/// <summary>
|
||||
/// SystemHelper 测试
|
||||
/// 展示系统相关的实用工具方法
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class SystemHelperTests
|
||||
{
|
||||
#region GetAvailablePort 测试
|
||||
|
||||
/// <summary>
|
||||
/// 测试:获取可用端口返回有效端口号
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetAvailablePort_ReturnsValidPort()
|
||||
{
|
||||
// Act
|
||||
var port = SystemHelper.GetAvailablePort();
|
||||
|
||||
// Assert: 端口应该在有效范围内
|
||||
Assert.IsTrue(port > 0);
|
||||
Assert.IsTrue(port <= 65535);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:多次调用获取不同的可用端口
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetAvailablePort_MultipleCallsReturnDifferentPorts()
|
||||
{
|
||||
// Act: 多次获取端口
|
||||
var port1 = SystemHelper.GetAvailablePort();
|
||||
var port2 = SystemHelper.GetAvailablePort();
|
||||
var port3 = SystemHelper.GetAvailablePort();
|
||||
|
||||
// Assert: 每次应该返回不同的端口(由操作系统分配)
|
||||
var ports = new[] { port1, port2, port3 };
|
||||
Assert.AreEqual(3, ports.Distinct().Count(), "Should get different ports");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:获取的端口确实可用
|
||||
/// 演示如何验证端口是否真的可以使用
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void GetAvailablePort_PortIsActuallyAvailable()
|
||||
{
|
||||
// Act: 获取可用端口
|
||||
var port = SystemHelper.GetAvailablePort();
|
||||
|
||||
// Assert: 尝试在该端口上创建监听器
|
||||
using var listener = new System.Net.Sockets.TcpListener(
|
||||
System.Net.IPAddress.Loopback,
|
||||
port
|
||||
);
|
||||
|
||||
// 如果端口被占用,Start 会抛出异常
|
||||
listener.Start();
|
||||
listener.Stop();
|
||||
|
||||
// 如果没有抛出异常,说明端口确实可用
|
||||
Assert.IsTrue(true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IsAdministrator 测试
|
||||
|
||||
/// <summary>
|
||||
/// 测试:IsAdministrator 在 Windows 上正常工作
|
||||
/// 注意:此测试在非 Windows 平台会被跳过
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void IsAdministrator_OnWindows_ReturnsBoolean()
|
||||
{
|
||||
// Arrange: 检查是否为 Windows 平台
|
||||
if (!OperatingSystem.IsWindows())
|
||||
{
|
||||
Assert.Inconclusive("This test only runs on Windows");
|
||||
return;
|
||||
}
|
||||
|
||||
// Act
|
||||
var isAdmin = SystemHelper.IsAdministrator();
|
||||
|
||||
// Assert: 应该返回布尔值(无论是 true 还是 false 都是有效的)
|
||||
Assert.IsInstanceOfType(isAdmin, typeof(bool));
|
||||
|
||||
// 输出结果供参考
|
||||
Console.WriteLine($"Running as administrator: {isAdmin}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试:IsAdministrator 在非 Windows 平台抛出异常
|
||||
/// 注意:此测试在 Windows 平台会被跳过
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void IsAdministrator_OnNonWindows_ThrowsException()
|
||||
{
|
||||
// Arrange: 检查是否为非 Windows 平台
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
Assert.Inconclusive("This test only runs on non-Windows platforms");
|
||||
return;
|
||||
}
|
||||
|
||||
// Act & Assert: 在非 Windows 平台应该抛出异常
|
||||
var exception = Assert.ThrowsException<PlatformNotSupportedException>(() =>
|
||||
{
|
||||
SystemHelper.IsAdministrator();
|
||||
});
|
||||
|
||||
Assert.IsTrue(exception.Message.Contains("Windows"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 实际应用场景测试
|
||||
|
||||
/// <summary>
|
||||
/// 场景测试:为开发服务器分配端口
|
||||
/// 演示如何在启动服务器时动态分配端口
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Scenario_AllocatePortForDevelopmentServer()
|
||||
{
|
||||
// Arrange: 需要启动多个开发服务器
|
||||
var serverCount = 3;
|
||||
var allocatedPorts = new List<int>();
|
||||
|
||||
// Act: 为每个服务器分配端口
|
||||
for (int i = 0; i < serverCount; i++)
|
||||
{
|
||||
var port = SystemHelper.GetAvailablePort();
|
||||
allocatedPorts.Add(port);
|
||||
Console.WriteLine($"Server {i + 1} allocated port: {port}");
|
||||
}
|
||||
|
||||
// Assert: 所有端口都应该是唯一的
|
||||
Assert.AreEqual(serverCount, allocatedPorts.Distinct().Count());
|
||||
Assert.IsTrue(allocatedPorts.All(p => p > 0 && p <= 65535));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 场景测试:检查权限后执行特权操作
|
||||
/// 演示如何在需要管理员权限的功能中使用 IsAdministrator
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Scenario_CheckAdminBeforePrivilegedOperation()
|
||||
{
|
||||
// 跳过非 Windows 平台
|
||||
if (!OperatingSystem.IsWindows())
|
||||
{
|
||||
Assert.Inconclusive("This test only runs on Windows");
|
||||
return;
|
||||
}
|
||||
|
||||
// Arrange: 定义需要管理员权限的操作
|
||||
void PrivilegedOperation()
|
||||
{
|
||||
if (!SystemHelper.IsAdministrator())
|
||||
{
|
||||
throw new UnauthorizedAccessException(
|
||||
"This operation requires administrator privileges"
|
||||
);
|
||||
}
|
||||
|
||||
// 执行需要管理员权限的操作...
|
||||
Console.WriteLine("Executing privileged operation");
|
||||
}
|
||||
|
||||
// Act & Assert: 根据当前权限决定行为
|
||||
if (SystemHelper.IsAdministrator())
|
||||
{
|
||||
// 如果是管理员,操作应该成功
|
||||
PrivilegedOperation();
|
||||
Assert.IsTrue(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果不是管理员,应该抛出异常
|
||||
Assert.ThrowsException<UnauthorizedAccessException>(() =>
|
||||
{
|
||||
PrivilegedOperation();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 场景测试:动态端口绑定用于测试
|
||||
/// 演示如何在集成测试中使用动态端口避免冲突
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Scenario_DynamicPortForIntegrationTesting()
|
||||
{
|
||||
// Arrange: 模拟需要启动测试服务器的场景
|
||||
var testServers = new List<TestServer>();
|
||||
|
||||
try
|
||||
{
|
||||
// Act: 启动多个测试服务器
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
var port = SystemHelper.GetAvailablePort();
|
||||
var server = new TestServer($"TestServer{i + 1}", port);
|
||||
server.Start();
|
||||
testServers.Add(server);
|
||||
|
||||
Console.WriteLine($"{server.Name} started on port {server.Port}");
|
||||
}
|
||||
|
||||
// Assert: 所有服务器都应该成功启动
|
||||
Assert.AreEqual(3, testServers.Count);
|
||||
Assert.IsTrue(testServers.All(s => s.IsRunning));
|
||||
|
||||
// 验证端口都不相同
|
||||
var ports = testServers.Select(s => s.Port).ToList();
|
||||
Assert.AreEqual(3, ports.Distinct().Count());
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Cleanup: 停止所有测试服务器
|
||||
foreach (var server in testServers)
|
||||
{
|
||||
server.Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 场景测试:端口范围检查
|
||||
/// 演示验证分配的端口在合理范围内
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Scenario_VerifyPortRange()
|
||||
{
|
||||
// Arrange: 获取多个端口样本
|
||||
var sampleSize = 10;
|
||||
var ports = new List<int>();
|
||||
|
||||
// Act: 收集端口样本
|
||||
for (int i = 0; i < sampleSize; i++)
|
||||
{
|
||||
ports.Add(SystemHelper.GetAvailablePort());
|
||||
}
|
||||
|
||||
// Assert: 验证所有端口都在合理范围内
|
||||
// 动态端口范围通常是 49152-65535
|
||||
Assert.IsTrue(ports.All(p => p > 0), "All ports should be positive");
|
||||
Assert.IsTrue(ports.All(p => p <= 65535), "All ports should be <= 65535");
|
||||
|
||||
// 统计信息
|
||||
Console.WriteLine($"Port range: {ports.Min()} - {ports.Max()}");
|
||||
Console.WriteLine($"Average port: {ports.Average():F0}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 场景测试:跨平台兼容性处理
|
||||
/// 演示如何编写跨平台友好的代码
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void Scenario_CrossPlatformCompatibility()
|
||||
{
|
||||
// Act: 尝试检查管理员权限,优雅处理不同平台
|
||||
bool? isAdmin = null;
|
||||
Exception? caughtException = null;
|
||||
|
||||
try
|
||||
{
|
||||
isAdmin = SystemHelper.IsAdministrator();
|
||||
}
|
||||
catch (PlatformNotSupportedException ex)
|
||||
{
|
||||
caughtException = ex;
|
||||
}
|
||||
|
||||
// Assert: 根据平台验证行为
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
// Windows: 应该返回结果
|
||||
Assert.IsNotNull(isAdmin);
|
||||
Assert.IsNull(caughtException);
|
||||
Console.WriteLine($"Windows platform - Is Admin: {isAdmin}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 非 Windows: 应该抛出异常
|
||||
Assert.IsNull(isAdmin);
|
||||
Assert.IsNotNull(caughtException);
|
||||
Console.WriteLine($"Non-Windows platform - Exception caught: {caughtException.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 测试辅助类型
|
||||
|
||||
/// <summary>
|
||||
/// 测试用的简单服务器模拟
|
||||
/// </summary>
|
||||
private class TestServer
|
||||
{
|
||||
public string Name { get; }
|
||||
public int Port { get; }
|
||||
public bool IsRunning { get; private set; }
|
||||
|
||||
private System.Net.Sockets.TcpListener? _listener;
|
||||
|
||||
public TestServer(string name, int port)
|
||||
{
|
||||
Name = name;
|
||||
Port = port;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_listener = new System.Net.Sockets.TcpListener(
|
||||
System.Net.IPAddress.Loopback,
|
||||
Port
|
||||
);
|
||||
_listener.Start();
|
||||
IsRunning = true;
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_listener?.Stop();
|
||||
IsRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in new issue
Block a user