From df4a7252c1a4e43796b017a5d46025c0de3db59e Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Sat, 15 Nov 2025 12:20:46 +0800 Subject: [PATCH] refactor(utils): improve TasStorage implementation and tests - Change exception type from ArgumentOutOfRangeException to ArgumentException - Fix node seeking logic by correcting candidate order - Update Visit, Insert, and Remove methods with proper range checks - Enhance cursor management after removal operations - Add comprehensive test cases for edge scenarios - Introduce AssertExtension for better exception testing - Handle empty collection states more robustly --- BallanceTasEditor/Utils/TasStorage.cs | 46 +++++++++++++------ BallanceTasEditorTests/AssertExtension.cs | 28 +++++++++++ .../BallanceTasEditorTests.csproj | 1 + .../Utils/TasStorageTests.cs | 44 +++++++++++++++--- 4 files changed, 98 insertions(+), 21 deletions(-) create mode 100644 BallanceTasEditorTests/AssertExtension.cs diff --git a/BallanceTasEditor/Utils/TasStorage.cs b/BallanceTasEditor/Utils/TasStorage.cs index be1e7b3..4b07531 100644 --- a/BallanceTasEditor/Utils/TasStorage.cs +++ b/BallanceTasEditor/Utils/TasStorage.cs @@ -16,7 +16,7 @@ namespace BallanceTasEditor.Utils { /// /// 要访问的单元的索引。 /// 被访问的单元。 - /// 给定的索引超出范围。 + /// 给定的索引超出范围。 T Visit(int index); /// /// 在给定的索引之前插入给定的项目。 @@ -29,14 +29,14 @@ namespace BallanceTasEditor.Utils { /// /// 要在前方插入数据的元素的索引。 /// 要插入的元素的迭代器。 - /// 给定的索引超出范围。 + /// 给定的索引超出范围。 void Insert(int index, IEnumerable items); /// /// 从给定单元开始,移除给定个数的元素。 /// /// 要开始移除的单元的索引。 /// 要移除的元素的个数。 - /// 给定的索引超出范围。 + /// 给定的索引超出范围。 void Remove(int index, int count); /// @@ -154,7 +154,7 @@ namespace BallanceTasEditor.Utils { public int Offset; public int CompareTo(NodeSeekInfo other) { - return this.Offset.CompareTo(other.Offset); + return Math.Abs(this.Offset).CompareTo(Math.Abs(other.Offset)); } } @@ -173,8 +173,8 @@ namespace BallanceTasEditor.Utils { // 创建三个候选方案。 var candidates = new NodeSeekInfo[3] { new NodeSeekInfo() { Origin = NodeSeekOrigin.Head, Offset = desiredIndex }, - new NodeSeekInfo() { Origin = NodeSeekOrigin.Cursor, Offset = desiredIndex - (GetCount() - 1) }, - new NodeSeekInfo() { Origin = NodeSeekOrigin.Tail, Offset = desiredIndex - m_CursorIndex.Value }, + new NodeSeekInfo() { Origin = NodeSeekOrigin.Tail, Offset = desiredIndex - (GetCount() - 1) }, + new NodeSeekInfo() { Origin = NodeSeekOrigin.Cursor, Offset = desiredIndex - m_CursorIndex.Value }, }; // 确定哪个候选方案最短。 var bestCandidate = candidates.Min(); @@ -211,15 +211,25 @@ namespace BallanceTasEditor.Utils { } public T Visit(int index) { - MoveToIndex(index); - return m_Cursor.Value; + if (index < 0 || index >= GetCount()) { + throw new ArgumentOutOfRangeException("Index out of range."); + } else { + MoveToIndex(index); + return m_Cursor.Value; + } } public void Insert(int index, IEnumerable items) { - if (index == GetCount()) { + if (index < 0 || index > GetCount()) { + throw new ArgumentOutOfRangeException("Index out of range."); + } else if (index == GetCount()) { foreach (T item in items) { m_Container.AddLast(item); } + + m_Cursor = m_Container.First; + if (m_Cursor is null) m_CursorIndex = null; + else m_CursorIndex = 0; } else { MoveToIndex(index); @@ -233,7 +243,9 @@ namespace BallanceTasEditor.Utils { } public void Remove(int index, int count) { - if (index + count >= GetCount()) + if (count == 0) + return; + if (index + count > GetCount()) throw new ArgumentOutOfRangeException("Expected removed items out of range."); MoveToIndex(index); @@ -253,14 +265,20 @@ namespace BallanceTasEditor.Utils { } // 然后设置Cursor和Index - // 如果全部删完了,就清除这两个的设置。 - // 否则就以prevNode为当前Cursor,Index--为对应Index。 if (IsEmpty()) { + // 如果全部删完了,就清除这两个的设置。 m_Cursor = null; m_CursorIndex = null; } else { - m_Cursor = prevNode; - --m_CursorIndex; + if (prevNode is null) { + // 如果是按头部删除的,则直接获取头部及其Index。 + m_Cursor = m_Container.First; + m_CursorIndex = 0; + } else { + // 否则就以prevNode为当前Cursor,Index--为对应Index。 + m_Cursor = prevNode; + --m_CursorIndex; + } } } diff --git a/BallanceTasEditorTests/AssertExtension.cs b/BallanceTasEditorTests/AssertExtension.cs new file mode 100644 index 0000000..ca2e247 --- /dev/null +++ b/BallanceTasEditorTests/AssertExtension.cs @@ -0,0 +1,28 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BallanceTasEditorTests { + public static class AssertExtension { + public static T ThrowsDerivedException(Action action) where T : Exception { + try { + action(); + } catch (T ex) { + return ex; + } catch (Exception ex) { + if (ex is T derivedEx) + return derivedEx; + + throw new AssertFailedException( + $"Expected exception of type {typeof(T)} or derived type, but got {ex.GetType()}. " + + $"Message: {ex.Message}"); + } + + throw new AssertFailedException( + $"Expected exception of type {typeof(T)} or derived type, but no exception was thrown."); + } + } +} diff --git a/BallanceTasEditorTests/BallanceTasEditorTests.csproj b/BallanceTasEditorTests/BallanceTasEditorTests.csproj index 5edd915..97453ba 100644 --- a/BallanceTasEditorTests/BallanceTasEditorTests.csproj +++ b/BallanceTasEditorTests/BallanceTasEditorTests.csproj @@ -49,6 +49,7 @@ + diff --git a/BallanceTasEditorTests/Utils/TasStorageTests.cs b/BallanceTasEditorTests/Utils/TasStorageTests.cs index 09995e5..2aab09d 100644 --- a/BallanceTasEditorTests/Utils/TasStorageTests.cs +++ b/BallanceTasEditorTests/Utils/TasStorageTests.cs @@ -10,6 +10,7 @@ namespace BallanceTasEditorTests.Utils { [TestClass] public class TasStorageTests { + private static readonly int[] BLANK = { }; private static readonly int[] PROBE = { 10, 20, 30, 40, 50 }; private static IEnumerable TasStorageInstanceProvider { @@ -28,18 +29,18 @@ namespace BallanceTasEditorTests.Utils { [DynamicData(nameof(TasStorageInstanceProvider))] public void VisitTest(ITasStorage storage) { // 空时访问 - Assert.ThrowsException(() => storage.Visit(-1)); - Assert.ThrowsException(() => storage.Visit(0)); - Assert.ThrowsException(() => storage.Visit(1)); + AssertExtension.ThrowsDerivedException(() => storage.Visit(-1)); + AssertExtension.ThrowsDerivedException(() => storage.Visit(0)); + AssertExtension.ThrowsDerivedException(() => storage.Visit(1)); // 设置数据 storage.Insert(0, PROBE); // 访问数据 - Assert.ThrowsException(() => storage.Visit(-1)); + AssertExtension.ThrowsDerivedException(() => storage.Visit(-1)); for (int i = 0; i < PROBE.Length; i++) { Assert.AreEqual(storage.Visit(i), PROBE[i]); } - Assert.ThrowsException(() => storage.Visit(PROBE.Length)); + AssertExtension.ThrowsDerivedException(() => storage.Visit(PROBE.Length)); } /// @@ -52,8 +53,8 @@ namespace BallanceTasEditorTests.Utils { // 和在非空时的头,中,尾分别插入的结果。 // 先检测空插入 - Assert.ThrowsException(() => storage.Insert(-1, PROBE)); - Assert.ThrowsException(() => storage.Insert(1, PROBE)); + AssertExtension.ThrowsDerivedException(() => storage.Insert(-1, PROBE)); + AssertExtension.ThrowsDerivedException(() => storage.Insert(1, PROBE)); storage.Insert(0, PROBE); for (int i = 0; i < PROBE.Length; i++) { Assert.AreEqual(storage.Visit(i), PROBE[i]); @@ -78,6 +79,7 @@ namespace BallanceTasEditorTests.Utils { Assert.AreEqual(storage.Visit(i), expected[i]); } } + } /// @@ -86,7 +88,28 @@ namespace BallanceTasEditorTests.Utils { [TestMethod] [DynamicData(nameof(TasStorageInstanceProvider))] public void RemoveTest(ITasStorage storage) { + // 在空的时候删除0项 + storage.Remove(0, 0); + // 插入项目后尝试在头中尾分别删除 + var indices = new int[] { 0, PROBE.Length / 2, PROBE.Length - 1 }; + foreach (var index in indices) { + // 清空,插入,删除 + storage.Clear(); + storage.Insert(0, PROBE); + storage.Remove(index, 1); + + // 用List做正确模拟 + var expected = new List(); + expected.AddRange(PROBE); + expected.RemoveRange(index, 1); + + // 检查结果 + Assert.AreEqual(storage.GetCount(), expected.Count); + for (int i = 0; i < expected.Count; i++) { + Assert.AreEqual(storage.Visit(i), expected[i]); + } + } } /// @@ -159,6 +182,13 @@ namespace BallanceTasEditorTests.Utils { // 再次检查数据 Assert.IsTrue(storage.IsEmpty()); Assert.AreEqual(storage.GetCount(), 0); + + // 清空后插入0项,然后确认 + storage.Clear(); + storage.Insert(0, BLANK); + AssertExtension.ThrowsDerivedException(() => storage.Visit(-1)); + AssertExtension.ThrowsDerivedException(() => storage.Visit(0)); + AssertExtension.ThrowsDerivedException(() => storage.Visit(1)); } } }