浏览 1846 次
锁定老帖子 主题:实践中的重构16_多即是少
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2011-01-16
最后修改:2011-01-16
UT也是代码,并且其重要性和产品代码相比,只高不低。 以下是一段产品代码的框架。 public interface UserQueryManager { /** * 获得历史库和生产库的分界时间点。 * */ public Date getHisDate(); /** * 统计两个时间点之间的用户数。由调用方保证该时间范围不是跨库时间范围。 * */ public int countUsersNumber(Date start, Date end); /** * 查找两个时间点之间的用户。由调用方保证该时间范围不是跨库时间范围。 pageNum从1开始计数。 * */ public List<User> findUsers(Date start, Date end, int pageSize, int pageNum); } public class UserQueryService { /** * 时间段是否跨库。 * */ private static boolean isCross(Date start, Date end, Date hisDate) { return start.before(hisDate) && end.after(hisDate); } private UserQueryManager manager; /** * 分页查询,查找start和end之间的用户。 pageNum从1开始计数。 * */ public List<User> findUsers(Date start, Date end, int pageSize, int pageNum) { Date hisDate = manager.getHisDate(); // 是否跨库查询 boolean isCross = isCross(start, end, hisDate); if (isCross) { return findUsers(start, end, hisDate, pageSize, pageNum); } else { return manager.findUsers(start, end, pageSize, pageNum); } } /** * 跨库分页查询,查找start和end之间的用户。 pageNum从1开始计数。 * */ private List<User> findUsers(Date start, Date end, Date his, int pageSize, int pageNum) { // 历史库中的用户数。 int hisTotalSize = manager.countUsersNumber(start, his); int startNum = (pageNum - 1) * pageSize + 1; int endNum = pageNum * pageSize; // 全部在历史库中。 if (endNum <= hisTotalSize) { return manager.findUsers(start, his, pageSize, pageNum); } // 全部在生产库。 if (startNum > hisTotalSize) { int remainder = hisTotalSize % pageSize; // 页面边界整齐或者不整齐。 if (remainder == 0) { int newPageNum = pageNum - hisTotalSize / pageSize; return manager.findUsers(his, end, pageSize, newPageNum); } else { int newPageNum = pageNum - hisTotalSize / pageSize - 1; List<User> firstUserList = manager.findUsers(his, end, pageSize, newPageNum); List<User> secondUserList = manager.findUsers(his, end, pageSize, newPageNum + 1); return combinePagedUserList(firstUserList, secondUserList, pageSize - hisTotalSize % pageSize, pageSize); } } // 跨库查询 List<User> firstUserList = manager.findUsers(start, his, pageSize, pageNum); List<User> secondUserList = manager.findUsers(his, end, pageSize, 1); return combinePagedUserList(firstUserList, secondUserList, 0, pageSize); } /** * 合并分页用户列表,删除最前面的deleteSize个元素。如果结果list的大小大于maxSize,删除尾部的元素使list的 * size==maxSize. * * <pre> * deleteSize>=0 * maxSize>0 * </pre> * * */ private List<User> combinePagedUserList(List<User> list1, List<User> list2, int deleteSize, int maxSize) { List<User> userList = new ArrayList<User>(); userList.addAll(list1); userList.addAll(list2); int fromIndex = deleteSize; int toIndex = Math.min(userList.size(), deleteSize + maxSize); return userList.subList(fromIndex, toIndex); } public void setManager(UserQueryManager manager) { this.manager = manager; } } 这段代码提供了用户分页查询功能。但是用户的信息是保存在不同的数据库里面的。所以,要由应用程序来处理历史库和生产库的查询定向和分页组装功能。 稍稍分析一下,这里主要有如下情况需要处理: 1 时间范围在历史库中,只查询历史库。 2 时间范围在生产库中,只查询生产库。 3 时间范围跨库,但是结果数据只在历史库中。 4 时间范围跨库,结果数据一部分在历史库中,一部分在生产库中,需要做拼接处理。 5 时间范围跨库,但是结果数据只在生产库中,视页面边界是否和数据边界对齐的情况要分别处理。 原始的UT的思路是使用JMock,不同的场景mock出不同的manager。导致UT的可读性比较差。代码中充斥着硬编码,重复等不好的味道。 mock本来是为了简化生活的,但是这个地方的mock却是越看越别扭。 这个时候,我想起了“少即是多”这句名言,少即是多,那么多即是少。如果不使用mock,直接写一个简单的manager实现,这样一来,UT应该更简明。 class MockUserQueryManager implements UserQueryManager { private int hisSize; private int prodSize; private Date hisDate; private List<User> hisUserList; private List<User> prodUserList; private void initUserList() { hisUserList = new ArrayList<User>(); for (int i = 1; i <= hisSize; i++) { User user = new User(); user.setName("his_" + i); hisUserList.add(user); } prodUserList = new ArrayList<User>(); for (int i = 1; i <= prodSize; i++) { User user = new User(); user.setName("prod_" + i); prodUserList.add(user); } } public MockUserQueryManager(Date hisDate, int hisSize, int prodSize) { this.hisDate = hisDate; this.hisSize = hisSize; this.prodSize = prodSize; initUserList(); } @Override public Date getHisDate() { return hisDate; } @Override public int countUsersNumber(Date start, Date end) { if (!hisDate.before(end)) { return hisSize; } else { return hisSize + prodSize; } } @Override public List<User> findUsers(Date start, Date end, int pageSize, int pageNum) { if (!hisDate.before(end)) { int fromIndex = (pageNum - 1) * pageSize; int toIndex = Math.min(pageSize * pageNum, hisSize); return hisUserList.subList(fromIndex, toIndex); } else { int fromIndex = (pageNum - 1) * pageSize; int toIndex = Math.min(pageSize * pageNum, prodSize); return prodUserList.subList(fromIndex, toIndex); } } } manager的实现如上,其思路如下: 1 在内存中存储用户对象。模拟一个简单的用户数据库。 2 使用构造函数来构造该manager的数据,包括历史库时间分界点,历史库和生产库用户数多少,自动构建用户。 3 构建用户的同时,设置一个特殊值,用来验证返回结果。 4 实现了manager的接口方法。 构思和实现这个UT的架构比较费时间,但是一旦完成了,UT的编写就是简单到了极点。下面的UT写起来基本不用费什么脑力。这样,我们通过编写一个相对复杂的内存实现,简化了UT的编写。 UT如下: public class TestUserQueryService { /** * 解析"yyyyMMdd"形式的字符串为日期。 * */ private static Date parseDate(String dateStr) { Date date = null; try { DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd"); date = dateFormat.parse(dateStr); } catch (Exception e) { throw new RuntimeException(e); } return date; } private UserQueryService queryService; private MockUserQueryManager mockUserQueryManager; /** * 初始化测试环境。 * */ private void setUpEnv(String hisDate, int hisSize, int prodSize) { queryService = new UserQueryService(); mockUserQueryManager = new MockUserQueryManager(parseDate(hisDate), hisSize, prodSize); queryService.setManager(mockUserQueryManager); } /** * 验证返回的结果。 * * @param userList * 用户列表。 * @param size * 总用户个数。 * @param hisSize * 历史库返回的用户个数。 * @param hisFrom * 历史库返回用户的起始index。 * @param prodSize * 生产库返回的用户个数。 * @param prodFrom * 生产库返回的用户起始index。 * * */ public void assertUserList(List<User> userList, int size, int hisSize, int hisFrom, int prodSize, int prodFrom) { Assert.assertNotNull(userList); Assert.assertEquals(size, hisSize + prodSize); Assert.assertEquals(size, userList.size()); for (int i = 0; i < hisSize; i++) { User user = userList.get(i); Assert.assertEquals("his_" + (hisFrom + i), user.getName()); } for (int i = 0; i < prodSize; i++) { User user = userList.get(hisSize + i); Assert.assertEquals("prod_" + (prodFrom + i), user.getName()); } } /** * 用户查询(只查历史库,满页) * */ @Test public void testQuery_00() { setUpEnv("19820110", 40, 20); List<User> userList = queryService.findUsers(parseDate("19820101"), parseDate("19820107"), 10, 1); assertUserList(userList, 10, 10, 1, 0, 0); } /** * 用户查询(只查历史库,不满页) * */ @Test public void testQuery_01() { setUpEnv("19820110", 43, 20); List<User> userList = queryService.findUsers(parseDate("19820101"), parseDate("19820107"), 10, 5); assertUserList(userList, 3, 3, 41, 0, 0); } /** * 用户查询(只查生产库,满页) * */ @Test public void testQuery_10() { setUpEnv("19810804", 40, 20); List<User> userList = queryService.findUsers(parseDate("19820101"), parseDate("19820107"), 10, 2); assertUserList(userList, 10, 0, 0, 10, 11); } /** * 用户查询(只查生产库,不满页) * */ @Test public void testQuery_11() { setUpEnv("19810801", 43, 23); List<User> userList = queryService.findUsers(parseDate("19820101"), parseDate("19820107"), 10, 3); assertUserList(userList, 3, 0, 0, 3, 21); } /** * 用户查询(跨库,满页) * */ @Test public void testQuery_20() { setUpEnv("19820103", 43, 20); List<User> userList = queryService.findUsers(parseDate("19820101"), parseDate("19820107"), 10, 5); assertUserList(userList, 10, 3, 41, 7, 1); } /** * 用户查询(跨库,不满页) * */ @Test public void testQuery_21() { setUpEnv("19820103", 43, 4); List<User> userList = queryService.findUsers(parseDate("19820101"), parseDate("19820107"), 10, 5); assertUserList(userList, 7, 3, 41, 4, 1); } /** * 用户查询(只查生产库,对齐满页) * */ @Test public void testQuery_30() { setUpEnv("19820103", 40, 60); List<User> userList = queryService.findUsers(parseDate("19820101"), parseDate("19820107"), 10, 6); assertUserList(userList, 10, 0, 0, 10, 11); } /** * 用户查询(只查生产库,对齐不满页) * */ @Test public void testQuery_31() { setUpEnv("19820103", 40, 17); List<User> userList = queryService.findUsers(parseDate("19820101"), parseDate("19820107"), 10, 6); assertUserList(userList, 7, 0, 0, 7, 11); } /** * 用户查询(只查生产库,不对齐满页) * */ @Test public void testQuery_40() { setUpEnv("19820103", 43, 40); List<User> userList = queryService.findUsers(parseDate("19820101"), parseDate("19820107"), 10, 6); assertUserList(userList, 10, 0, 0, 10, 8); } /** * 用户查询(只查生产库,不对齐不满页) * */ @Test public void testQuery_41() { setUpEnv("19820103", 43, 20); List<User> userList = queryService.findUsers(parseDate("19820101"), parseDate("19820107"), 10, 7); assertUserList(userList, 3, 0, 0, 3, 18); } } 每一个UT都很简洁,唯一不足的地方在于重复。 但是由于UT的代码比较简洁,很容易看清楚重复的地方,因此,对于这些重复代码的重构也一下子简单起来。(未完待续) 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |