** 摘要 ** :本文从实际应用出发,提出一种轻量级 .NET 应用程序性能测试框架设计方案。该方案是对已有结果的进一步扩充,具有更强的实用性和扩展性。
** 1 ** ** 引言 ** **
**
文 [1] 提出一种 .NET 应用程序“性能测试框架”,其基本思路是通过多个线程执行通过委托传递过来的待测试的程序块,各线程所运行的程序块的主逻辑是相同的,不同的是执行条件(如初始参数、执行次数等)。这样就可以得到不同“环境”下算法的执行时间,进而得到整体的时间消耗分布情况。应该说,这种方法的思路是很清晰的,使用也很方便。文 [1] 还比较详细地分析了为何采用委托而放弃“反射”、“接口”等手段,主要是为了获取更为准确的测试结果。
但从实际应用来看这种测试还是显得不够全面,它只是通过编写类似测试脚本的测试程序来对某段核心程序或算法进行测试。如果一个应用程序的核心算法很复杂,中间步骤也很复杂,则相应的测试程序编写就变得比较烦琐。比如 B/S 程序就存在大量的交互过程,这时如果想获取一个重要业务操作的执行时间,上述测试框架实现起来就比较麻烦,甚至有些就不能实现。另外,在表述上我们认为文 [1] 所提性能测试方法应当称为轻量级性能测试,因为真正的性能测试包括的参考指标是很多的,不仅仅是执行时间。
针对这些问题,本文对文 [1] 进行了扩展,给出一种更为合理的轻量级 .NET 应用程序性能测试框架( Light-weight Performance Testing Framework of .NET application ,简称 LPTF )设计方法。
** 2 ** ** 概念与结构设计 ** **
**
首先,我们引入“正向测试”和“反向测试”两个概念。
所谓正向测试是指顺序运行应用程序或功能模块,通过嵌入测试点,最终得到各个阶段的运行时间、 CPU 利用率、内存使用等性能指标的结果。反向测试则是通过编写测试程序对某个功能点或某段应用逻辑进行深度测试,进而得出一组性能指标得测试结果。这里的深度测试是指给定不同的测试条件,如执行次数、初始参数等。
不难看出,正向测试和反向测试是相互补充的,文 [1] 中的测试框架即是我们这里所说的反向测试。
下面来看一下结构设计:
图 2-1 整体结构图
_ 解释 _ : LPTF 整体上包括测试逻辑、结果输出两部分。测试逻辑由正向测试( StraightTest )和反向测试( ReverseTest )组成。在 StraightTest 中我们看到有一个叫作 GlobalStraightTest 的类,这是因为 B/S 程序在进行 StraightTest 时可能会有多个页面间跳转的情况,这时如果我们要做全局的正向测试就要保存一个 StraightTest 的执行器( TestRunner )的状态。
测试结果输出模块( ResultOutput )会根据传递过来的测试结果和指定的输出模式进行结果输出,它和测试逻辑是相对独立的。输出模式有两种 DisplayOption 、 WebDisplayOption ,前者适用于 C/S 程序,后者适用于 B/S 程序。
图 2-2 正向测试结构图
_ 解释 _ :正向测试的实现并不复杂,在对 B/S 程序进行全局测试时要传入一个 System.Web.UI.Page 对象(一般是当前页),用来保存 TestRunner 到 session 。如果需要显示测试进度还要传入一个用于反馈进度的 URL 地址,简单说就是 LPTF 会提供一个 aspx 文件,实际使用时可以将其放到当前项目中编译,在运行时把这个文件对应的 URL 作为 TestRunner 的构造参数即可。
StraightTest 的具体执行顺序大致是“生成 TestRunner 对象→调用 TestRunner 的 RunTests 方法启动测试→调用 TestRunner 的 PhaseEnd 方法结束一个阶段的测试→ … →调用 TestRunner 的 End 方法结束测试”。每一次 PhaseEnd 和最后的 End 方法都会记录当前的测试结果,测试结束后可调用 GetTestResult 方法获取全部测试结果。测试结果记录每个阶段的名称和相应的执行时间。
图 2-3 反向测试结构图
_ 解释 _ :反向测试比正向测试要复杂一些,主要是针对同一段程序要准备不同的执行程序(这里称之为测试用例 TestCase ,一般分“长时间 -Max ”、“中等时间 -Median ”、“短时间 -Min ”三个测试环境)。如果每个测试用例要进行一些初始、扫尾和验证动作,则还要把相应的方法委托给 PreTestCleanup 、 PostTestCleanup 和 TestValidityCheck 。测试执行器会根据整体测试情况计算出一个一般运算时间值(这里称之为规范值 NormalisedTimeSpan )。
反向测试的执行过程是首先开辟一个单独的线程,根据指定的执行次数和迭代次数执行用例。执行用例的顺序是随机的。最后,筛选并统计每个用例的执行时间。
另外,反向测试中的测试进度( Progress )直接写在了 LPTF 中,这是因为反向测试的测试程序都是 C/S 程序,不存在 URL 的问题,可以直接写成 dll 。
图 2-4 测试结果输出引擎结构
_ 解释 _ : LPTF 提供了丰富的输出格式。通过一个抽象类 output 规范了统一接口,各种输出方式继承 output 并实现 OutputResults 方法, output 还提供了一个 DisplayResults 方法动态调用具体的输出方法。由于正向测试和反向测试的测试结果形式不同,在 ChartOutputForm 中提供了一个属性 IsDisplayReverseTest 用于指定是否显示反向测试结果。对于 WebChartOutput 类,因为会有显示图表控件、 URL 、编译等问题,最好由用户在自己的应用程序中实现。
** 3 ** ** 使用示例 ** **
**
下面是后面部分示例中会用到的一段程序。 **
**
// 被测试程序
private UInt16 _mediumFactor, _maxFactor;
public UInt16 MediumFactor
{
get { return _mediumFactor;}
set {_mediumFactor = value ;}
}
public UInt16 MaxFactor
{
get { return _maxFactor;}
set {_maxFactor = value ;}
}
public void FastMethod(Int32 NumberIterations)
{
int j = 0;
for ( int i = 0;i < NumberIterations;++i)
{
j += i;
}
}
public void MediumMethod(Int32 NumberIterations)
{
int j = 0;
for ( int i = 0;i < NumberIterations;++i)
{
for ( int k = 0; k < _mediumFactor; ++k)
j += k;
}
}
public void SlowMethod(Int32 NumberIterations)
{
int j = 0;
for ( int i = 0;i < NumberIterations;++i)
{
for ( int k = 0; k < _maxFactor; ++k)
j += k;
}
}
** □ ** ** ** ** 正向测试 ** **
**
首先来看 C/S 程序的正向测试,
public DotNetPerformance.StraightTest.TestResult[] RunSTest()
{
const int numberIterations = 50000000;
DotNetPerformance.StraightTest.TestRunner tr = new DotNetPerformance.StraightTest.TestRunner();
tr.RunTests();
FastMethod(numberIterations);
tr.PhaseEnd( "Phase1" );
MediumMethod(numberIterations);
tr.PhaseEnd( "Phase2" );
SlowMethod(numberIterations);
tr.End( " 最后一个阶段 " );
return tr.GetTestResult();
}
private void btnST_Click( object sender, System.EventArgs e)
{
DotNetPerformance.ResultOutput.Output.DisplayResults(RunSTest(),
DotNetPerformance.ResultOutput.DisplayOption.Chart, null );
}
图 3-1 正向测试结果 - 图形输出
再来看 B/S 程序单个页面里的正向测试(这里用 System.Threading.Thread.Sleep 来模拟一段程序的执行 ),在结果输出时要传入页面对象用于注册脚本,从而显示输出测试结果。
m_Tester = new DotNetPerformance.StraightTest.TestRunner();
m_Tester.RunTests();
System.Threading.Thread.Sleep(1000);
m_Tester.PhaseEnd( " 第一阶段 " );
System.Threading.Thread.Sleep(100);
m_Tester.PhaseEnd( " 第二阶段 " );
System.Threading.Thread.Sleep(2000);
m_Tester.PhaseEnd( " 第三阶段 " );
System.Threading.Thread.Sleep(50);
m_Tester.PhaseEnd( " 第四阶段 " );
System.Threading.Thread.Sleep(50);
m_Tester.End( " 第五阶段 " );
WebDisplayOption _wdo = UIHelper.ToWebDisplayOption( this .ddlOutputMode.SelectedValue);
if (_wdo == WebDisplayOption.WebAlert)
{
DotNetPerformance.ResultOutput.Output.DisplayResults(m_Tester.GetTestResult(),_wdo, new object []{ this .Page});
}
else if (_wdo == WebDisplayOption.WebTable)
<span lang="EN-US" style="FONT-SIZE: 9pt; FONT-FAM