敏捷开发

敏捷软件开发又称敏捷开发,是一种从上世纪 90 年代开始引起开发人员注意的新型软件开发方法。和传统瀑布式开发方法对比,敏捷开发强调的是在几周或者几个月很短的时间周期,完成相对较小功能,并交付使用。在项目周期内不断改善和增强。

2001 年初,在美国犹他州雪鸟滑雪胜地,17 名编程大师分别代表极限编程、Scrum、特征驱动开发、动态系统开发方法、自适应软件开发、水晶方法、实用编程等开发流派,发表“敏捷软件开发”宣言。其内容主要包括:

  • 人和交互重于过程和工具;
  • 可以工作的软件重于求全责备的文档;
  • 客户协作重于合同谈判;
  • 随时应对变化重于循规蹈矩;

可见在敏捷软件开发中,交付高质量的软件是非常重要的。只有交付可以工作的软件,开发人员才能不断地完成更多功能,而不是将大部分时间投入在修复软件产品缺陷 (Bug) 。所以如何提高交付软件的质量,在敏捷开发实施过程非常重要。

测试驱动开发

测试驱动开发,它是敏捷开发的最重要的部分。方法主要是先根据客户的需求编写测试程序,然后再编码使其通过测试。在敏捷开发实施中,开发人员主要从两个方面去理解测试驱动开发。

  • 在测试的辅助下,快速实现客户需求的功能。通过编写测试用例,对客户需求的功能进行分解,并进行系统设计。我们发现从使用角度对代码的设计通常更符合后期开发的需求。可测试的要求,对代码的内聚性的提高和复用都非常有益。
  • 在测试的保护下,不断重构代码,提高代码的重用性,从而提高软件产品的质量。可见测试驱动开发实施的好坏确实极大的影响软件产品的质量,贯穿了软件开发的始终。

在测试驱动开发中,为了保证测试的稳定性,被测代码接口的稳定性是非常重要的。否则,变化的成本就会急剧的上升。所以,自动化测试将会要求您的设计依赖于接口,而不是具体的类。进而推动设计人员重视接口的设计,体现系统的可扩展性和抗变性。

利用伪对象 (Mock Obect) 实现接口测试

在实施测试驱动开发过程中,我们可能会发现需要和系统内的某个模块或系统外某个实体交互,而这些模块或实体在您做单元测试的时候可能并不存在,比如您遇到了数据库,遇到了驱动程序等。这时开发人员就需要使用 MO 技术来完成单元测试。

最开始,Mock Object 是完全由测试者自己手工撰写的。在这里我们可以举个简单的例子。

我们有一个移动数字电视卡的接口程序。

清单 1. VideoCardInterface 代码
1
2
3
4
5
6
7
8
9
10
11
12
public interface VideoCardInterface {
 
    public void open();
 
    public void changeChannel(int i);
 
    public void close();
 
    public Byte[] read();
 
    public boolean fault();
}

下面是每个方法的功能说明:

  • open 打开移动数字电视卡。
  • changeChannel 切换移动数字电视频道。必须在打开之后才可以正常工作,否则就提示错误信息。
  • close 关闭移动移动电视卡。必须在打开之后才可以正常工作,否则就提示错误信息。
  • read 读取字节流。必须在打开之后才可以正常工作,否则就提示错误信息。
  • fault 显示当前工作状态。

由于相对应的硬件开发工作还没有完成,我们无法基于这样的接口程序进行实际的测试。所以开发人员基于接口,实现了部分移动电视卡的逻辑。

