如何进行单元测试

单元测试可以有效提高代码质量保证
服务器君一共花费了204.641 ms进行了5次数据库查询,努力地为您提供了这个页面。
试试阅读模式?希望听取您的建议

单元测试是开发人员对其所实现功能的代码进行的另外编写的测试,用于检测其代码功能的完整性、正确性和其运行效率,从而提高代码质量,并且在写单元测试时发现功能代码间的依赖等设计问题,从而提高产品的可扩展性。

单元测试会让开发人员费更多的时间关注于这个,在短时期来看确实是这样,但放到整个产品周期中来评估的话,其时间上来说是节约的,在后期不会陷入整个产品代码无尽的Bug状态,可以说越是在后期越能显现UT的威力,还可以更自信的交出产品。

单元测试用来作用于:

  • 代码质量保证:在开发人员修改代码后保证不会对之前、其它代码产生影响,通过简单的运行测试就可以知道当前修改会产生哪些影响(那些Failed掉的Case即是所受影响的代码)。
  • 代码清晰、提高可维护性:有点代码先过自己一关的味道,这样写出来的代码更优美,更具可维护性。
  • 使项目架构更合理:在进行UT,可以发现代码设计中不合理的地方,提高代码的配置性,可扩展性。

测试框架不仅提供了一整套的判断机制,还提供了运行测试用例的工具,很方便的去运行单元测试用例。

在.NET中有几个著名的测试框架,NUnit和MS Unit是其中比较火的,关于两者的对比大家可以网上找下,选择一款适合自己的,这里呢,我们使用MS Unit。

单元测试的方法

public int Add(int m, int n)
{
	return m + n;
}

在这种情况下,我们设定一个期待值,并传入两个参数进行运算,然后判断结果是不和期待值相同来进行单元测试。

public void Add_Normal_Test()
{
	Calculator cal = new Calculator();
	int expect = 3;
	int actual = cal.Add(1, 2);
	Assert.AreEqual(expect, actual);
}

好的测试代码总是从多方面去进行测试,如考虑边界,错误情况下的处理...

public void Add_Error_Test()
{
	Calculator cal = new Calculator();
	int expect = 3;
	int actual = cal.Add(-3000, 2);
	Assert.AreNotEqual(expect, actual);
}

再看下边界性况下的测试

public void Add_Limit_Test()
{
	Calculator cal = new Calculator();
	Int64 expect = 4394967294;
	//// int.MaxValue = 2147483647
	int actual = cal.Add(int.MaxValue, int.MaxValue);
	Assert.AreEqual(expect, actual);
}

在上面的例子中,我们在每一个Case中都去声明了Calculator对象的实例,并且都没有对该类的属性做出任何修改。那可以将其提出作为公用。并在测试类初始化时去生成Calculator的实例。

在MS Test中有两种办法在所有用例运行之前生成该依赖对象的实例:

构造函数

public class CalculatorTest
{
	private Calculator cal;
	public CalculatorTest()
	{
		cal = new Calculator();
	}
	~CalculatorTest()
	{
		cal = null;
	}
	[TestMethod]
	public void Add_Normal_Test()
	{
		int expect = 3;
		int actual = cal.Add(1, 2);
		Assert.AreEqual(expect, actual);
	}
}

每一个类中构造函数总是第一个被执行,所以可以将对象实例化放在这里进行,同时在析构函数中对其进行删除。

ClassInitialize特性方法

public static void MyClassInitialize(TestContext testContext){}

此处的用法和构造函数相似,只是这里为静态函数,要求外面所定义的变量也必须为静态。(这一点不大符合OO的习惯,更多时间我偏向用构造函数的方式)其对应的也有一个方法,在所有的用例执行完才去执行。

每个方法前执行的函数,如果方法中改变了对象的属性,那就不适合该场景,但确又每个Case都用到该对象怎么办,有一种能在每个方法前去执行的函数吗?Of Course:

public void MyTestInitialize(){}

该特性方法会在每个单元测试用例运行之行启动,用作于初始化一些对象,它对应的[TestCleanup()]特性方法则是用于在每个Case结束后进行的操作,有点像AOP的理念。

在刚才的代码中我们讲了怎么去基于一个函数去写UT,怎么写出更好的UT,为了让学习起来更简单,我们的示例代码也足够简洁。但事实上你直正写测试时不会碰到这样的代码。

你更多时会遇到这样的代码:

public class UserService
{
	public string GetUserNameById(string id)
	{
		IUserRepository userRepository = new UserRepository();
		User user = userRepository.GetUser(id);
		return user.Name;
	}
}

在方法中调用另外一个对象的方法去取得数据,那怎么去测试?

直接写一个对应的单元测试用例,传入一个id看返回值是否为我们想要?这种方法不可取,试想,如果用例失败,无法判断是哪一个函数出错,UserService.GetUserNameById() 本身还是UserRepository.GetUser()。同时,如果连接测试数据库,还要保证测试数据的有效性,所以这样的测试不好。

