Files
ui/docs/architecture/testing-strategy.md
2025-10-16 15:08:42 +08:00

8.7 KiB
Raw Blame History

Testing Strategy

本节定义控件库的测试方法和组织策略,确保核心功能的稳定性和可靠性。


Testing Pyramid

       E2E Tests (手动测试)
      /                    \
     Integration Tests (可选)
    /                          \
   Unit Tests (核心逻辑)

测试重点分配:

  • Unit Tests (70%):核心业务逻辑、数据模型、工具类
  • Integration Tests (可选):控件与 ReactiveUI 的集成、主题系统
  • Manual Tests (必须)UI 渲染、主题切换、用户交互

MVP 阶段不包括:

  • UI 自动化测试Avalonia 提供 Headless 测试包,但 MVP 阶段依赖手动测试)
  • 性能基准测试(除非发现明显性能问题)
  • 跨平台兼容性测试(仅在 Windows 上测试)

Test Organization

测试项目结构:

Penguin.AvaloniaUI.Tests/
├── Controls/
│   ├── PropertyGridTests.cs
│   ├── UserGuideTests.cs
│   └── ...
├── Layouts/
│   └── TwoColumnLayoutTests.cs
├── Themes/
│   └── ThemeManagerTests.cs
├── Utils/
│   ├── ReflectionHelperTests.cs
│   └── Converters/
│       └── TypeToEditorConverterTests.cs
└── TestHelpers/
    ├── MockObjects.cs
    └── TestBase.cs

命名约定:

  • 测试类:{ClassName}Tests
  • 测试方法:{MethodName}_{Scenario}_{ExpectedResult}

Unit Test Examples

示例 1测试 PropertyGrid 的反射逻辑

namespace Penguin.AvaloniaUI.Tests.Controls;

public class PropertyGridTests
{
    [Fact]
    public void RefreshProperties_WithValidObject_ShouldParseAllPublicProperties()
    {
        // Arrange
        var testObject = new TestSettings
        {
            Name = "Test",
            Age = 25,
            IsEnabled = true
        };
        var propertyGrid = new PropertyGrid
        {
            SelectedObject = testObject
        };

        // Act
        var properties = propertyGrid.Properties;

        // Assert
        Assert.Equal(3, properties.Count);
        Assert.Contains(properties, p => p.Name == "Name" && p.PropertyType == typeof(string));
        Assert.Contains(properties, p => p.Name == "Age" && p.PropertyType == typeof(int));
        Assert.Contains(properties, p => p.Name == "IsEnabled" && p.PropertyType == typeof(bool));
    }

    [Fact]
    public void RefreshProperties_WithCategoryAttribute_ShouldGroupByCategory()
    {
        // Arrange
        var testObject = new CategorizedSettings();
        var propertyGrid = new PropertyGrid
        {
            SelectedObject = testObject
        };

        // Act
        var properties = propertyGrid.Properties;
        var generalCategory = properties.Where(p => p.Category == "General").ToList();
        var advancedCategory = properties.Where(p => p.Category == "Advanced").ToList();

        // Assert
        Assert.NotEmpty(generalCategory);
        Assert.NotEmpty(advancedCategory);
    }

    [Fact]
    public void RefreshProperties_WithNullObject_ShouldClearProperties()
    {
        // Arrange
        var propertyGrid = new PropertyGrid
        {
            SelectedObject = new TestSettings()
        };

        // Act
        propertyGrid.SelectedObject = null;

        // Assert
        Assert.Empty(propertyGrid.Properties);
    }
}

示例 2测试 ThemeManager

namespace Penguin.AvaloniaUI.Tests.Themes;

public class ThemeManagerTests
{
    [Fact]
    public void ApplyTheme_WithLightTheme_ShouldLoadLightResources()
    {
        // Arrange
        var app = CreateTestApplication();

        // Act
        ThemeManager.ApplyTheme(ThemeType.Light);

        // Assert
        var textPrimary = app.Resources["TextPrimary"] as SolidColorBrush;
        Assert.NotNull(textPrimary);
        // 浅色主题的主要文本应为深色
        Assert.True(textPrimary.Color.R < 128);
    }

    [Fact]
    public void ApplyTheme_SwitchingThemes_ShouldTriggerThemeChangedEvent()
    {
        // Arrange
        var eventTriggered = false;
        ThemeManager.ThemeChanged += (sender, type) => eventTriggered = true;

        // Act
        ThemeManager.ApplyTheme(ThemeType.Dark);

        // Assert
        Assert.True(eventTriggered);
    }

