我是个测试专业的Java开发, 最近做毕业设计时总结的心得, 在这分享给大家.
由于对Dao层的测试时间成本相对来说比较大,而Dao层本来就没有复杂的逻辑所以在这选了Service和Controller层的单元测试.
基于Spring的单元测试库Spring-Test
这对于在用Spring的亲们应该不陌生, SpringTest可以在单元测试中初始化Spring的上下文, 这一点就已经相当方便了.
通过
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath*:applicationContext.xml")
这两个注解就可以实现, Spring的配置文件应该放在classpath中. Maven的就放在test文件夹中的resources文件夹中.
注意:
我曾经遇到过有朋友会将HttpSesson或者ServletContext注入到Service中.
@Autowired HttpSession session; @Autowired ServletContext context;
这样用起来虽然简单, 但是会给单元测试造成困难. 原因: session和servletContext都是在服务器启动时创建, 而单元测试并没有去启动服务器所以Spring是没有办法去注入这两个东西的. 而且(个人经验)这种写法并不符合常规,我们可以通过Controller层去获取然后传入到Service层中, 我暂时还没有找到在单元测试注入到Spring容器的方法. (由于上面用的注解注入, 用setter并不能解决问题, 去掉注解则会造成代码污染.) 如果一定要用到的~只要不使用注解的方法去注入,,然后加上setter都可以让spring成功初始化,在测试用到时通过setter注入就可以.
Service层.
在工作的时候,我也只是用SpringTest初始化容器,然后调用一下Service的方法这样去测试,但是这样的测试当遇到错误时,我们还需要去考虑到底是Service的错还是Dao层的问题.而且Spring去初始化用时非常长,花这么多时间去做一个单元测试并不是太合理. 这里我们可以使用EasyMock去构造一个Dao层方法的Mock对象, 录制所需要的执行的方法并在Service层中回放.下面我大概说说用法.
private BaseDAO<Admin> adminBaseDao; //需要通过setter方法注入到Service中 //在测试方法中 EasyMock.createMock(BaseDAO.class); EasyMock.expect(adminBaseDao.get("from Admin a where a.phone=? and a.password=?", new Object[]{"110","123"})).andReturn(result); EasyMock.replay(adminBaseDao);
测试Service层的时候其实已经没有必要去初始化Spring了,
至于注入到Service 方法1:可以直接new一个~然后通过setter将用到的mock出来的Dao注入进去.
方法2:使用Unitils中的Inject模块使用@InjectInto注解就可以不用添加setter方法
@TestedObject private AdminService adminService; @Mock @InjectInto(target = "adminService", property = "adminBaseDao") private BaseDAO<Admin> adminBaseDao;
这样去测试Service层的好处是, Dao层的方法都是自己模拟出来的, 如果Service操作指定的数据是正常的那么当出现问题的时候就知道应该是Dao层的问题. 而且跳过了Spring初始化, 那个速度真的快了超多.
对于Controller层
使用Struts2的小伙伴可以使用Struts2的 Struts2Test的模块
public class AdminActionTest extends StrutsSpringJUnit4TestCase<AdminAction>
注意:@Before的方法名不能用setUp. 根据JUnit3 初始化方法会使用setUp作为方法名,但是在这里Struts2Test在源码里面已经有这个setUp方法, 你在写一个会覆盖他的方法, 导致request和response无法mock出来.
贴段Struts2Test源代码:
@Before public void setUp() throws Exception { super.setUp(); this.initServletMockObjects(); this.setupBeforeInitDispatcher(); this.initDispatcherParams(); this.initDispatcher(this.dispatcherInitParams); }
在测试时按上面EasyMock的方法将Service Mock出来, 然后再通过setter或者Unitils inject 注入到Action里面, 然后需要用ActionProxy来注入..(在里面有一个executeAction的方法可以直接执行Action,如果是通过初始化Spring来测试的就可以使用,但是这样没办法注入MockService), 因为ActionProxy并不是通过request来获取参数的, 所以参数用到的对象也应该用setter或者inject 注入到Action中.
ActionProxy proxy = getActionProxy("/admin/login.action"); AdminAction action = ((AdminAction) proxy.getAction()).setAdminService(adminService); action.setAdmin(admin); action.setSession(request.getSession()); String result = proxy.execute(); Assert.assertEquals("success", result);
如果是使用SpringMVC的朋友其实道理都是一样的.都是Mock这个注入进去, Mock那个注入进去, SpringMVC的,mock request和mock response需要自己去创建, 直接上代码了;
private MockHttpServletRequest request = new MockHttpServletRequest(); private MockHttpServletResponse response = new MockHttpServletResponse(); @Before public void setup() { awardControler = new AwardControler(); awardService = EasyMock.createMock(AwardService.class); awardControler.setAwardService(awardService); } @Test public void testGetAwardDetail() throws Exception { //create mock return object Award award = new Award(); award.setId("1"); award.setName("测试1"); award.setStatus("未发放"); request.setRequestURI("/awardController/getAwardDetail.do"); request.setMethod(HttpMethod.POST.name()); request.setParameter("id", "1"); EasyMock.expect(awardService.getAwardById("1")).andReturn(award); EasyMock.expect(awardService.getAwardStatusJSON()).andReturn("ok"); EasyMock.replay(awardService); ModelAndView modelAndView = awardControler.getAwardDetail(request, response); //create expect model Map<String, Object> expected = new HashMap<String, Object>(); expected.put("id", "1"); expected.put("Id", "1"); expected.put("status", "ok"); ModelAndViewAssert.assertViewName(modelAndView, "souche/award/detail.jsp"); ModelAndViewAssert.assertModelAttributeValues(modelAndView, expected); }
PS: 单元测试的核心是隔离代码, 所以很多地方用到了Mock技术, Mock的方法有很多 JMock, EasyMock甚至自己去建一个Mock对象, 本文没有详尽去介绍每一句代码的意思, 本意只是为了分享下单元测试的方法或者一些可能用到的技术, 读者可以根据本文去搜索相关资料, 如果有小弟哪里写错的请亲们在评论中指正, 大神们你们有权去说这是垃圾文章, 不过我希望大神们能够指出小弟的不足, 让我去好好学习相关的知识提升自己, 希望大家做个负责任的读者, 如果想交个朋友的我QQ是41369927 小弟非常乐意广交朋友..讨论技术,讨论理想,讨论生活. 测试的毕业设计真不好做, 不过学到的知识始终的自己.