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

六种关于部分/模拟测试的重要用途

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (16投票s)

2012年10月18日

CPOL

12分钟阅读

viewsIcon

85160

downloadIcon

169

本文将介绍部分/模拟测试的 6 种重要用途

第一次世界大战期间的木制机械马模型

版权所有 Crown

引言

前提条件

如果您从未做过单元测试,那么本文对您来说可能是中文,如果您已经是中文,那么对您来说就是法文 :)。您可能需要先阅读我的 单元测试 文章,然后再继续。

部分测试的世界

每个人或多或少都会遇到部分测试。而且,每个人都有自己的实现方式。让我快速回顾一下我所说的“部分测试”是什么意思,首先是那些对此不熟悉的人,或者更确切地说,我会说他们已经遇到过,我们只需要将其关联起来。

“首先,‘部分测试’这个词不是官方术语;至少我不知道。我个人觉得使用它很舒服。所以如果我在任何方面越界了,请原谅我。”

现在,假设您有一个很棒的数据访问层组件,它在数据库上执行 CRUD(创建、读取、更新、删除)操作。当数据访问层执行这些操作时,它需要发送一封关于该活动的电子邮件。简单来说,数据库组件依赖于发送电子邮件组件。

现在让我们设想一个场景。假设您想单元测试数据库组件,而发送电子邮件组件尚未就绪SMTP 配置不可用。因此,如果您运行数据库组件的测试用例,您将收到一个致命错误,这并没有帮助。

如果您不熟悉单元测试,请从这里开始:VSTS 单元测试。

简单来说,您只想测试“数据库组件”并模拟“发送电子邮件”组件。总而言之,您想部分地测试您系统的一部分,并模拟您系统尚未完成的另一部分。

部分测试显而易见之处有六个

部分测试的需求有不同的形式。以下是部分测试非常有用的六种场景。

不可用/复杂环境:我们都知道所有代码都需要一个环境。有时,当您进行测试时,这些环境不可用/未完成/未配置,但我们仍然希望测试其余已完成的代码。例如,在之前的介绍中,我们想测试数据库代码,但 SMTP 配置不可用。

另一个经典例子是,如果您想测试 ASP.NET 后端代码,它需要一个 HTTP 上下文对象、请求对象和响应对象。这些对象是在拥有像 IIS 这样的完整 Web 服务器时创建的。当我们进行单元测试时,我们无法以简单的方式创建这些对象。

回归测试:第二个非常需要部分测试的地方是“维护”。维护是软件开发生命周期中的重要组成部分。维护的重要活动之一是“变更请求”。当应用这些变更请求时,它们只会更改代码的某些部分。从逻辑上讲,您只想测试那些已更改的代码以及使用这些代码的影响区域。换句话说,您想进行“回归测试”。

例如,我们可以看到我们有一个发票和会计应用程序正在维护中,现在假设我们在产品类(发票部分)中应用了一些变更请求。在这种情况下,我们只想运行产品类及其周围受影响区域的测试用例。但我们会避免运行会计测试用例。因为会计部分根本没有受到影响。

所以,在这里我们仍然想实现部分测试,即我们只“模拟”会计组件的所有类,只运行产品测试用例和受影响的发票测试用例。

测试驱动开发:我们不是 TDD 的忠实粉丝(无冒犯之意),但这又是部分测试非常需要的一个领域。如果您不熟悉 TDD,请观看此有关 TDD(测试驱动开发) 的视频。

在 TDD 中,我们首先从测试用例开始,而不是实际代码。

所以我们编写测试用例,我们编写足够的代码以使测试用例通过,我们添加一些更多功能,我们测试,我们再次添加一些更多功能,再次测试,以此类推,直到逻辑完成。

现在在这个场景中,在任何给定时刻,您都会有一半的代码未完成,但您仍然想测试完整的代码。再次出现一个非常需要部分测试的情况。

减少测试时间:如今,人们希望测试更快速、更高效。许多时候,测试用例得到了优化,但业务系统执行本身就需要一些时间。

例如,您有一个组件调用另一个组件“同步”。现在假设另一个组件是一个耗时任务。

现在,如果您想测试这个组件,您还必须运行您的耗时任务。由于组件同步调用它,测试用例将花费很长时间来执行。所以,如果您能只测试组件并“模拟”耗时任务,那么您的测试用例可以更快地完成。

开发依赖:您正在处理/测试一段代码,而这段代码需要另一个组件或第三方代码。这个其他组件已完成一半或根本未完成。所以您想对已完成的代码进行部分测试,并“模拟”依赖的代码。

工作流程测试:有时“模拟”测试的一个间接好处是 行为测试。在模拟测试中,从技术上讲,我们可以完全控制方法。通过完全控制,我们可以知道方法是否已执行。所以,如果您想测试工作流程顺序的正确性,或者方法被调用的次数等,“模拟”测试会有所帮助。

