JUnit介紹

Introducing JUnit

作者: Alan Griffiths

翻譯:Areca Chen


軟體開發最有趣的的事就是你總是可以發現新的事物。而Junit測試框架在『網路時代(internet time)』並不是最新的;既使對我而言是全新的領域--部分是因為我最近加入了一個主要是使用Java的組織。

檢視我之前撰寫的測試控制工具(harnesses)並研究JUnit;我發現我的測試控制工具在結構上有些類似JUnit框架--主要的差異是我的測試控制工具類 別有重複『一成不變(boiler plate)』的程式碼區段加到測試本身。我曾經思索分解這個程式碼成為一個單元測試框架的方式--但是我的原型設計並未達到JUnit發展的水準。我覺得JUnit已達到我必須『拋棄一個(thrown one away)』(Brooks)[譯註1]的地步。

這篇文章是為開發者引介一個單元測試先導專案的概觀。包括使用單元測試的戰略理由及使用免費的JUnit框架撰寫單元測試控制工具(harness)以簡化工作的手法。本文並未涵蓋所有使用Junit的議題。有關JUnit更詳細的內容請參閱http://www.junit.org/.

單元測試的目的是甚麼?

一個單元測試從整個系統中單獨檢驗產品程式碼的『一個單元』並檢查其得到的結果是否是預期的。要測試的『一個單元』其大小是依據一組連貫的(coherent)功能 的大小及介於一個類別及一個包裹(package)之間實際上的變化(varies)。其目的是在整合程式碼到系統的其餘部分之前先測試以便找出程式碼中的臭蟲。JUnit支援 在Java程式碼中撰寫單元測試。

在整合之前於系統其他部分隔離起來抓蟲的理由是因為那是比較容易的找到臭蟲(亦即比較快且便宜)及比較容易修正問題(並顯示其解決方式是可行的)。

單元測試對於在初始整合一小部分程式碼以及整合其餘的改變之前提供了一些利益。如果有人需要變動現有的程式碼事實上單元測試仍然可以讓他對於其最後的程式碼更有信心;即他的改變不會破壞任何東西。愈好的單元測試讓人 愈有信心--理想上測試必須在加入的新功能前隨之更新(當然那是會測試失敗的除非變動完成)。

誰來撰寫單元測試及何時撰寫單元測試?

有許多學派提出許多理論也各有優點。其方式多數是界定單元測試及產出的程式碼被獨立的開發的問題而其中有許多相反的陳述。 從這些陳述中學到的很可能是誤解或模擬兩可。不管如何投入兩個開發者是最耗成本的,同時在他們的工作中有密切的關係(既使是方法介面小的改變都會導致瓦解)。

有一種方式目前相當普遍的--被倡議為屬於XP的部分;就是發展介面、測試控制工具及實作。(其順序是約略的--既使有部分的實作可能在這個測試控制工具不相干的部分之前完成)。事實上,因為XP也倡議『雙人組設計』這也投入兩個開發者(雖然他們是共同合作,但那不屬於上述投入兩個人分別實作及測試的議題)。不考慮XP其他的作法;這種單元測試的作法就很有價值。

測試控制工具中要有甚麼?

不考慮誰撰寫單元測試或何時撰寫單元測試,我們的焦點應該放在檢驗程式碼;主要是在於產生錯誤的風險。(經由檢驗一般的『setName()/getName()』方法 很可能被察覺的錯誤數量往往不會因透過為這些方法寫一個測試而得到證明)。

如果設計文件包含被測試物件的使用情節;便可成為好的測試來源。不管如何,這些情節寫得不是很明確;因為這些情節實際上是以設計觀點所寫的--因此適當的測試應該有對等的情節。

另一個測試案例好的來源是在整合後從產品程式碼當中找到的問題,維修問題的處理方式往往值得封裝成為測試案例。

JUnit的目的是甚麼?

前面的論述說明為什麼我們需要測試控制工具,但為什麼我們使用JUnit?

一段測試的程式碼無法單獨的執行,它需要是執行環境的一部份。同時,它需要自動執行的單元測試--譬如在系統中週期性的執行所有的測試控制工具以證明沒有任何東西被破壞。由於單元測試需要符合特定的準則:一個成功的測試不應該是人工檢查的,一個未通過測試的失敗應可以產出文件以供診斷。

測試的執行、結果的紀錄及錯誤的報告對於所有的測試都是共通的,而JUnit提供這些精準的功能。所有測試開發者所需撰寫的只是測試碼本身--而且(雖然有廣告的嫌疑但那是我看到的)這不是在做廣告。

範例

最簡單的JUnit測試案例看起來像這樣:


import junit.framework.TestCase;

public class Test extends TestCase {
    public Test(String name) {
        super(name);
    }

    public void testSimpleTest() {
        // Test Code goes here
    }
}

