Mock Object工具横向比较(上)

博客围绕Mock Object展开,提及了Mock Object以及Mock Object生成工具,还涉及MockObjects相关内容,与单元测试等信息技术领域相关。

前言

  本文:
  1、仅对动态Mock工具和能自动生成Mock Object实现的工具进行比较
  2、仅对文中所列的几种有限的工具进行比较
  3、仅进行使用方面的对比,不涉及技术方面的比较
  可以看作是一份使用Mock Object辅助单元测试的入门手册。

  在现在流行的各种软件开发的实践或理论中,单元测试(UT,Unit Test)都已经作为一个软件开发的“最佳实践”而被普遍接受。而JUnit则是单元测试工具中无可争议的王者。
  当我们对简单对象或算法进行测试时,撰写基于JUnit的单元测试很简单。当我们准备测试一个依赖于其他对象的类或算法时,就需要构造出这些合作者的实例。这时,测试用例的撰写就略显复杂了。更进一步,当我们准备对一个Web系统或复杂的业务进行单元测试的时候,构造“合作者”实例的工作可能就会是低效而昂贵的。
  例如:测试一个使用HttpRequest实例的对象,需要启动Web服务器,构造Request实例并填入所需要的值,测试完后需要停止Web服务器。这样,测试数据的构造和环境的准备可能会非常复杂,甚至会难以达到“自动测试”的目标。

Mock Object

  此时,Mock Object给我们提出一个解决方案:Mock Object拥有与被测对象的“合作者”完全一致的接口,在测试中作为“合作者”被传递给被测对象;当被测对象调用合作者时,Mock Object根据测试者的意愿改变某些状态或返回期望的结果,以检查被测程序是否按照所期望的逻辑进行工作,达到单元测试的目的。
图1
图2
  或者说,Mock Object作为“仿真器”出现在单元测试用例中,对被测对象进行“欺骗”和跟踪。而只要Mock Object的行为与被测对象所期望的一致,就不会对被测对象产生任何影响。
  当我们借助Mock Object撰写单元测试用例时,代码样式通常会是这样:
1.        创建:创建Mock Object的实例
2.        训练:设置Mock Object中的状态和期望值
3.        测试:将Mock Object作为参数来调用被测对象
4.        验证:验证Mock Object中的一致性和被测对象的返回值或状态
  第2步我们通常称之为Mock Object的训练。此时,我们需要“告诉”Mock Object被测对象会调用它的哪些接口、调用的顺序和次数是什么、它应该做出什么反应。第4步的时候,Mock Object就会按照前面训练的内容一一检查被测对象是否调用了那些指定的接口、调用的顺序和调用次数是否正确。如果正确的话就一切OK,否则说明被测对象没有实现所期望的功能。
  从上面的描述中,我们可以看出,Mock Objects
1.        可以仿真被模拟的对象、构建虚拟测试环境;
2.        能够设定有哪些接口将要被调用、并在那时执行期望的动作;
3.        可以验证被测对象是否按照设定的规则调用Mock Object。

Mock Object Generate Tool

  最开始,Mock Object是完全由测试者自己手工撰写的。这样,无可避免的会带来编写测试用例效率低下和测试用例编写困难的弊病,甚至可能会影响XP实践者“测试先行”的激情。此时,各种各样帮助创建Mock Object的工具就应运而生了。
  这些工具中,有MockObjects、XDoclet等帮助程序员编写Mock Object实现的工具,也有EasyMock、MockCreator等自动创建Mock Object实例/类的工具。本文仅针对后者进行描述。
  在这些能自动创建Mock Object实例/类的工具中,又分为两种:一种类似于EasyMock,他们能够动态创建Mock Object实例;一种就像MockCreator,他们可以生成静态的Mock Object代码。最有名且目前仍在维护的有如下几个工具:MockObjects、EasyMock、MockCreator。下面,我们将使用这几种工具分别为下面这个类编写单元测试代码,逐次比较他们使用上的异同:
 
 

package mocktest;
 
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
 
public class SimpleCalcServlet extends HttpServlet
{
    public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
    {
        Integer a = Integer.decode(request.getParameter("a"));
        Integer b = Integer.decode(request.getParameter("b"));
 
        int result = a.intValue() + b.intValue();
        response.setContentType("text/html");
 
        PrintWriter out = response.getWriter();
        out.print("Result = " + result);
        out.flush();
    }
}

  这个类是一个Servlet,我们可以通过JUnit的一个扩展:HttpUnit来编写它的测试用例。不过,如何使用HttpUnit进行测试不是本文所讨论的内容。
  如果利用Mock Object来为这个类编写单元测试用例的话,问题的实质在于如何构造HttpServletRequest和HttpServletResponse的实例。下面,我们将一一展示如何使用上面提到的工具来解决这个问题:

MockObjects

  我们有四种方式来利用MockObjects编写测试用例:
1、基于MockObjects的框架编写自己的Mock Object实现并与其他人分享;
2、直接使用MockObjects提供的Mock Object进行测试;
3、使用其中包含的DynaMock模块快速获取Mock Object实例;
4、通过它提供的Helpers对象帮助我们快速构建测试环境。
  不过,本文仅关注如何使用DynaMock来编写测试用例 [1]
 
 

