Files
sharp-basic/test/Common.Test/CollectionExpandTests.cs
T
2025-12-30 09:17:25 +08:00

629 lines
18 KiB
C#

using Common.ExpandFun;
namespace Common.Test;
/// <summary>
/// CollectionExpand 集合扩展测试
/// 展示分页、树形结构、批处理等集合操作功能
/// </summary>
[TestClass]
public class CollectionExpandTests
{
#region 测试数据类
private class TestItem
{
public int Id { get; set; }
public string Name { get; set; } = "";
public int? ParentId { get; set; }
}
#endregion
#region 分页测试
[TestMethod]
public void ToPagedResult_BasicPaging_ReturnsCorrectPage()
{
// Arrange
var items = Enumerable.Range(1, 100).ToList();
// Act
var page1 = items.ToPagedResult(1, 10);
var page2 = items.ToPagedResult(2, 10);
// Assert
Assert.AreEqual(1, page1.PageIndex);
Assert.AreEqual(10, page1.PageSize);
Assert.AreEqual(100, page1.TotalCount);
Assert.AreEqual(10, page1.TotalPages);
Assert.AreEqual(10, page1.Items.Count);
Assert.AreEqual(1, page1.Items.First());
Assert.AreEqual(10, page1.Items.Last());
Assert.AreEqual(11, page2.Items.First());
Assert.AreEqual(20, page2.Items.Last());
}
[TestMethod]
public void ToPagedResult_LastPage_ReturnsPartialPage()
{
// Arrange
var items = Enumerable.Range(1, 25).ToList();
// Act
var lastPage = items.ToPagedResult(3, 10);
// Assert
Assert.AreEqual(3, lastPage.PageIndex);
Assert.AreEqual(5, lastPage.Items.Count); // 最后一页只有 5 个元素
Assert.AreEqual(21, lastPage.Items.First());
Assert.AreEqual(25, lastPage.Items.Last());
}
[TestMethod]
public void ToPagedResult_InvalidPageIndex_ClampsToValidRange()
{
// Arrange
var items = Enumerable.Range(1, 50).ToList();
// Act
var zeroPage = items.ToPagedResult(0, 10); // 应该被修正为第 1 页
var negativePage = items.ToPagedResult(-1, 10); // 应该被修正为第 1 页
// Assert
Assert.AreEqual(1, zeroPage.PageIndex);
Assert.AreEqual(1, negativePage.PageIndex);
}
[TestMethod]
public void PagedResult_Navigation_PropertiesWorkCorrectly()
{
// Arrange
var items = Enumerable.Range(1, 50).ToList();
// Act
var firstPage = items.ToPagedResult(1, 10);
var middlePage = items.ToPagedResult(3, 10);
var lastPage = items.ToPagedResult(5, 10);
// Assert
Assert.IsFalse(firstPage.HasPreviousPage);
Assert.IsTrue(firstPage.HasNextPage);
Assert.IsTrue(middlePage.HasPreviousPage);
Assert.IsTrue(middlePage.HasNextPage);
Assert.IsTrue(lastPage.HasPreviousPage);
Assert.IsFalse(lastPage.HasNextPage);
}
#endregion
#region 树形结构测试
[TestMethod]
public void ToTree_FlatListToTree_BuildsCorrectHierarchy()
{
// Arrange: 创建平铺的列表
var items = new List<TestItem>
{
new() { Id = 1, Name = "根节点1", ParentId = null },
new() { Id = 2, Name = "根节点2", ParentId = null },
new() { Id = 3, Name = "子节点1-1", ParentId = 1 },
new() { Id = 4, Name = "子节点1-2", ParentId = 1 },
new() { Id = 5, Name = "子节点2-1", ParentId = 2 },
new() { Id = 6, Name = "孙节点1-1-1", ParentId = 3 }
};
// Act
var tree = items.ToTree(
x => x.Id,
x => x.ParentId,
null
);
// Assert
Assert.AreEqual(2, tree.Count); // 两个根节点
Assert.AreEqual("根节点1", tree[0].Data.Name);
Assert.AreEqual(2, tree[0].Children.Count); // 根节点1 有两个子节点
Assert.AreEqual("子节点1-1", tree[0].Children[0].Data.Name);
Assert.AreEqual(1, tree[0].Children[0].Children.Count); // 子节点1-1 有一个孙节点
Assert.AreEqual("孙节点1-1-1", tree[0].Children[0].Children[0].Data.Name);
}
[TestMethod]
public void ToTree_BuildsCorrectLevels()
{
// Arrange
var items = new List<TestItem>
{
new() { Id = 1, Name = "根", ParentId = null },
new() { Id = 2, Name = "子", ParentId = 1 },
new() { Id = 3, Name = "孙", ParentId = 2 }
};
// Act
var tree = items.ToTree(x => x.Id, x => x.ParentId);
// Assert
Assert.AreEqual(0, tree[0].Level); // 根节点 Level = 0
Assert.AreEqual(1, tree[0].Children[0].Level); // 子节点 Level = 1
Assert.AreEqual(2, tree[0].Children[0].Children[0].Level); // 孙节点 Level = 2
}
[TestMethod]
public void ToFlatList_TreeToFlatList_PreservesAllNodes()
{
// Arrange
var items = new List<TestItem>
{
new() { Id = 1, Name = "根", ParentId = null },
new() { Id = 2, Name = "子1", ParentId = 1 },
new() { Id = 3, Name = "子2", ParentId = 1 },
new() { Id = 4, Name = "孙", ParentId = 2 }
};
var tree = items.ToTree(x => x.Id, x => x.ParentId);
// Act
var flatList = tree.ToFlatList();
// Assert
Assert.AreEqual(4, flatList.Count);
Assert.AreEqual("根", flatList[0].Name);
Assert.AreEqual("子1", flatList[1].Name);
Assert.AreEqual("孙", flatList[2].Name); // 深度优先遍历
Assert.AreEqual("子2", flatList[3].Name);
}
[TestMethod]
public void TraverseTree_ExecutesActionForAllNodes()
{
// Arrange
var items = new List<TestItem>
{
new() { Id = 1, Name = "A", ParentId = null },
new() { Id = 2, Name = "B", ParentId = 1 },
new() { Id = 3, Name = "C", ParentId = 1 }
};
var tree = items.ToTree(x => x.Id, x => x.ParentId);
var visitedNames = new List<string>();
// Act
tree.TraverseTree(node => visitedNames.Add(node.Data.Name));
// Assert
Assert.AreEqual(3, visitedNames.Count);
CollectionAssert.Contains(visitedNames, "A");
CollectionAssert.Contains(visitedNames, "B");
CollectionAssert.Contains(visitedNames, "C");
}
#endregion
#region 批处理测试
[TestMethod]
public void Batch_DividesIntoCorrectBatches()
{
// Arrange
var items = Enumerable.Range(1, 10).ToList();
// Act
var batches = items.Batch(3).ToList();
// Assert
Assert.AreEqual(4, batches.Count); // 10 个元素,每批 3 个,共 4 批
Assert.AreEqual(3, batches[0].Count());
Assert.AreEqual(3, batches[1].Count());
Assert.AreEqual(3, batches[2].Count());
Assert.AreEqual(1, batches[3].Count()); // 最后一批只有 1 个
}
[TestMethod]
public void Batch_EmptyCollection_ReturnsEmpty()
{
// Arrange
var items = new List<int>();
// Act
var batches = items.Batch(5).ToList();
// Assert
Assert.AreEqual(0, batches.Count);
}
[TestMethod]
public void ForEachBatch_ExecutesActionForEachBatch()
{
// Arrange
var items = Enumerable.Range(1, 25).ToList();
var processedBatches = new List<int>();
// Act
items.ForEachBatch(10, batch => { processedBatches.Add(batch.Count()); });
// Assert
Assert.AreEqual(3, processedBatches.Count);
Assert.AreEqual(10, processedBatches[0]);
Assert.AreEqual(10, processedBatches[1]);
Assert.AreEqual(5, processedBatches[2]);
}
#endregion
#region 空集合安全操作测试
[TestMethod]
public void OrEmpty_NullCollection_ReturnsEmptyCollection()
{
// Arrange
List<int>? nullList = null;
// Act
var result = nullList.OrEmpty();
// Assert
Assert.IsNotNull(result);
Assert.AreEqual(0, result.Count());
}
[TestMethod]
public void OrDefault_EmptyCollection_ReturnsDefaultValue()
{
// Arrange
var emptyList = new List<int>();
var defaultList = new List<int> { 1, 2, 3 };
// Act
var result = emptyList.OrDefault(defaultList);
// Assert
Assert.AreEqual(3, result.Count());
CollectionAssert.AreEqual(defaultList, result.ToList());
}
[TestMethod]
public void IsNullOrEmpty_VariousInputs_ReturnsCorrectResult()
{
// Arrange
List<int>? nullList = null;
var emptyList = new List<int>();
var nonEmptyList = new List<int> { 1 };
// Act & Assert
Assert.IsTrue(nullList.IsNullOrEmpty());
Assert.IsTrue(emptyList.IsNullOrEmpty());
Assert.IsFalse(nonEmptyList.IsNullOrEmpty());
}
[TestMethod]
public void HasItems_VariousInputs_ReturnsCorrectResult()
{
// Arrange
List<int>? nullList = null;
var emptyList = new List<int>();
var nonEmptyList = new List<int> { 1 };
// Act & Assert
Assert.IsFalse(nullList.HasItems());
Assert.IsFalse(emptyList.HasItems());
Assert.IsTrue(nonEmptyList.HasItems());
}
#endregion
#region DistinctBy 测试
[TestMethod]
public void DistinctBy_RemovesDuplicatesByKey()
{
// Arrange
var items = new List<TestItem>
{
new() { Id = 1, Name = "A" },
new() { Id = 2, Name = "B" },
new() { Id = 3, Name = "A" }, // 重复的 Name
new() { Id = 4, Name = "C" }
};
// Act (.NET 6+ 内置 DistinctBy)
var distinct = items.DistinctBy(x => x.Name).ToList();
// Assert
Assert.AreEqual(3, distinct.Count);
Assert.AreEqual(1, distinct[0].Id); // 保留第一个 "A"
Assert.AreEqual(2, distinct[1].Id);
Assert.AreEqual(4, distinct[2].Id);
}
#endregion
#region 其他实用扩展测试
[TestMethod]
public void ForEach_ExecutesActionForAllElements()
{
// Arrange
var items = new List<int> { 1, 2, 3 };
var sum = 0;
// Act
items.ForEach(x => sum += x);
// Assert
Assert.AreEqual(6, sum);
}
[TestMethod]
public void ForEach_WithIndex_ProvidesCorrectIndex()
{
// Arrange
var items = new List<string> { "A", "B", "C" };
var results = new List<string>();
// Act
items.ForEach((item, index) => results.Add($"{index}:{item}"));
// Assert
CollectionAssert.AreEqual(new[] { "0:A", "1:B", "2:C" }, results);
}
[TestMethod]
public void JoinString_ConcatenatesWithSeparator()
{
// Arrange
var items = new[] { "apple", "banana", "cherry" };
// Act
var result = items.JoinString(", ");
// Assert
Assert.AreEqual("apple, banana, cherry", result);
}
[TestMethod]
public void Shuffle_ChangesOrder()
{
// Arrange
var items = Enumerable.Range(1, 100).ToList();
var original = items.ToList();
// Act
var shuffled = items.Shuffle().ToList();
// Assert: 打乱后的顺序应该不同(理论上有极小概率相同)
Assert.AreEqual(original.Count, shuffled.Count);
// 检查是否至少有一些元素位置变化
var differentPositions = original.Where((item, index) => shuffled[index] != item).Count();
Assert.IsTrue(differentPositions > 10); // 至少 10% 的元素位置不同
}
[TestMethod]
public void TakeRandom_ReturnsSpecifiedCount()
{
// Arrange
var items = Enumerable.Range(1, 100).ToList();
// Act
var random10 = items.TakeRandom(10).ToList();
// Assert
Assert.AreEqual(10, random10.Count);
Assert.IsTrue(random10.All(x => items.Contains(x)));
}
[TestMethod]
public void Split_DividesIntoParts()
{
// Arrange
var items = Enumerable.Range(1, 10).ToList();
// Act
var parts = items.Split(3).ToList();
// Assert
Assert.AreEqual(3, parts.Count);
Assert.AreEqual(4, parts[0].Count()); // 10 / 3 = 向上取整 4
Assert.AreEqual(4, parts[1].Count());
Assert.AreEqual(2, parts[2].Count());
}
[TestMethod]
public void AllUnique_ChecksUniqueness()
{
// Arrange
var uniqueList = new[] { 1, 2, 3, 4 };
var duplicateList = new[] { 1, 2, 2, 3 };
// Act & Assert
Assert.IsTrue(uniqueList.AllUnique());
Assert.IsFalse(duplicateList.AllUnique());
}
[TestMethod]
public void HasDuplicates_DetectsDuplicates()
{
// Arrange
var uniqueList = new[] { 1, 2, 3, 4 };
var duplicateList = new[] { 1, 2, 2, 3 };
// Act & Assert
Assert.IsFalse(uniqueList.HasDuplicates());
Assert.IsTrue(duplicateList.HasDuplicates());
}
[TestMethod]
public void GetDuplicates_ReturnsOnlyDuplicates()
{
// Arrange
var items = new[] { 1, 2, 2, 3, 3, 3, 4 };
// Act
var duplicates = items.GetDuplicates().ToList();
// Assert
Assert.AreEqual(2, duplicates.Count);
CollectionAssert.Contains(duplicates, 2);
CollectionAssert.Contains(duplicates, 3);
CollectionAssert.DoesNotContain(duplicates, 1);
CollectionAssert.DoesNotContain(duplicates, 4);
}
[TestMethod]
public void Interleave_MergesAlternately()
{
// Arrange
var first = new[] { 1, 3, 5 };
var second = new[] { 2, 4, 6 };
// Act
var result = first.Interleave(second).ToList();
// Assert
CollectionAssert.AreEqual(new[] { 1, 2, 3, 4, 5, 6 }, result);
}
[TestMethod]
public void Interleave_DifferentLengths_MergesCorrectly()
{
// Arrange
var first = new[] { 1, 3 };
var second = new[] { 2, 4, 6, 8 };
// Act
var result = first.Interleave(second).ToList();
// Assert
CollectionAssert.AreEqual(new[] { 1, 2, 3, 4, 6, 8 }, result);
}
[TestMethod]
public void WhereNotNull_FiltersNullValues()
{
// Arrange
var items = new List<string?> { "A", null, "B", null, "C" };
// Act
var result = items.WhereNotNull().ToList();
// Assert
Assert.AreEqual(3, result.Count);
CollectionAssert.AreEqual(new[] { "A", "B", "C" }, result);
}
[TestMethod]
public void ToDictionaryList_GroupsCorrectly()
{
// Arrange
var items = new List<TestItem>
{
new() { Id = 1, Name = "A", ParentId = 1 },
new() { Id = 2, Name = "B", ParentId = 1 },
new() { Id = 3, Name = "C", ParentId = 2 },
new() { Id = 4, Name = "D", ParentId = 2 }
};
// Act
var grouped = items.ToDictionaryList(
x => x.ParentId!.Value,
x => x.Name
);
// Assert
Assert.AreEqual(2, grouped.Count);
Assert.AreEqual(2, grouped[1].Count);
CollectionAssert.Contains(grouped[1], "A");
CollectionAssert.Contains(grouped[1], "B");
Assert.AreEqual(2, grouped[2].Count);
CollectionAssert.Contains(grouped[2], "C");
CollectionAssert.Contains(grouped[2], "D");
}
#endregion
#region 实际应用场景测试
[TestMethod]
public void Scenario_PaginationWithSearch()
{
// Arrange: 模拟产品列表搜索和分页
var products = Enumerable.Range(1, 100)
.Select(i => new { Id = i, Name = $"Product {i}", Price = i * 10 })
.ToList();
// Act: 搜索价格大于 500 的产品,并分页
var searchResults = products
.Where(p => p.Price > 500)
.ToPagedResult(1, 10);
// Assert
Assert.AreEqual(10, searchResults.Items.Count);
Assert.IsTrue(searchResults.Items.All(p => p.Price > 500));
Assert.IsTrue(searchResults.TotalCount > 40); // 总共有 50+ 个符合条件的产品
}
[TestMethod]
public void Scenario_OrganizationTree()
{
// Arrange: 模拟组织架构树
var departments = new List<TestItem>
{
new() { Id = 1, Name = "总公司", ParentId = null },
new() { Id = 2, Name = "技术部", ParentId = 1 },
new() { Id = 3, Name = "市场部", ParentId = 1 },
new() { Id = 4, Name = "前端组", ParentId = 2 },
new() { Id = 5, Name = "后端组", ParentId = 2 },
new() { Id = 6, Name = "运营组", ParentId = 3 }
};
// Act: 构建组织树
var tree = departments.ToTree(x => x.Id, x => x.ParentId);
// Assert: 验证树形结构
Assert.AreEqual(1, tree.Count); // 1 个根节点(总公司)
Assert.AreEqual(2, tree[0].Children.Count); // 2 个一级部门
Assert.AreEqual(2, tree[0].Children[0].Children.Count); // 技术部有 2 个组
Assert.AreEqual("前端组", tree[0].Children[0].Children[0].Data.Name);
}
[TestMethod]
public void Scenario_BatchProcessing()
{
// Arrange: 模拟批量导入数据
var records = Enumerable.Range(1, 1000).ToList();
var processedCount = 0;
// Act: 每批处理 100 条记录
records.ForEachBatch(100, batch =>
{
// 模拟批量插入数据库
processedCount += batch.Count();
});
// Assert
Assert.AreEqual(1000, processedCount);
}
[TestMethod]
public void Scenario_DataCleaning()
{
// Arrange: 模拟清洗数据(去除 null、去重、过滤)
var rawData = new List<string?> { "A", null, "B", "A", null, "C", "B", "D" };
// Act: 数据清洗流水线
var cleanedData = rawData
.WhereNotNull() // 去除 null
.DistinctBy(x => x) // 去重
.Where(x => x != "D") // 过滤不需要的数据
.ToList();
// Assert
Assert.AreEqual(3, cleanedData.Count);
CollectionAssert.AreEqual(new[] { "A", "B", "C" }, cleanedData);
}
#endregion
}