OK,我有說謊--不只需要寫測試程式碼還需要寫一個簡單的建構式(constructor)。但上面的已足夠我們編譯並執行測試。編譯及執行測試的指令如下:


javac -classpath ".;../junit/junit.jar" Test.java
java -classpath ".;../junit/junit.jar" junit.textui.TestRunner Test

JUnit TestRunner傳回的報告如下:


.
Time: 0

OK (1 tests)

測試類別可以輕易地增加功能測試來擴充,舉例如下:


public void testHelloWorld()
    {
        String hello = "Hello";
        String world = "world";

        assertEquals("Hello world", hello+world);
    }

測試程式碼使用assertEquals--是由JUnit的Assert介面所提供的數字證明(assertion)方法之一。assertEquals有各種不同的覆載(overloaded)版本這些版本有不同的參數型別,包括基本型別、容器(containers)、及子串(strings)--如果兩個參數相等那麼只是簡單的返回,否則從框架中丟出一個不合格的例外以回報錯誤。

上述測試被執行的結果(以相同的方式)顯示失敗的情況如下:


..F
Time: 0.01
There was 1 failure:
1) testHelloWorld(Test) "expected: but was:"

FAILURES!!!
Tests run: 2,  Failures: 1,  Errors: 0

使用JUnit工作

我所介紹的;在工作實務上每一個開發者的責任是確保JUnit測試類別是支援所有片段的工作。除非測試程式碼被證明而且單元測試已被完整的執行;每一片段的程式碼不能算完成。(有些片段的程式碼可以是例外,例如使用者介面元件--那是視覺化的測試。)

當然這種方式是介於完全信賴開發者以及獨立證實所有的工作已完成之間的折衷辦法(compromise)。這種方式不可能在所有的環境中或對所有的開發者都是可行的。不管如何,開發者在交付程式的壓力及缺乏組織適當的支援之下要維護一個程式碼專業級的水準,這是我所面對的狀況;在我們的業界是很普遍的(非常的普遍)。我們都知道這個結果:草率的程式碼僅僅能編譯,長時間的除蟲(或重寫程式碼)結果長久以來都已『完蛋了』。

在我過去採用JUnit的經驗;JUnit使得導入測試控制工具的程序變的更容易。當然還有其他的變數(管理的支援是一個大的變數)。但我可以確認其效力;因此從特定測試採用JUnit設計個別的『框架』是一個主要的因素。其他的因素是整合『Ant(http://jakarta.apache.org/)』--而Ant是其他文章的主題。

結論

因此,從「我希望我首先寫它」的部分,我認為的JUnit是甚麼?那是容易使用,容易導入到專案中,而且完成它的工作。

在使用單元測試的先導計畫中對於進度(任務很少逾期因為需要除蟲的『完蛋了』的程式碼已經在專案的程式碼庫(codebase)中)及士氣(發展的任務有明確劃分的邊界)提供了視覺的效果。

參考

The Mythical Man-Month Brooks

 

Copyright 2001 Alan Griffiths

譯註1:『拋棄一個』引用Brooks的說法:化學工程很早就瞭解到在實驗室的成果無法一步便可以從工廠中生產。使用一種過渡型的步驟即所謂的『先導設備(pilot plant)』是必要的.....在多數的軟體專案,第一個系統僅僅是可以使用。這個系統可能太慢,太大,不容易使用,或者三者兼具。要改善這種情況沒有其它的辦法只有重新來過,痛苦但是比較精明的方式,並且建構一個重新設計的版本;於其中解決這些問題.....把這個併入客戶的購買時間(buys time)中,但這並不只是使用者痛苦的成本,對於建構者重新設計也是非常困擾,而產品的臭名很難經由最佳的重新設計改善。因此,不管如何你應該規劃『拋棄一個』。(感謝朱子提供原文)

本文獲授權同意翻譯函:

In message <000e01c1b086$c667c440$f3bccbcb@c63918006>,
arecagiga@giga.net.tw writes
>?
>Dear Alan,
>I've read the articles " Introducing JUnit"onhe web site and like it very
>much.
>It will be great if I could translatet into traditional Chinese and publish on
>our own site http://www.dotspace.idv.tw) to help those who want to explore
>JUnit more. I will, of course, declare the source and "hyperlink"hose to
>your original documents. I'll be very grateful if you could give your
>authorization for me to do this.

Hello Areca,

I'm pleased that you consider my work worthy of the effort. I'm happy
for you to do this provided (as you have already indicated) that the
source is acknowledged.

Could you send me a link to the finished article - I'd like to review
the translation and link to it.

>Our web site contents about software engineering topics like XP, UML,
>Rup, Design Patterns, Framework, those are all in traditional Chinese.

Sadly, I am unfamiliar with Chinese.

Alan.
--
Alan Griffiths <alan@octopull.demon.co.uk>  ACCU Chairman <chair@accu.org>
http://www.octopull.demon.co.uk/            http://accu.org/
Mobile: +44 (0)798 9938 758