模拟/假对象/模拟 - 相同的目标

坦白说,英语是我们的第二语言,所以有时我们使用自己的词汇,这让我们感觉舒服。但由于文化差异(Sukesh 和我住在亚洲),地球的其他地方可能会误解我们的意思。

在上一节中,我们强调了部分测试的重要性;现在,这是事物的另一方面。为了实现部分测试,我们也说我们需要“模拟”不可用的代码的另一部分。

出于各种原因,“模拟”是一个更官方的词,而不是“模拟”。所以,从现在开始,我想随波逐流,我们将称“模拟”为“模拟”。

VS 2012 通过使用“Fakes”实现了部分测试,http://msdn.microsoft.com/en-us/library/hh549175.aspx。所以又多了一个词汇来增加混乱。

但无论您使用哪个术语,目标都是相同的。因此,为了与广泛接受的词汇保持一致,我们将在本文中将部分测试称为“模拟”测试。

快速说明:MOQ 测试团队选择“模拟”这个词是因为它是一个更广泛接受的术语,在此处阅读了解更多详情。以下是 MOQ 团队的一句话。

“但我认为‘模拟’这个通用概念在一般意义上比‘存根’甚至‘假对象’使用得更广泛” - Daniel Cazzulino 先生 MOQ 测试团队。

首先,让我们找一个好的模拟工具

好消息是,我们有很多“模拟”测试工具,包括 VS 2012 中引入的 Fakes。但为了让本文简短明了,我们将选择其中两个:MOQ(开源)和 JustMock(付费)。在未来的文章中,我将讨论开源 MOQ 的一些限制,这些限制促使我们讨论付费模拟工具 JustMock。

您可以从 http://code.google.com/p/moq/ 下载 MOQ,从 http://www.telerik.com/products/mocking.aspx 下载 JustMock。

感谢 William Wake 先生的 AAA 风格

在开始使用上述任何工具进行模拟编码之前,让我们先采用一种标准的单元测试用例编写结构,即 AAA 风格。几乎所有的模拟测试工具都遵循这种结构。这将使我们的测试看起来整洁、有序且易于阅读。

现在,所有的单元测试用例都可以可视化为三个较大的部分:Arrange(设置)、Act(执行)和 Assert(断言)。

Arrange:为测试用例做好准备。

Mathsobj = new Maths(); //Arrange

Act:执行测试用例。

int total = obj.Add(10,10); //Act

Assert:检查测试用例是否通过。

Assert.AreEqual(20,Total); // Assert

模拟测试的示例代码

让我们首先为 MOQ 测试创建一个简单的示例代码,然后我们将为“JustMock”做一个代码。下面是我们要做的事情。我们有一个简单的 Customer 业务类,如下面的代码所示。该类有一个简单的函数 called SaveRecord。此函数将客户数据保存到数据库。

在客户数据保存到数据库之前,它会使用 SendEmail 函数发送电子邮件。目前,我们假设 SendEmail 未配置,因为我们没有 SMTP 详细信息。所以我们想模拟 SendEmail 函数并测试 SaveRecord 函数。

public class ClsCustomerBAL
{
public  virtual bool SendEmail()
{
....
....
}
 
public bool SaveRecord(string strCustomerName)
{    
this.SendEmail(); // This line throws a error

using(SqlConnection objConnection = new SqlConnection(ClsCustomerBAL.ConnectionString))
{
SqlCommand objSelectCommand = objConnection.CreateCommand();
objSelectCommand.CommandText = "Insert Into TblCustomer(CustomerName) values(@CustomerName)";
objSelectCommand.Parameters.AddWithValue("@CustomerName", strCustomerName.Trim());
objConnection.Open();
objSelectCommand.ExecuteNonQuery();
}
return true;
}
}

使用 MOQ 和 JustMock

下面是针对上述 ClsCustomerBAL 类,采用 AAA 风格编写的“MOQ”和“JustMock”的代码。Act 和 Assert 部分的代码非常简单且易于理解。现在唯一需要解释的是 Arrange 部分。

  MOQ JustMock
Arrange
Mock<ClsCustomerBAL> target = new 
Mock<ClsCustomerBAL>();

target.Setup(x => x.SendEmail()).Returns(true);
ClsCustomerBAL target = new ClsCustomerBAL();
 
bool called = true;
 
target.Arrange(() => 
  target.SendEmail()).DoInstead(()=> called = true);
Act
string strCustomerName = "MOQ Test Data"; 
 
bool returnvalue = 
  target.Object.SaveRecord(strCustomerName);
string strCustomerName ="Just Mock Test Data"; 
 
bool returnvalue = target.SaveRecord(strCustomerName);
断言(Assert)
Assert.AreEqual(true,returnvalue);
Assert.AreEqual(true, returnvalue);

理解 Arrange 部分

