论坛首页 综合技术论坛

关于EasyMock的困惑,感觉虽然隔离了层的依赖,但却不能进行所谓的暗盒测试!

浏览 11457 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2006-11-16  
我想大家使用easyMock的目的都是想去隔离层于层的依赖关系,防止由于被依赖层的错误导致依赖层的测试失败。 但是现在使用了EasyMock后发现了一个问题, 由于层之间的依赖而导致测试失败的情况是给隔离了。但是现在带来一个比较头痛的问题, 就是我必须去关注被测试对象的内部逻辑了,那这样的测试并不能叫做所谓了暗盒测试了, 请看下面的例子:

public class MaintainRoleService extends ServiceSupport implements IMaintainRoleService{
    private IRoleDAO roleDAO;

    public void setRoleDAO(IRoleDAO roleDAO) {
        this.roleDAO = roleDAO;
    }

    public void saveRole(RoleVO roleVO) throws BusinessException {
        if( roleVO == null || StringUtils.isBlank(roleVO.getId()) ){
            throw new BusinessException(new Message("Arguments error"));
        }
        RoleVO returnedRoleVO = roleDAO.getByPk(roleVO.getId());
        if( null != returnedRoleVO ){
            throw new BusinessException( new Message("Inputed role number has existed in DB"));
        }

        if( null != roleVO.getParentRole() && !StringUtils.isBlank(roleVO.getParentRole().getId()) ){

            RoleVO returnedParentRole = roleDAO.getByPk(roleVO.getParentRole().getId());
            if ( returnedParentRole == null ){
                throw new BusinessException( new Message("Unexpected error") );
            }
            if( !RoleVO.ROLE_ST_VALID.equalsIgnoreCase(returnedParentRole.getStCd()) ){
                throw new BusinessException( new Message( "Cannot add a sub role with a inactived parent role" ) );
            }
            roleVO.setParentRole(returnedParentRole);
        }
        roleVO.setCreateDttm(DateUtil.getSystemTimestamp());
        roleVO.setLastUpdDttm(DateUtil.getSystemTimestamp());
        roleVO.setVersionNum(0);
        roleDAO.add(roleVO);

    }
}


上面是一个经常可以见到的一个insert操作,因为这里的service方法依赖了DAO,也就是上面的IRoleDAO, 所以我在写testCase的时候就需要用mock来模拟roleDAO. 但是在我的service方法内部可能会调用好多次roleDAO方法。 比如我上面的saveRole方法就可能调用两次或者三次roleDAO方法, 其实上面Service方法也就决定了我不可能先写测试类,再去写实现类使测试通过! 因为我前面的测试数据,主要也就是对mock的expect的设置需要去了解service方法里面的逻辑才能知道,到底被测试的方法调用了多少次, 并且分别是调用了哪些DAO方法,到底我应该怎么样去设置mock的expect...

所以我感觉我要是这样写unit test 是不成功,不知道有没有朋友知道什么好的方法,或者给我点好的建议呢!

   发表时间:2006-11-16  
引用

但是现在带来一个比较头痛的问题, 就是我必须去关注被测试对象的内部逻辑了,那这样的测试并不能叫做所谓了暗盒测试了


单元测试本来就是白盒测试啊!

当然你需要知道已知的输入参数下,应该会有如何的返回值,不然如何Mock? Mock只是让你不去关注方法的实现,但是并不代表你可以不关注这个方法的行为啊!
0 请登录后投票
   发表时间:2006-11-16  
Morgan0916 写道
引用

但是现在带来一个比较头痛的问题, 就是我必须去关注被测试对象的内部逻辑了,那这样的测试并不能叫做所谓了暗盒测试了


单元测试本来就是白盒测试啊!

当然你需要知道已知的输入参数下,应该会有如何的返回值,不然如何Mock? Mock只是让你不去关注方法的实现,但是并不代表你可以不关注这个方法的行为啊!



