junit实战随手笔记

1. junit起步

框架: 框架是一个应用程序的半成品。框架提供了一个可复用的公共结构,可以在多个应用程序之间进行共享。开发人员将框架散入到他们自己的应用程序中,并且加以扩展以满足他们特定的需求。框架与工具包的不同之处在于,框架提供了一致的结构,而不只是一组简单的工具类。

单元测试:单元测试检查一个独立工作单元的行为。在Java应用程序中,独立工作单元经常是(但不总是)一个独立方法。相比之下,集成测试和验收测试检查的是各种组件如何交互。 一个工作单元就是一项任务,不直接依赖于其他任何任务的完成。

单元测试框架目标

  • 框架必须帮助我们编写有用的测试;
  • 框架必须帮助我们创建具有长久价值的测试;
  • 框架必须帮助我们通过复用代码来降低编写测试的成本。

2. junit核心

几个关注点

  • 定义一个测试类的要求是,这个类必须是公共的并且包含了一个无参数的构造函数
  • 创建-个测试方法的要求是, 这个方法必须使用@Test注释,是公共的, 不带任何参数, 返回void类型
  • JUnit在调用(执行)每个@Test方法之前,为测试类创建一个新的实例,所以找们就不能在测试方法之间重用各个实例变量值
junit核心
Assert
测试:一个以@Test注释的方法定义了一个测试
测试类:@Test方法的容器
Suite:允许你将测试类归成一组
Runner:Runner类用来运行测试

2.1 参数化测试

@RunWith. 当类被@RunWith注解修饰,或者类继承了一个被该注解修饰的类,JUnit将会使用这个注解所指明的运行器(runner)来运行测试,而不是JUnit默认的运行器。要进行参数化测试,需要在类上面指定如下的运行器:@RunWith (Parameterized.class)

然后,在提供数据的方法上加上一个**@Parameters**注解,这个方法必须是静态static的,并且返回一个集合Collection。

在测试类的构造方法中为各个参数赋值,(构造方法是由JUnit调用的),最后编写测试类,它会根据参数的组数来运行测试多次。

注意:必须要为类的所有字段赋值,不管你是不是都用到!否则,Junit会出错。

2.2 测试运行器

junit提供了不同的测试运行器,提供了一个facade(外观模式),它可以运行任何测试运行器,决定使用哪个运行器来运行测试。

2.3 Suite

Suite是一个容器,用来把几个测试归在一起,并把它们作为一个集合一起运行。Suite对象其实是一个Runner,可以执行测试类中所有@Test注释的方法。

如果你没有提供一个自己的Suite,那么测试运行器会自动创建一个。默认的Suite会扫描你的测试类,找出所有@Test注释的方法。默认的Suite会在内部为每个@Test方法创建一个测试类的实例。

//测试类组合为Suite
@RunWith(Suite.class)
@Suite.SuiteClasses({TestA.class,TestB.class})
public class TestSuite {

}

//组合测试集
@RunWith(Suite.class)
@Suite.SuiteClasses({TestSuiteA.class,TestSuiteB.class})
public class MasterTestSuite {

}

3. 掌握junit

controller: 接收一个Request,分发给RequestHandler,并返回一个Response

控制反转: 向controller注册一个处理器就是控制反转模式的一个例子。你可能知道这个模式叫做好莱坞原则(Hollywood Principle), 即“不要给我们打电话,我们会打给你的"。对象注册为事件的处理器。当事件发生时,被注册的对象上的一个钩子(hook)方法就会被调用。当允许开发者为框架事件插入自定义的处理器时,控制反转就让框架来管理事件的生命周期。

3.1 junit详细说明

  • @Before和@After注释的方法的执行恰好发生在每一个@Test方法的执行之前/之后,并且不管测试是通过还是失败。这可以帮助你提取所有的公共逻辑、如实例化领域对象(Domain Object)并在一些已知的状态中设置它们。你可能有许多这样的方法,和你在需要的一样多,但要意识到,如果你不止有一个@Before/@After方法,那么这些方法的执行顺序就还没被定义。
  • junit也提供了@BeforeClass和@AfterClass注释来注释某个类中的多个方法。这些注释的方法只会在所有的@Test方法之前/之后被执行一次。你可能有许多@BeforeClass和@AfterClass注释的方法,并且它们的执行顺序同样也没有被指定。
  • 需要牢记@Before/@After和@BeforeClass/@AfterClass这两组注释的方法必须是公有的。其中@BeforeClass/@AfterClass注释的方法必须是公有且静态的。

3.2 一次只测一个对象

单元测试的要点在于,一次只测试一个对象。在一个面向对象环境中,如Java,对象会被设计成要同其他对象交互。由此可知,要创建一个单元测试,你需要两种类型的对象: 一种处你要测试的领域对象( Domain Object ),另一种是用来与被测对象交互的测试对象(Test Object)。

测试类放在何处?

  • 测试类作为包( package )中的公有类,
  • 把测试类作为测试用例类的内部类。

分离初始化逻辑

  • 尽可能删除重复代码,移入@Before

3.3 测试异常处理

测试任何可能失败的事物
如果方法以任何方式改变了参数值或者字段值,那么这个方法就在提供独特的行为,这就需要测试。这个方法并不仅仅是个中间人一它是一个具有自己的行为的方法,可以想象,未来的改变可能会破坏它的行为。如果一个方法被改变了,变得不再简单,那么当发生这个改变时,你就应当为这个方法增加一个测试,但在改变之前则不得要增加。”一般的观点是这样的:如果它自身不可能出错,那么它就太简单了,简单得无法出错。”这也是极限编程规则所持的观点“不要过早添加功能”

模拟异常

  • 异常的测试用例才是单元测试大放异彩的地方。单元测试可以模拟异常条件,其他类型的测试(功能测试和接受测试)都是在实际运行层面执行的,是否遇到系统错误往往属于突发事件,而单元测试可以根据需要产生异常条件。
  • @Test(expected=RunTimeException.class)

让测试改善代码
编写单元测试常常有助于你写出更好的代码。理由很简单。一个测试用例就是一位你的代码的用户。只有在使用代码时你才能发现代码的缺点。因此,不要犹豫,应当根据测试时发现的问题重构代码,使其更加易于使用。TDD的实践就依赖于这条原则。透过先编写测试,你就可以从代码用户的角度来开发你的类。

3.4 超时测试

@Test(timeout=130) 以毫秒为单位,超时测试失败

跳过测试:@Ignore(value=""),value说明为什么跳过测试

3.5 Hamcrest匹配器简化了断言

  • allOf - 所有匹配条件都匹配则通过
  • anyOf - 任何一个匹配条件匹配则通过
  • not - 与匹配条件违背则通过
  • equalTo - 使用Object.equals方法测试对象相等
  • is - 与equalTo相同,仅用来提高代码可读性
  • hasToString - 测试 Object.toString方法
  • instanceOf,isCompatibleType - 测试类型
  • notNullValue,nullValue - 测试null
  • sameInstance - 测试是否是同一实例
  • hasEntry,hasKey,hasValue - 测试一个Map包含entry,key或者value
  • hasItem,hasItems - 测试一个集合包含对应元素
  • hasItemInArray - 测试一个数组包含某个元素
  • closeTo - 测试浮点值接近于给定值
  • greaterThan, greaterThanOrEqualTo, lessThan, lessThanOrEqualTo
  • equalToIgnoringCase - 测试字符串相等且忽略大小写
  • equalToIgnoringWhiteSpace - 测试字符串相等且忽略空白符
  • containsString, endsWith, startsWith - 匹配字符串

4. 软件测试原则

4.1 单元测试必要性

  • 带来比功能测试更广范围的测试覆盖,
  • 提高团队工作效率;
  • 监测衰退,降低对调试的需要
  • 能为我们带来重构的信心,以及在一般情况下做出改变的信心;
  • 改进实现;
  • 将期望的行为文档化,
  • 启用代码覆盖率以及其他指标。

4.2 测试类型

软件测试类型

  • 集成:几个工作单元合并到一个工作流,各方法间的交互
  • 功能:开发者经常将功能测试与集成测试合并在一起,检查公共API边界处的代码
  • 压力与负荷:能否在某个时间段响应大量用户请求,JMeter软件。
  • 验收:以确保应用程序是否已经满足了客户或者利益相关者所提出的任何目标。

单元测试类型

  • 逻辑单元测试:针对一个单独的方法,mock、stub控制边界
  • 集成单元测试:真实环境中不同组件的交互
  • 功能单元测试:为了确认一个刺激响应

测试覆盖率与开发

5.1 cobertura

是与Junit集成的代码覆盖率测量工具,在maven中的使用:mvn cobertura:cobertura

5.2 编写可测试的代码

  • 提供向后兼容的软件状态的原则之一是:“永远不要改变一个公共方法的签名”
  • 类之间的相互依赖会使测试变复杂,必要的时候mock:传递接口,在实例化的过程中mock
  • 迪米特法则(Law of Demeter),最少知识原则,一个类只要知道它需要知道的那么多
  • 避免隐藏的依赖关系与全局状态
  • 优先使用通用方法,设置关键点,利用多态,将程序代码替换为测试代码
  • 组合优先于继承
  • 多态优先于条件语句,当在代码中看见长的条件语句,考虑一下多态