让我们先来讲解 MOQ。我们需要创建的第一个步骤是使用 Mock 创建 Mock 实例。下面是 MOQ 的代码。

Mock<ClsCustomerBAL> target = new Mock<ClsCustomerBAL>(); 

对于 JustMock,您可以创建一个简单的对象,也可以使用 Mock.Create

ClsCustomerBAL target = new ClsCustomerBAL>(); 

ClsCustomerBAL target = Mock.Create<ClsCustomerBAL>(); 

创建 Mock 对象后,我们需要指定如何模拟 SendEmail 函数。下面是 MOQ 代码,它指定 SendEmail 函数应仅返回 true 并避免运行实际逻辑。

target.Setup(x => x.SendEmail()).Returns(true); 

以下是 SendEmail 函数如何模拟的简单代码。您可以看到 Arrange 这个词,它非常符合“AAA”的思路。在 JustMock 中,我们有一个 DoInstead 函数,它接受一个委托作为输入并定义输出。

bool called = true;
Mock.Arrange(() => target.SendEmail()).DoInstead(()=> called = true); 

关于 MOQ 的视频

视觉效果就是视觉效果。下面是一个简单的视频,演示了 MOQ 的一个简单示例。

模拟私有方法还是不模拟?

如果您仔细观察前面的示例,您会发现 SendEmail 函数是 public(公共)和 virtual(虚方法)。现在,作为开发人员,您会希望遵循 OOP 原则,使 SendEmail 方法为私有,而不是公开。如果您编译,您会使用 MOQ 遇到如下错误:

public class ClsCustomerBAL
{
public  virtual bool SendEmail()
{
}
}
public bool SaveRecord(string strCustomerName)
{    
this.SendEmail(); // This line throws a error
}

简单来说,MOQ 不允许模拟“私有”方法。给出的原因是:

“测试私有方法对于模拟纯粹主义者来说品味不佳。从模拟测试的角度来看,我们只测试作为逻辑封闭单元的公共 API。内部发生的事情是黑箱,孤立或模拟这些部分是不合逻辑的。但是,如果您认为这些单独的方法应该单独测试,那么它们可能值得一个单独的类。”

这个论点听起来完全合乎逻辑。我们只关心给出结果的公共 API,而这正是我们应该作为一个逻辑单元进行测试的。

但我个人仍然认为,当您着眼于减少测试时间模拟复杂环境时,模拟私有方法仍然是有价值的。所以,如果您的某些私有方法花费的时间过多,那么模拟可能是一个好主意。

由于 MOQ 有限制,或者我会说这更多是设计使然,我们可以为之使用 JustMock。下面是一个使用 AAA 风格的简单示例代码。

代码
Arrange
Customerobj = new Customer();
bool called = false;
Mock.NonPublic.Arrange(obj, "Validate").DoInstead(() => called = true);
Act
obj.Add();
断言(Assert)
Assert.IsTrue(called);  

Validate 方法是客户类中的一个私有方法。通过使用 NonPublic 函数,我们可以模拟 Validate 以返回 true。

Mock.NonPublic.Arrange(obj, "Validate").DoInstead(() => called = true);

模拟测试的副产品:行为验证

既然我们可以拦截对“方法”的调用,我们也可以实现一个称为“行为验证”的不错副产品。在我们上面正在测试的代码中,假设我们想确保电子邮件首先被发送,然后客户稍后被插入数据库。简单来说,我们想测试这些方法的调用顺序。

下面是使用 MOQ 进行行为测试的简单代码。

首先创建 mock 对象。

int order=0;
Mock<clscustomerbal> target = new Mock<clscustomerbal>(); 

附加一个回调,递增一个计数器,该计数器将检查方法的触发顺序。例如,您可以在下面的代码中看到 SendEmail 递增顺序计数器并检查值。我们为 SaveRecord 做了同样的事情。对于发送电子邮件,顺序计数器的值为零,对于保存记录,它将是 1。

target.Setup(d => d.SendEmail()).Callback(() => Assert.AreEqual(order++, 0));
target.Setup(d => d.SaveRecord()).Callback(() => Assert.AreEqual(order++, 1));
target.Object.SaveAll();

以下是 JustMock 的代码。

ClsCustomerBAL target = new ClsCustomerBAL();
Mock.Arrange(() => target.SendEmail()).DoInstead(() =>
{
Assert.AreEqual(1, counter++);
});
 
Mock.Arrange(() => target.InsertCustomer(Arg.IsAny<string>())).DoInstead(() =>
{
Assert.AreEqual(2, counter++);
});

源代码下载

您可以 在此处 下载本文的完整源代码。

感谢 Sukesh 先生

以上所有代码和输入均由 Sukesh 提供:http://www.sukesh-marla.com/。没有他,我将通宵达旦地研究各种问题。

如需进一步阅读,请观看以下面试准备视频和分步视频系列。

© . All rights reserved.