怎么写出好的测试用例?使用Mock,我们将对IUserRepository 接口进行另一个实现,在该实现中返回指定的值,从而使GetUserNameById方法的测试不依赖于UserRepository对象。

在进行Mock时我们发现,IUserRepository 放在方法中,无法在对象以外进行配置,可扩展性不强,于是我们将其提取为字段,并通过构造函数来进行赋值。

public class UserService
{
	public IUserRepository userRepository;
	public UserService(IUserRepository userRepository)
	{
		this.userRepository = userRepository;
	}
	public string GetUserNameById(string id)
	{
   
		User user = userRepository.GetUser(id);
		return user.Name;
	}
}

这个是我们的Mock对象,实现了接口,对其中的方法进行指定值的返回。

public class MockUserRepository : IUserRepository
{
	public User GetUser(string id)
	{
		return new User(){Id = "3577", Name = "Lanvige"};
	}
}

在测试用例中,我们可以用该Mock的对象作为Service的操作对象。这样,我们总能保证测试的最小性和准确性。

public class UserSericeTest
{
	public void Get_UserName_By_Id_Test()
	{
		IUserRepository userRepository = new MockUserRepository();
		UserService userSerivce = new UserService(userRepository);
		User user = userSerivce.GetUserNameById("3277");
		Assert("3277", user.Id);
		Assert("Lanvige", user.Name);
	}
}

上面的依赖是通对接口来进行解耦的,那对抽像类呢?

答案也是可以的,我们通过父类的overwite来重写方法的返回值。达到Mock的功能。

通过上面的代码,我们即写好了一个测试,又发现了代码中的架构设计问题,那么接下来的就是不停的去Run这个测试,当你有改动代码时就去运行这些Unit test,一旦发现Failed,立即停下手头工作,找到原因,Fix it。

原则

  • 所有的测试都必须通过:这一点很重要,不能因为懒惰而产生Failed掉的Case。
  • 可重复性:每一个测试用例在不同时间运行都应得到相同的结果。
  • 无依赖性:测试不应依赖于某一环境、测试数据。在不同的机器上应该都能得到相同的运行结果。
  • 最小测:一个测试用例仅只用来测试一个功能函数,这样的好外是,当这个用例失败时,能很清楚的知道出错位置。

如何放置测试数据?在我们一个项目中便出现这么一种问题,测试代码要对从TFS中取得的数据进行测试,来验证功能的正确性,在最初的版本中先行者将从TFS中取得的代码放在了C盘,在测试结束后并不进行删除,于是每个开发人员的C盘中就莫名的多了很多的文件,并在不同的机器上运行时出现很多的错误。

解决方案:每次运行完都记得删除所有的影响。每次运行Test Case,VS都会为我们创建一个测试目录,并随着运行而进行删除,我们可以将数据放在该目录下,从而保证每次运行时的测试数据都是干净的。所有的写操作都是基于该目录进行,不会产生污染。

本文地址:http://www.nowamagic.net/librarys/veda/detail/650,欢迎访问原出处。

不打个分吗?

转载随意,但请带上本文地址:

http://www.nowamagic.net/librarys/veda/detail/650

如果你认为这篇文章值得更多人阅读,欢迎使用下面的分享功能。
小提示:您可以按快捷键 Ctrl + D,或点此 加入收藏

阅读一百本计算机著作吧,少年

很多人觉得自己技术进步很慢,学习效率低,我觉得一个重要原因是看的书少了。多少是多呢?起码得看3、4、5、6米吧。给个具体的数量,那就100本书吧。很多人知识结构不好而且不系统,因为在特定领域有一个足够量的知识量+足够良好的知识结构,系统化以后就足以应对大量未曾遇到过的问题。

奉劝自学者:构建特定领域的知识结构体系的路径中再也没有比学习该专业的专业课程更好的了。如果我的知识结构体系足以囊括面试官的大部分甚至吞并他的知识结构体系的话,读到他言语中的一个词我们就已经知道他要表达什么,我们可以让他坐“上位”毕竟他是面试官,但是在知识结构体系以及心理上我们就居高临下。

所以,阅读一百本计算机著作吧,少年!

《JavaScript DOM编程艺术(第2版)》 基思(Jeremy Keith) (作者), 桑布尔斯(Jeffrey Sambells) (作者), 魏忠 (合著者), 杨涛 (译者), 王建桥 (译者), 杨晓云 (译者), 等 (译者)

《JavaScript DOM编程艺术(第2版)》内容简介:JavaScript是Web开发中最重要的一门语言,它强大而优美。无论是桌面开发,还是移动应用。JavaScript都是必须掌握的技术。W3C的DOM标准是开发Web应用的基石。已经得到所有现代浏览器的支持,这使得跨平台Web开发成了一件轻松惬意的事。《JavaScript DOM编程艺术(第2版)》是超级畅销书的升级版,由倡导Web标准的领军人物执笔,揭示了前端开发的真谛,是学习JavaScript和DOM开发的必读之作。

更多计算机宝库...