65.9K
CodeProject 正在变化。 阅读更多。
Home

Visual studio 2012 Fakes

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (86投票s)

2013年11月10日

CPOL

14分钟阅读

viewsIcon

166134

downloadIcon

2562

在本文中,我们将深入探讨 VS 2012 Fakes。

介绍  

在本文中,我们将深入探讨 VS 2012 Fakes。

为了正确理解 VS 2012 中的 Fakes,您必须首先理解单元测试。这就是为什么我先谈论它会更好。

我将本文分为三个部分。

第一部分我们将了解单元测试的基础知识

  • 什么是单元测试?
  • 它的好处是什么? 
  • 使用 Visual Studio 进行单元测试的简单示例。

第二部分,我们将深入探讨单元测试

  • 好的单元测试有什么特性?
  • 什么是不可测试代码?

在最后一个部分,我们将讨论 VS 2012 Fakes。

  • VS 2012 中的 Stubs 是什么?
  • Stubs 解决了什么问题?
  • VS 2012 中的 Shims 是什么?
  • Shims 解决了什么问题?

我强烈建议您根据您的理解水平选择合适的章节,然后继续。

单元测试

什么是单元测试?

单元测试是指“测试最小的可独立于其他组件进行测试的逻辑”。

这是一个技术定义,让我们尝试简化它。

每个系统/应用程序都包含一个或多个模块,其中每个模块又包含子模块。为了构建这些子模块,我们创建了许多包含函数/方法的类,这些类执行特定的操作。

  • 在单元测试中,我们编写代码(测试方法)来代表我们测试这些函数。我们为应用程序中的每个具有重要逻辑的函数编写测试方法。当函数(代码块)不满足我们的要求时,单元测试将失败。
  • 我们在开发阶段编写和执行单元测试。
  • 在单元测试中,一个测试只关注软件系统的一个单元(需求——每一个最小的需求),因此它被称为单元测试。

它的好处是什么? 

简单来说 

“计算器比手动计算更快、更准确。”

如果你让某人计算 9*8,他/她不会花超过 2 秒。但如果你让同一个人计算 999*888,你不仅会发现响应时间有巨大差异,而且你也不能确定输出的正确性。
如果你考虑使用计算器来实现相同的功能,你会发现对于我们输入的每个输入,输出都会很快,并且更加可靠。

我们开发东西时会做什么?

当开发人员手动测试他们编写的代码时,最大的问题是它们(测试逻辑/过程)不可重用。我们身处 IT 行业,开发人员之间的跳槽非常普遍,而且开发某项功能的人和修改该功能的人很可能是不同的人。

让我们设想一个场景。假设公司要求开发人员 1 开发一个数学库。

步骤 1 - Developer1 创建以下代码。

public class CustomMaths
{
	public int Number1 { get; set; }
	public int Number2 { get; set; }
 
	public CustomMaths(int num1,int num2)
    	{
       	 	Number1 = num1;
        	Number2 = num2;
    	}
	public int Add()
    	{
		return Number1 + Number2;
    	}
	public int Sub()
    	{
		return Number1 - Number2;
    	}        
}  

步骤 2 – 测试逻辑

Developer1 将测试 Add 和 Sub 函数,并确认一切正常。

就这样结束了吗 - ?

不,过了一段时间,Developer 1 离开了公司,Developer 2 加入作为替代。公司要求他在数学库中添加新功能。他们想添加 Div 功能,并要求 Developer 2 确保处理 DivideByZeroException。(简而言之,他们还想为 Numbe2 添加零验证)。

结果代码 ->

public CustomMaths(int num1,int num2)
{
	Number1 = num1;
	if (num2 == 0) { Number2 = 1; }
        else { Number2 = num2; }
}
.
.
.
public int Div()
{
	return Number1/Number2;
}

现在 Developer2 只会测试 Div 函数,他会认为 Add 和 Sub 函数完好无损并且可以正常工作。

