求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
  
 
 
 
测试代码重构实例
 

2010-07-30 作者:magustest 来源:进化的测试博客

 

Martin Fowler的《重构》这本书基本上每个程序员都会看,对于做单元测试的测试工程师来说,测试的代码本身也是程序,也需要重构。最近在看《XUnit Test Patterns》,把以前的做的东西重新梳理了一下,并且落实到新的项目中。

首先来看看一个最原始的单元测试代码:

[TestMethod]
public void GetPaymentAccountByOwnerID()
{
 int ownerId = 1300100000;
 //Delete the account
 TestHelper.DeletePaymentAccountByOwnerId(ownerId);
 
 //Here should be create an account
 PaymentAccount paymentAccount = PaymentGateway.PaymentProvider.GetPaymentAccountByOwnerID(ownerId, AccountOwnerType.NormalUser);
 
 //Verify the payment account instance
 Assert.IsTrue(paymentAccount.AccountID > 0); 
 Assert.AreEqual(DateTime.Now.DayOfYear, paymentAccount.CreateTime.DayOfYear);                       
 Assert.AreEqual(DateTime.Now.DayOfYear, paymentAccount.UpdateTime.DayOfYear);  
 Assert.AreEqual(0, paymentAccount.Balance, 0.0001);
 Assert.AreEqual(0, paymentAccount.AvailableBalance, 0.0001);
 Assert.AreEqual(0, paymentAccount.FreezeAccount, 0.0001);
}

以上是个很简单的单元测试代码,应用了AAA原则,首先做好准备(删掉原有的数据),然后执行测试,最后再验证返回结果。看起来很好也很清晰。首先发现一个问题,就是那一堆Assert让人觉得迷惑,究竟想干啥?其实那6句Assert都是验证程序返回的PaymentAccount对象是是否符合设计。我把这些Assert语句提取出来,作为一个方法,让测试代码更加容易让人明白。也就有了版本2。

[TestMethod]
public void GetPaymentAccountByOwnerID()
{
 int ownerId = 1300100000;
 //Delete the account
 TestHelper.DeletePaymentAccountByOwnerId(ownerId);
 
 //Here should be create an account
 PaymentAccount paymentAccount = PaymentGateway.PaymentProvider.GetPaymentAccountByOwnerID(ownerId, AccountOwnerType.NormalUser);
 //Verify the payment account instance
 PaymentAccountAssertion(paymentAccount);
}
 
private void PaymentAccountAssertion(PaymentAccount paymentAccount)
{
 Assert.IsTrue(paymentAccount.AccountID > 0);
 Assert.AreEqual(DateTime.Now.DayOfYear, paymentAccount.CreateTime.DayOfYear); 
 Assert.AreEqual(DateTime.Now.DayOfYear, paymentAccount.UpdateTime.DayOfYear);
 Assert.AreEqual(0, paymentAccount.Balance, 0.0001);
 Assert.AreEqual(0, paymentAccount.AvailableBalance, 0.0001);
 Assert.AreEqual(0, paymentAccount.FreezeAccount, 0.0001);
}

以上代码看起来就舒服多了,比较清晰、简短。但是有一个问题,就是后面的3条Assert语句的期望结果是Hard code的,这样很不利于这个自定义的Assert方法的重用。改!我会把那3个〇抽取成这个方法的一个参数,这个自定义的Assert方法变得更加灵活了。代码不贴上来了,因为有一个问题,就是如果我们还需要对PaymentAccount增加一些属性,而这些属性的值都需要被验证,那么这个 PaymentAccountAssertion方法的参数就会越来越长。超长的方法参数也是一个不好的味道,我改!改为传递一个期望的 PaymentAccount对象进来,这样就是两个参数了,很好。

[TestMethod]
public void GetPaymentAccountByOwnerID()
{
 int ownerId = 1300100000;
 //Delete the account
 TestHelper.DeletePaymentAccountByOwnerId(ownerId);
 
 //Here should be create an account
 PaymentAccount paymentAccount = PaymentGateway.PaymentProvider.GetPaymentAccountByOwnerID(ownerId, AccountOwnerType.NormalUser);
 //Verify the payment account instance
 PaymentAccount expected = new PaymentAccount();
 PaymentAccountAssertion(expected, paymentAccount);
}
 
private void PaymentAccountAssertion(PaymentAccount expectedObject, PaymentAccount paymentAccount)
{
 Assert.IsTrue(paymentAccount.AccountID > 0);
 Assert.AreEqual(DateTime.Now.DayOfYear, paymentAccount.CreateTime.DayOfYear); 
 Assert.AreEqual(DateTime.Now.DayOfYear, paymentAccount.UpdateTime.DayOfYear);
 Assert.AreEqual(expectedObject.Balance, paymentAccount.Balance, 0.0001);
 Assert.AreEqual(expectedObject.AvailableBalance, paymentAccount.AvailableBalance, 0.0001);
 Assert.AreEqual(expectedObject.FreezeAccount, paymentAccount.FreezeAccount, 0.0001);
}