那也就是说, 如果用到了mock, 我也就没有办法去先写测试类再去写实现的具体类了?? 我的本意是测试类可以将被测试对象当作一个完全封闭的api来测试. 也就是从这个api的调用者去考虑! 本来是可以这样做的,不过现在加了mock后我就不可以这样做了......  其实我们写的代码除了底层的 util 类, 有多少类是可以不用写mock直接测试的呀! 这样不就是永远也不可能达到从调用者去考虑写测试代码的境界了。
0 请登录后投票
   发表时间:2006-11-16  
有没有朋友能帮我提供一个我上面代码中saveRole方法的测试呢!看看你们是怎么样去使用mock的呢!
0 请登录后投票
   发表时间:2006-11-16  
你不知道调用多少次就不要在mock里边写明次数呗.那个次数是用来测试调用次数是否正确的.你还没有些实现,何必要测试那个依赖方法调用了多少次?可以等实现完毕后再补上这个测试用例.
0 请登录后投票
   发表时间:2006-11-16  
我稍微写了一下测试类,请大家看看了! 不过我感觉这样的testCase很不稳定,太依赖被测试类了。如果被测试类做了些改动估计就要回头来改对应的testCase了。

public class MaintainRoleServiceTest extends TestCase {
    private MaintainRoleService maintainRoleService;
    private IRoleDAO roleDAOMock;
    protected void setUp() throws Exception {
        super.setUp();
        maintainRoleService = new MaintainRoleService();
        roleDAOMock = createMock(IRoleDAO.class);
        maintainRoleService.setRoleDAO(roleDAOMock);
    }

    protected void tearDown() throws Exception {
        super.tearDown();    //To change body of overridden methods use File | Settings | File Templates.
    }

    public void testSaveRole(){

        //1. role have no parent role and passed all validation
        RoleVO roleVO = new RoleVO();
        roleVO.setId("ROLE_USER");// Role Number
        roleVO.setRoleDesc("users"); // Role Description
        roleVO.setStCd(RoleVO.ROLE_ST_VALID);
        roleVO.setCreateUsrNum("DenisLing");
        roleVO.setLastUpdUsrNum("DenisLing");
        reset(roleDAOMock);
        expect(roleDAOMock.getByPk("ROLE_USER")).andReturn(null);        
        expect(roleDAOMock.add(roleVO)).andReturn( "ROLE_USER");
        replay(roleDAOMock);
        try{
            maintainRoleService.saveRole(roleVO);
        }catch( BusinessException be ){
            fail("Should not run here");
        }
        verify(roleDAOMock);
        //2.role have no parent role but inputed role number has existed in db
        reset(roleDAOMock);
        expect(roleDAOMock.getByPk("ROLE_USER")).andReturn(roleVO);
        replay(roleDAOMock);
        try{
            maintainRoleService.saveRole(roleVO);
            fail("should not run here");//
        }catch( BusinessException be ){
            assertEquals("Inputed role number has existed in DB",be.getExceptionMessage().getMessageKey());
        }

        //3. test the case with parent Role but parent role does not existed in DB

        RoleVO parentRoleVO = new RoleVO();
        parentRoleVO.setId("ROLE_PARENT_USER");
        roleVO.setParentRole(parentRoleVO);

        reset(roleDAOMock);
        expect( roleDAOMock.getByPk("ROLE_USER") ).andReturn(null); // no inputted role existed in DB
        expect( roleDAOMock.getByPk("ROLE_PARENT_USER") ).andReturn(null);// parent role doesn't exsited in DB
        replay(roleDAOMock);
        try{
            maintainRoleService.saveRole(roleVO);
            fail();// should not run here
        }catch( BusinessException be ){
            assertEquals("Unexpected error",be.getExceptionMessage().getMessageKey());
        }
        verify(roleDAOMock);

        //4. test the case with parent Role but parent role's ST_CD is "S" suspend or "T"
        RoleVO returnedParentRoleVO = new RoleVO();
        returnedParentRoleVO.setId("ROLE_PARENT_USER");
        returnedParentRoleVO.setStCd("S");
        reset(roleDAOMock);
        expect(roleDAOMock.getByPk("ROLE_USER")).andReturn(null);
        expect(roleDAOMock.getByPk("ROLE_PARENT_USER")).andReturn(returnedParentRoleVO);
        replay(roleDAOMock);
        try{
            maintainRoleService.saveRole(roleVO);
            fail();
        }catch(BusinessException be){
            assertEquals("Cannot add a sub role with a inactived parent role",be.getExceptionMessage().getMessageKey());
        }
        verify(roleDAOMock);

        returnedParentRoleVO.setStCd("T");
        reset(roleDAOMock);
        expect(roleDAOMock.getByPk("ROLE_USER")).andReturn(null);
        expect(roleDAOMock.getByPk("ROLE_PARENT_USER")).andReturn(returnedParentRoleVO);
        replay(roleDAOMock);
        try{
            maintainRoleService.saveRole(roleVO);
            fail();
        }catch(BusinessException be){
            assertEquals("Cannot add a sub role with a inactived parent role",be.getExceptionMessage().getMessageKey());
        }
        verify(roleDAOMock);
        
    }

}
0 请登录后投票
   发表时间:2006-11-16  