如果你仔细观察,你会明白

  • Add 和 Sub 函数内部使用 Number1 和 Number2 变量。
  • Number1 和 Number2 在构造函数中使用。
  • 构造函数已修改,以使 Div 函数能够正常工作。
  • 这意味着 Add 和 Sub 功能也可能以意想不到的方式运行。
  • 最终这意味着 Developer2 也需要测试 Add 和 Sub 函数(他/她现在没有这样做)。

完善的文档在这里有用吗? 

假设 Developer1 在第一次发布后会创建某种文档,解释如何进行测试。

在现实生活中这是不可能的。Developer1 会用自己的想法来测试 Add 和 Sub 函数。假设他记录了测试时需要遵循的每一步。但是我们如何确保 Developer 2 正确地遵循所有步骤呢?很有可能 Developer2 不如 Developer1 聪明。

最佳解决方案是 Developer 1 会写一些代码,

  • 这将测试 Add 和 Sub 函数
  • Developer 2 将执行这些代码,确认测试通过,从而确认函数正常工作。

这就是单元测试发挥作用的地方。

Developer 1 将创建快速可靠的测试方法,这些方法将在未来所有版本中重复使用。

注意:开发者普遍存在一种误解。他们认为编写带有单元测试用例的代码会花费更多时间,而我们没有时间去做——实际上,从长远来看,单元测试会节省您的开发时间。

使用 Visual Studio 进行单元测试的简单示例

您会找到大量关于此的 articol,但我认为在这里进行一个小演示会更好,这样所有的食物都在同一个盘子里。

Developer 1 任务

步骤 1 - 创建 CustomMaths 类库(就像上面一样),其中包含两个函数 Add 和 Sub

步骤 2 - 创建单元测试项目,如下所示

步骤 3 - 为此新创建的项目添加之前类库的引用

步骤 4 - 创建测试类,如下所示

[TestClass]
public class TestMathsClass
{
    	[TestMethod]
	publicvoidTestAdd()
    	{
		//Arrange
		CustomMaths maths = new CustomMaths(6, 5);
 
		//Act
		int result = maths.Add();
 
		//Assert
		Assert.AreEqual<int>(11, result);
    	}
 
    	[TestMethod]
	public void TestSub()
    	{
		//Arrange
		CustomMaths maths = new CustomMaths(6, 5);
 
		//Act
		int result = maths.Sub();
 
		//Assert
		Assert.AreEqual<int>(1, result);
    	}
} 

说明 – 如您所见,单元测试遵循三个简单步骤。

  • Arrange - 创建对象并准备测试功能所需的一切
  • Act – 执行并获取输出
  • Assert – 将最终输出与预期输出进行比较

步骤 5 – 生成解决方案并从 Test > Windows > Test Explorer 打开测试浏览器窗口

步骤 6 – 右键单击测试用例并选择 Run Selected Tests。

注意:在本例中,我们使用硬编码值(5 和 6)进行测试,但在实际场景中,我们不会使用硬编码值,而是会使用一些数据源,如 Excel 或数据库,用于输入参数和返回值。

什么使单元测试成为好的单元测试?

我认为现在您已经掌握了以下几点

  1. 什么是单元测试?
  2. 我们为什么要这样做?
  3. 如何做到?

因此,我认为在进行了 WWH(是什么、为什么以及如何)之后,是时候学习如何编写好的单元测试了。

在数据库领域,任何 SQL 开发人员都可以创建数据库,但那些了解规范化的人可以开发和设计不包含任何冗余数据的数据库。
同样,任何开发人员都可以编写单元测试,但我们不能称每一个单元测试都是好的单元测试。以下是每个单元测试都必须具备的关键点。

  1. 性能 – 单元测试必须非常快速。它不应花费太多时间来执行。
  2. 易于设置 – 编写大量代码来测试您的逻辑不是一个好习惯。您不应该最终编写一个逻辑来测试另一个逻辑。请记住,您试图测试的是一些复杂的东西,并确保您的测试方法非常简单。      a.  Arrange 测试我们的逻辑所需的一切。
          b.  Act 通过调用我们的逻辑来执行。
          c.  Assert 并确认输出是否与预期输出匹配。
  3. 结果导向 – 测试应始终通过或失败。必须有结果。不应有任何中间停止。
  4. 顺序无关 – 每个测试方法都应独立于其他测试方法。测试方法不应在它们之间共享状态。因此,测试方法的执行顺序不应固定。任何人都可以随时执行任何方法,它要么失败,要么通过。
  5. 可重用 – 测试方法应可重用。我认为这是每个测试方法最重要的特性。我们应该创建对未来更有益的单元测试。更改是任何应用程序开发不可或缺的一部分。但是,随着每一次更改,我们不应重新创建单元测试,而应重用。
  6. 单一逻辑测试 – 每个测试方法都应只测试一个工作单元。如果它测试了多个行为,则是不合适的。
  7. 低依赖性 – 测试方法应完全独立于外部依赖项(如邮件服务器、其他类)和环境依赖项(如当前日期时间)。
  8. 有意义 – 我们不应为应用程序中的每一个逻辑都编写测试方法。我们的测试方法应具有价值和意义。它应该是值得的。例如,在实际场景中,我们不应编写测试加法逻辑的测试方法。J. 简单来说,只为那些您真正想测试并且您觉得值得测试的逻辑编写测试方法。

不可测试代码?

违反上述原则的代码可被视为不可测试代码。

我们 IT 界一位伟大的领导者有句名言:“如果您的系统易于单元测试,那么代码就易于维护。” 

除了“低依赖性”这一点,其他都可以由开发人员轻松管理和处理。对 SOLID 原则的正确理解可以设计出一个符合所有这 7 个原则的系统。
您可以详细了解 SOLID 原则
此处.

我们处于面向对象编程的世界,因此我们将以现实世界中的对象来讨论。对象通过调用方法进行交互,这就是我们所说的依赖关系。

 

有时我们也有对某些环境因素的依赖,例如系统日期时间、文件访问权限等,这些也会使我们的代码变得不可测试。

依赖关系如何真正影响单元测试? 

让我们讨论上面图中所示的场景。我们有一个 CustomerMiddleLayer 类,其中有一个 SaveCustomer 函数。此函数的作用是在数据库中添加新客户条目,并在成功插入后向管理员发送电子邮件。

在我们为 SaveCustomer 方法创建测试方法之前,让我们尝试思考当我们的邮件服务器配置不正确或测试环境中没有邮件服务器时会发生什么?

答案是,无论我们的数据库访问层生成什么结果,测试方法都将始终失败。这使得我们的“保存客户”逻辑完全不可测试。我们将确定邮件服务器将在稍后配置好,所以现在我们只想确保“保存客户”函数中的其他内容正常工作,但我们做不到。

Visual Studio 2012 Fakes 助您一臂之力

Visual Studio 2012 Ultimate 版附带了一个新的 Faking 框架,使开发人员能够将其单元测试与其环境隔离开来。现在是时候理解它们了。

当我们说外部依赖时,可能有两种类型

1) 对接口或抽象类的依赖

示例

客户数据访问层

public class CustomerDataAccessLayer
{
	publicbool Insert()
    	{
		try
        	{
			//Insert into database
			return true;
        	}
		catch (Exception e)
        	{
			return false;
        	}
    	}
}

电子邮件库

public interface IEmailLibrary
{
	bool SendEmail();
}
 
public class EmailLibrary : IEmailLibrary
{
	public bool SendEmail()
    	{
	 	//Send email code - > But right now email server is configure
		return true;
    	}
}

客户中间层

public class CustomerMiddleLayer
{
	public string AddCustomer(string CustomerName,IEmailLibrary emailLibrary)
    	{
		CustomerDataAccessLayer ObjLayer = new CustomerDataAccessLayer();
		ObjLayer.Insert();
 
		emailLibrary.SendEmail();
 
		return CustomerName;
    	}
}

单元测试

如果我们按照简单的单元测试步骤来测试 CustomerMiddleLayer 类中的 AddCustomer 方法,它将不起作用。测试将始终失败。我们甚至无法测试 AddCustomer 方法中的其他逻辑。

