JUnit学习笔记8---mock object进行独立测试2

来源:互联网 发布:json get post 编辑:程序博客网 时间:2024/04/20 23:21

          使用mock objects,重构将变得容易!

      有些人说过,单元测试应该对测试中的代码透明:你不应为了简化测试而改变运行时的代码。这是错误的!单元测试是对运行时代码最好的运用。应该同其他运用同等的看待。如果你的代码不能在测试中运用,你应该纠正代码。

       例如,你看下面的代码怎样?

[...] import java.util.PropertyResourceBundle; import java.util.ResourceBundle;; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; [...]  public class DefaultAccountManager implements AccountManager{ private static final Log LOGGER= LogFactory.getLog(AccountManager.class); public Account findAccountForUser(String userId){ LOGGER.debug("Getting account for user ["+userId+"]"); ResourceBundle bundle=   PropertyResourceBundle.getBundle("technical"); String sql=bundle.getString("FIND_ACCOUNT_FOR_USER");// Some code logic to load a user account using JDBC [...]          }               }
   用LogFactory取得一个Log对象。用PropertyResourceBundle获得了SQL命令。
     你觉得这个代码看起来很好么?我们可以透过这个代码看见两个问题!它们都是同代码的适应性和承受力相关的。第一个问题是,你无法使用一个不同
的Log对象,因为这是由类内部创建的。一般来说,像这样的类应该能使用给定的任何Log,但是不行,做不到!第二个问题,就是PropertyResourceBundl
e。它看上去不错,但是,就是它决定了实现的方式,假使我们想用XML存储配置,就被这个类束缚了!再一次的,不应该由这个类决定使用何种实现,一个有
效的设计策略是,对象不应限制在其现有商业逻辑之外的可传入的对象。外围对象的选择应该能由在更高调用链上的某个人控制。随着调用层的上升,决定使
用哪个特定的记录器或是配置可以在最高层决定。这种策略最大化的提供了代码的灵活性。我们知道唯一不变的就是变化!
         重构所有的代码以传递领域对象是件费时的事。也许你不准备重构整个程序,而仅仅是为了一个单元测试,幸运的是这里有方便的重构技术,让你的代码保持相同的接口,并传递给它没有创建的域对象。让我们看看重构后的DefaultAccountManager类是什么样的!
[...] import java.util.PropertyResourceBundle; import java.util.ResourceBundle;; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; [...]  public class DefaultAccountManager implements AccountManager{private Log logger;private Configuration configuration;public DefaultAccountManager(){   this.LogFactory.getLog(DefaultAccountManager.class),new DefaultConfiguration("technical"));    }public DefaultAccountManager(Log logger, Configuration configuration){ this.logger=logger;  this.configuration=configuration;    }public Account findAccountForUser(String userId){this.logger.debug("Getting account for user["+userId+"]");this.configuration.getSQL("FIND_ACCOUNT_FOR_USER");}}
   注意到,它使用了一个全新的Configuration接口,替换了前一个代码上的PropertyResourceBundle类。由于引入了一个接口(这很容易mock),同
时Configuration接口可以实现你想要的任何东西,这就使得代码变得灵活!你可以用Log和Configuration接口实现任何的实现复用DefaultAccountMan
ager类。这个设计变得更加的优秀了。类可以由外围(它的调用者)控制,同时你并没有破坏已有的接口,因为你只是添加了一个新的构造函数,同时还保留
了原有的构造函数,它仍然对logger和configuration进行初始化,赋给成员默认值。
   重构提供了在测试中控制领域对象的一个暗门。在保留向后兼容性的同时,也重构了一条通向未来的路!
     对于引入暗门使你的代码更加的容易测试,这让很多人担心,那么看看大名鼎鼎的极限编程专家是怎么说的:
    我的车里有一个诊断器和一个测油汁。在炉子的上面和两个侧面都有一个检查孔。我的笔筒是透明的,所以没有墨水,我就可以看见。
    如果我发现在类中有必要添加以个方法,使我能测试它,我也会去做。这经常发生,比如有着简单的接口和复杂的内部函数类可能需要一个
    Extract Class重构。我只是把我理解的类的需求给予类,同时关注它下一步需要什么。

             允许灵活的代码

          我们在前面描述了一个设计模式称为控制反转(IOC)的著名模式。主要的想法是从类/方法之外实例化所有的

领域对象,同时传递各种参数。对象实例是传递给类的(通常通过接口),而不是由类来创建对象实例。在上面的例子中,这表现在把Log和Configuration对像传递给DefaultAccountManager类。DefaultAccountManager没有表明传递了哪些实例以及他们是如何构造的。只知道Log和Configuration的接口。

        IOC使单元测试变得简单,为了证明这点,让我们看看你可以多么简单的进行findAccountByUser测试!

  public void testFindAccountByUser(){ MockLog logger=new MockLog(); MockConfiguration Configuration=new  MockConfiguration();          Configuration.setSQL("SELECT*[...]");           DefaultAccountManager am=      new DefaultAccountManager(logger,configuration);          Account account=am.findAccountForUser("1234");           //          Perform asserts here           [...]        }
          你已经完全可以在外围控制你的记录和配置行为了!这样你的代码就更加的灵活了。
原创粉丝点击