5.3 TDD

  • 测试、编码、重构、(重复)、提交
  • 使用旧的测试来防止新的变化是一种回归测试的形式,确保回归测试进行的最好方法是将测试集自动化。

6. 使用stub进行粗粒度测试

对于那些依赖于某个特定运行环境的应用程序,编写单元测试是个挑战。你编写的测试需要具有稳定性,并且在被反复运行时,它们产生的结果必须是一致的。所以,你需要找到一个方法来控制运行测试的环境。一种解决办法是建立真正的需求环境作为测试的一部分,并在其上运行测试,但并不总行得通。

这里有两个策略供我们生成模拟对象: stub技术和使用mock objects 。stub是一种原始的方法,但如今仍然很流行, 其主要原因是它们允许用户在不修改代码(修改是为了使代码具有可测性)的情况下测试代码。mock objects则适用于另一种情况。

6.1 stub

stub 是一种机制,用来模拟真实代码或者尚未完成的代码所产生的行为。stub允许用户测试系统的某一部分,即使其他部分应不可用。通常,stub不会改变你所测试的代码,但是会适当调整代码以提供无缝集成。
stub是一段代码,通常在运行期间使用插入的stub来代替真实的代码,以便将其调用者与真正的实现隔离开来。其目的是用一个简单一点的行为替换一个复杂的行为,从而允许独立地测试真实代码的某一部分。

缺点:

  • stub往往比较复杂难以编写, 并且它们本身还需要调试
  • 因为stub的复杂性,它们可能很难维护
  • stub不能很好地运用于细粒度测试
  • 不同的情况需要不同的stub策略

6.2 jetty做为嵌入式服务器

书中的例子主要通过定义不同的handler extends AbstractHandler来stub替换web资源。

关于jetty的使用:
http://www.eclipse.org/jetty/documentation/9.4.x/embedding-jetty.html
http://www.cnblogs.com/mymelody/p/9366131.html

6.3 stub和mock的不同

参见: http://www.blogjava.net/aoxj/archive/2010/08/26/329975.html

  • 类实现方式:从类的实现方式上看,stub有一个显式的类实现,按照stub类的复用层次可以实现为普通类(被多个测试案例复用),内部类(被同一个测试案例的多个测试方法复用)乃至内部匿名类(只用于当前测试方法)。对于stub的方法也会有具体的实现,哪怕简单到只有一个简单的return语句。
    而mock则不同,mock的实现类通常是有mock的工具包如easymock, jmock来隐式实现,具体mock的方法的行为则通过record方式来指定。
    对比可以看出,mock编写相对简单,只需要关注被使用的函数,所谓"just enough"。stub要复杂一些,需要实现逻辑,即使是不需要关注的方法也至少要给出空实现。
  • 测试逻辑的可读性:从上面的代码可以看出,在形式上,mock通常是在测试代码中直接mock类和定义mock方法的行为,测试代码和mock的代码通常是放在一起的,因此测试代码的逻辑也容易从测试案例的代码上看出来。
    而stub的测试案例的代码中只有简单的UserDao userDao = new UserDaoStub ();构造语句和service.setUserDao(userDao);设置语句,我们无法直接从测试案例的代码中看出对依赖的预期,只能进入具体的UserServiceImpl类的query()方法,看到具体的实现是调用userDao.getById(userId),这个时候才能明白完整的测试逻辑。因此当测试逻辑复杂
  • 复用性:Mock通常很少考虑复用,每个mock对象通过都是遵循"just enough"原则,一般只适用于当前测试方法。因此每个测试方法都必须实现自己的mock逻辑,当然在同一个测试类中还是可以有一些简单的初始化逻辑可以复用。
    stub则通常比较方便复用,尤其是一些通用的stub,比如jdbc连接之类。spring框架就为此提供了大量的stub来方便测试,不过很遗憾的是,它的名字用错了:spring-mock!
  • 设计和使用:总结来说,stub是state-based,关注的是输入和输出。mock是interaction-based,关注的是交互过程。
  • expectiation/期望:对于mock来说,exception是重中之重:我们期待方法有没有被调用,期待适当的参数,期待调用的次数,甚至期待多个mock之间的调用顺序。所有的一切期待都是事先准备好,在测试过程中和测试结束后验证是否和预期的一致。
    而对于stub,通常都不会关注exception,就像上面给出的UserDaoStub的例子,没有任何代码来帮助判断这个stub类是否被调用。虽然理论上某些stub实现也可以通过自己编码的方式增加对expectiation的内容,比如增加一个计数器,每次调用+1之类,但是实际上极少这样做。

7. 使用mock objects

