软件测试之单元测试
作者:网络转载 发布时间:[ 2016/5/16 11:16:00 ] 推荐标签:软件测试 单元测试
稍微看了一下这段代码实现会发现这个系统是难以测试的,为什呢?因为内存中的K-V对会缓存一个小时,如果要测试缓存时间1个小时的逻辑对不对,那么要等上1个小时才可以,这显然是不可能的。System.currentTimeMillis() 是JDK中一个的方法,用来获取当前时间的毫秒值,这里我用来获取当前时间以便判断是否超出1个小时。问题出在这里,如果能够让事件变得可控制,那么不容易测试了吗?于是有了新的设计:
// 设计时间接口
public interface SystemClock {
public long getCurrentTime();
}
// 业务代码中的实现
public class SystemClockImpl implements SystemClock {
@Override
public long getCurrentTime() {
return System.currentTimeMillis();
}
}
// 改进后的 ShortLifeMemoCache<T>
public class ShortLifeMemoCache<T> implements CacheService<T> {
private static final long DEFAULT_TTL = 1L*60*60*1000;
/**
* 系统时间,单位:毫秒
*/
private SystemClock systemClock;
/**
* 生存时间(Time to live),单位:毫秒
*/
private long ttl = DEFAULT_TTL;
private final Map<String, Value<T>> cache = new HashMap<String, Value<T>>();
@Override
public void put(String key, T t) {
long life = systemClock.getCurrentTime() + ttl;
cache.put(key, new Value<T>(t, life));
}
@Override
public T get(String key) {
Value<T> v = cache.get(key);
if (v != null){
if(v.getLife() > systemClock.getCurrentTime()) {
return v.getValue();
} else {
cache.remove(key);
}
}
return null;
}
}
有了这样的设计可以随意的控制时间了,比如我们的单元测试代码可以写成这样(并且瞬间执行完测试用例):
// 测试用例:设置ttl=10,时间由FakeSystemClock控制
public void testValueTimeout() {
long timeToLive = 10;
FakeSystemClock systemClock = new FakeSystemClock();
systemClock.setCurTime(0);
ShortLifeMemoCache<String> cacheService = new ShortLifeMemoCache<String>();
cacheService.setSystemClock(systemClock);
cacheService.setTtl(timeToLive);
cacheService.put("key1", "value1");
// after 10 seconds
systemClock.setCurTime(10);
assertNull(cacheService.get("key1"));
}
// Mock 时间接口
static class FakeSystemClock implements SystemClock {
private long curTime;
@Override
public long getCurrentTime() {
return curTime;
}
public long getCurTime() {
return curTime;
}
public void setCurTime(long curTime) {
this.curTime = curTime;
}
}
这种设计是我们大家熟悉的 - 面向接口编程!我们把时间的获取抽象成一个接口,这样可以在测试时不再依赖于物理时间。事实上在我们的程序设计中,对于一切需要可控的依赖都要设计成接口,这样系统才会变得容易测试。经常在一些Android开发群里看到大家在抱怨MVP模式的复杂 - 其中一点是说MVP用了很多接口,把View和Presenter都抽象成了接口。而事实情况是MVP从来都没有要求使用过接口,MVP模式在90年代已经出现了,10年后才有面向接口编程相关书籍出版,而现在大家能看到的MVP使用了大量接口是因为测试!这种MVP实现全称叫PassiveView-MVP,也是目前用的多的MVP模式(具体见:UI架构小史3)。
在一个容易测试的系统中必然会大量使用接口,然而这并不能解决所有问题,有些测试依然很难,比如需要Mock一些系统库提供的API,这时候可以使用一些Mock工具,比如 EasyMock,mockito 都是很好用的Mock工具,跑单元测试当然离不开jUnit,UI相关还可以使用 Robotium 等.
对于Android的UI测试还需要多说几句,因为大部分时候我们还会把UI布局/效果和UI/业务逻辑混淆了,这样会导致测试很难进行。通常情况下需要把逻辑(无论是UI逻辑还是业务逻辑)抽象到Model里面(Model是个动词,意指ModelingTheWorld),Model不依赖于系统框架容易测试。
PS:依赖注入
在上面的方案中我们默认省略了 getter 和 setter 方法的代码,但是这是必须要有的。我们通常会听到一个名词叫“依赖注入”,那么什么是依赖注入呢?看下面这段代码:
public class Foo {
private String message = "Hello World";
private String View view = new View();
public void print() {
System.out.println(message);
}
public void showMessage() {
view.display(message);
}
}
这段代码中 message 和 view 变量在Foo初始化的时候确定了,无法再改变 - 这是典型的 依赖无法注入 的例子。解决办法有很多种,比如:
//提供一个可以传参的构造方法
publicFoo(Stringmessage,Viewview){
this.message=message;
this.view=view;
}
//或者提供setter方法
publicvoidsetMessage(Stringmessage){
this.message=message;
}
publicvoidsetView(Viewview){
this.view=view;
}
上面这两者都是典型的依赖注入的实现,关于依赖注入可以看很早前写过的一篇文章:依赖注入,还有很多比较自动化的基于Ioc的实现方式,比如Spring提供的依赖注入框架或者是这几年在Android届比较流行的Dagger框架。
本文内容不用于商业目的,如涉及知识产权问题,请权利人联系SPASVO小编(021-61079698-8054),我们将立即处理,马上删除。
相关推荐
iOS单元测试mocha、chai、sinon和istanbul实现百分之百的单元测试覆盖率关于单元测试的总结及思考编写更好的Java单元测试的7个技巧Android单元测试框架Robolectric3.0介绍(一)使用Kiwi单元测试总结单元测试如此重要,为什么你不知道Python单元测试??使用装饰器实现测试跳过和预期故障对Controller的单元测试写好单元测试的10个技巧单元测试的重要性Angular单元测试系列??Component、Directive、Pipe 以及ServiceAndroid单元测试的整理提升单元测试体验的利器--Mockito使用总结iOS UnitTest单元测试Vue的单元测试探索(二)
更新发布
功能测试和接口测试的区别
2023/3/23 14:23:39如何写好测试用例文档
2023/3/22 16:17:39常用的选择回归测试的方式有哪些?
2022/6/14 16:14:27测试流程中需要重点把关几个过程?
2021/10/18 15:37:44性能测试的七种方法
2021/9/17 15:19:29全链路压测优化思路
2021/9/14 15:42:25性能测试流程浅谈
2021/5/28 17:25:47常见的APP性能测试指标
2021/5/8 17:01:11热门文章
常见的移动App Bug??崩溃的测试用例设计如何用Jmeter做压力测试QC使用说明APP压力测试入门教程移动app测试中的主要问题jenkins+testng+ant+webdriver持续集成测试使用JMeter进行HTTP负载测试Selenium 2.0 WebDriver 使用指南