a我考网

 找回密码
 立即注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

查看: 134|回复: 3

[C语言] 2011年计算机等级考试二级C辅导实例编程(12)

[复制链接]
发表于 2012-7-31 21:48:08 | 显示全部楼层 |阅读模式
 使用MOCK对象进行单元测试的实例讲解  1.出了什么问题?) Y4 H9 I, V& g
  单元测试的目标是一次只验证一个方法,小步的前进,细粒度的测试,但是假如某个方法依赖于其他一些难以操控的东东,比如说网络连接,数据库连接,或者是Servlet容器,那么我们该怎么办呢?6 x0 z3 y# H+ D' X* ^0 M/ h
  要是你的测试依赖于系统的其他部分,甚至是系统的多个其他部分呢?在这种情况下,倘若不小心,你最终可能会发现自己几乎初始化了系统的每个组件,而这只是为了给一个测试创造足够的运行环境让它们可以运行起来。忙乎了大半天,看上去我们好像有点违背了测试的初衷了。这样不仅仅消耗时间,还给测试过程引入了大量的耦合因素,比如说,可能有人兴致冲冲地改变了一个接口或者数据库的一张表,突然,你那卑微的单元测试的神秘的挂掉了。在这种情况发生几次之后,即使是最有耐心的开发者也会泄气,甚至最终放弃所有的测试,那样的话后果就不能想像了。
! u) n( H. m) m% L( F1 a' V1 A4 s  再让我们看一个更加具体的情况:在实际的面向对象软件设计中,我们经常会碰到这样的情况,我们在对现实对象进行构建之后,对象之间是通过一系列的接口来实现。这在面向对象设计里是最自然不过的事情了,但是随着软件测试需求的发展,这会产生一些小问题。举个例子,用户A现在拿到一个用户B提供的接口,他根据这个接口实现了自己的需求,但是用户A编译自己的代码后,想简单模拟测试一下,怎么办呢?这点也是很现实的一个问题。我们是否可以针对这个接口来简单实现一个代理类,来测试模拟,期望代码生成自己的结果呢?7 q8 |+ D/ @# m" Z$ o; j
  幸运的是,有一种测试模式可以帮助我们:mock对象。Mock对象也就是真实对象在调试期的替代品。
, ]! g  O5 c- y# r" [  2.现在需要Mock对象吗?
4 |0 C6 o' H/ G- n+ O1 y) c2 i  关于什么时候需要Mock对象,Tim Mackinnon给我们了一些建议:- r, x- a  E, {: `
  ----- 真实对象具有不可确定的行为(产生不可预测的结果,如股票的行情)
5 I4 C' Q$ {. y( ~7 p) ^1 y  ----- 真实对象很难被创建(比如具体的web容器)
1 N5 I% V3 V9 n5 K" n7 E  ----- 真实对象的某些行为很难触发(比如网络错误)
, O" |8 ]" v/ `% h  e9 r  ----- 真实情况令程序的运行速度很慢
( {  z* j, {. E0 ]  ----- 真实对象有用户界面" Z6 R8 k2 T+ [
  ----- 测试需要询问真实对象它是如何被调用的(比如测试可能需要验证某个回调函数是否被调用了)+ S, H+ A( C! R3 d9 u+ ~7 j
  ----- 真实对象实际上并不存在(当需要和其他开发小组,或者新的硬件系统打交道的时候,这是一个普遍的问题)
回复

使用道具 举报

 楼主| 发表于 2012-7-31 21:48:09 | 显示全部楼层

2011年计算机等级考试二级C辅导实例编程(12)

  3.如何实现Mock对象?
5 k; X" K, x; y) h  使用mock对象进行测试的时候,我们总共需要3个步骤,分别是:' T4 e9 e! B$ K: I
  ----- 使用一个接口来描述这个对象
( s- e0 X4 s' t! c" y& b  ----- 为产品代码实现这个接口
. F0 \+ F8 \0 T0 ~  ----- 以测试为目的,在mock对象中实现这个接口
$ W, A$ R% ~, N; T# P8 l  在此我们又一次看到了针对接口编程的重要性了,因为被测试的代码只会通过接口来引用对象,所以它完全可以不知道它引用的究竟是真实的对象还是mock对象,下面看一个实际的例子:一个闹钟根据时间来进行提醒服务,如果过了下午5点钟就播放音频文件提醒大家下班了,如果我们要利用真实的对象来测试的话就只能苦苦等到下午五点,然后把耳朵放在音箱旁...我们可不想这么笨,我们应该利用mock对象来进行测试,这样我们就可以模拟控制时间了,而不用苦苦等待时钟转到下午5点钟了。下面是代码:
2 k; X7 l7 J" v2 s6 g  public interface Environmental {
3 g; w7 R' d! ~- D$ F  private boolean playedWav = false;
. j) F7 X9 j- @  public long getTime();
& U4 ?6 i' w: t, g  public void playWavFile(String fileName);
; u( `: E6 |' D: U1 q( X  public boolean wavWasPlayed();
# y9 t1 b9 O9 D6 ]  public void resetWav();8 I1 w: b! N; E; Y# _* b
  }
