java 板


LINE

此文是讨论这篇提问的案例: ┌─────────────────────────────────────┐ │ 文章代码(AID): #1Hj2uDh_ (java) [ptt.cc] [问题] 请问关於Junit Test Code? │ │ 文章网址: http://webptt.com/cn.aspx?n=bbs/java/M.1370762765.A.AFF.html │ │ 这一篇文章值 229 Ptt币 │ └─────────────────────────────────────┘ 由於上一篇的 Unit Test 实在长得不好看, 加上它被锁文了就不针对他的问题去回应。 另外程式码长得不好看,看了只会想谯人。 回到那个问题的本质是或一组管理学生分数的程式, 它的测试重点在於分数经过修改後仍然是符合逻辑的。 至於符合逻辑的细节是什麽? 1. 符合 spec 2. 符合常理(虽然这点偶尔跟 1. 冲突) 我说那个 code 很丑是因为测试程试还要去搞定 user input 的东西, 要去弄 System.in System.out,有太多跟 business logic 本身无关的东西。 由於没看过完整的需求,由原先的 test case 试着还原设计。 以下是我的设想。 /* GradeSystem 长成这个样子,可以帮你找出某个 ID 的分数 */ public class GradeSystem { Map<String, Grade> grades = new HashMap<String, Grade>(); public Grade findGradeByID(String userId) { Grade grade = grades.get(userId); if (grade == null) { throw new NoSuchIDExceptions(); } return grade; } public void setGrade(String userId, Grade grade) { grades.put(userId, grade); } } /* Grade 代表分数,它同时知道它代表哪一个 ID 与名称 */ public class Grade { String id; String name; Vector<GradeItem> items = new Vector<GradeItem>(); public void setId(String id) { this.id = id; } public void setName(String name) { this.name = name; } public void newItem(String name, int score) { GradeItem item = new GradeItem(); item.setName(name); item.setScore(score); items.add(item); } public GradeItem getItem(String name) { for (GradeItem item : items) { if (item.getName() != null && item.getName().equals(name)) { return item; } } return null; } public int sum() { int sum = 0; for (GradeItem item : items) { sum += item.getScore(); } return sum; } } /* 由於要计分几次未定,在 Grade 内另外用 GradeItem 记录实际的分数 */ public class GradeItem { String name; int score; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getScore() { return score; } public void setScore(int score) { this.score = score; } } /* 一个基本的测程式,它目前只完成了初始资料设定 */ public class GradeSystemTest { private static final String USER_NAME = "凌宗廷"; private static final String USER_ID = "962001044"; Random random = new Random(); GradeSystem gradeSystem; String[] gradeItmeNames = { "Lab1", "Lab2", "Lab3", "Mid-Term", "Final exam" }; int totalScore = 0; @Before public void setUp() { gradeSystem = new GradeSystem(); prepareTestData(); } private void prepareTestData() { Grade grade = new Grade(); grade.setId(USER_ID); grade.setName(USER_NAME); for (String itemName : gradeItmeNames) { int score = anyScore(); totalScore += score; grade.newItem(itemName, score); } assertEquals(grade.sum(), totalScore); gradeSystem.setGrade(USER_ID, grade); } @Test public void testChangeScore() { /* 在这里我们要测试程式逻辑对不对 */ } protected int anyScore() { int score = random.nextInt(200); if (score >= 0 && score <= 100) { return score; } return anyScore(); } } ====================================================================== 关於测试资料,尽可能使用随机产生的。 除了一些边界条件的 CASE 都用随机的! 所以在初始分数的设定,我们用随机的分数,只要它是 0~100 就行了。 更理想是连 item names 都随机(产生出 1 笔以上的 item) private void prepareTestData() { Grade grade = new Grade(); grade.setId(USER_ID); grade.setName(USER_NAME); for (String itemName : gradeItmeNames) { int score = anyScore(); totalScore += score; grade.newItem(itemName, score); } assertEquals(grade.sum(), totalScore); gradeSystem.setGrade(USER_ID, grade); } 在准备资料的过程,我们已经有第 1 个验证: assertEquals(grade.sum(), totalScore); 测试分数有没有改好的整套逻辑是,各别分数要对,总分也要对。 因此我们验证它的时候,不是只有验证单一项目的分数 还有综合起来的结果是不是对的,更符合实际的情况应该要有权重配分。 那麽回到主要的「改变分数」的逻辑验证: @Test public void testChangeScore() { } 要改变分数我们必需要: 1. 找到改变的项目 2. 重新设定该项目的分数 改变完成後,我们需 3. 取出该项目的分数是否符合预期 @Test public void testChangeScore() { Grade grade = gradeSystem.findGradeByID(USER_ID); for (String itemName : gradeItmeNames) { GradeItem item = grade.getItem(itemName); // 1. int oldScore = item.getScore(); int newScore = anyScore(); item.setScore(newScore); // 2. assertEquals(item.getScore(), newScore); // 3. } } 单项的验证虽然做了,但它仍是不足的。 做 Unit Test 不是让你单纯测试你的 getter & setter 有没有正常运作 改变单一项变的分数会影响最终的结果,这个结果的改变需要符合期待 这才是 Unit Test 要做的工作: 「验证物件随着状态改变後,行为仍符合预期」 那我们该多期待什麽?对一个算分系统来说,至少你改单项总分要对。 (电视台那个选票系统常常教坏别人,常弄得单项加起来不是总分XD) 那麽总分该怎麽测试它呢?在你设定新的分数前,你可以用新的分数预测出总分 所以我们会这麽写: @Test public void testChangeScore() { Grade grade = gradeSystem.findGradeByID(USER_ID); for (String itemName : gradeItmeNames) { GradeItem item = grade.getItem(itemName); int oldScore = item.getScore(); int newScore = anyScore(); item.setScore(newScore); assertEquals(item.getScore(), newScore); int newTotal = totalScore + (newScore - oldScore); assertEquals(grade.sum(), newTotal); totalScore = newTotal; } } 这样子的设计与验证不是简单又易懂吗? 要如何证名简单又易懂?(思)... 大概只能说,我注解比你少,但多数人应该觉得我的 code 比你好看!? --