第三版出来了,这时候我想起淘宝QA TEAM的一篇文章,我想到用反射去做一个简单的比较,而不用对于每个属性都做对比。(但是我犯了一个错误,我只是“想到”,问没有去重新看一次那篇文章,我写的那个方法有缺点!)

先看看用反射比较属性方法的代码:

public void ObjectAreEqual<t>(T expected, T actual, List<string> excludedProperties)
{
 System.Type myType = typeof(T);
 PropertyInfo[] props = myType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
 foreach (var item in props)
 {
  if (null == excludedProperties || !excludedProperties.Contains(item.Name))
  {
   if (item.PropertyType.Equals(typeof(double)))
   {
    Assert.AreEqual((double)item.GetValue(expected, null), (double)item.GetValue(actual, null), 0.0001);
   }
   else if (item.PropertyType.Equals(typeof(float)))
   {
    Assert.AreEqual((float)item.GetValue(expected, null), (float)item.GetValue(actual, null), 0.0001);
   }
   else
   {
    Assert.AreEqual(item.GetValue(expected, null), item.GetValue(actual, null));
   }
  }
 }
}</string></t>

比较简单,就是获取所有的Property,然后做Assert,特别针对了double和float做了处理,因为对于浮点数,Assert.AreEqual方法需要指定一个可接受的误差值。excludedProperties这个列表保存了不需要验证的属性名字。整个测试代码如下:

[TestMethod]
public void GetPaymentAccountByOwnerID()
{
 int ownerId = 1300100000;
 //Delete the account
 TestHelper.DeletePaymentAccountByOwnerId(ownerId);
 
 //Here should be create an account
 PaymentAccount paymentAccount = PaymentGateway.PaymentProvider.GetPaymentAccountByOwnerID(ownerId, AccountOwnerType.NormalUser);
 //Verify the payment account instance
 PaymentAccount expected = new PaymentAccount();
 PaymentAccountAssertion(expected, paymentAccount);
}
 
private void PaymentAccountAssertion(PaymentAccount expectedObject, PaymentAccount paymentAccount)
{
 Assert.IsTrue(paymentAccount.AccountID > 0);
 Assert.AreEqual(DateTime.Now.DayOfYear, paymentAccount.CreateTime.DayOfYear);
 Assert.AreEqual(DateTime.Now.DayOfYear, paymentAccount.UpdateTime.DayOfYear);
 
 List<string> ExcludedProperties = new List</string><string>() { "AccountID", "CreateTime", "UpdateTime" };
 
 TestUtilityGateway.CustomizedAssert.ObjectAreEqual<paymentaccount>(expectedObject, paymentAccount, ExcludedProperties);
}</paymentaccount></string>

下来说说问题:

1. ObjectAreEqual这个方法的最后一个参数不好,那个ExcludedProperties的列表,其实是我懒惰的产物,应该参照子排牛柳的设计方法,把需要比较的属性名传递进去。我当初的想法是,那个自定义Assert方法里面的前3条语句已经验证了我那3个属性了,然后我传个参数进去告诉那个方法“你不要验证这3个属性了”。懒惰啊!这样的设计有个问题,我用个比喻吧,我让我女朋友下楼去超市帮我买点东西,我是应该告诉她,“你帮我买 ABCDE”呢?还是应该说“你别给我买FGHIJK…”呢?

2. if (null == excludedProperties || !excludedProperties.Contains(item.Name)) 这句话写的真别扭!也不好!

可以看到这个简单的测试方法的进化,经过了若干个版本,测试方法的可读性,可维护性变好了;而且还写了一个对比两个对象的属性是否一致的公共方法,以后可以重用。测试代码本身也是程序,也需要被重构。



LoadRunner性能测试基础
软件测试结果分析和质量报告
面向对象软件测试技术研究
设计测试用例的四条原则
功能测试中故障模型的建立
性能测试综述
更多...   


性能测试方法与技术
测试过程与团队管理
LoadRunner进行性能测试
WEB应用的软件测试
手机软件测试
白盒测试方法与技术


某博彩行业 数据库自动化测试
IT服务商 Web安全测试
IT服务商 自动化测试框架
海航股份 单元测试、重构
测试需求分析与测试用例分析
互联网web测试方法与实践
基于Selenium的Web自动化测试
更多...