工作多年后我更了解了UT的重要性

对于有经验的开发写单元测试是非常有必要的,并且对自己的代码质量以及编码能力也是有提高的。单元测试可以帮助减少bug泄露,通过运行单元测试可以直接测试各个功能的正确性,bug可以提前发现并解决,由于可以跟断点,所以能够比较快的定位问题,比泄露到生产环境再定位要代价小很多。同时充足的UT是保证重构正确性的有效手段,有了足够的UT防护,才能放开手脚大胆重构已有代码,工 作多年后更了解了UT,了解了UT的重要性。 
# 单元测试   #

 在敏捷的开发理念中,覆盖全面的自动化测试是添加新特性和重构的必要前提。单元测试在软件开发过程中的重要性不言而喻,特别是在测试驱动开发的开发模式越来越流行的前提下,单元测试更成为了软件开发过程中不可或缺的部分。同时单元测试也是提高软件质量,花费成本比较低的重要方法。

## 1.单元测试的时机和测试点 ##

### 1.1单元测试的时机 ###

1. 在业务代码前编写单元测试采用测试驱动开发,这是我们经常使用和推荐的。
2. 在业务代码过程中进行单元测试,对重要的业务逻辑和复杂的业务逻辑进行添加测试。
3. 在业务逻辑之后再编写测试是我们不建议的,除非对遗留代码的修改,需要先进行测试用例的添加,保证我们修改和重构后的代码不会破坏之前的业务逻辑。

### 1.2单元测试的测试点 ###

1. 在逻辑复杂的代码中添加测试。
2. 在容易出错的地方添加测试。
3. 不易理解的代码中添加测试,在以后看到测试就可以非常清楚代码要实现的逻辑。
4. 在考虑后期需求变更相对较大的代码中添加测试,这样后期需求更变修改代码之后就不用太担心写的代码对不对以及是否破坏了已有代码逻辑。
5. 外部接口处添加解耦代码、同时增加单元测试。

## 2.代码不可测试性的根源 ##

1. 代码中调用到了底层平台的接口或只有系统运行后才能获得的资源(数据库连接、发送邮件,网络通讯,远程服务, 文件系统等)但业务代码与这些资源未解耦。这样在测试代码需要创建这个类的时候会去初始化这些资源时导致无法测试。
2. 在方法内部new一个与本次测试无关的对象。
3. 代码依赖层次很深,逻辑复杂,一次方法的往往要调用N次底层的接口,或者类的方法非常多。这样的代码我们需要对类进行重构,尽量保证类的单一职责:这个类在系统中的意图应当是单一的,且修改它的原因应该只有一个。
4. 使用单例类和静态方法,并且单例类和静态方法使用到了我们底层的接口或者其他接口。

## 3.测试工具使用和测试方法介绍 ##

在做单元测试的时候,我们会发现我们要测试的方法会引用很多外部依赖的对象,如调用平台接口、连接数据库、网络通讯、远程服务、FTP、文件系统等等。 而我们没法控制这些外部依赖的对象,为了解决这个问题,我们就需要用到Mock工具来模拟这些外部依赖的对象,来完成单元测试。
现在比较流行的Mock工具有JMock、EasyMock、Mockito、PowerMock。我们使用的是Mockito和PowerMock。PowerMock弥补了其他3个Mock工具不能mock静态、final 、私有方法的缺点。
在下面的情况下我们可以使用Mock对象来完成单元测试。

1. 实对象具有不可确定的行为,会产生不可预测的结果。 如:数据库查询可以查出一条记录、多条记录、或者返回数据库异常等结果。
2. 真实对象很难被创建。如:平台代码,或者Web、JBoss容器等。
3. 真实对象的某些行为很难触发。 如:代码中需要处理的网络异常、数据库异常、消息发送异常等。
4. 真实情况令程序运行很慢。 在敏捷的实践中我们完成了CI,在开发提交代码前需要执行整个项目的单元测试用例,只有测试通过才可以提交代码。这就要求我们每个单元测试用例需要尽可能的短,整个项目的测试时间才会短。当有的测试用例需要测试大数据量情况下系统的预期时,就需要使用Mock对象。
   如我们代码中需要判断只有当系统的缓存队列大于40000时,我们开始考虑丢弃非关键的消息,当超过48000时,需要只处理最重要的消息,当超过50000时需要丢弃全部消息。此时就需要对此缓存队列进行Mock,根据调用返回不同的数据量给测试。