※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 114.43.116.109 ※ 编辑: qrtt1 来自: 114.43.116.109 (06/09 17:06)
1F:推 pugboy:非常感谢你Orz 06/09 17:22







like.gif 您可能会有兴趣的文章
icon.png[问题/行为] 猫晚上进房间会不会有憋尿问题
icon.pngRe: [闲聊] 选了错误的女孩成为魔法少女 XDDDDDDDDDD
icon.png[正妹] 瑞典 一张
icon.png[心得] EMS高领长版毛衣.墨小楼MC1002
icon.png[分享] 丹龙隔热纸GE55+33+22
icon.png[问题] 清洗洗衣机
icon.png[寻物] 窗台下的空间
icon.png[闲聊] 双极の女神1 木魔爵
icon.png[售车] 新竹 1997 march 1297cc 白色 四门
icon.png[讨论] 能从照片感受到摄影者心情吗
icon.png[狂贺] 贺贺贺贺 贺!岛村卯月!总选举NO.1
icon.png[难过] 羡慕白皮肤的女生
icon.png阅读文章
icon.png[黑特]
icon.png[问题] SBK S1安装於安全帽位置
icon.png[分享] 旧woo100绝版开箱!!
icon.pngRe: [无言] 关於小包卫生纸
icon.png[开箱] E5-2683V3 RX480Strix 快睿C1 简单测试
icon.png[心得] 苍の海贼龙 地狱 执行者16PT
icon.png[售车] 1999年Virage iO 1.8EXi
icon.png[心得] 挑战33 LV10 狮子座pt solo
icon.png[闲聊] 手把手教你不被桶之新手主购教学
icon.png[分享] Civic Type R 量产版官方照无预警流出
icon.png[售车] Golf 4 2.0 银色 自排
icon.png[出售] Graco提篮汽座(有底座)2000元诚可议
icon.png[问题] 请问补牙材质掉了还能再补吗?(台中半年内
icon.png[问题] 44th 单曲 生写竟然都给重复的啊啊!
icon.png[心得] 华南红卡/icash 核卡
icon.png[问题] 拔牙矫正这样正常吗
icon.png[赠送] 老莫高业 初业 102年版
icon.png[情报] 三大行动支付 本季掀战火
icon.png[宝宝] 博客来Amos水蜡笔5/1特价五折
icon.pngRe: [心得] 新鲜人一些面试分享
icon.png[心得] 苍の海贼龙 地狱 麒麟25PT
icon.pngRe: [闲聊] (君の名は。雷慎入) 君名二创漫画翻译
icon.pngRe: [闲聊] OGN中场影片:失踪人口局 (英文字幕)
icon.png[问题] 台湾大哥大4G讯号差
icon.png[出售] [全国]全新千寻侘草LED灯, 水草

请输入看板名称,例如:Tech_Job站内搜寻

TOP