这个test方法也太长了吧
0 请登录后投票
   发表时间:2006-11-17  
lingcm 写道
Morgan0916 写道
引用

但是现在带来一个比较头痛的问题, 就是我必须去关注被测试对象的内部逻辑了,那这样的测试并不能叫做所谓了暗盒测试了


单元测试本来就是白盒测试啊!

当然你需要知道已知的输入参数下,应该会有如何的返回值,不然如何Mock? Mock只是让你不去关注方法的实现,但是并不代表你可以不关注这个方法的行为啊!



那也就是说, 如果用到了mock, 我也就没有办法去先写测试类再去写实现的具体类了?? 我的本意是测试类可以将被测试对象当作一个完全封闭的api来测试. 也就是从这个api的调用者去考虑! 本来是可以这样做的,不过现在加了mock后我就不可以这样做了......  其实我们写的代码除了底层的 util 类, 有多少类是可以不用写mock直接测试的呀! 这样不就是永远也不可能达到从调用者去考虑写测试代码的境界了。


现在就拿你的这个例子来说吧.比如你想测试MaintainRoleService,而MaintainRoleService又依赖IRoleDAO object,那么你就需要对IRoleDAO做Mock.但是你必须先要写一个IRoleDAO interface,你并不需要去写实现类!但是你必须要在Mock中设定IRoleDAO interface中每个method的正确的行为,已知的输入参数下,应该会有如何的返回值,不然如何断言?

比如你要Mock roleDAO.add(roleVO), 再add()的实现中,或许是许多的数据库操作,但是我们此时mock的时候不用去关心这些,我们要关心的是调用这个方法后,如果添加成功,返回什么? 添加失败,又返回什么?此时不就可以去mock它可能的这些情况吗? 你不用去看别人的实现代码,看看设计文档就可以知道啦     



0 请登录后投票
   发表时间:2006-11-17  
参考Martin Fowler的这篇文章:
http://www.martinfowler.com/articles/mocksArentStubs.html

区别就在于测试是基于状态的还是基于交互的。个人比较喜欢基于状态的,使用Mock也不喜欢做verify,拿来当stub用。
0 请登录后投票
   发表时间:2007-01-08  
你的感觉是对的,mock的方式其实就是在测试类的实现过程,已经不仅仅是在测试接口了。
对于一个代码来说,如果你测试他的时候感觉非mock不可,不然就没什么可测的,其实他的设计就已经出问题了,一定是各个功能模块的耦合性太高了。
mock和stub的区别是很大的,简单的说:mock是在测试一个类的内部是否正确;stub是假设这个类的接口完全正常,用它去测试其他的类。
0 请登录后投票
论坛首页 综合技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics