作者swpoker (swpoker)
看板java
标题Re: [案例] Unit Test
时间Thu Jun 13 17:05:21 2013
我跟我的厂商说
来导入TDD在这个专案吧
并且跟他们说
一边写SPEC 一边就可以写测试案例啦
他们总是跟我回应这不适合这个专案
对於她们来说测试是最後写的
并且总是可以找到理由证明测试是不切实际的
我才发现书上写的还真有道理
(我忘记是重构还是敏捷还是clean code了)
当我展示我的web service测试案例的时候
并且展示如何用TDD去修正专案MVC及DAO架构的时候
我才发现环境建立的重要性
对於测试,我认为有
1.针对需求建立的测试,主要是SPEC
2.检测系统元件架构的模组化程度,如果有某元件无法测试,
就表示架构有问题,而非测试不适合(哀~这就是厂商跟我说的不适合阿)
因此根据着眼点的不同,要建立的环境也不太一样
以我的web service为例
测试的范围是该服务的全部,并且要是黑盒子测试
因此每个测试基本上只有三行(其实不止拉)
1.建立资料
2.执行某服务
3.检查结果
public void test_helloworld(){
Data data = loadHelloworldData();
webservice.hellowWorld(data);
Assert.assertEquals("Hello World",data.getRespose());
}
接着根据你的需求分别定义
例如我的测试资料都是以资料库为导向
所以我就是
public void test_helloworld(){
String id = "helloworld";
loadSQL(id+".sql");
Data data = instanceData(id);
webservice.hellowWorld(data);
Assert.assertEquals("Hello World"
,queryforOne("select message from xxx where id=?",id));
}
通常SQL也要抽离,但这是我的习惯啦 XD
因为测试有的时候是很赤裸裸的啦
因为测试环境的不良
往往导致测试案例不好撰写
1.例如测试资料要如何建立呢?
2.而且有些元件竟然很难测试
在此我的感想就是
1.建立良好的环境供测试
2.如果你无法测试某个元件,一定是你的元件有问题,而非不适合测试
所以可以倒过来说,如果对於某物件架构感到疑惑,那麽为该架构写测试案例
如果发现没办法好好写,那就是架构有问题,
同时也可以从写测试案例中,获得架构的灵感喔
因此测试并非是在专案开发好才做,是可以先做或是一边做都非常适合喔
所以大家一起来TDD吧
※ 引述《qrtt1 (有些事,有时候。。。)》之铭言:
: 此文是讨论这篇提问的案例:
: ┌─────────────────────────────────────┐
: │ 文章代码(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: 163.29.29.131