Re: [案例] Unit Test

看板java作者 (swpoker)時間11年前 (2013/06/13 17:05), 編輯推噓0(001)
留言1則, 1人參與, 最新討論串2/2 (看更多)
我跟我的廠商說 來導入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://www.ptt.cc/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

06/13 18:00, , 1F
不知為什想起這了個 http://bit.ly/195Xbkt
06/13 18:00, 1F
文章代碼(AID): #1HkOjK0L (java)
討論串 (同標題文章)
本文引述了以下文章的的內容:
案例
1
1
完整討論串 (本文為第 2 之 2 篇):
案例
1
1
文章代碼(AID): #1HkOjK0L (java)