如何使用Xunit测试WPF的DataTableModel泛型类?
针对DataTableModel的Xunit单元测试解决方案
1. 测试保护方法(InstantiateTable、TriggerUpdate等)
直接调用保护方法有两种实用方式:
- 派生测试类暴露方法:写一个继承自
DataTableModel<T>的测试类,把需要测试的保护方法包装成公共方法:
public class TestableDataTableModel<T> : DataTableModel<T> { // 按需暴露需要测试的保护方法 public new void InstantiateTable() => base.InstantiateTable(); public new void TriggerUpdate() => base.TriggerUpdate(); public new void UpdateTable() => base.UpdateTable(); }
测试时直接实例化TestableDataTableModel<T>,调用这些公共包装方法即可。
- 反射调用:如果不想修改类结构,用反射获取保护方法并执行:
var model = new DataTableModel<TestItem>(); var method = typeof(DataTableModel<TestItem>).GetMethod("TriggerUpdate", BindingFlags.Instance | BindingFlags.NonPublic); method.Invoke(model, null);
2. 验证构造函数中的命令对象
假设构造函数初始化了ICommand类型的命令(比如WPF常用的RelayCommand):
- 先通过测试类暴露命令(如果命令是protected),或者用反射获取私有命令字段;
- 测试命令的
CanExecute初始状态,以及执行Execute后的逻辑变化:
[Fact] public void Constructor_InitializesCommands() { var model = new TestableDataTableModel<TestItem>(); // 假设Testable类暴露了构造函数中初始化的RefreshCommand var refreshCmd = model.RefreshCommand; Assert.NotNull(refreshCmd); Assert.True(refreshCmd.CanExecute(null)); // 验证初始可执行状态 // 执行命令后验证数据更新逻辑 refreshCmd.Execute(null); Assert.NotNull(model.AllItems); }
3. 分页逻辑测试
构造模拟数据集,设置分页参数后验证结果:
[Fact] public void Pagination_ReturnsCorrectPageData() { // 构造25条测试数据 var testItems = Enumerable.Range(1, 25).Select(i => new TestItem { Id = i }).ToList(); var model = new TestableDataTableModel<TestItem>(); // 假设Testable类提供设置内部数据源的方法 model.SetInternalData(testItems); // 设置分页参数 model.PageSize = 10; model.CurrentPage = 2; // 触发数据更新 model.TriggerUpdate(); // 断言分页结果 Assert.Equal(10, model.CurrentPageItems.Count); Assert.Equal(11, model.CurrentPageItems.First().Id); Assert.Equal(3, model.TotalPages); }
4. Filter委托测试
给Filter属性赋值自定义逻辑,验证过滤结果:
[Fact] public void Filter_AppliesExpectedLogic() { var testItems = new List<TestItem> { new TestItem { Id = 1, Category = "Fruit" }, new TestItem { Id = 2, Category = "Vegetable" }, new TestItem { Id = 3, Category = "Fruit" } }; var model = new TestableDataTableModel<TestItem>(); model.SetInternalData(testItems); // 设置过滤委托 model.Filter = item => item.Category == "Fruit"; model.TriggerUpdate(); // 断言过滤后的结果 Assert.Equal(2, model.FilteredItems.Count); Assert.All(model.FilteredItems, item => Assert.Equal("Fruit", item.Category)); }
5. Mock依赖的实现
如果DataTableModel<T>依赖外部服务(比如数据仓储),用Moq库模拟依赖:
[Fact] public void UpdateTable_FetchesDataFromRepository() { // 模拟仓储接口 var mockRepo = new Mock<IItemRepository>(); var expectedData = new List<TestItem> { new TestItem { Id = 1 } }; mockRepo.Setup(repo => repo.GetAll()).Returns(expectedData); // 注入模拟依赖实例化模型 var testableModel = new TestableDataTableModel<TestItem>(mockRepo.Object); // 触发数据更新 testableModel.TriggerUpdate(); // 验证仓储方法是否被调用,以及数据是否正确加载 mockRepo.Verify(repo => repo.GetAll(), Times.Once); Assert.Equal(expectedData, testableModel.AllItems); }
新手测试小贴士
- 优先测试公共方法的行为:很多时候不需要直接测保护方法,通过调用公共方法(比如
GetTableColumnInformation)间接验证内部逻辑; - 用简单测试实体:创建极简的
TestItem类(只有必要属性)代替真实业务实体,避免依赖复杂逻辑; - 先测核心逻辑:优先覆盖分页、过滤、数据更新这些核心功能,再补充边缘场景。
内容的提问来源于stack exchange,提问作者Dane Brazinski
相关产品推荐
相关产品推荐