5. 测试需要知道真实对象是如何被调用的。如:测试用例需要验证是否发送了JMS,此时就可以通过Mock对象是否被调用来测试。
6. 真实对象实际不存在时。 如:当我们与其他模块交互时,或者与新的接口打交道时,更有就是对方的代码还没有开发完毕时,我们可以通过Mock来模拟接口的行为,实现代码逻辑的验证和测试。 

### 3.1 Mocktio简单使用说明 ###

mock可以模拟各种各样的对象,从而代替真正的对象做出希望的响应。

1、模拟对象的创建

```java
List cache = mock(ArrayList.class);
System.out.println(cache.get(0));
//-> null 由于没有对mock对象给预期,所以返回都是null
```

2、模拟对象方法调用的返回值

```java
List cache = mock(ArrayList.class);
when(cache.get(0)).thenReturn("hello");
System.out.println(cache.get(0));
//-> hello
```

3、模拟对象方法多次调用和多次返回值

```java
List cache = mock(ArrayList.class);
when(cache.get(0)).thenReturn("0").thenReturn("1").thenReturn("2");
System.out.println(cache.get(0));
System.out.println(cache.get(0));
System.out.println(cache.get(0));
System.out.println(cache.get(0));
//-> 0,1,2,2 如果实际调用的次数超过了预期的次数,则会一直返回最后一次的预期值。
```


4、模拟对象方法调用抛出异常
    

```java
List cache = mock(ArrayList.class);
when(cache.get(0)).thenReturn(new Exception("Exception"));
System.out.println(cache.get(0));
```

5、模拟对象方法在没有返回值时也可以抛异常

```java
List cache = mock(ArrayList.class);
doThrow(new Exception("Exception")).when(cache).clear();
```

6、模拟方法调用时的参数匹配

```java
AnyInt的使用,匹配任何int参数
List cache = mock(ArrayList.class);
when(cache.get(anyInt())).thenReturn("0");
System.out.println(cache.get(0));
System.out.println(cache.get(2));
//-> 0,0
```

7、模拟方法是否被调用和调用的次数,预期调用了一次

```java
List cache = mock(ArrayList.class);
cache.add("steven");
verify(cache).add("steven");
```

预期调用了两次入缓存,没有调用清除缓存的方法

```java
List cache = mock(ArrayList.class);
cache.add("steven");
cache.add("steven");
verify(cache,times(2)).add("steven");
verify(cache,never()).clear();
```

还可以通过atLeast(int i)和atMost(int i)来替代times(int i)来验证被调用的次数最小值和最大值。
**【注意】**
Mock对象默认情况下,对于所有有返回值且没有预期过的方法,Mocktio会返回相应的默认值。对于内置类型会返回默认值,如int会返回0,布尔值返回false。对于其他type会返回null。mock对象会覆盖整个被mock的对象,因此没有预期的方法只能返回默认值。这个在初次使用Mock时需要注意,经常会发现测试结果不对,最后才发现自己未给相应的预期。

### 3.2 PowerMock简单使用说明 ###

PowerMock使用一个自定义类加载器和字节码操作来模拟静态方法,构造函数,final类和方法,私有方法,去除静态初始化器等等。
PowerMock使用简单,在类名前添加注解,在预期前调用PowerMock的mock静态类方法,其他的预期方法和Mockito类似。

```java
@PrepareForTest(System.class)
@RunWith(PowerMockRunner.class)
public class Test {
@org.junit.Test
public void should_get_filed() {
    System.out.println(System.getProperty("myName"));
    PowerMockito.mockStatic(System.class);
    PowerMockito.when(System.getProperty("myName")).thenReturn("steven");
    System.out.println(System.getProperty("myName"));
    //->null steven
    }
}
```

### 3.3 Fake对象的使用 ###

测试中需要模拟对象,除了常用的mock对象外,我们还会经常用到Fake对象。Mock对象是预先计划好的对象,带有各种期待,他们组成了一个关于他们期待接受的调用的详细说明。而Fake对象是有实际可工作的实现,但是通常有一些缺点导致不适合用于产品,我们通常使

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值