1.   public void testSimpleAdditionUsingDynaMock() throws ServletException, IOException {
2.       final Mock mockHttpServletRequest = new Mock(HttpServletRequest. class);
3.       final Mock mockHttpServletResponse = new OrderedMock(HttpServletResponse. class,
4.               "Response with non-default name");
5.
6.      mockHttpServletRequest.expectAndReturn("getParameter", "b", "4");
7.       mockHttpServletRequest.expectAndReturn("getParameter", "a", "3");
8.
9.       final StringWriter output = new StringWriter();
10.      final PrintWriter contentWriter = new PrintWriter(output);
11.
12.      mockHttpServletResponse.expect("setContentType", "text/html");
13.      mockHttpServletResponse.expectAndReturn("getWriter", contentWriter);
14.
15.      SimpleCalcServlet aServlet = new SimpleCalcServlet();
16.      aServlet.doGet((HttpServletRequest) mockHttpServletRequest.proxy(), 
17.                     (HttpServletResponse) mockHttpServletResponse.proxy());
18.
19.      mockHttpServletRequest.verify();
20.      mockHttpServletResponse.verify();
21.      assertEquals("Output should be an addition", "Result = 7", output.toString());
22.  }

  对照“Mock Object”一节所言的“代码样式”,Line2~4即为“创建”Mock Object实例:
●我们使用了两个不同的类来构造Mock Object:一个是Mock,一个是OrderedMock。他俩的异同在于一个不检测接口被调用的次序,一个需要检测接口被调用的次序。
  Line6~13为“训练”Mock Object:
●在训练Mock Object的时候,我们调用了它的expectAndReturn()方法。此方法有多个重载。对于测试方法第6行来说,它表达了这样一个意思:被测代码将传入参数“b”来调用mockHttpServletRequest的getParameter()方法;当接收到此消息时,mockHttpServletRequest将返回字符串“4”。
  Line15~17为“测试”:
●实际进行测试的时候,通过调用Mock类实例的proxy()方法将Mock Object实例传递给被测代码。
  Line19~21为“验证”:
●此时,使用Mock类实例的verify()方法进行一致性验证。


[1] 为了节省篇幅、简单起见,本文只列出JUnit测试用例中test方法的代码。
### 使用 Mock Object Mocking Framework 进行单元测试的具体实现 #### 什么是 Mock ObjectMock 对象是一种用于替代实际依赖的对象,在单元测试中用来模拟外部系统的交互行为。它允许开发者隔离待测代码,从而专注于验证核心逻辑而无需考虑真实的外部环境。 --- #### Java 中使用 Mockito 实现 Mock Object 在 Java 生态中,`Mockito` 是最流行的 mocking framework 工具之一。以下是其基本用法: 1. **创建 Mock 对象** 可以通过 `@Mock` 注解或者 `mock()` 方法来创建一个 mock 对象。 ```java @Mock private MyDependency myDependency; // 或者手动创建 MyDependency myDependency = mock(MyDependency.class); ``` 2. **设置 Mock 行为** 使用 `when(...).thenReturn(...)` 定义 mock 对象的行为。 ```java when(myDependency.getUser()).thenReturn(new User("John")); ``` 3. **注入到被测类** 将 mock 对象注入到被测类中,通常可以通过构造函数或 setter 方法完成。 ```java MyClassToTest classUnderTest = new MyClassToTest(myDependency); ``` 4. **执行测试并断言** 调用被测方法后,利用 JUnit 提供的断言工具验证结果是否符合预期。 ```java User result = classUnderTest.getUser(); assertNotNull(result); assertEquals("John", result.getName()); verify(myDependency, times(1)).getUser(); // 验证 getUser() 是否只调用了 1 次 ``` 上述过程展示了如何基于 Mockito 创建配置 Mock 对象,并结合 JUnit 断言进行单元测试[^2]。 --- #### C# 中使用 Moq 实现 Mock Object C# 开发环境中常用的是 `Moq` 库作为 mocking framework。以下是一个简单的例子: 1. **安装 NuGet 包** 确保项目已安装 `Moq` 包。 2. **定义接口及其 Mock** 利用 `Mock<T>` 类型创建一个 mock 对象。 ```csharp var mockContext = new Mock<MyDbContext>(); var mockUser = new Mock<User>(); mockContext.Setup(c => c.User).Returns(mockUser.Object); ``` 3. **集成到测试场景** 将 mock 上下文传递给被测类实例化。 ```csharp var service = new UserService(mockContext.Object); var userResult = service.GetUserById(1); Assert.AreEqual("ExpectedName", userResult.Name); mockContext.Verify(c => c.User, Times.Once()); // 验证上下文中 User 属性访问次数 ``` 此示例说明了如何借助 Moq 设置复杂对象之间的关系以及验证它们间的互动情况[^1]。 --- #### Golang 中使用 gomock 实现 Mock Object 对于 Go 编程语言而言,`gomock` 是一种强大的 mocking 解决方案。下面是它的典型应用流程: 1. **安装 gomock 工具链** 执行命令安装必要的二进制文件: ```bash go install github.com/golang/mock/mockgen@latest ``` 2. **生成 Mock 文件** 根据目标接口自动生成对应的 mock 结构体。 ```bash mockgen -source=path/to/your_interface.go -destination=mocks/mock_your_interface.go -package=mocks ``` 3. **编写测试案例** 导入生成好的 mock 包,并设定期望行为。 ```go import ( "testing" . "path/to/mocks" ) func TestMyFunction(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() mockDep := NewMockYourInterface(ctrl) mockDep.EXPECT().DoSomething(gomock.Any()).Return(nil) sut := YourService{Dependency: mockDep} err := sut.CallMethod() if err != nil { t.Errorf("Unexpected error occurred: %v", err) } } ``` 这段代码片段体现了如何运用 gomock 控制依赖项的动作路径,进而保障服务层功能正常运行[^3]。 --- #### 总结 无论是在哪种编程语言环境下,mock object 的主要目的都是为了简化对外部资源的管理难度,使得单个模块能够独立于其他部分接受严格审查。以上分别介绍了三种主流技术栈下的实践技巧,希望对你有所帮助! ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值