清单 2. MockVCHandler 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class MockVCHandler implements VideoCardInterface {
 
    private boolean initialized = false;
    private boolean error = false;
    private int channel;
    private static final int DEFAULTCHANNEL = 1;
 
    public void open() {
        initialized = true;
        channel = DEFAULTCHANNEL;
    }
 
    public void changeChannel(int i) {
        if (!initialized) {
            Assert.fail("Trying to change channel before open");
        }
        if (i <= 0) {
            Assert.fail("Specified channale is out-of-range");
        }
        this.channel = i;
    }
 
    public void close() {
        if (!initialized) {
            Assert.fail("Trying to close before open");
        }
    }
 
    public Byte[] read() {
        if (!initialized) {
            Assert.fail("Trying to read before open");
            return null;
        }
        if (channel > 256) {
            error = true;
            Assert.fail("Channel is out-of-range");
        }
        return new Byte[] { '0', '1' };
    }
 
    public boolean fault() {
        return error;
    }
}

通过以上的实现,我们可以测试每个动作之间的先后逻辑关系,同时可以测试数据流读取出错的逻辑。如果测试人员进一步对数据流进行测试。还可以自我生成一段二进制字节流并进行测试。通过以上的实现,我们可以大大加快开发进度。在传统开发流程中,软件开发和测试不得不等大部分硬件都已经可以使用的条件下才可以开发测试。使用 MO 技术,使得硬件和软件在一定程度上可以同步开发和测试。

但是测试人员完全依靠自己实现这些类,不可避免的会带来测试用例编写效率低下和测试用例编写困难的弊病,甚至可能会影响 XP 实践者“测试先行”的激情。此时,各种各样帮助创建 Mock Object 的工具就应运而生了。目前,在 Java 阵营中主要的 Mock 测试工具有 jMock,MockCreator,MockRunner,EasyMock,MockMaker 等,在微软的 .Net 阵营中主要是 NMock,.NetMock,Rhino Mocks 和 Moq 等。

jMock 框架介绍

总体上来说,jMock 是一个轻量级的模拟对象技术的实现。它具有以下特点:

  • 可以用简单易行的方法定义模拟对象,无需破坏本来的代码结构表;
  • 可以定义对象之间的交互,从而增强测试的稳定性;
  • 可以集成到测试框架;
  • 易扩充;

与大多数 MOCK 框架一样,我们可以在 IDE 中使用并进行开发。本文以最常用的 Eclipse 为例。

下载 jMock

在 jMock 官方网站,我们可以下载当前稳定版本 jMock2.5.1 。

配置类路径

为了使用 jMock 2.5.1,您需要加入下面的 JAR 文件到当前的类路径。

  • jmock-2.5.1.jar
  • hamcrest-core-1.1.jar
  • hamcrest-library-1.1.jar
图 1. 已添加到 TestingExample 项目中 jMock 的 JAR 文件

使用 jMock 模拟接口

我们首先必须引入 jMock 的类,定义我们的测试类,创建一个 Mockery 的对象用来代表上下文。上下文可以模拟出对象和对象的输出,并且还可以检测应用是否合法。

1
2
3
4
5
6
7
8
import org.jmock.Mockery;
 import org.jmock.Expectations;
 
 public class AJmockTestCase {
     
     Mockery context = new Mockery();
 
 }

然后我们创建一个 calcService 去模拟 ICalculatorService 接口。在这里我们以 add() 方法为例,我们针对 add() 方法定义预期值 assumedResult 。之后我们去调用 add(1,1) 时,就可以得到预期值。

1
2
3
4
5
6
7
8
9
// set up
     final ICalculatorService calcService = context.mock(ICalculatorService.class);
         
     final int assumedResult = 2;
         
        // expectations
        context.checking(new Expectations() {{
             oneOf (calcService).add(1, 1); will(returnValue(assumedResult));
 }});

清单 3 和 4 分别显示了 ICalculatorService 和 AJmockTestCase 的代码。

清单 3. ICalculatorService 代码
1
2
3
4
5
public interface ICalculatorService {
 
    public int add(int a, int b);
 
}
清单 4. AJmockTestCase 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import org.jmock.Mockery;
import org.jmock.Expectations;
 
public class AJmockTestCase {
 
