開發中的單元測試 原


我是個測試專業的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  小弟非常樂意廣交朋友..討論技術,討論理想,討論生活. 測試的畢業設計真不好做, 不過學到的知識始終的自己.


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章