一种实现方法是

  1. 创建自己的临时类,继承自 IEmailLibrary 并使用
  2. 使用 Mock 框架,如 MOQ、Rhino MOCK 等。要了解更多信息,请点击
    此处.

但我既不相信

  1. 重新发明轮子
  2. 也不在我妻子能做更好的饭时向邻居要食物。

我的意思是,如果我们可以使用 Visual Studio 2012 来实现相同的行为
fakes,为什么要舍近求远 J   

那么,我们开始吧。

步骤 1

在测试项目中,展开引用选项卡,右键单击您的程序集(MiddleLayer),然后选择生成 Fake 程序集。

第二步

在顶部导入 {YourNames}.Fakes。在这种情况下,它将是

using MiddleLayer.Fakes;

步骤 3

创建一个新的测试方法,并使用 IEmailLibrary 的 Stub 版本而不是原始的 Email library 类。

[TestMethod]
public void TestAddMethodWithStubs()
{
	//Arrange
	CustomerMiddleLayer middleLayer = new CustomerMiddleLayer();
	string CustomerName = "Sukesh Marla";
	IEmailLibrary e = new StubIEmailLibrary
    	{
		SendEmail=()=>true
    	};
 
	//Act
	string returnCustomerName = middleLayer.AddCustomer(CustomerName, e);
 
	//Assert
	Assert.AreEqual<string>(CustomerName, returnCustomerName);
}

如果您尝试运行此单元测试,它将始终通过,除非 Add Customer Function 的其他部分存在错误。它之所以这样表现,是因为我们传递的是 Stub 版本的 EmailLibrary 类,而不是原始类。在 Stub 版本中,我们实现了 SendEmail 函数,它什么也不做,直接返回 true。

现在,让我们用技术术语定义 Stubs。

Stubs 只是接口和抽象类的具体实现,您可以跨它们传递。使用 lambda 表达式,我们为它们提供方法实现。

2) 对具体类的依赖

Stubs 确实易于使用和理解,但在实际项目中,我们不会发现到处都遵循 DIC(依赖倒置原则)。在实际中,我们无法每次都创建接口来处理依赖关系。很多时候,我们会坚持使用具体类而不是接口和抽象类。让我们重写上面的代码,但这次不使用接口。

客户数据访问层

(与上面相同)

电子邮件库

public class EmailLibrary 
{
	public bool SendEmail()
    	{
	 	//Send email code - > But right now email server is configure        
		return true;
    	}
}

客户中间层

public class CustomerMiddleLayer
{
	public string AddCustomer(string CustomerName)
    	{
		CustomerDataAccessLayer ObjLayer = new CustomerDataAccessLayer();
		ObjLayer.Insert();
 
                EmailLibrary emailLibrary= new EmailLibrary();
		emailLibrary.SendEmail();
 
		return CustomerName;
    	}
}

单元测试

我们现在没有接口,所以 Stubs 将不起作用,但我们可以使用 Shims。Shims 是运行时方法拦截器。它们比 Stubs 更强大。使用它们,我们可以为我们自己的程序集或 .NET 基类库中的任何可用类的任何方法或属性添加我们自己的实现。

使用 Shims 被认为是不好的做法,因为您使用 Shims 是因为您的代码不符合标准,否则它本可以允许您生成 Stubs(不总是如此)。很多时候,我们别无选择,只能使用 Shims。

步骤 1 和步骤 2 与上面相同。

步骤 3

创建一个新的测试方法,并在其中创建 Shims 上下文。

[TestMethod]
public void TestMethodWithShims()
{
	//Arrange
	CustomerMiddleLayer middleLayer = new CustomerMiddleLayer();
	string CustomerName = "Sukesh Marla";
 
	//Act
	string returnCustomerName = null;
 
	using (ShimsContext.Create())
    	{
		ShimEmailLibrary.AllInstances.SendEmail = (@this) =>{ return true; };
		returnCustomerName=middleLayer.AddCustomer(CustomerName);
    	}

	//Assert
	Assert.AreEqual<string>(CustomerName, returnCustomerName);
} 

使用 Shims 时,必须使用 ShimsContext.Create() 块定义调用将被替换的范围。在定义的区域内,对 SendEmail 函数的每一次调用都会被替换为我们定义的 SendEmail 函数的调用(我们什么也不做,只是返回 true)。

用于覆盖环境依赖项的 Shims

现在,让我们看一个小的演示,其中我们将尝试使用 DateTime.Now 等环境依赖项。

首先,让我们创建 SUT – System Under Test – 需要测试的代码。

public class YearUtilities
{
	publicbool IsTodayLastDateOfYear()
    	{
		DateTime TodayDate = DateTime.Now;
		if (TodayDate.Day == 31 && TodayDate.Month == 12)
        	{
			return true;
        	}
		else
        	{
			return false;
        	}
    	}
}

现在,让我们用正常的方式创建测试方法。

[TestMethod]
public void TestForLastDay()
{
	//Arrange
	YearUtilities yearUtilities = new YearUtilities();
 
	//Act
	bool CurrenrResult = yearUtilities.IsTodayLastDateOfYear();
 
	//Assert
	Assert.AreEqual<bool>(true, CurrenrResult);
}
 
[TestMethod]
public void TestForNotLastDay()
{
	//Arrange
	YearUtilities yearUtilities = new YearUtilities();
 
	//Act
	bool CurrenrResult = yearUtilities.IsTodayLastDateOfYear();
 
	//Assert
	Assert.AreEqual<bool>(false, CurrenrResult);
}

当您尝试运行这些测试方法时,第一个测试方法只会在一年中的最后一天通过,而第二个方法会在除最后一天之外的所有日子通过。在实际项目中这是不可行的。我们不能等到一年中的最后一天再去测试我们的代码。

让我们使用 Shims 重写上面的测试方法。

步骤 1

在测试项目中,展开引用选项卡,右键单击名为 System 的程序集,然后选择生成 Fake 程序集。

第二步

在顶部导入命名空间,如 using System.Fakes;

步骤 3

像这样编写测试方法

[TestMethod]
public void TestForLastDay()
{
	//Arrange
	YearUtilities yearUtilities = new YearUtilities();
 
	//Act
	bool CurrenrResult = false;
	using (ShimsContext.Create())
    	{
		ShimDateTime.NowGet = () =>new DateTime(2012, 12,31);
		CurrenrResult =yearUtilities.IsTodayLastDateOfYear();            
    	}
 
	//Assert
	Assert.AreEqual<bool>(true, CurrenrResult);
}
 
[TestMethod]
public void TestForNotLastDay()
{
	//Arrange
	YearUtilities yearUtilities = new YearUtilities();
 
	//Act
	bool CurrenrResult = false;
	using (ShimsContext.Create())
    	{
		ShimDateTime.NowGet = () =>new DateTime(2012, 12,15);
		CurrenrResult = yearUtilities.IsTodayLastDateOfYear();
    	}
 
	//Assert
	Assert.AreEqual<bool>(false, CurrenrResult);
}

现在,当执行测试方法时,DateTime.Now 将分别返回 2012 年 12 月 15 日或 2012 年 12 月 31 日。这意味着您可以全年、随时随地测试您的代码。

结论

Visual Studio Fakes 是一个非常强大的功能。就最佳实践而言,Stubs 是首选,但在某些情况下,我们别无选择,只能使用 Shims。

我已尽力将事情摆在您面前。但如果您仍然有任何疑问、反馈、评论或建议,请随时转发至 SukeshMarla@Gmail.com。我非常感谢您的每一份意见。

继续编码,继续学习,继续分享。 

别忘了投票和评论。

如需关于 WCF、MVC、商业智能、设计模式、WPF 和基础知识等各种主题的技术培训,请随时联系 SukeshMarla@Gmail.com 或访问 http://www.sukesh-marla.com/ 

要获取更多类似内容,请单击 此处。订阅 文章更新 或在 Twitter 上关注 @SukeshMarla

查看 .NET、C#、ASP.NET、SQL、WCF、WPF、WWF、SharePoint、设计模式、UML 等方面的 600 多个常见问题解答 此处

© . All rights reserved.