mock objects策略允许在可能的最细等级上进行单元测试以及逐个方法地进行开发,同时为每一种方法者限供了单元测试。

7.1 简介

mock objects(或者简称为 mocks ),非常适用于将某一部分代码与其他代问隔离开来,并对这部分代码进行测试。mocks替换了测试中与你的方法协作的对象,从而提供了一个隔离层。从这一点来讲,它与stub有些类似。不过相似之处也仅限于此,因为mocks并不实现任何逻辑:它们只是提供了一种使测试能够控制仿造类的所有业务逻辑方法行为的方法的空壳。

JUnit最佳实践:不要在 mock objects 中写入业务逻辑
在编写一个 mock 时,要考虑的最重要的一点是不应该有任何的业务逻辑。它必须是一个“ 傻 ”对象,只做测试要求它做的事情。也就是说,它纯粹是由测试来驱动的。这个特征恰好与stub相反,stub包含了所有的逻辑。

mock objects可很容易生成,因为是空壳,太简单了不至于出错,可以不测试

7.2 使用一个mock objects来重构

tip:重构代码实例:构造函数提供入参,可以自己填写入参,同时提供无参构造函数,调用有参构造函数,填入默认值。这比直接在代码中指定默认值,可测性高。

IoC设计模式
应用IoC模式到一个类中,意味着该类不再创建其不直接负责的对象实例,取而代之的是传递任何所需要的实例。实例可以通过使用一个具体的构造器、setter 或者需要这些实例地方法的参数,而被传递过去。在被调用类上正确地设置这些域对象就成了调用代码的责任。

7.2.1 简单方法重构技巧

上面tip给出了使用组合的方式来实现测试,但是如果某个类是final的,无接口,无法直接创建它的mock类来替换呢?可以通过重构,将调用此final类获取对象的代码抽取为一个方法,用一个Test…Client类继承被测类,方法返回一个mock对象。(描述得不好)

这是一种常用的重构方法,称方法工厂重构,尤其在需要mock的类没有接口时这个方法非常有用

7.2.2 类工厂重构

进一步将对象创建任务从webClient中提取到ConnectionFactory接口中,这样client中只要调用getContent,什么连接类型,返回什么值,都可以在ConnectionFactory中控制,也很容易对ConnectionFactory进行mock。

tip:mock不包含任何逻辑,完全由外部来控制(通过调用set方法来控制)

7.3 把mocks用作特洛伊木马

预期expection:内嵌在mock里的一个特性,用来验证调用此mock的外部类是否具有正确的行为。

比如:stream的close方法是否调用了一次;假如你有一个组件管理器,在组件生命周期内调用不同的方法,那么你可能会预期这些方法是按照一定的顺序被调用的。或者,你可能会期望某个指定的值作为参数传递给mock。一般可以认为,除了能够在测试中提供你想要的行为, mock 同样可以就其使用情况提供有用的反馈。

7.4 mock框架

EasyMock、JMock,目前项目里用的多的是Mockito

8. 容器内测试

8.1 标准单元测试局限性

组件与容器:一个组件在一个容器中执行.容器为存放在内的组件提供了各种服务,如生命周期、安全、事务和分布等。只要一个容器在运行期间创建和管理了对象,我们就无法使用标准的junit技术来测试这些对象。

8.2 容器内测试解决方案

可以采用mock框架解决。可进一步简化。

8.3 容器内测试

一旦测试被打包部署到容器内测试:和客户端上,junit的测试运行器就会执行客户端上的测试类。一个测试类通过如HTTP(S)之类的协议来打开一个连接,并且在服务器端调用相通的测试用例。服务器端的测试用例运行在服务器端对象之上,这些服务器端对象都可以正常使用,(HttpServletRequest、BundleContext)测试我们的领域对象,将来自测试的结果返回给客户端。

后面介绍容器内测试的各类框架。

8.4 比较

stub的优缺点
优点:

  • 它们是快速和轻量级的
  • 它们易于编写和理解
  • 它们功能强大
  • 测试是粗粒度的

缺点:

  • 需要特定的方法来验证状态
  • 它们不测试模拟对象的行为
  • 对于复杂的交互,它们非常耗时
  • 当代码发生变化,它们需要更多的维护

mock objects
缺点:

  • 这个方法不测试容器与组件或者组件之间的交互,它也不测试组件的部署情况;它要求对模拟的API极其熟悉,而这可能很难达到(尤其对外部库而言)
  • 它是更加细粒度的,这可能使你被大量的接口所淹没
  • 像stub一样,在代码发生变化时它也需要维护

容器内测试
需要特定工具、更长的执行时间、复杂的配置。但可以确保在目标环境中正常工作,不仅可以针对正常的行为测试,也可以针对错误的条件。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值