    Mockery context = new Mockery();
 
    public void testCalcService() {
 
        // set up
        final ICalculatorService calcService = context
                .mock(ICalculatorService.class);
 
        final int assumedResult = 2;
 
        // expectations
        context.checking(new Expectations() {
            {
                oneOf(calcService).add(1, 1);
                will(returnValue(assumedResult));
            }
        });
 
        System.out.println(calcService.add(1, 1));
 
    }
 
}

在 jMock 中,开发人员可以按照下面的语法定义预期值,从而实现更复杂的应用。例如我们可以模拟底层驱动程序的输出,在上层应用程序中使用这些模拟数据。具体可以参考 jMock 的官方网站。

1
2
3
4
5
invocation-count (mock-object).method(argument-constraints);
    inSequence(sequence-name);
    when(state-machine.is(state-name));
    will(action);
 then(state-machine.is(new-state-name));

EasyMock 框架介绍

在实际开发中,不少开发人员也使用 EasyMock 来进行测试驱动开发。 EasyMock 具有以下的特点

  • 在运行时 (runtime) 改变方法名或参数顺序,测试代码不会破坏;
  • 支持返回值和异常;
  • 对于一个或多个虚拟对象,支持检查方法调用次序;
  • 只支持 Java 5.0 及以上版本;

与大多数 MOCK 框架一样,我们可以在 IDE 中使用并进行开发。本文以最常用的 Eclipse 为例。

下载 EasyMock

在 EasyMock 官方网站,我们可以下载当前稳定版本 EasyMock2.4 。

配置类路径

为了使用 EasyMock 2.4,您需要加入下面的 JAR 文件到当前的类路径。

  • easymock.jar
图 2. 已添加到 TestEasyMock 项目中 EasyMock 的 JAR 文件

使用 EasyMock 模拟接口

清单 5. ILEDCard 代码
1
2
3
4
5
public interface ILEDCard {
    String getMessage();
 
    void setMessage(String message);
}
清单 6. LED 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class LED {
    private ILEDCard ledCard;
 
    public LED(ILEDCard ledCard) {
        this.ledCard = ledCard;
    }
 
    public String ShowMesage() {
        return this.ledCard.getMessage();
    }
 
    public void setMessage(String message) {
        this.ledCard.setMessage(message);
    }
}

我们首先创建一个 Mock 的对象 mockLEDCard 来代表 LED 卡的行为,并初始化 LED 对象。

1
2
3
4
5
protected void setUp() throws Exception {
        super.setUp();
        mockLEDCard = createMock(ILEDCard.class);
        led = new LED(mockLEDCard);
}

之后我们对 ShowMessage 方法进行测试。

1
2
3
4
5
6
7
public void testGetWord() {
       expect(mockLEDCard.getMessage()).andReturn("This is a EasyMock Test!");
       replay(mockLEDCard);
 
       led.ShowMesage();
       verify(mockLEDCard);
}

清单 7 显示了完整的代码。

清单 7. AEasyMockTestCase 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import static org.easymock.EasyMock.*;
import junit.framework.TestCase;
 
public class AEasyMockTestCase extends TestCase {
 
    private LED led;
    private ILEDCard mockLEDCard;
 
    protected void setUp() throws Exception {
        super.setUp();
        mockLEDCard = createMock(ILEDCard.class);
        led = new LED(mockLEDCard);
    }
 
    protected void tearDown() throws Exception {
        super.tearDown();
    }
 
    public void testGetWord() {
        expect(mockLEDCard.getMessage()).andReturn("This is a EasyMock Test!");
        replay(mockLEDCard);
 
        led.ShowMesage();
        verify(mockLEDCard);
    }
 
    public void testSetWord() {
        mockLEDCard.setMessage("Another test");
        replay(mockLEDCard);
 
        led.setMessage("Another test");
        verify(mockLEDCard);
    }
}