# j+ W- j) A6 s' ?5 I  真实的实现代码:
" y& }1 m2 F, [- g" U7 C  public class SystemEnvironment implements Environmental {
4 K- H9 q. {( p! k+ Y% g" f# n" ?  public long getTime() {
, V+ _0 |3 X7 S2 \( A  return System.currentTimeMillis();* s( H0 T& i& T
  }, z! B1 u* W' G& S8 ?
  public void playWavFile(String fileName) {
% C9 m: d$ ]" }  R7 F; ~8 T  playedWav = true;( m0 B# n# D# v& ]+ S& Q7 b( w4 d. W! y
  }
" {3 |  c5 Z$ j" j" X  Q7 [1 Z  public boolean wavWasPlayed() {) A6 H: }+ P+ _' L
  return playedWav;
7 Y+ l6 E* |9 j. B' ]6 I  }
3 H; o8 O4 c- [  public void resetWav() {
- H* m# m" H7 D; j  playedWav = false;7 V7 t: j) J( j, G3 P9 W8 X+ y3 W
  }
  X9 A. R: J( X) l4 h8 S7 e6 J  }
2 J0 L; \  J5 x4 q! R/ \8 s2 F  下面是mock对象:: b' t% g2 k2 b; l( T6 Y9 `
  public class MockSystemEnvironment implements Environmental {$ T0 P' X% T% `' h! _
  private long currentTime;* N0 P1 e* R# C3 L8 H  j
  public long getTime() {) E# h$ [2 `/ @# B: T
  return currentTime;
; z+ [2 ~, w1 b, o4 }) @& q5 |  }
- w* W  F+ r- C: f4 f1 N& A  public void setTime(long currentTime) {
, b* F. @6 S3 v# J# i3 p  this.currentTime = currentTime;
* D' u6 W8 O0 j, `( X( O3 s/ `  }4 A: Q! L1 ]3 S* @' U; p
  public void playWavFile(String fileName) {& f8 }& k: T* W6 a8 l9 k; i! }6 h' h
  playedWav = true;
* d5 M! O9 a  D2 f7 _! j  }
: f* e; P( v+ X% K* n2 f6 L  public boolean wavWasPlayed() {
* q# O, d+ q" Z* v; I  return playedWav;" E, s% d2 ]# y8 z9 _4 q
  }
, {. G( P( x2 r4 [1 D8 D7 R  public void resetWav() {0 a3 l. G, e5 {
  playedWav = false;
# k, I5 n" E6 B' x  }
9 E9 f4 F& B7 l  }
# t, \* V; y2 w2 X( Q/ {( Y! g, p  下面是一个调用getTime的具体类:' |; C0 s3 X7 X6 R, W0 ?
  import java.util.Calendar;3 e" C+ ]9 a3 r6 ?$ I# I
  public class Checker {
) }9 X1 z2 E( @2 q, x/ L  private Environmental env;
! @) g0 H; e0 Z$ }" D' C  public Checker(Environmental env) {
- S4 q0 {. t) ?& c9 l7 K5 T7 h4 }  this.env = env;
回复 支持 反对

使用道具 举报

 楼主| 发表于 2012-7-31 21:48:10 | 显示全部楼层

2011年计算机等级考试二级C辅导实例编程(12)

 }  public void reminder() {
5 n* o" z* y0 r6 Q; b  Calendar cal = Calendar.getInstance();: B: a/ a3 W4 x. [3 Z% I
  cal.setTimeInMills(env.getTime());. }  j% g6 u9 W6 i! t0 a" T% v2 r
  int hour = cal.get(Calendar.HOUR_OF_DAY);
; a4 c+ g) B9 D* M  if(hour >= 17) {2 N. L+ I( A9 n. ]+ |: {3 T
  env.playWavFile("quit_whistle.wav");
% U& `; \3 U1 l/ W6 {; g6 I  }: j* @4 M7 j  T: o' a# k0 Z
  }  D' o2 C# n: X$ o. k
  }! Q/ S# b, U# a: l% u
  使用env.getTime()的被测代码并不知道测试环境和真实环境之间的区别,因为它们都实现了相同的接口。现在,你可以借助mock对象,通过把时间设置为已知值,并检查行为是否如预期那样来编写测试。
: D" K9 m3 l( C2 \3 @9 a, f  import java.util.Calendar;
3 Z) s6 j) C3 |8 R9 ~6 W  import junit.framework.TestCase;7 |( {$ s* k. {& O0 k
  public class TestChecker extends TestCase {4 ~$ @) Z5 @3 N& @
  public void testQuittingTime() {
$ B9 r  }7 S6 J9 F8 W' x' B  MockSystemEnvironment env = new MockSystemEnvironment();4 O" {$ I. U4 O, q8 ]7 z, ^
  Calendar cal = Calendar.getInstance();9 [# B4 I2 T8 B7 E
  cal.set(Calendar.YEAR, 2006);5 K! o2 z, G) V! |
  cal.set(Calendar.MONTH, 11);. f9 b8 b6 M) u; n; u' j# H! t
  cal.set(Calendar.DAY_OF_MONTH,7);- u/ z, i4 ^1 E6 A
  cal.set(Calendar.HOUR_OF_DAY, 16);
+ F8 l1 y$ n* K' p  cal.set(Calendar.MINUTE, 55);
1 o) \; U# ]. a  long t1 = cal.getTimeInMillis();
. `2 n$ n+ E( g  K" F4 ~- Q  env.setTime(t1);: ~$ E. x2 A4 `. ^+ i6 K
  Checker checker = new Checker(env);, v- C8 D0 u& S. f+ \" E
  checker.reminder();
3 J3 v# s" j$ a5 W" d7 e8 k! o( }  assertFalse(env.wavWasPlayed());" V4 ?9 T& t) U) L2 p8 Q5 s
  t1 += (5*60*1000);
+ R2 z+ H* _0 ^  env.setTime(t1);- w4 s! f+ W) v6 a/ m8 g
  checker.reminder();
. w4 T# ~- V' o( _  assertTrue(env.wavWasPlayed());
" G6 Y1 L% `" w  env.resetWav();
4 g5 O/ z8 `' ^- [6 X  t1 += 2*60*60*1000;
/ r5 c# x" L: f: B2 u1 j  env.setTime(t1);
. y) ]# j& t' _2 m. K  checker.reminder();
% O$ m* L% H6 Z/ u2 H+ Q  _8 b  assertTrue(env.wavWasPlayed());
回复 支持 反对

使用道具 举报

 楼主| 发表于 2012-7-31 21:48:11 | 显示全部楼层

2011年计算机等级考试二级C辅导实例编程(12)

 }  }
# C1 L5 R" j3 s7 j4 {7 J4 r- d  这就是mock对象的全部:伪装出真实世界的某些行为,使你可以集中精力测试好自己的代码。
+ z, s  \: L1 {, B+ d: p* L$ v  内容导航 4.好像有一些麻烦
- T' a- r" T# u- ]. \8 e! N  如果每次都像上面那样自己写具体的mock对象,问题虽然解决了,但是好像有一些麻烦,不要着急,已经有一些第三方现成的mock对象供我们使用了。使用Mock Object进行测试,主要是用来模拟那些在应用中不容易构造(如HttpServletRequest必须在Servlet容器中才能构造出来)或者比较复杂的对象(如JDBC中的ResultSet对象)从而使测试顺利进行的工具。目前,在Java阵营中主要的Mock测试工具有JMock,MockCreator,Mockrunner,EasyMock,MockMaker等,在微软的.Net阵营中主要是Nmock,.NetMock等。/ b! Q$ W% x$ @* p' Y$ Z6 N( `  L! K7 v
  下面就以利用EasyMock模拟测试Servlet组件为例,代码如下: 编译并将其当做一个Test Case运行,会发现两个测试方法均测试成功。我们可以看到easymock已经帮助我们实现了一些servlet组件的mock对象,这样我们就可以摆脱web容器和servlet容器来轻松的测试servlet了。7 N( s7 d: i; L7 h" ^
  import org.easymock.*;
/ J# {! I( t# Q% c8 ~. i# E  import junit.framework.*;
( P# X  V' w+ a! d! P! @: I  import javax.servlet.http.*;! \7 u$ {0 H/ h& H- ?& J
  public class MockRequestTest extends TestCase{, ?* j" d8 a9 E" c- t$ S7 ]8 W' n
  private MockControl control;
5 `6 v" j. Q3 H: I  private HttpServletRequest mockRequest;( Z2 e+ }% d1 t, b  z. c
  public void testMockRequest(){
" Z; k' n" C: j, h: j  //创建一个Mock HttpServletRequest的MockControl对象
& G, H5 d9 x8 L) b8 h0 d  control = MockControl.createControl(HttpServletRequest.class);
1 ?9 Q6 D0 H5 S* d  //获取一个Mock HttpServletRequest对象5 j% g# l0 G- C! M
  mockRequest = (HttpServletRequest) control.getMock();
" a( K. k2 b$ M! i) S' C8 H) }  //设置期望调用的Mock HttpServletRequest对象的方法
# l) u6 x" ^. T4 W2 J0 F  mockRequest.getParameter("name");2 X# ^/ y# H* G& c: e  ^
  //设置调用方法期望的返回值,并指定调用次数  s5 h6 ^) t! A" }- |6 [
  //以下后两个参数表示最少调用一次,最多调用一次; n! B+ I8 g2 y+ e6 n
  control.setReturnValue("kongxx" ,1 ,1);% o1 l, z; I, Y8 g
  //设置Mock HttpServletRequest的状态,
: q. m; b1 ~  e# d% S+ R( V9 C  //表示此Mock HttpServletRequest对象可以被使用: a4 z. ^8 ^4 ^+ A  Z0 |
  control.replay();, M0 u. @5 u5 T" @2 G7 b& I
  //使用断言检查调用( S: W' x9 l5 {1 |7 [
  assertEquals("kongxx",mockRequest.getParameter("name"));
8 Q; x! Q5 M. Y0 q$ O8 O  //验证期望的调用& p. h) i5 ]$ o* k9 `/ K4 {
  control.verify();; c8 `9 F2 N' y; D3 r  I  x' X4 |% [
  }2 [/ i! z4 r$ e& Q( T
  }  s  @( i1 V3 o) |9 w/ L/ P- ]8 V; b
  编译并将其当做一个Test Case运行,会发现两个测试方法均测试成功。我们可以看到easymock已经帮助我们实现了一些servlet组件的mock对象,这样我们就可以摆脱web容器和servlet容器来轻松的测试servlet了。) X5 ?9 T% o& v, k) C
  5.底层技术是什么?
! t$ s# L. L; d3 k$ I. H  让我们来回忆一下,如果用户使用C++和java的程序的生成,C++在最后的阶段还需要连接才能生成一个整体程序,这在灵活性与java源代码的机制是不能比的,java的各个类是独立的,打包的那些类也是独立的,只有在加载进去才进行连接,这在代码被加载进去的时候,我们还可以执行很多的动作,如插入一些相关的业务需求,这也是AOP的一个焦点,javassit代码库的实现类似于这,正是利用这些,所以用java实现Mock对象是很简单的。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|Woexam.Com ( 湘ICP备18023104号 )

GMT+8, 2024-5-18 04:11 , Processed in 0.378879 second(s), 27 queries .

Powered by Discuz! X3.4 Licensed

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表