    private Application CreateTestApplication()
    {
        // 创建测试用的 Application 实例
        return new Application();
    }
}

示例 3测试 UserGuide 步骤流转

namespace Penguin.AvaloniaUI.Tests.Controls;

public class UserGuideTests
{
    [Fact]
    public void NextStepCommand_WhenNotLastStep_ShouldIncrementIndex()
    {
        // Arrange
        var userGuide = new UserGuide();
        userGuide.Steps.Add(new GuideStep { Title = "Step 1", Order = 0 });
        userGuide.Steps.Add(new GuideStep { Title = "Step 2", Order = 1 });
        userGuide.Steps.Add(new GuideStep { Title = "Step 3", Order = 2 });

        // Act
        userGuide.NextStepCommand.Execute(null);

        // Assert
        Assert.Equal(1, userGuide.CurrentStepIndex);
    }

    [Fact]
    public void NextStepCommand_WhenLastStep_ShouldTriggerCompleteEvent()
    {
        // Arrange
        var userGuide = new UserGuide();
        userGuide.Steps.Add(new GuideStep { Title = "Step 1", Order = 0 });
        var completedTriggered = false;
        userGuide.Completed += (sender, args) => completedTriggered = true;

        // Act
        userGuide.NextStepCommand.Execute(null);

        // Assert
        Assert.True(completedTriggered);
    }

    [Fact]
    public void PreviousStepCommand_WhenFirstStep_ShouldNotExecute()
    {
        // Arrange
        var userGuide = new UserGuide();
        userGuide.Steps.Add(new GuideStep { Title = "Step 1", Order = 0 });

        // Act
        var canExecute = userGuide.PreviousStepCommand.CanExecute(null);

        // Assert
        Assert.False(canExecute);
    }
}

Manual Testing Checklist

以下场景必须通过示例应用手动测试

PropertyGrid

  • 绑定包含 6 种基础类型的对象,所有编辑器正确显示
  • 修改属性值SelectedObject 对象同步更新
  • 只读属性禁用编辑器或显示为灰色
  • 属性按 Category 分组显示,分组标题清晰
  • 主题切换时PropertyGrid 颜色正确更新
  • 50 个属性的生成时间 < 200ms使用秒表或 Stopwatch 测量)

UserGuide

  • 引导流程正确显示 Overlay 遮罩
  • 目标控件区域挖空显示(清晰可见)
  • 提示框在目标控件附近正确定位Top/Bottom/Left/Right
  • "下一步"/"上一步"按钮功能正常
  • 步骤进度显示正确(如 "2/5"
  • "跳过"和"完成"按钮触发完成事件

主题系统:

  • 主题切换在 100ms 内完成,无闪烁
  • 所有控件的颜色正确响应主题变化
  • 浅色和暗色主题的对比度舒适(文本清晰可读)
  • 主题切换时无控件错位或布局异常

跨功能集成:

  • 在 PropertyGrid 编辑时切换主题,无崩溃
  • 在 UserGuide 进行中修改 PropertyGrid无异常
  • 窗口缩放时,所有控件布局正确调整

Test Helpers

创建测试辅助类减少重复代码:

namespace Penguin.AvaloniaUI.Tests.TestHelpers;

public static class MockObjects
{
    public class TestSettings
    {
        public string Name { get; set; } = "Default";
        public int Age { get; set; } = 0;
        public bool IsEnabled { get; set; } = false;
    }

    public class CategorizedSettings
    {
        [Category("General")]
        public string Name { get; set; } = "Default";

        [Category("Advanced")]
        public int Timeout { get; set; } = 5000;
    }
}

public class TestBase
{
    protected Application CreateTestApplication()
    {
        var app = new Application();
        // 加载测试用主题资源
        return app;
    }
}

Running Tests

# 运行所有测试
dotnet test

# 运行特定测试类
dotnet test --filter "FullyQualifiedName~PropertyGridTests"

# 运行特定测试方法
dotnet test --filter "Name~RefreshProperties_WithValidObject"

# 生成代码覆盖率报告
dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=html

Visual Studio 测试运行器:

  1. 打开 Test Explorer测试 → 测试资源管理器)
  2. 点击"运行所有测试"或右键运行特定测试
  3. 查看测试结果和失败详情