通过上文对 jMock 和 EasyMock 的介绍,我们可以发现 jMock 可以灵活的定义对象的行为。例如 mock.expects(once()).method("method2").with( same(b1), ANYTHING ).will(returnValue(method2Result)); 这点在 EasyMock 里比较难于实现。

Rmock 及其它

目前比较流行的 mock 工具,还有 RMock, 目前的版本的是 2.0,当使用 jUnit 开发测试用例时,它支持设置-修改-运行-验证这样的工作流。它加强了基于交互和基于状态的测试,同时有更好的测试工作流定义。 Rmock 还可以使用 DynamicSuite 来解决维护 TestSuites 的问题。

市场上还有支持各种语言的 Mock object 的框架,如 pMock(Python),NMockLib(C#),Mocha(Ruby),JSMock(JavaScript),mockpp(C++) 。

结语

在软件开发过程中,开发人员需要注重测试驱动开发,并利用模拟对象的技术来帮助进行测试。许多开发人员不习惯于频繁编写测试。即使需要编写测试,通常都是简单的进行主要功能测试。如果要测试代码的某些难以到达的部分,选择各种 Mock object 的框架可以降低开发测试的复杂度。

同时在硬件相关的程序开发中尤其是驱动开发,应用 Mock 技术将极大地提高软件开发的进度,并且减少代码中的缺陷,大大提高硬件软件兼容性和可靠性。

 

相关主题

  • “架构宣言:采用敏捷开发,第 1 部分”(developerWorks,2008 年 6 月):您可以通过本文了什么是解敏捷开发,如何利用其优点,以及对实现敏捷流程和架构对组织的要求。
  • “developerWorks 访谈:Scott Ambler 谈敏捷开发”(developerWorks,2008 年 7 月):IBM 敏捷开发实践领导者解释敏捷开发及其业务实例,并且澄清处理过程中的一些真相。
  • “EasyMock 使用方法与原理剖析”(developerWorks,2007 年 10 月):本文将对 EasyMock 的功能和原理进行介绍,并通过示例来说明如何使用 EasyMock 进行单元测试。
  • “使用模仿对象进行单元测试”(developerWorks,2003 年 3 月):本文将演示一种重构技术,该技术根据工厂方法设计模式来创建模仿对象。
  • 若要获得 jMock 的副本,您可以从 jMock 下载某个版本。
  • 若要获得 EasyMock 的副本,您可以从 EasyMock 下载某个版本。

from: https://www.ibm.com/developerworks/cn/java/j-lo-mockobject/index.html

使用模拟对象(Mock Object)技术进行测试驱动开发的更多相关文章

  1. Scrum敏捷软件开发之技术实践——测试驱动开发TDD

    重复无聊的定义 测试驱动开发,英文全称Test-Driven Development,简称TDD,是一种不同于传统软件开发流程的新型的开发方法.它要求在编写某个功能的代码之前先编写测试代码,然后只编写 ...

  2. 软件工程 - Test-Driven Development (TDD),测试驱动开发

    参考 https://baike.baidu.com/item/%E6%B5%8B%E8%AF%95%E9%A9%B1%E5%8A%A8%E5%BC%80%E5%8F%91/3328831?fr=al ...

  3. Visual Studio 2012 Fakes框架测试驱动开发TDD教程

    一.前言 最近团队要尝试TDD(测试驱动开发)的实践,很多人习惯了先代码后测试的流程,对于TDD总心存恐惧,认为没有代码的情况下写测试代码时被架空了,没法写下来,其实,根据个人实践经验,TDD并不可怕 ...

  4. TDD测试驱动开发

    TDD测试驱动开发 一.概念 TDD故名思意就是用测试的方法驱动开发,简单说就是先写测试代码,再写开发代码.传统的方式是先写代码,再测试,它的开发方式与之正好相反. TDD是极限编程的一个最重要的设计 ...

  5. iOS 测试驱动开发

    测试驱动开发是敏捷开发的一部分,它一般有“red-green- refactor”步骤 iOS测试驱动开发的工具 一. OCUnit 是Xcode自带的测试工具 其使用步骤分为 1 建立测试的Targ ...

  6. Nodejs的测试和测试驱动开发

    测试是保证软件质量必不可少的一环.测试有很多形式:手动.自动.单元测试等等.这里我们只聊使用Mocha这个框架在Nodejs中实现单元测试.单元测试是测试等重要组成,这样的测试只对于一个方法,这样的一 ...

  7. 测试驱动开发TDD(test drive development)

    classpath,路径列表.告诉java需要加载类的存放位置, java会去搜寻.这种机制实现了动态加载. java -cp 加载类路径 执行类名   : 加载类路径可是绝对,也可以相对. 代码重构 ...

  8. Cucumber测试驱动开发

     Cucumber是一种BDD实践开发工具,属于敏捷开发的组成部分.      在敏捷开发中,对用户进行需求分析时,不是像传统的P&D的开发方式,首先编写大量的用户需求分析文档,而是通过一个个 ...

  9. Django 1.6 的测试驱动开发

    http://www.oschina.net/translate/django-1-6-test-driven-development 测试驱动开发(TDD)是一个迭代的开发周期,强调编写实际代码之前 ...

随机推荐

  1. MVC插件

    MVC插件 最近领导让我搞一下插件化,就是实现多个web工程通过配置文件进行组装.之前由于做过一个简单的算是有点经验,当时使用的不是area,后来通过翻看orchard源码有点启发,打算使用area改 ...

  2. Codeforces Round #480 (Div. 2) E - The Number Games

    题目大意:给你n个点的一棵树, 每个点的权值为2^i ,让你删掉k个点使得剩下的权值和最大. 思路:这题还是比较好想的, 我们反过来考虑, 剩下一个的情况肯定是选第n个点,剩下两个 我们肯定优先考虑第 ...

  3. Successor hdu 4366 线段树

    题意: 现在n个人,其中编号0的是老板,之后n-1个员工,每个员工只有一个上司,有一个忠诚值和能力值.每次要解雇一个人的时候,从他的下属中选取能力值大于他的且忠诚值最高的一个,若不存在则输出-1.共m ...

  4. Channel Allocation HDU1373

    染色问题:相邻不能染同一种颜色 最少需要的颜色的数量=最大团点的数量 #include<bits/stdc++.h> using namespace std; #define N 27 i ...

  5. P3905 道路重建

    P3905 道路重建我一开始想错了,我的是类似kruskal,把毁坏的边从小到大加,并且判断联通性.但是这有一个问题,你可能会多加,就是这条边没用,但是它比较小,你也加上了.居然还有10分,数据也是水 ...

  6. java数组反射实现动态的判断一个对象是否是数组并且对数组进行拆包输出

    public static Map<String, String> maptoMapString(Map<String, ?> map) { return map.entryS ...

  7. [转]C++中vector使用详细说明

    一.向量的介绍    向量 vector 是一种对象实体, 能够容纳许多其他类型相同的元素, 因此又被称为容器. 与string相同, vector 同属于STL(Standard Template ...

  8. 循序渐进学.Net Core Web Api开发系列【12】:缓存

    系列目录 循序渐进学.Net Core Web Api开发系列目录 本系列涉及到的源码下载地址:https://github.com/seabluescn/Blog_WebApi 一.概述 本篇介绍如 ...

  9. 表达式树(Expression Tree)

    饮水思源 本文并非原创而是下面网址的一个学习笔记 https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/e ...

  10. BZOJ2888 : 资源运输

    显然资源集合处就是树的重心,这题需要动态维护树的重心. 每个连通块以重心为根,用link-cut tree维护每个点的子树大小以及子树内所有点到它的距离和. 合并两个连通块时,考虑启发式合并,暴力往大 ...