Windows Store 应用的测试与诊断策略
在开发 Windows Store 应用时,测试和诊断是确保应用质量和性能的关键环节。下面将详细介绍 Windows Store 应用的测试策略和诊断监控策略。
1. 测试策略设计与实施
在开发 Windows Store 应用时,可考虑采用以下两种测试计划:
- 功能测试计划 :从用户角度验证软件的所有功能需求是否正常工作。
- 单元测试计划 :验证与外部依赖隔离的软件单元是否按预期运行。
Windows Store 应用的单元测试框架位于 Microsoft.VisualStudio.TestPlatform.UnitTestFramework 命名空间,与 .NET 应用的“经典”测试框架不同。该命名空间包含为测试 Windows Store 应用提供特定支持的类型和方法,并依赖于 Windows Store 应用的同一 WinRT 引擎。
在 Windows Store 应用的单元测试框架中, ExpectedException 属性不再受支持,应使用 Assert.ThrowsException<TException> 方法代替。可以使用 DataTestMethod 属性实现数据驱动测试。
实施单元测试时,需遵循“Three A’s”规则:
1. Arrange(安排) :设置要测试的对象。
2. Act(行动) :执行要测试的方法。
3. Assert(断言) :对对象进行断言。
以下是一些单元测试相关的问题及答案:
| 问题 | 选项 | 答案 |
| — | — | — |
| 以下关于单元测试的陈述中,哪一项是不正确的? | A. 软件的小单元在与外部依赖隔离后进行测试。
B. 测试不应相互依赖以确定成败,也不应要求按特定顺序执行。
C. 单元测试鼓励关注点分离和针对接口编码。
D. 应用的不同组件和资源一起测试以验证它们是否按预期工作。 | D |
| 标记有 TestInitialize 和 TestCleanup 属性的方法何时执行? | A. 在程序集中的所有测试执行前后。
B. 在属于同一类的所有测试执行前后。
C. 在每个测试执行前后。
D. 在所有标记有 DataTestMethod 属性的测试执行前后。 | C |
| 以下哪个功能在 Windows Store 应用的单元测试框架中不受支持? | A. Assert.ThrowsException<TException> 方法
B. TestMethod 属性
C. DataTestMethod 属性
D. ExpectedException 属性 | D |
2. 诊断和监控策略设计
此部分将介绍如何使用 Visual Studio 2012 提供的分析工具对 Windows Store 应用进行性能分析,以检测可能影响应用的性能问题。还将学习如何使用 Windows 事件跟踪 (ETW) 记录应用生命周期中遇到的最重要事件,以及如何实施不同的日志记录策略。
2.1 应用性能分析和性能计数器收集
Windows Store 应用是在沙盒化且响应性高的环境中执行的客户端应用。但在某些情况下,应用可能会运行缓慢,原因可能包括低效代码、对第三方库或远程服务的调用耗时过长、复杂计算消耗过多 CPU 等。
Visual Studio 2012 为 Windows Store 应用提供了一套调试、分析、测试和收集信息的工具。分析工具使用采样方法,定期从 CPU 调用堆栈收集信息,记录应用的性能计数器,评估每个函数的成本。需要注意的是,Windows Store 应用不支持像传统 .NET 应用那样以编程方式收集性能计数器,只能依赖提供的分析工具。
Visual Studio 2012 中许多分析工具和选项在 Windows Store 应用中不可用,以下是 C# 开发的 Windows Store 应用不支持的功能:
- Instrumentation profiling(插桩分析) :有采样和插桩两种主要分析技术,Windows Store 应用仅支持采样分析,但有一些限制,如设置采样事件和时间间隔、收集额外性能计数器数据等功能不支持。插桩分析会在每个函数的开头和结尾注入特定的跟踪代码。
- Concurrency profiling(并发分析) :该分析方法在竞争线程等待访问共享资源时从调用堆栈收集详细信息,可帮助识别性能瓶颈和同步问题。
- .NET memory profiling(.NET 内存分析) :收集有关内存分配和垃圾回收的详细信息。
- Tier interaction profiling (TIP)(层交互分析) :收集有关 ADO.NET 对 SQL Server 数据库的函数调用信息,Windows Store 应用无法使用此功能,因为应用无法访问 System.Data* 命名空间,需依赖远程服务或本地存储来消费数据。
如果想收集更广泛的应用性能信息,可以使用 Windows Performance Toolkit (WPT),可从 http://msdn.microsoft.com/en-us/performance/cc825801.aspx 下载。若遇到与内存相关的问题,还可尝试 Microsoft NP .NET Profiler Tool,下载地址为 http://www.microsoft.com/en-us/download/details.aspx?id=35370 。
以下是一个简单的示例,帮助理解 Visual Studio 2012 中的分析工具。假设有一个仅用于显示名称列表的虚拟应用,其 XAML 代码如下:
<Page
x:Class="PerformanceAnalysisSample.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:PerformanceAnalysisSample"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<StackPanel Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<Button Width="300" Height="50" Content="Customer list"
Click="GetCustomerListButton_Click" Margin="20, 5"/>
<ListView x:Name="CustomerListView" DisplayMemberPath="Name" Margin="20, 5" />
</StackPanel>
</Page>
页面代码隐藏部分的按钮点击事件处理程序如下:
private void GetCustomerListButton_Click(object sender, RoutedEventArgs e)
{
var biz = new FakeBiz();
var customers = biz.GetCustomers();
CustomerListView.ItemsSource = customers;
}
FakeBiz 类的完整代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PerformanceAnalysisSample
{
public class FakeBiz
{
public List<Person> GetCustomers()
{
return this.SimulateRemoteServiceCall();
}
private List<Person> SimulateRemoteServiceCall()
{
for (int i = 0; i < Int32.MaxValue; i++)
{
// code omitted
}
return new List<Person>()
{
new Person() { Name = "Roberto Brunetti" },
new Person() { Name = "Vanni Boncinelli" },
new Person() { Name = "Luca Regnicoli" },
new Person() { Name = "Katia Egiziano" },
new Person() { Name = "Paolo Pialorsi" },
new Person() { Name = "Marco Russo" },
};
}
}
public class Person
{
public string Name { get; set; }
}
}
要在 Visual Studio 中分析代码,可按以下步骤操作:
1. 点击“Debug”菜单中的“Start Performance Analysis”菜单项。
2. Visual Studio 启动分析工具并启动应用。在应用的默认页面中,点击“Get Customers”按钮,等待客户列表显示在屏幕上,分析器会记录代码在后台执行的操作。
3. 分析完成后,返回 Visual Studio 并点击“Stop Profiling”链接。
停止分析后,Visual Studio 2012 会生成一个包含采样期间收集的所有信息的报告。报告包含应用执行期间收集的数据,屏幕上部是一个显示应用执行期间 CPU 使用率百分比的图表。可以选择图表中的特定区域进行缩放或过滤特定时间间隔内收集的样本。CPU 使用率图表下方的“Hot Path”部分显示了 CPU 使用率最高的调用路径。每个路径中的函数调用有两个指标:包含样本和独占样本。
- 包含样本百分比(或包含时间) :表示完成特定函数所花费的 CPU 时间,包括等待它可能调用的任何其他函数所花费的时间。
- 独占样本百分比(或独占时间) :不考虑等待当前函数调用的其他函数完成所花费的时间,仅表示函数内部工作所消耗的时间。
在这个示例中, SimulateRemoteServiceCall 方法在两个指标(包含和独占 CPU 使用率)中都显示了 98.95% 的值,这意味着应用消耗的几乎所有 CPU 时间都用于该方法。其他函数如 GetCustomerListButton_Click 和 GetCustomers 方法的包含时间为 98.95%,独占时间接近零,这意味着它们只是等待 SimulateRemoteServiceCall 方法完成。
还可以使用其他更具体的视图(如 Call Tree 视图)浏览收集的数据。点击某个函数名称可以获取该函数的更多详细信息。
此外, Application 类的 DebugSettings 属性提供了一个额外的分析选项。 DebugSettings 类的 EnableFrameRateCounter 静态方法可在应用窗口的左上角显示每秒帧数 (FPS) 计数器,该计数器可帮助观察应用用户界面的整体性能,包括纹理的内存利用率和 CPU 利用率。以下是在 App 类中激活帧率计数器的代码示例:
public App()
{
this.InitializeComponent();
this.Suspending += OnSuspending;
DebugSettings.EnableFrameRateCounter = true;
}
计数器显示的数字从左到右提供以下指标:
- Composition thread FPS :合成线程的每秒帧数。
- UI thread FPS :UI 线程的每秒帧数。
- Memory :纹理的视频内存利用率。
- Batch :发送到视频卡进行绘制的表面数量。
- Composition thread CPU :合成线程处理器上花费的时间(毫秒)。
- UI CPU :UI 线程处理器上花费的时间(毫秒)。
DebugSettings 类的 IsOverdrawHeatMapEnabled 属性可用于检测用户界面中应用绘制对象重叠的区域。将该属性设置为 true 后,颜色越深表示重叠程度越高。可以通过以下代码激活此可视化:
public App()
{
this.InitializeComponent();
this.Suspending += OnSuspending;
DebugSettings.EnableFrameRateCounter = true;
DebugSettings.IsOverdrawHeatMapEnabled = true;
}
在应用开发过程中,这种可视化有助于检测布局、动画和其他图形处理密集型操作。在这些情况下,可以考虑将包含整个概念元素的 UIElement 的 CacheMode 属性设置为 BitmapCache ,这样框架可以将元素渲染为位图一次,然后使用该位图而不是为每一帧重新渲染子对象。
2.2 事件跟踪和日志记录
System.Diagnostics.Tracing 命名空间提供了创建强类型事件以用于日志记录的类型和成员,这些事件可由 ETW 捕获。但 Windows Runtime 仅支持部分可用类型,例如,在 Windows Store 应用中, TraceListener 类(及其派生类型,如 DefaultTraceListener 、 TextWriterTraceListener 和 EventLogTraceListener 类型)不受 Windows Runtime 支持。
首先需要提供自己的 EventSource 实现。 EventSource 类可创建需要跟踪的不同事件类型。以下是一个实现示例:
using System;
using System.Collections.Generic;
using System.Diagnostics.Tracing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SimpleEventTracing
{
public class MyCustomEventSource : EventSource
{
[Event(1, Level = EventLevel.LogAlways)]
public void WriteDebug(string message)
{
this.WriteEvent(1, message);
}
[Event(2, Level = EventLevel.Informational)]
public void WriteInfo(string message)
{
this.WriteEvent(2, message);
}
[Event(3, Level = EventLevel.Warning)]
public void WriteWarning(string message)
{
this.WriteEvent(3, message);
}
[Event(4, Level = EventLevel.Error)]
public void WriteError(string message)
{
this.WriteEvent(4, message);
}
[Event(5, Level = EventLevel.Critical)]
public void WriteCritical(string message)
{
this.WriteEvent(5, message);
}
}
}
每个事件都用 EventAttribute 属性标记,可指定额外的事件信息,供事件监听器过滤要跟踪的事件。每个事件可指定的信息如下:
- EventId :事件的标识符。
- Keywords :指定与事件关联的关键字,定义在 EventKeywords 枚举中。
- Level :事件的级别,可取值(定义在 EventLevel 枚举中):
- LogAlways :通常用于指示监听器不对事件进行过滤。
- Critical :对应导致重大失败的关键错误。
- Error :表示标准错误。
- Warning :表示警告事件。
- Informational :提供关于不被视为错误的事件的额外信息(如应用的进度状态)。
- Verbose :添加冗长的事件或消息。
- Message :事件的消息。
- Opcode :事件的操作码。
- Task :事件的任务。
- TypeId :在派生类中实现时,表示此属性的唯一标识符。
- Version :事件的版本。
由于同一事件源实例可在整个应用中共享,可使用单例模式获取自定义事件源的引用,示例代码如下:
using System;
using System.Collections.Generic;
using System.Diagnostics.Tracing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SimpleEventTracing
{
public static class EventSourceFactory
{
private static MyCustomEventSource _eventSource;
public static MyCustomEventSource EventSource
{
get
{
if (_eventSource == null)
{
_eventSource = new MyCustomEventSource();
}
return _eventSource;
}
}
}
}
决定要跟踪的事件后,必须实现一个事件监听器。由于 EventListener 类是抽象类,必须派生自定义监听器并提供 OnEventWritten 抽象方法的实现,该方法将接收跟踪事件的回调。可以定义多个事件监听器,每个监听器在逻辑上独立于其他监听器,可使用不同的监听器跟踪不同类型的事件。例如,一个监听器可以只跟踪最严重的错误,将其立即发送到云或远程服务进行进一步分析,而另一个监听器可以处理不太关键的事件,将其记录在本地存储中,稍后再收集。以下是两个实现 EventListener 抽象类的示例:
using System;
using System.Collections.Generic;
using System.Diagnostics.Tracing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SimpleEventTracing
{
public class RemoteEventListener : EventListener
{
protected override void OnEventWritten(EventWrittenEventArgs eventData)
{
// log the event via a remote service or in the cloud
}
}
public class LocalStorageEventListener : EventListener
{
protected override void OnEventWritten(EventWrittenEventArgs eventData)
{
// log the event in the local storage;
}
}
}
OnEventWritten 方法在关联的 EventSource 的 WriteEvent 方法被调用时被调用。该方法接收一个 EventWrittenEventArgs 类的实例作为唯一参数,包含事件源写入的事件的所有信息。以下代码示例展示了事件监听器如何根据与监听器关联的 EventSource 中定义的事件属性过滤要跟踪的事件集:
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
this.InitializeEventListener();
// code omitted
}
private void InitializeEventListener()
{
EventListener remoteListener = new RemoteEventListener();
remoteListener.EnableEvents(EventSourceFactory.EventSource,
EventLevel.Error | EventLevel.Critical);
}
综上所述,通过合理设计和实施测试策略以及有效的诊断和监控策略,可以提高 Windows Store 应用的质量和性能,为用户提供更好的体验。
Windows Store 应用的测试与诊断策略
3. 测试策略与诊断策略的协同作用
测试策略和诊断策略在 Windows Store 应用开发中并非孤立存在,它们相互配合,共同保障应用的质量和性能。
3.1 测试为诊断提供基础
单元测试和功能测试能够提前发现应用中的潜在问题,这些问题信息可以为后续的诊断工作提供重要线索。例如,单元测试中发现某个函数的执行结果不符合预期,通过分析测试结果,可以初步定位到可能存在问题的代码区域。在进行性能诊断时,这些前期测试发现的问题点可以作为重点排查对象,提高诊断效率。
3.2 诊断为测试提供方向
诊断过程中发现的性能瓶颈、内存泄漏等问题,可以反馈到测试策略中。针对这些问题,可以增加特定的测试用例,以确保问题得到彻底解决。例如,如果在性能分析中发现某个模块的 CPU 使用率过高,那么可以在后续的单元测试中增加对该模块的性能测试用例,验证优化措施是否有效。
4. 实际案例分析
为了更好地理解上述测试和诊断策略的应用,下面通过一个实际案例进行详细分析。
假设我们正在开发一个 Windows Store 音乐播放应用,该应用允许用户在线播放音乐、创建播放列表等。在开发过程中,我们按照前面介绍的策略进行测试和诊断。
4.1 测试阶段
- 功能测试 :从用户的角度出发,对应用的各项功能进行全面测试。例如,测试音乐播放功能是否正常,包括播放、暂停、停止、切换歌曲等操作;测试播放列表的创建、编辑和删除功能是否可用。
- 单元测试 :对应用中的各个独立单元进行测试,如音乐加载模块、播放控制模块等。确保每个单元在与外部依赖隔离的情况下能够正常工作。
在单元测试过程中,我们发现音乐加载模块在处理大文件时会出现异常。通过分析测试结果,定位到问题出在文件读取的代码逻辑上。经过修复后,再次进行单元测试,确保问题得到解决。
4.2 诊断阶段
在应用上线后,用户反馈应用在播放音乐时会出现卡顿现象。为了解决这个问题,我们使用 Visual Studio 2012 的分析工具进行性能分析。
- 性能分析 :按照前面介绍的步骤,启动性能分析工具,在应用中进行音乐播放操作。分析结果显示,音乐解码模块的 CPU 使用率过高,导致应用卡顿。
- 事件跟踪和日志记录 :通过自定义的
EventSource和事件监听器,记录音乐播放过程中的关键事件。发现每次卡顿发生时,都会有大量的内存分配和释放操作,进一步定位到内存管理可能存在问题。
针对这些问题,我们对音乐解码模块进行了优化,采用更高效的算法和数据结构,同时优化内存管理。优化后再次进行性能分析,发现 CPU 使用率明显降低,应用的卡顿现象得到了有效解决。
5. 总结与建议
通过以上对 Windows Store 应用测试和诊断策略的介绍以及实际案例分析,我们可以总结出以下几点建议:
- 全面规划测试策略 :在开发初期就制定详细的功能测试计划和单元测试计划,确保应用的各项功能和独立单元都能得到充分测试。
- 合理运用诊断工具 :充分利用 Visual Studio 2012 提供的分析工具和 ETW 进行性能分析和事件跟踪,及时发现和解决应用中的问题。
- 建立反馈机制 :测试和诊断过程中要建立有效的反馈机制,将测试结果和诊断信息及时反馈给开发人员,以便快速修复问题。
- 持续优化 :应用开发是一个持续的过程,要不断对应用进行测试和诊断,持续优化应用的性能和质量。
以下是一个简单的流程图,展示了 Windows Store 应用测试和诊断的整体流程:
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px
A([开始开发]):::startend --> B(制定测试策略):::process
B --> C(功能测试):::process
B --> D(单元测试):::process
C --> E{是否通过测试?}:::decision
D --> E
E -->|否| F(修复问题):::process
F --> C
E -->|是| G(应用上线):::process
G --> H(用户反馈问题):::process
H --> I(使用分析工具诊断):::process
I --> J{是否找到问题?}:::decision
J -->|是| K(修复问题):::process
K --> C
J -->|否| I
总之,通过科学合理地运用测试和诊断策略,可以有效提高 Windows Store 应用的质量和性能,为用户提供更加稳定、流畅的使用体验。在实际开发过程中,要根据应用的特点和需求,灵活调整和完善这些策略,以适应不断变化的开发环境和用户需求。
超级会员免费看
10万+

被折叠的 条评论
为什么被折叠?



