代码改变世界

Windows phone 应用开发[11]-Pex 构建自动化白盒测试[上]

2012-01-19 18:59  chenkai  阅读(3029)  评论(11编辑  收藏  举报

昨天一位园友sinodragon21Windows phone应用开发[9]-单元测试评论中.提出关于Windows phone 单元测试中能否使用微软的Pex自动化生成工具生成单元测试用例.和单元测试质量即代码覆盖率统计问题.很有价值.

针对这两个问题.首先需要解释.关于Windows phone 中单元测试现状.针对Windows phone应用程序Unit Test 官方并没有在IDE提供对应的测试框架,目前开发者社区使用比较广泛框架是MS Windows phone 产品组Jeff.wilcox维护的Silverlight Unit Test Framework[SUTF] Windows phone版本.详见Blog:Updated Silverlight Unit Test Framework bits for Windows Phone and Silverlight 3 . 但这个版本始终作为个人形式对开发者发布.并没有以正式的官方渠道向Windows phone开发者推送,所以官方对此并没有提供指定的维护和开发者社区支持.

至此官方也就没有提供自动化测试工具道理. 类似Android 平台自动化Monkeyrunner之外还有更多选择.而Windows phone自动化测试只能靠开发者自己想办法.

那么关于SUTF 中Code Converage代码覆盖率呢? 针对这个问题 特别咨询SUTF WP版本维护作者Jeff.Wilcox.得到回复信息如下:

  • HI Jeff,
    how to take code coverage from SilverLight Unit test framework for WP7?

  • jeffwilcox Reply:

    Code coverage is not available, it was a feature that was cut before
    shipping for 2010 from the toolkit.

在2010年发布SUTF版本时已经去掉对Code Converage支持. so.既然如此是否真的就此定论了.? Now 现在提到Pex and Moles.还是不甘心.打算动手在应用程序中亲自验证.本篇并不打算立即采用Pex在SUTF进行验证.有必要系统了解Pex and Moles.

<1>构建Pex测试环境

Well.关于Pex And Moles其实可以分为两个部分.其一Pex-全称是[Program EXploration]是微软研究院的一个关于白盒测试自动生成工具.原来开发人员只能通过指定路径编写独立的测试用例.PEX通过分析代码来自动生成测试用例。对于程序里面的每一行代码,PEX都会尽可能地生成合适的输入值来达到提高覆盖率的目标。同时PEX还会分析代码中的分支,生成覆盖更多分支的测试代码(输入数据);PEX在执行代码的同时会监控和分析代码的控制流和数据流,了解程序的行为。每运行完单一个测试以后,PEX会选择一条在前面的测试中没有覆盖到的路径,并且尝试执行它.本篇主要来讲到的是PEX.

pex-testing

PEX自动化生成用例并执行UT返回测试结果的过程.大大减少开发人员手动编写大量测试用例情况发生.

关于PEX and Moles可通过如下链接了解更多:

PEX And Moles:

Pex and Moles - Isolation and White box Unit Testing for .NET

Download Page:
Pex and Moles – Download Page Link

Get Start Online PDF Document:

http://research.microsoft.com/en-us/projects/pex/digger.pdf

找到下载页面.PEX现在有3个Flavor,支持2个版本的Visual Studio,分别是VSTS2010和VSTS2008,这3个版本的PEX在功能上存在区别,这主要因为下载受众群体不同,一般用户只能使用到PEX功能.本篇所有演示都是基于Visual Studio 2010. 基于MSDN订阅权限下载PEX And Moles完整版本.

2012-01-18_190501

安装完成后.可以打开Visua Studio 立即创建一个Console Application命名为BasicConsolePexComponent_Demo,在此先不要把焦点放在PEX原理实现上.来快速体验一下PEX.首先需要在Program类中顶一个转换字符串的方式.作为测试用例的宿主程序.创建如下:

 1:  /// <summary>
 2:  /// Test Case Convert String To Upper
 3:  /// </summary>
 4:  /// <param name="inputString">Input String</param>
 5:  /// <returns>Converted String</returns>
 6:  public string ConvertStringToUpper(string inputString)
 7:  {
 8:  string convertString = string.Empty;
 9:  if (!string.IsNullOrEmpty(inputString))
 10:  {
 11:  char[] convertCharArray = inputString.ToCharArray();
 12:  if (convertCharArray.Length > 0)
 13:  {
 14:  int count = 0;
 15:  foreach (char currentChar in convertCharArray)
 16:  {
 17:  if (count % 2 == 0)
 18:  convertString += currentChar;
 19:  }
 20:  }
 21:  }
 22:  return convertString;
 23:  }

well一般情况下.关于如何建立单元测试应用程序,一般如果没有PEX之前.可能作为开发人员更多的是根据该方法实现.创建一个新的Test测试项目.构建对应测试用例.实现对该方法的UT.其实这个过程已经Visual Studio内置了这些工作已经自动创建.只需右键点击就能看到Create Unit Test选项:

2012-01-19_141603

点击后能发现.其实Create Unit TEst为了避免测试框架和测试用例污染源代码本身.一般情况要对单元测试用例和源代码执行分离.也就是创建一个新的单独测试项目.

2012-01-19_141211

此时在执行测试之前需要做一些设置点击Settings按钮:

2012-01-19_141311

需要设置创建默认文件以及对应测试类和测试方法名称.点击ok.如果没有设置对应测试项目名称会如下提示:

2012-01-19_141347

修改完成后开始创建.创建过程可能会有如下提示:

2012-01-19_141420

创建前提示主要提到两点.需要为测试项目BasiceConsolePexComponent_Demo设置InternalsVisible属性可用.这是设置其实内在的原因是.对一个组件或模块进行单元测试时.单元测试用例需要调用定义在测试组件或模块中的Internal成员对象时. 需要跨程序集访问.而Modifier的Internal类型成员默认设置仅限于当前程序集访问.而对宿主程序而言需要暴露给测试程序集调用.

另外一点在很多情况下,我们需要将最终的程序集以强命名的形式发布。为此,我们修改Lib项目设置,开启"Sign the assembly”开关,并创建一个密钥文件.不能通过编译,具体的错误信息为:“Friend assembly reference 'Test' is invalid. Strong-name signed assemblies must specify a public key in their InternalsVisibleTo declarations.”,针对需要指定的不是程序名的强命名,而是指定对程序集进行签名时采用的公钥.把公钥指定到InternalsVisibleToAttribute特性中即可.

点击ok.看一下项目结构:

2012-01-19_143556

多了一个测试项目和该项对应Program方法测试类.now构建好了测试项目.在回到宿主程序Program类中对字符串转换操作的方法见一个UT.找到该方法名右键依然可见Create Unit Test选项点击看到:

2012-01-19_142616

可以看到针对方法做UT时会把该UT的应用程序的输出默认为刚才创建的测试项目:AutomationConsole.Test.点击ok.找到测试项目下ProgramProgramTestHelper类可以看到增加一个测试方法如下:

 1:  /// <summary>
 2:  ///A test for ConvertStringToUpper
 3:  ///</summary>
 4:  [TestMethod()]
 5:  public void ConvertStringToUpperTestConvertString_Test1()
 6:  {
 7:  Program target = new Program(); // TODO: Initialize to an appropriate value
 8:  string inputString = string.Empty; // TODO: Initialize to an appropriate value
 9:  string expected = string.Empty; // TODO: Initialize to an appropriate value
 10:  string actual;
 11:  actual = target.ConvertStringToUpper(inputString);
 12:  Assert.AreEqual(expected, actual);
 13:  Assert.Inconclusive("Verify the correctness of this test method.");
 14:  }

可以看到该测试方法命名依然采用默认命名方式.方法内代码实现关于调用Program类中字符串转换大写操作的方法ConvertStringToUpper. 分别定义三个变量的字.其实从变量名称完全可以看出这是一个默认的测试用例.要想改UT达到想做.只需修改对应变量参数的值来匹配.InputString作为方法参数输入.经过测试用例会会生成对应期望值和方法执行实际值.最后通过对期望值和实际比对获得对应测试结果.直接运行 可以看到测试结果如下:

2012-01-19_144542

well因为两个测试用例都没做了修改没有实际值传入所以都是Inconcluetive测试结果不确定.现在看看这种传统方式下一Visual Studio Unit TEst框架为主UT编写开发人员要重复做哪些工作.?首先要重复手动编写大量的UT测试用例.目的是在给定的方法中为每个执行路径手写独立测试. 而且针对这种无法避免的要创建一个独立的测试项目.其实在实际编码中.并不是所有模块都需要即时建立UT.可能需求发生变更.从开发人员角度来说创建一个笨重测试项目这意味更多精力发在测试代码维护上.可能我们更需要一种既能够创建对应测试项目同时也能即时看到当前版本Code UT效果.

Now.UT属于白盒测试的范畴.如果能够有一个很好白盒测试工具能够封装这些重复的行为.并能够重复执行UT测试用例.让开发人员从Ut的过多细节和维护中解放出来关注更核心的业务逻辑.Pex and moles就是这样的一个工具.

<2>Pex 构建白盒测试

虽然本篇幅没有核心焦点放到Pex实现原理上.但关于Pex and Moles其实分为两块整理官方原文翻译如下[直译]:

  • Pex   Pex 是一个 Visual Studio 外接程序,用于补充 .NET Framework 应用程序上的单元测试。 Pex 在 Visual Studio 代码编辑器中查找对应程序方法的输入和输出值。 PEx将这些值另存为将具有高代码覆盖率的小型测试套件。

  • Moles   Moles 允许开发人员将任何 .NET 方法替换为委托。 Moles 通过使用 Detour 和 Stub 提供隔离来支持单元测试。 因为 Moles 在方法级别工作,所以当目标 API 不支持它时,它提供替代项进行模拟。 SharePoint 是一个受益于隔离的常见 API 示例,但不直接支持模拟。Moles 还可用于错误植入,因为它使得开发人员在测试下向代码中注入任意行为变得轻松.

Pex核心是给开发人员一个手写的参数化单元测试,Pex完全自动地分析代码,来决定相关的测试输入。其结果就是生成一个有着高度代码覆盖的传统单元测试,另外,Pex还会建议开发人员如何去修复所发现的Bug.

安装完Pex and Moles工具后.打开Visual Studio 可以看到右键选项中多了:

2012-01-19_152753

Run Pex 和Pex 下Create Parameterized Unit Tests两个可操作选项.针对Pex测试效果.首先在Programe类添加一个字符串合并操作方法代码如下:

 1:  /// <summary>
 2:  /// Test Case Spilt String And Merge Operator
 3:  /// </summary>
 4:  /// <param name="firstname">First Name</param>
 5:  /// <param name="lastname">Last Name</param>
 6:  /// <returns>Merge String</returns>
 7:  public string SpiltStringToMerge(string firstname, string lastname)
 8:  {
 9:  string mergeString = string.Empty;
 10:  char[] firstNameArray = firstname.ToCharArray();
 11:  char[] lastNameArray = lastname.ToCharArray();
 12:  int count = 0;
 13:  foreach (char currentChar in firstNameArray)
 14:  {
 15:  if (count % 2 == 0)
 16:  mergeString += currentChar + lastNameArray[count];
 17:  }
 18:  return mergeString;
 19:  }

注意该方法的输入参数firstname和lastname在方法内都没有判断是否空字符串或是Null而且里面涉及字符数组的操作.针对该方法采用Pex方式执行UT操作.右键调集选中Run Pex 提示如下:

2012-01-19_153430

注意我们当前类是在Program类下.该类的签名如下:

 1: class Program
 2:  {
 3:  }

提示的意思当前类型不可见.无法通过Pex方式创建UT,需要把当前类的签名改成公开 虚类型即可:

 1:  public partial class Program
 2:  {
 3:  }

在来运行Run Pex 弹出执行 结果窗体如下:

2012-01-19_154740

可以看到Pex在没有创建一个独立单元测试项目和对应测试类情况.自动对当前方法进行白盒测试封装.运行测试109 Runs次.创建了7个测试用例. PEx会监视当前类,并且运行这个方法.执行多次测试.其中有三次测试失败的.并Sunmmary/Exception 提出具体测试参数和出现的异常信息.

点击对应失败的测试用例.可以在右侧弹出对应异常堆栈详细信息.:

2012-01-19_155250

点击DEtails可以看到执行该测试用例的代码和对应异常信息:

2012-01-19_155427

在查看异常详情右侧窗口中.可以执行如下操作:

2012-01-19_155609

Save Test保存当前PEX的测试用例为普通单元测试用例.点击保存:

2012-01-19_155734

如果在此之前没有创建对应Pex测试项目会提示当前测试用例输出.或是提示创建一个新的测试项目:

2012-01-19_155905

上面Project Under Test是待测试的宿主项目.而下面是需要创建新的测试项目设置.点击OK创建一个新的测试项目.这里在RunPex时并没有强制开发人员强制建立一个新的测试项目.但是这里SAve 目的是在于如果觉得当前测试代码可以留存做以后回归测试.PEX也是支持测试代码保存的.

add Precondition是用来通过Pex自动添加代码来避免错误的发生.而All Exception则允许次异常在当前应用程序中出现.send to如果你的项目采用TFS版本控制.sendto操作会吧所有信息发到文本或剪切板中.这里所有的信息包括Detail和STack Trace 如果TFS里配置WorkItem,PEX会自动发送对应的WorkItem中.

well现在既然存在异常.通过Add PreCondition来通过Pex来修正.会看到如下菜单:

2012-01-19_160539

这个窗体中上半部分告诉开发人员PEX在当前会做哪些操作.操作会修改哪些方法等.下半部分则是PEX具体的添加的修复代码.可以看到对firstname输入参数因当前NullException异常所以添加了是否非Null的判断。点击Apply可以看到原来代码中添加一段判断代码 完整如下:

 1:  public string SpiltStringToMerge(string firstname, string lastname)
 2:  {
 3:  // <pex>
 4:  if (firstname == (string)null)
 5:  throw new ArgumentNullException("firstname");
 6:  // </pex>
 7:  string mergeString = string.Empty;
 8:  char[] firstNameArray = firstname.ToCharArray();
 9:  char[] lastNameArray = lastname.ToCharArray();
 10:  int count = 0;
 11:  foreach (char currentChar in firstNameArray)
 12:  {
 13:  if (count % 2 == 0)
 14:  mergeString += currentChar + lastNameArray[count];
 15:  }
 16:  return mergeString;
 17:  }

<pex>标签之间注释就是PEX修复是添加的代码.如果觉得PEX代码不够美化.开发人员也完全可以手工修改.自此你会看到PEX帮开发人员找到一个Bug并尝试不在维护UT代码的情况下并修复该异常.well.当然如上演示只是PEX作为白盒测试工具的丰富功能一个特性.其实刚开始看官方给出解释最吸引我莫过于它支持写参数化单元测试[Parameterized Unit Test].方式.

<3>Pex参数化单元测试

在Pex中针对白盒测试提出参数化单元测试的概念-Parameterized Unit Test。Dang我们通过PEX实现更多模块的UT操作.确保所有单个模块能够按照程序设计预期执行. 其实实际效果并非如此.PEX通过一个指令一个指令地分析.NET代码,解释代码执行时的动作,然后“以一种完全自动的方式,计算出那些能触发边角代码的相关测试输入。”

PEX在执行和分析代码行为时.只是得到代码最基本代码模块行为简单异常类似NullException.但是实际上开发人员并没有写过一个关于所有模块集成起来之后完整测试用例. 无法预期当前软件的行为.PEX提供参数化的单元测试提供一种强大的方式来评估当前代码在集成后是否如预期一样行为.

找到上次增加SpiltStringToMerge字符串合并操作方法做实例.创建参数化单元测试:

2012-01-19_170850

找到该方法选中右键点击可以看到Pex->Create Parameterized Unit Tests选项:

2012-01-19_171118

如果原来没有创建对应的PEX测试项目会提示创建一个新的Test Project.与宿主程序分离.可以看到增对测试文件目标PEX增加多重过滤项. 在Test Project项目输出上如果没有创建Test Project则创建.当然针对创建的测试项目可以指定不同的测试框架.PEX中集成了NUnit/Visual Studio Unit TEst/MbUnit V系列等.让开发人员以自己最熟悉的测试框架来维护测试代码.Pex还基于扩展反射可管理子协议API (Extended Reflection managed profiling API)对监测应用程序的集成提供了支持.在创建看一下设置项Settings:

2012-01-19_171629

可以设置默认创建的命名空间和测试类下默认PexMethod测试方法.这里采用默认方式创建:

2012-01-19_171955

创建成功后Solution目录:

2012-01-19_172228

打开ProgramTest.cs文件可以看到这个PEX自动生成的参数化单元测试方法:

 1:  [PexClass(typeof(Program))]
 2:  [PexAllowedExceptionFromTypeUnderTest(typeof(InvalidOperationException))]
 3:  [PexAllowedExceptionFromTypeUnderTest(typeof(ArgumentException), AcceptExceptionSubtypes = true)]
 4:  [TestClass]
 5:  public partial class ProgramTest
 6:  {
 7:  /// <summary>Test stub for SpiltStringToMerge(String, String)</summary>
 8:  [PexMethod]
 9:  public string SpiltStringToMerge(
 10:  [PexAssumeUnderTest]Program target,
 11:  string firstname,
 12:  string lastname
 13:  )
 14:  {
 15:  string result = target.SpiltStringToMerge(firstname, lastname);
 16:  return result;
 17:  // TODO: add assertions to method ProgramTest.SpiltStringToMerge(Program, String, String)
 18:  }
 19:  }

其实关于PEX参数化单元测试的概念.其实它就是一个带有参数并通过调用宿主测试程序简单测试方法.其实这里很多人都很疑惑怎么和传统创建单元测试方法和PEX参数化方法区别在哪呢?其实昨天我在看到这时也很困惑很久.后来在官方找了很多资料.总算是找到一点权威的论述:

A unit test is a method without parameters represents a test case which typically executes. a method of a class-under-test with fixed arguments and verifies that it returns the.


expected result. A parameterized unit test (PUT) is the straightforward generalization。of a unit test by allowing parameters. PUTs make statements about the code’s behavior.


for an entire set of possible input values, instead of just a single exemplary input value.

相对传统创建的单元测试一般情况参数都是指定固定的参数值.并在执行后会预期函数中返回对应的结果.PEX参数化单元测试概念则不同.在PEX中单元测试的参数可以输入任何可能的参数用来验证程序代码执行中所有可能的行为.你大概明白PEX提出这个概念的目的了吧.也就是说PEX在执行的时候会分析PEX参数化单元测试代码方式来查看宿主程序代码的行为.当然这个过程需要集成宿主程序的代码.而相对于传统固定参数值.PEX单元测试参数可以是任意的.这样更便于测试过程中测试宿主程序代码可能出现崩溃的几率和行为.

当前已经创建PEX对应的测试项目.在回到宿主横须Program类中.再次运行Run PEX:

2012-01-19_152753

出了能看到原来Pex Test Results窗体测试结果.注意这时PEX内部根据测试需要会在测试项目对应测试ProgramTEst类中.创建普通测试方法[TestMethod]同时也会创建PEX下参数化单元测试. 创建普通测试的数量和测试结果中测试用例的数量是一致的.如果TestReaults.PEX使用7个用例那么在ProgramTest.SpiltStringToMerme.g.cs文件中肯定会有相同数量的普通测试方法存在.PEX参数化单元测试方法则放在ProgramTest.cs文件中.

如果你觉得这个过程不明显.或是想了解一些PEX工作处理过程.可以选中右键可以PEX->Delete Generated Unit Tests操作:

2012-01-19_182301

其实该操作的目的会自动删除ProgramTest.SpiltStringToMerme.g.cs文件中所有的普通测试方法[TestMethod]也就是当前使用测试用例.然后找到该方法在运行Run PEX./运行玩后可以找到对应测试实例的Save Test方式在把指定的测试用例保存到ProgramTest.SpiltStringToMerme.g.cs文件中.well 这里保存第一个通过的测试用例 低级Save TEst:

2012-01-19_183133

Apply.完成后.会看到ProgramTest.SpiltStringToMerme.g.cs文件中多了一个普通测试方法.同时在Test Result窗体中发现每个测试的图标不再包含一个磁盘位图而是一个对号.:

 1:  public partial class ProgramTestTest
 2:  {
 3:  [TestMethod]
 4:  [PexGeneratedBy(typeof(ProgramTestTest))]
 5:  public void SpiltStringToMerge355()
 6:  {
 7:  string s;
 8:  ProgramTest s0 = new ProgramTest();
 9:  Program s1 = new Program();
 10:  s = this.SpiltStringToMerge(s0, s1, "", "");
 11:  Assert.AreEqual<string>("", s);
 12:  Assert.IsNotNull((object)s0);
 13:  }
 14:  }

这样有选择的方式让开发人员很轻松能够保存在不同版本需求变动生成的测试用例/.而且是可选择的.当然在添加过程往往会提示一些命名上问题例如如下提示:

2012-01-19_183143

这是因为现在PEX现在对中文GB2312格式支持存在乱码的问题. 当然在创建是PEX为了防止访问路径过长.就限制在PEX所有文件命名长度.

其实到这里很多人依然对PEX 参数化单元测试的概念依然有些困惑.这个概念其实很简单.但在实际运用你会发现它的实际用途的好处.如果你熟悉并常用的单元测试.但还不理解PEX中参数化单元测试概念.可以从以下几个方向去考虑[仅作参考]:

理解参数化单元测试的方向:

[1]:当时你手写普通的单元测试.思考以下你要测试代码的行为.在构建单元测试时你的需要多少个独立参数传入.?

[2]:目前尚不清楚确切的数字可能会是什么.但在测试过程它是很重要的,因PEX角度来说开发人员永远有时间来即时创建和维护测试代码.

当然更多还请参考官方的资料:http://research.microsoft.com/en-us/projects/pex/default.aspx

<4>Pex小结

本篇PEX 自身应用角度来阐述PEX相对传统单元测试所具有的特点.作为一个白盒的测试工具.开发人员常在程序开发过程中.需求往往也是不断变化的.当然也无法避免未来开发过程中.或进入代码维护阶段是.所需要面对代码修正的问题.为了每次都能保证软件代码工作都正确无误.而且即时在需求发生变更需要添加新功能时.也能保证原有功能正常运行.使用可控的测试导向的开发模式是必行的.

当然作为开发人员要在开发过程手工编写大量的测试用例并维护笨重测试的代码.这会在一定程度上导致项目开发进度延缓。这也是很多开发人员忽视UT一个原因之一.但现在PEX白盒测试工具.可以把测试流程完全的自动化.把开发人员从单元测试的需求中解脱出来专注于核心业务开发之上.

本篇并没有急于在Windows phone Application 中验证PEX是否可行.但是PEX透露出来关于测试理念确实很有深入的价值.稍后会在下篇副中无论成功还是失败.演示Windows phone应用程序中使用PEX整个过程.

如有问题请在评论中提出.本篇所有源代码下载地址如下:/Files/chenkai/BasicConsolePexComponent_Demo.rar

无觅相关文章插件,快速提升流量