Linq to Object在测试中的应用

.NET Framework 3.5中引入了一个新特性LINQ(集成语言查询),据说.NET Framework 3.5中很多特性都是为LINQ而服务的,例如Lambda表达式的支持,匿名类型,等等……这篇文章会讲述一个把Linq to Object应用于测试的例子。

前一阵子需要测试一个搜索在线会员的功能,如果一个用户是在线的,那么他所能够被搜索到的信息都会作为一条记录,保存在一个表中,主要的字段有5个,也就是根据这5个字段的信息可以查询出用户想要的在线会员。一个简单的方案就是写一个比较复杂的存储过程,然后根据5个输入来查询出不同的结果,不过DBA说在SQL SERVER中进行逻辑运算的性能不是很好,所以开发人员写了12条存储过程,分别对应不同的组合,那么对于我做集成测试来说,我起码要有12个测试方法对应这12条存储过程。同时我还要设计一定数量的测试数据,供我查询测试,而比较要命的是,这些测试数据随着我对这个功能的理解的深入,在不断地增加,结果就是如果我写第一个测试的时候,我准备的数据是30条,OK,测试通过;等我写到第五个测试的时候,测试数据可能有40条了,当我用这40条测试数据重新指向第一个测试的时候,FAILED!!!这让人非常郁闷。所以我想到了能不能用round trip的方法来进行测试。做一个比喻,假如说我想证明WIN7的计算器程序是正确的,那么可以把相同的计算在WIN XP的计算器中跑一遍,如果两者结果一样,那么我可以认为WIN7的计算器程序也是正确的(如果XP的计算器有错怎么办?先别较真,有风险,但很小)。

我的做法就是,准备一些数据,首先用SUT进行查询,然后用LINQ进行查询,如果两者查询结果一致,那么可以认为程序是正确的,否则就是两者之一存在问题。

首先准备一些测试数据,保存为XML文件,第一方便对测试数据进行CRUD,第二可以用XmlSerializer把这些数据转换为对象,方便用LINQ查询。

< ?xml version="1.0" encoding="utf-8" ?>

    
        
            1300010000
            北京市
            北京市
            18
            2
            true
        
        
            1300010002
            北京市
            北京市
            27
            2
            false
        
        
            1300010004
            广东省
            广州市
            45
            1
            true
        
    

然后需要有一个类,利用XmlSerializer来反序列化这些测试数据

[XmlRoot("OnlineFriends")]
public class OnlineFriends
{
	private bool IsEmpty = true;

	private static OnlineFriends fs = new OnlineFriends();

	private OnlineFriends()
	{ }

	public static OnlineFriends Instance
	{
		get
		{
			if (fs.IsEmpty)
			{
				fs.Deserialize();
			}
			return fs;
		}
	}

	private void Deserialize()
	{
		var serialzer = new XmlSerializer(typeof(OnlineFriends));
		using (XmlReader xmlreader = XmlReader.Create("OnlineFriends.xml"))
		{
			fs = serialzer.Deserialize(xmlreader) as OnlineFriends;
			fs.IsEmpty = false;
		}
	}

	public class OnlineFriendData
	{
		[XmlElement("FriendId")]
		public int FriendId { get; set; }

		[XmlElement("Province")]
		public string Province { get; set; }

		[XmlElement("City")]
		public string City { get; set; }

		[XmlElement("Age")]
		public int Age { get; set; }

		[XmlElement("Gender")]
		public byte Gender { get; set; }

		[XmlElement("HasPhoto")]
		public bool HasPhoto { get; set; }
	}

	[XmlArray("Friends")]
	[XmlArrayItem("OnlineFriendData")]
	public OnlineFriendData[] Friends { get; set; }
}

数据的准备工作就差不多了,看一个简单的测试。首先这个测试方法会重新初始化测试数据,然后查询27岁的在线会员,查询出来的结果保存在ids的INT列表中。OnlineFriends.Instance.Friends对象中保存了所有在线好友的数据,它是一个实现了IEnumerable接口的类,所以可以使用LINQ相关的扩展方法

在OnlineFriends.Instance.Friends.Where(x => x.Age == age)语句中,“x => x.Age == age”是一个应用了Lambda表达式的匿名委托,整个语句的意思就是在OnlineFriends.Instance.Friends这个集合中,找到年龄==age的元素。

不过找到了以后还不行啊,SUT返回的是INT列表,而刚才查到的是OnlineFriendData对象的集合,接下来可以用Select(y => y.FriendId).ToList()对集合做一个投影,只取出FriendId属性,并且转换为INT列表。

最后用CollectionAssert.AreEquivalent()方法对两个INT列表进行比较,看列表是否相等。

[TestMethod]
public void GetOnlineFriendsIdList_Query_Age_LowerUpperDiffOne()
{
	//重新初始化测试数据
	TestHelp.InitForSearch();
	int age = 27;
	OnlineFriendQuery query = new OnlineFriendQuery();
	query.AgeLowerBound = age;
	query.AgeUpperBound = age;
             //根据条件查询在线会员,在这里就是要查询27岁的会员
	List ids = FriendListGateway.FriendListProvider.GetOnlineFriendsIdList(query, MyAllPc);

	//Two collects are equivalent
	CollectionAssert.AreEquivalent(OnlineFriends.Instance.Friends.Where(x => x.Age == age).Select(y => y.FriendId).ToList(), ids);
}

假如使用我最开始的测试方法(就是先准备测试数据,然后人工统计一下里面有多少个27岁的数据,最后进行比较),我在完成了这一条测试以后,接下来还要小心翼翼地添加测试数据,不能再添加年龄是27岁的数据,要不然就会影响到测试结果。但是应用了LINQ以后,我在后续的测试数据设计中就无需要担心新设计的测试数据会对老的测试造成影响。甚至我还可以用程序生成各种不同的数据,填入数据库,供我测试用。

2 thoughts on “Linq to Object在测试中的应用”

  1. Hi 博主,
    您好,看到你很多测试的经验分享,学到不少东西。我也建立个博客:www.mytestlife.com,想跟你做个友情链接:
    地址:www.mytestlife.com
    链接文字:C++test
    希望能够加上,我先加上你的友情链接,谢谢!

Leave a Reply

Your email address will not be published. Required fields are marked *