<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    zhyiwww
    用平實的筆,記錄編程路上的點點滴滴………
    posts - 536,comments - 394,trackbacks - 0
    JUnit best practice
    2006-5-31

    JUnit best practices 
    JUnit 最佳實踐

    Techniques for building resilient, relocatable, multithreaded JUnit tests
    一項靈活的、可重定位的多線程JUnit測試技術 
    作者 Andy Schneider

    譯者 雷云飛 javawebstart Barret gstian [AKA]
    校對 gstian [AKA]


    Summary
    摘要 
    Extreme Programming's rise in popularity among the Java community has prompted more development teams to use JUnit: a simple test framework for building and executing unit tests. Like any toolkit, JUnit can be used effectively and ineffectively. In this article, Andy Schneider discusses good and bad ways to use JUnit and provides practical recommendations for its use by development teams. In addition, he explains simple mechanisms to support: 
    Java社區里面流行的編程熱的不斷升溫使越來越多的開發團隊使用 JUnit進行測試。JUnit 是一種構造和進行單元測試的簡便的測試框架。就象所有的工具包一樣,JUnit 可以被高效的使用,也可以被低效的使用。在這篇文章種,Andy Schneider討論了JUnit 的高效和低效的使用方法,并且為開發團隊提供了實用的JUnit使用建議。另外,他提供了幾種簡單的機制來解釋兩種方法的差別: 


    Automatic construction of composite tests 
    組合測試的自動構件 
    Multithreaded test cases 
    多線程測試用例 
    This article assumes some familiarity with JUnit. (4,000 words) 
    閱讀本篇文章,需要您對JUnit略知一二。 



    JUnit is a typical toolkit: if used with care and with recognition of its idiosyncrasies, JUnit will help to develop good, robust tests. Used blindly, it may produce a pile of spaghetti instead of a test suite. This article presents some guidelines that can help you avoid the pasta nightmare. The guidelines sometimes contradict themselves and each other -- this is deliberate. In my experience, there are rarely hard and fast rules in development, and guidelines that claim to be are misleading. 
    JUnit是一個有特色的工具包:熟知它的特性的情況下并細心的使用,它在你開發優良的健壯的測試上市有幫助的。如果被盲目的使用,它可能就像一堆意大利面條,而不是測試集。本文給出了一些可以幫助你避免這些生面團惡夢的指導方針。這些指導方針有時看起來會相互矛盾————這是故意的。以我的經驗,在開發中很少有硬性而方便的規則。任何聲稱是這種規則的指導方針都是誤導。 

    We'll also closely examine two useful additions to the developer's toolkit: 
    我們同時還將深入檢查開發者的工具包里的兩個有用的附加物: 


    A mechanism for automatically creating test suites from classfiles in part of a filesystem 
    一種可以從部分文件系統里面自動創建測試集的機制 
    A new TestCase that better supports tests in multiple threads 
    一種更好支持多線程的新測試用例。 
    When faced with unit testing, many teams end up producing some kind of testing framework. JUnit, available as open source, eliminates this onerous task by providing a ready-made framework for unit testing. JUnit, best used as an integral part of a development testing regime, provides a mechanism that developers can use to consistently write and execute tests. So, what are the JUnit best practices? 
    當面對單元測試時,許多團隊都會自己去完成某種測試框架。JUnit做為一種開放軟件,通過為單元測試提供一種現成的測試框架,來消除這種繁重的任務。JUnit作為一個開發測試體制整體中的一部分給開發者提供了一種可以一致地編寫和執行測試的機制。既然如此,那么,什么是JUnit的最佳實踐? 

    Do not use the test-case constructor to set up a test case 
    不要使用測試用例構造器來創建一個測試用例

    Setting up a test case in the constructor is not a good idea. Consider: 
    使用構造器來建立一個測試用例并不是個好主意,例如: 

    public class SomeTest extends TestCase 
    public SomeTest (String testName) { 
    super (testName); 
    // Perform test set-up 




    Imagine that while performing the setup, the setup code throws an IllegalStateException. In response, JUnit would throw an AssertionFailedError, indicating that the test case could not be instantiated. Here is an example of the resulting stack trace: 
    想象一下當執行安裝時,代碼拋出一個IllegalStateException異常。做為回應,JUnit也會拋出一個AssertionFailedError異常來指示測試用例無法實例化。下面是一個堆棧跟蹤結果示例: 

    junit.framework.AssertionFailedError: Cannot instantiate test case: test1 at 
    junit.framework.Assert.fail(Assert.java:143) at 
    junit.framework.TestSuite$1.runTest(TestSuite.java:178) at 
    junit.framework.TestCase.runBare(TestCase.java:129) at 
    junit.framework.TestResult$1.protect(TestResult.java:100) at 
    junit.framework.TestResult.runProtected(TestResult.java:117) at 
    junit.framework.TestResult.run(TestResult.java:103) at 
    junit.framework.TestCase.run(TestCase.java:120) at 
    junit.framework.TestSuite.run(TestSuite.java, Compiled Code) at 
    junit.ui.TestRunner$12.run(TestRunner.java:429) 


    This stack trace proves rather uninformative; it only indicates that the test case could not be instantiated. It doesn't detail the original error's location or place of origin. This lack of information makes it hard to deduce the exception's underlying cause. 
    這個堆棧跟蹤沒有提供多少有價值的信息。它只是表明測試用例不能被實例化。它并沒有初始化時產生錯誤的錯誤位置和錯誤來源的詳細信息。信息的缺乏使得推斷該異常出現的原因變得困難。 

    Instead of setting up the data in the constructor, perform test setup by overriding setUp(). Any exception thrown within setUp() is reported correctly. Compare this stack trace with the previous example: 
    放棄在構造器中創建數據,通過重載setUp()來執行測試創建,。任何在setUp()中產生的異常都會被準確的報告。與前一個例子對照,比較下面的堆棧跟蹤: 

    java.lang.IllegalStateException: Oops at bp.DTC.setUp(DTC.java:34) at 
    junit.framework.TestCase.runBare(TestCase.java:127) at 
    junit.framework.TestResult$1.protect(TestResult.java:100) at 
    junit.framework.TestResult.runProtected(TestResult.java:117) at 
    junit.framework.TestResult.run(TestResult.java:103) 
    ... 


    This stack trace is much more informative; it shows which exception was thrown (IllegalStateException) and from where. That makes it far easier to explain the test setup's failure. 
    這個堆棧跟蹤含有更多的信息量。它表明了異常類型(IllegalStateException), 以及產生位置。這使得可以更容易解釋為何測試建立失敗。 

    Don't assume the order in which tests within a test case run 
    不要推測一個測試用例運行中各測試的執行順序

    You should not assume that tests will be called in any particular order. Consider the following code segment: 
    你不應該認為各測試用例會按照任何特定順序被調用。考慮下面的代碼片斷: 

    public class SomeTestCase extends TestCase { 
    public SomeTestCase (String testName) { 
    super (testName); 

    public void testDoThisFirst () { 
    ... 

    public void testDoThisSecond () { 




    In this example, it is not certain that JUnit will run these tests in any specific order when using reflection. Running the tests on different platforms and Java VMs may therefore yield different results, unless your tests are designed to run in any order. Avoiding temporal coupling will make the test case more robust, since changes in the order will not affect other tests. If the tests are coupled, the errors that result from a minor update may prove difficult to find. 
    在這個例子中,當使用映射時,JUnit將按照何種順序執行這些測試并不能確定。在不同的平臺及Java VM上,可能產生不同的結果,除非你的測試被事先設計為按某種順序執行。由于執行順序的改變不會影響其它測試,避免這種短暫的耦合使得你的測試用例更加健壯。如果測試耦合在一起,由于一個小變動引起的錯誤也許會難于發現。 

    In situations where ordering tests makes sense -- when it is more efficient for tests to operate on some shared data that establish a fresh state as each test runs -- use a static suite() method like this one to ensure the ordering: 
    在某些情況下,測試的順序還是有意義的————例如,測試們可以使用一些共享數據時來提高效率。這些共享數據對于每個測試運行時都會建立一個新的狀態。————可以使用一個靜態的 suite() 方法來保證執行順序,如下: 

    public static Test suite() { 
    suite.addTest(new SomeTestCase ("testDoThisFirst";)); 
    suite.addTest(new SomeTestCase ("testDoThisSecond";)); 
    return suite; 



    There is no guarantee in the JUnit API documentation as to the order your tests will be called in, because JUnit employs a Vector to store tests. However, you can expect the above tests to be executed in the order they were added to the test suite. 
    在JUnit API 文檔中并沒有保證你的測試被調用的順序,因為JUnit使用V一個區段來存放測試。 然而,你可以保證上面的測試按照它們被加入測試集的順序被執行。 

    Avoid writing test cases with side effects 
    避免寫帶有副作用的測試用例 
    Test cases that have side effects exhibit two problems: 
    帶有副作用的測試用例會出現下面兩個問題: 


    They can affect data that other test cases rely upon 
    它們會影響其他測試用例所依賴的數據 
    You cannot repeat tests without manual intervention 
    你不能在沒有手工干預的情況下重復測試 
    In the first situation, the individual test case may operate correctly. However, if incorporated into a TestSuite that runs every test case on the system, it may cause other test cases to fail. That failure mode can be difficult to diagnose, and the error may be located far from the test failure. 
    在第一種情況下,獨立的測試用例也許可以正確的執行,然而,當它們被置入一個執行 該系統中所有測試的測試集時,可能導致其他測試用例失敗。但這種失敗的做法很難 診斷出來,錯誤也許離失敗的地方很遠。 

    In the second situation, a test case may have updated some system state so that it cannot run again without manual intervention, which may consist of deleting test data from the database (for example). Think carefully before introducing manual intervention. First, the manual intervention will need to be documented. Second, the tests could no longer be run in an unattended mode, removing your ability to run tests overnight or as part of some automated periodic test run. 
    在第二種情況下,一個測試用例可能運行后更改了系統狀態,以至于它不能在沒有手工干預 的情況下被再次執行。例如,這有可能是從數據庫中刪除了測試數據造成的。在手工 干預之前,仔細的考慮下面兩點:首先,手工干預應該被記錄在文檔當中,其次,這種測試不 能在無人監控的情況下被執行,應該去掉它們通宵執行測試或者作為自動運行的周期性測試的一部分的能力. 

    Call a superclass's setUp() and tearDown() methods when subclassing
    子類化的時候,調用父類的 setUp() 方法和 tearDown() 方法
    When you consider: 
    考慮如下情況: 

    public class SomeTestCase extends AnotherTestCase { 
    // A connection to a database 
    private Database theDatabase; 
    public SomeTestCase (String testName) { 
    super (testName); 

    public void testFeatureX () { 
    ... 

    public void setUp () { 
    // Clear out the database 
    theDatabase.clear (); 




    Can you spot the deliberate mistake? setUp() should call super.setUp() to ensure that the environment defined in AnotherTestCase initializes. Of course, there are exceptions: if you design the base class to work with arbitrary test data, there won't be a problem. 
    你能發現隱藏其中的那個需要深思的錯誤嗎?setUp()方法應該調用父類的setUp()方法以保證能夠初始化在父類AnotherTestCase 中定義的測試環境。當然,這也并不是絕對的--如果父類設計成可以通用的基類的話,那么,以上就不是一個問題。 


    Do not load data from hard-coded locations on a filesystem 
    不要從文件系統里那些代碼已固定的位置加載數據 
    Tests often need to load data from some location in the filesystem. Consider the following: 
    測試經常要從文件系統中讀入數據,如下: 

    public void setUp () { 
    FileInputStream inp ("C:\\TestData\\dataSet1.dat"); 
    ... 



    The code above relies on the data set being in the C:\TestData path. That assumption is incorrect in two situations: 
    上面這段代碼依賴于C:\TestDate中的數據。在下面2種情況下,上面的假設會出現如下問題: 


    A tester does not have room to store the test data on C: and stores it on another disk 
    可能在C盤沒有足夠的空間存儲測試數據,而把它存在其它的磁盤上 
    The tests run on another platform, such as Unix
    測試案例可能運行在另外的平臺上,比如Unix 
    One solution might be: 
    以下是一種解決方案: 

    public void setUp () { 
    FileInputStream inp ("dataSet1.dat"); 
    ... 



    However, that solution depends on the test running from the same directory as the test data. If several different test cases assume this, it is difficult to integrate them into one test suite without continually changing the current directory. 
    但是,上面的解決方案是把測試數據放在運行測試案例的目錄中,如果把這樣的幾個測試案例集成起來作為測試集來運行的話,只有測試集在運行過程中不斷的改變當前目錄才行。 

    To solve the problem, access the dataset using either Class.getResource() or Class.getResourceAsStream(). Using them, however, means that resources load from a location relative to the class's origin. 
    要解決這個問題,可以使用Class.getResource()或者Class.getResourceAsStream()這種訪問資源的形式來訪問數據,它們都是從類的相對路徑來訪問資源的。 

    Test data should, if possible, be stored with the source code in a configuration management (CM) system. However, if you're using the aforementioned resource mechanism, you'll need to write a script that moves all the test data from the CM system into the classpath of the system under test. A less ungainly approach is to store the test data in the source tree along with the source files. With this approach, you need a location-independent mechanism to locate the test data within the source tree. One such mechanism is a class. If a class can be mapped to a specific source directory, you could write code like this: 
    如果可能的話,測試數據應該和源程序一起存放到配置管理系統中。如果使用前面提到的訪問資源的形式,就要自己寫腳本來把所有的測試數據從配置管理系統中取出來加入要測試案例的classpath中。還有一種方法是把測試數據和源程序存放在一起,使用和位置無關的方法來查找測試數據。以類為例,如果類能夠被映射到一個特定的目錄,相應的代碼可以如下: 

    InputStream inp = SourceResourceLoader.getResourceAsStream (this.getClass (), "dataSet1.dat"); 


    Now you must only determine how to map from a class to the directory that contains the relevant source file. You can identify the root of the source tree (assuming it has a single root) by a system property. The class's package name can then identify the directory where the source file lies. The resource loads from that directory. For Unix and NT, the mapping is straightforward: replace every instance of '.' with File.separatorChar. 
    現在要考慮的是怎么把一個類映射到包含相應源文件的目錄。可以通過系統屬性來設置源文件根目錄。類的包名可以標志源文件的存放位置。在Unix和NT上,這種映射是直接的:把'.'替換成為File.separatorChar就可以了。 

    Keep tests in the same location as the source code 
    把測試案例和源文件放在一起
    If the test source is kept in the same location as the tested classes, both test and class will compile during a build. This forces you to keep the tests and classes synchronized during development. Indeed, unit tests not considered part of the normal build quickly become dated and useless. 
    如果測試案例和要測試的代碼放在一起,那么可以同時對這兩者編譯。這樣可以保證在開發過程中測試和代碼保持同步。實際上,不在正常的版本中的單元測試馬上會變的過時、無用。 

    Name tests properly
    正確命名測試案例 
    Name the test case TestClassUnderTest. For example, the test case for the class MessageLog should be TestMessageLog. That makes it simple to work out what class a test case tests. Test methods' names within the test case should describe what they test: 
    給測試案例起名TestClassUnderTest。例如,類MessageLog的測試案例的名字應該是TestMessageLog。這樣可以很容易的看出是對哪個類進行測試。同時,測試案例的方法應該清楚的表明要測什么: 


    testLoggingEmptyMessage() 
    testLoggingNullMessage() 
    testLoggingWarningMessage() 
    testLoggingErrorMessage() 
    Proper naming helps code readers understand each test's purpose. 
    正確的命名可以幫助別人理解每個測試的目的。 

    Ensure that tests are time-independent
    保證測試是和時間無關的 
    Where possible, avoid using data that may expire; such data should be either manually or programmatically refreshed. It is often simpler to instrument the class under test, with a mechanism for changing its notion of today. The test can then operate in a time-independent manner without having to refresh the data. 
    只要可能,避免使用可能過期的數據;這樣的數據要么手工,要么由程序來刷新。在測試下建立一個類經常需要更簡化些,要用一種可以與現在的思想保持同步的機制.這樣,測試案例就可以和時間無關,不需要刷新數據。 

    Consider locale when writing tests
    寫測試時考慮地址的影響 
    Consider a test that uses dates. One approach to creating dates would be: 
    考慮使用日期的一個測試案例。一種創建日期的方法: 

    Date date = DateFormat.getInstance ().parse ("dd/mm/yyyy"); 


    Unfortunately, that code doesn't work on a machine with a different locale. Therefore, it would be far better to write: 
    不幸的是,這段代碼在不同的機器上不能正常的運行。因此,可以換用下面較好的方式: 

    Calendar cal = Calendar.getInstance (); 
    Cal.set (yyyy, mm-1, dd); 
    Date date = Calendar.getTime (); 


    The second approach is far more resilient to locale changes. 
    第二中方法可以更靈活的適應地址的改變。 

    Utilize JUnit's assert/fail methods and exception handling for clean test code
    利用JUnit's的assert/fail方法和異常機制創建干凈的代碼 
    Many JUnit novices make the mistake of generating elaborate try and catch blocks to catch unexpected exceptions and flag a test failure. Here is a trivial example of this: 
    許多初學者可能會精心設計一些異常捕捉來捕捉異常,并標志測試出現錯誤。如下: 

    public void exampleTest () { 
    try { 
    // do some test 
    } catch (SomeApplicationException e) { 
    fail ("Caught SomeApplicationException exception"); 




    JUnit automatically catches exceptions. It considers uncaught exceptions to be errors, which means the above example has redundant code in it. 
    JUnit可以自動的捕捉異常,把沒有截獲的異常看作錯誤,所以,上面的代碼有冗余代碼。 

    Here's a far simpler way to achieve the same result: 
    以下以一種更簡潔的方式實現上面的例子: 

    public void exampleTest () throws SomeApplicationException { 
    // do some test 



    In this example, the redundant code has been removed, making the test easier to read and maintain (since there is less code). 
    在此,除去了冗余的代碼,使得測試易讀易維護(因為代碼很少)。 

    Use the wide variety of assert methods to express your intention in a simpler fashion. Instead of writing: 
    使用廣泛的多樣性的有效方法來表達你的意圖。不應該: 

    assert (creds == 3); 


    Write: 
    而是: 

    assertEquals ("The number of credentials should be 3", 3, creds); 


    The above example is much more useful to a code reader. And if the assertion fails, it provides the tester with more information. JUnit also supports floating point comparisons: 
    上面的代碼讓人很容易的讀懂,并且即使上面的維護失敗,可以提供給測試者更多的信息。JUnit同樣支持浮點數的比較: 

    assertEquals ("some message", result, expected, delta); 


    When you compare floating point numbers, this useful function saves you from repeatedly writing code to compute the difference between the result and the expected value. 
    當比較浮點類型的數據時,可以不必再寫同樣功能的代碼。 

    Use assertSame() to test for two references that point to the same object. Use assertEquals() to test for two objects that are equal. 
    要測試兩個引用是否指向同一個對象,使用assertSame()方法;要測試兩個對象是否相等,使用assertEquals()方法 

    Document tests in javadoc
    javadoc下的文檔測試 
    Test plans documented in a word processor tend to be error-prone and tedious to create. Also, word-processor-based documentation must be kept synchronized with the unit tests, adding another layer of complexity to the process. If possible, a better solution would be to include the test plans in the tests' javadoc, ensuring that all test plan data reside in one place. 
    在一個字處理器里創建需要歸檔的測試計劃易于出現錯誤且單調乏味。另外,基于字處理器的文件必須與單元測試保持同步,這給處理過程增加了額外一層的復雜性。如果可能,更好的解決方法是將測試計劃包括在測試的javadoc,確保所有的測試計劃數據保存在一個地方。 

    Avoid visual inspection
    避免目視檢查 
    Testing servlets, user interfaces, and other systems that produce complex output is often left to visual inspection. Visual inspection -- a human inspecting output data for errors -- requires patience, the ability to process large quantities of information, and great attention to detail: attributes not often found in the average human being. Below are some basic techniques that will help reduce the visual inspection component of your test cycle. 
    測試servlets,用戶界面,和其他產生復雜輸出的系統通常采用目視檢查。目視檢查——一個人為了發現錯誤檢查輸出的數據——需要耐心,處理大量信息的能力,以及對細節的洞察力:這些通常不會是一般人身上所具備的。以下是一些基本的技術可以用來幫助減少你的測試周期中目視檢查。 

    Swing
    Swing 
    When testing a Swing-based UI, you can write tests to ensure that: 
    當測試一個基于Swing的用戶界面時,你可以寫一些測試以保證: 


    All the components reside in the correct panels 
    所有的構件都在適當的面板里 
    You've configured the layout managers correctly 
    確保正確配置了版式管理器 
    Text widgets have the correct fonts 
    Text widgets(文字集)里有正確的字體 
    A more thorough treatment of this can be found in the worked example of testing a GUI, referenced in the Resources section.
    這方面更詳細的處理方式可以在一個測試某GUI的成功例子中找到,參考資料章節。 

    XML
    XML 
    When testing classes that process XML, it pays to write a routine that compares two XML DOMs for equality. You can then programmatically define the correct DOM in advance and compare it with the actual output from your processing methods. 
    當測試處理XML的類時,寫一個程序比較兩個XML DOM是否相等。這樣你可以預先精確地定義正確的DOM并且與使用你的處理方法得出的實際結果相比較。 

    Servlets
    Servlets 
    With servlets, a couple of approaches can work. You can write a dummy servlet framework and preconfigure it during a test. The framework must contain derivations of classes found in the normal servlet environment. These derivations should allow you to preconfigure their responses to method calls from the servlet. 
    在servlets方面,有一大堆的方法管用。你可以在測試中寫一個啞servlet框架并且預先配置它。框架必須包括在正常servlet環境中的類的出處。這些出處允許你預先配置它們對來自servlet的方法調用的反應。 

    For example: 
    例如: 


    HttpServletRequest can be subclassed to allow the test class to specify the header, method, path info, and other data 
    HttpServletRequest可以定義成子集以允許測試類指定其消息頭,方法,路徑信息,和其他數據 
    HttpServletResponse can be subclassed to return an output stream that stores the servlets' responses in a string for later checking 
    HttpServletResponse可以定義成子集返回一個將servlet響應保存在一個字符串的輸出流以備稍后的檢查。 
    A simpler solution is to use HttpUnit to test your servlets. HttpUnit provides a DOM view of a request's results, which makes it relatively simple to compare actual data with expected results. 
    一個比較簡單的解決方法是使用HttpUnit來測試你的servlet。 HttpUnit 提供了一個關于請求結果的DOM的視點,這使比較實際數據和期望得到的結果相對簡單。 

    You can avoid visual inspection in many ways. However, sometimes it is more cost-effective to use visual inspection or a more specialized testing tool. For example, testing a UI's dynamic behavior within JUnit is complicated, but possible. It may be a better idea to purchase one of the many UI record/playback testing tools available, or to perform some visual inspection as part of testing. However, that doesn't mean the general rule -- don't visually inspect -- should be ignored. 
    你可以用很多方法避免目視檢查。但是,有時使用目視檢查或一種更專用的測試工具會更加節省成本。例如,測試在JUnit的一個用戶界面的動態行為是復雜的,但卻有可能。購買可利用的眾多用戶界面記錄/回放的測試工具中的一種,或者進行一些目視檢查作為測試的一部分將是一個更好的主意。但是,那并不意味著一般的規則——不要進行目視檢查——應該被忽略。 


    Keep tests small and fast
    保持測試小而快速 
    Executing every test for the entire system shouldn't take hours. Indeed, developers will more consistently run tests that execute quickly. Without regularly running the full set of tests, it will be difficult to validate the entire system when changes are made. Errors will start to creep back in, and the benefits of unit testing will be lost. This means stress tests and load tests for single classes or small frameworks of classes shouldn't be run as part of the unit test suite; they should be executed separately. 
    執行每一項全面的系統測試不該花幾個小時。是的,開發者應該更加一貫的進行那些執行快速的測試。沒有有規律地進行全套測試,當作出改變時,將很難證實整個系統的有效性。錯誤將會躡手躡腳進來,單元測試的好處也會喪失。這意味著針對單一類和小框架類的重點測試和負載測試不該作為單元測試集合的一部分;它們應該分開進行。 

    Use the reflection-driven JUnit API
    使用映射驅動的JUnit API 
    Allowing TestSuite to populate itself with test cases using reflection reduces maintenance time. Reflection ensures that you don't need to update the suite() implementation whenever a new test is added. 
    允許TestSuite使用映射在測試環境下繁殖自己可以減少維護時間。當新的測試被加進來時,映射確保你不需要更新suite()。 

    Build a test case for the entire system
    為整個系統建立一個測試環境 
    It is important to build a test case for the entire system. If one test case exercises the whole system, then developers can test the impact their changes will have on every class in the system. This increases the chance of errors resulting from unanticipated side effects being caught earlier. Without a universal test case, developers tend to test only the class they have modified. Also, running all the tests for the system becomes a painstaking manual process.
    為整個系統建立一個測試環境是重要的。如果一個測試環境對于整個系統行之有效,那么開發者可以測試他們的改正會對系統中的每個類有什么影響。這增加了由于先前發現的不曾預料到的一面的影響而引起的犯錯誤的幾率。沒有一個通用的測試環境的話,開發者將只能測試他們修改過的類。此外,運行系統的每一項測試將成為一個艱苦的手工過程。 

    If we built a test case for the entire system, it would consist of all the other test cases, already defined. The test case would define the suite() method, which would add all test cases defined in the system to a TestSuite. This test suite would then be returned from the suite() method. If you had many test cases, building such a test suite would be time-consuming. In addition, you would have to update the universal test case when new test cases were added or existing test cases were renamed or deleted. Instead of manually building and maintaining the test suite, build a test case that automatically builds a TestSuite from all of your system's test cases. Here is an outline of the requirements for such a test case:
    如果我們為整個系統建立一個測試環境,這需包括所有已經定義的其它測試環境。測試環境需定義suite() 方法,這會使所有系統定義的測試環境加到TestSuite。 這個測試組合會由suite()方法返回。如果你有許多測試環境,建立這樣一個測試組合將耗費大量時間。 另外,當新的測試環境被加入或者現有的測試環境被改名或刪除時,你必須更新全局測試環境。取代手動建立和維護測試組合, 建立一個能自動建立和維護來自你系統的全部測試環境的測試組合。這里有一個對于創建一個測試環境需求的大概描述: 


    It should not be self-loading; that would cause recursion. As such, we need to mark test cases as not loadable.
    不應該自我調用;那會引起遞歸。同樣的,我們需要標注測試環境不可載入。 
    It should not load classes derived from TestCases that are meant to be subclasses, and not directly executed.
    不該載入那些衍生自TestCase的類,這意味著定義為子集,且不直接執行。 
    It should distinguish between unit tests and other tests, like load or stress tests. That will let different tests run at different times.
    單元測試和其他測試像負載或重點測試一樣應該有所區別。不同的測試運行不同的次數。 
    It should recurse down a directory structure, looking for test cases to add to the test suite.
    在一個目錄結構下遞歸,以尋找加入測試組合的測試環境。 
    We can use the Java type system to determine what sort of test a test case represents. We can have test cases extend classes like UnitTest, StressTest, LoadTest, and so on. However, this would make test case classes difficult to reuse between test types, because the test type decision is made near the root of the inheritance hierarchy; it should be made at each leaf instead. As an alternative, we can distinguish tests using a field: public static final String TEST_ALL_TEST_TYPE. Test cases will be loaded if they have this field declared with a value matching a string that the automatic test case has been configured with. To build this, we'll implement three classes:
    我們可以使用一個Java類型的系統來判斷一個測試環境代表什么樣的測試。我們可以有測試環境 擴展類如UnitTest,StressTest,LoadTest等等。 但是,這會使測試環境類在測試類型中難以再利用,因為測試類型的決定是在靠近繼承層次的根部作出的;它本該在每個葉下作出。 作為一個可選方案,我們可以使用一個域將測試區分開來:public static final String TEST_ALL_TEST_TYPE。如果這個域聲明的值符合測試環境所自動配置的字符串,那么測試環境將被讀入。建立這個,我們需要實現三個類。 


    ClassFinder recursively searches a directory tree for classfiles. Each classfile is loaded and the class's full class name is extracted. That class name is added to a list for later loading. 
    ClassFinder 在一個目錄樹遞歸搜索類文件。每個類文件被讀入,類的全稱被展開。這個類名稱被加到一個列表里以備稍后讀入。 
    TestCaseLoader loads each class in the list found by ClassFinder and determines if it is a test case. If it is, it is added to a list. 
    TestCaseLoader 讀入由ClassFinder找到的列表中的每個類并判斷它是否為一個測試環境。如果是,被加入一個列表。 
    TestAll is a subclass of TestCase with an implementation of suite() that will load in a set of test cases by TestCaseLoader. 
    TestAll 是TestCase的子集,具有一個suite()的實現,可以讀入一批由TestCaseLoader生成的的測試用例。 
    Let's look at each class in turn.
    讓我們按順序看一個每個類。 

    ClassFinder
    ClassFinder 
    ClassFinder locates the classes within the system to be tested. It is constructed with the directory that holds the system's classes. ClassFinder then finds all the classes in the directory tree and stores them for later use. The first part of ClassFinder's implementation is below:
    ClassFinder定位要測試的系統里的每個類。它使用一個保存系統所有類的目錄結構。ClassFinder然后在目錄樹里找到所有類,并保存它們以備稍后使用。第一部分的ClassFinder實現如下: 

    public class ClassFinder { 
    // The cumulative list of classes found. 
    final private Vector classNameList = new Vector (); 
    /** 
    * Find all classes stored in classfiles in classPathRoot 
    * Inner classes are not supported. 
    */ 
    public ClassFinder(final File classPathRoot) throws IOException { 
    findAndStoreTestClasses (classPathRoot); 


    /** 
    * Recursive method that adds all class names related to classfiles it finds in 
    * the currentDirectory (and below). 
    */ 
    private void findAndStoreTestClasses (final File currentDirectory) throws IOException { 
    String files[] = currentDirectory.list(); 
    for(int i = 0;i < files.length;i++) { 
    File file = new File(currentDirectory, files[i]); 
    String fileBase = file.getName (); 
    int idx = fileBase.indexOf(".class"); 
    final int CLASS_EXTENSION_LENGTH = 6; 
    if(idx != -1 && (fileBase.length() - idx) == CLASS_EXTENSION_LENGTH) { 


    In the code above, we iterate over all the files in a directory. If a filename has a ".class" extension, we determine the fully qualified class name of the class stored in the classfile, as seen here: 
    在上面的代碼中,我們重申了一個目錄里的所有文件。如果一個文件有.class的擴展名,我們就測定存在類文件里的類的有效類名稱,就如你所見: 

    JcfClassInputStream inputStream = new JcfClassInputStream(new FileInputStream (file)); 
    JcfClassFile classFile = new JcfClassFile (inputStream); 
    System.out.println ("Processing: " + classFile.getFullName ().replace ('/','.')); 
    classNameList.add (classFile.getFullName ().replace ('/','.')); 


    This code uses the JCF package to load the classfile and determine the name of the class stored within it. The JCF package is a set of utility classes for loading and examining classfiles. (See Resources for more information.) The JCF package allows us to find each class's full class name. We could infer the class name from the directory name, but that doesn't work well for build systems that don't store classes according to this structure. Nor does it work for inner classes. 
    這些代碼使用JCF包來讀入類文件以及測定保存在里面的類的名稱。JCF包是一套讀入和檢測類文件的工具類。(參閱資源以獲得進一步的信息。)JCF包允許我們尋找每個類的全稱。我們能從目錄名推斷出類的名稱,但對于那些不是以這種結構保存類的系統就行不通了。而且深層的類也不能用這種方法。 

    Lastly, we check to see if the file is actually a directory. (See the code snippet below.) If it is, we recurse into it. This allows us to discover all the classes in a directory tree:
    最后,我們查看一下文件是否確實是一個目錄。(請看下面的代碼片斷。)如果是,我們再遞歸進入。這允許我們發現所有在一個目錄樹里的類: 

    } else if(file.isDirectory()) { 
    findAndStoreTestClasses (file); 




    /** 
    * Return an iterator over the collection of classnames (Strings) 
    */ 
    public Iterator getClasses () { 
    return classNameList.iterator (); 




    TestCaseLoader
    TestCaseLoader 
    TestCaseLoader finds the test cases among the class names from ClassFinder. This code snippet shows the top-level method for adding a class that represents a TestCase to the list of test cases: 
    TestCaseLoader在ClassFinder中得到的類名字尋找測試環境。這個代碼片斷展示了增加一個對于所有測試環境列表體現TestCase的類的頂級方法: 

    public class TestCaseLoader { 
    final private Vector classList = new Vector (); 
    final private String requiredType; 

    /** 
    * Adds testCaseClass to the list of classdes 
    * if the class is a test case we wish to load. Calls 
    * shouldLoadTestCase () to determine that. 
    */ 
    private void addClassIfTestCase (final Class testCaseClass) { 
    if (shouldAddTestCase (testCaseClass)) { 
    classList.add (testCaseClass); 



    /** 
    * Determine if we should load this test case. Calls isATestCaseOfTheCorrectType 
    * to determine if the test case should be 
    * added to the class list. 
    */ 
    private boolean shouldAddTestCase (final Class testCaseClass) { 
    return isATestCaseOfTheCorrectType (testCaseClass); 



    You'll find the meat of the class in the isATestCaseOfTheCorrectType() method, listed below. For each class being considered, it:
    你會在下面找到在isATestCaseOfTheCorrectType()方法中isATestCaseOfTheCorrectType()中類的內容。對于每個在考慮范圍內的類:它將 


    Determines whether it is derived from TestCase. If not, it is not a test case.
    判斷是否從TestCase衍生而來。如果不是,它就不是測試用例。 
    Determines whether the field public final static TEST_ALL_TEST_TYPE has a value matching that specified in the member field requiredType.
    判斷public final static TEST_ALL_TEST_TYPE的域是否有一個符合在requiredType成員域中指定的值。 
    Here's the code:
    以下是代碼: 

    private boolean isATestCaseOfTheCorrectType (final Class testCaseClass) { 
    boolean isOfTheCorrectType = false; 
    if (TestCase.class.isAssignableFrom(testCaseClass)) { 
    try { 
    Field testAllIgnoreThisField = testCaseClass.getDeclaredField("TEST_ALL_TEST_TYPE"); 
    final int EXPECTED_MODIFIERS = Modifier.STATIC | Modifier.PUBLIC | Modifier.FINAL; 
    if (((testAllIgnoreThisField.getModifiers() & EXPECTED_MODIFIERS) != EXPECTED_MODIFIERS) || 
    (testAllIgnoreThisField.getType() != String.class)) { 
    throw new IllegalArgumentException ("TEST_ALL_TEST_TYPE should be static private final String"); 

    String testType = (String)testAllIgnoreThisField.get(testCaseClass); 
    isOfTheCorrectType = requiredType.equals (testType); 
    } catch (NoSuchFieldException e) { 
    } catch (IllegalAccessException e) { 
    throw new IllegalArgumentException ("The field " + testCaseClass.getName () + ".TEST_ALL_TEST_TYPE is not accessible."); 


    return isOfTheCorrectType; 



    Next, the loadTestCases() method examines each class name. It loads the class (if it can be loaded); if the class is a test case and of the required type, the method adds the class to its list of test cases:
    下一步,loadTestCases()方法檢查每個類的名字。它讀入類(如果可以讀入的話);如果類是測試環境并且符合所需類型,方法將加類到它的測試環境列表: 

    public void loadTestCases (final Iterator classNamesIterator) { 
    while (classNamesIterator.hasNext ()) { 
    String className = (String)classNamesIterator.next (); 
    try { 
    Class candidateClass = Class.forName (className); 
    addClassIfTestCase (candidateClass); 
    } catch (ClassNotFoundException e) { 
    System.err.println ("Cannot load class: " + className); 




    /** 
    * Construct this instance. Load all the test cases possible that derive 
    * from baseClass and cannot be ignored. 
    * @param classNamesIterator An iterator over a collection of fully qualified class names 
    */ 
    public TestCaseLoader(final String requiredType) { 
    if (requiredType == null) throw new IllegalArgumentException ("requiredType is null"); 
    this.requiredType = requiredType; 


    /** 
    * Obtain an iterator over the collection of test case classes loaded by loadTestCases 
    */ 
    public Iterator getClasses () { 
    return classList.iterator (); 



    TestAll 
    TestCall pulls everything together. It uses the aforementioned classes to build a list of test cases defined in the system. It adds those test cases to a TestSuite and returns the TestSuite as part of its implementation of the suite() method. The result: a test case that automatically extracts every defined test case in the system, ready for execution by JUnit.
    TestCall將所有的東西放在一起.它用以前提到過的類在系統里建立一個定義好的測試實例列表.然后添加這些測試實例到測試集里,并作為測試集方法的一部分返回到suite()函數.結果就是生成一個準備在JUnit例執行的實例,而這個實例可以自動釋放存儲在系統里的預先定義好的測試實例. 

    public class TestAll extends TestCase { 


    The addAllTests() method iterates over the classes loaded by the TestCaseLoader and adds them to the test suite:
    函數addAllTests()重申由TestCaseLoader所加載的類,并把他們添加到測試集里: 

    private static int addAllTests(final TestSuite suite, final Iterator classIterator) 
    throws java.io.IOException { 
    int testClassCount = 0; 
    while (classIterator.hasNext ()) { 
    Class testCaseClass = (Class)classIterator.next (); 
    suite.addTest (new TestSuite (testCaseClass)); 
    System.out.println ("Loaded test case: " + testCaseClass.getName ()); 
    testClassCount++; 

    return testClassCount; 



    With suite(), the test cases are added to the TestSuite, then returned to JUnit for execution. It obtains, from the system property "class_root", the directory where the classes are stored. It obtains, from the system property "test_type", the type of test cases to load. It uses the ClassFinder to find all the classes, and the TestCaseLoader to load all the appropriate test cases. It then adds these to a new TestSuite:
    用函數suite(),測試的情況可以被添加到測試集里面,然后返回到JUnit執行.它包含了從系統屬性"class_root" 里面那些類所存貯的地方.它也包含了從系統屬性"test_type"里面那些可以被加載的測試類的類型.它使用ClassF- inder來尋找所有的類,并用TestCaseLoader來加載所有的測試實例,并將這些實例添加到一個新的測試集里面: 

    public static Test suite() 
    throws Throwable { 
    try { 
    String classRootString = System.getProperty("class_root"); 
    if (classRootString == null) throw new IllegalArgumentException ("System property class_root must be set."); 
    String testType = System.getProperty("test_type"); 
    if (testType == null) throw new IllegalArgumentException ("System property test_type must be set."); 
    File classRoot = new File(classRootString); 
    ClassFinder classFinder = new ClassFinder (classRoot); 
    TestCaseLoader testCaseLoader = new TestCaseLoader (testType); 
    testCaseLoader.loadTestCases (classFinder.getClasses ()); 
    TestSuite suite = new TestSuite(); 
    int numberOfTests = addAllTests (suite, testCaseLoader.getClasses ()); 
    System.out.println("Number of test classes found: " + numberOfTests); 
    return suite; 
    } catch (Throwable t) { 
    // This ensures we have extra information. Otherwise we get a "Could not invoke the suite method." message. 
    t.printStackTrace (); 
    throw t; 



    /** 
    * Basic constructor - called by the test runners. 
    */ 

    public TestAll(String s) { 
    super(s); 




    To test an entire system using these classes, execute the following command (in a Windows command shell):
    為了用這些類來測試一個完整的系統,請執行下面的命令(在Windows環境下): 

    java -cp C:\project\classes;C:\junit3.2\junit.jar:C:\jcf\jcfutils.zip -Dclass_root=C:\project\classes -Dtest_type=UNIT junit.ui.TestRunner bp.TestAll 


    This command loads and runs all test cases of type UNIT that have classes stored under C:\project\classes.
    這個命令加載運行了所有的UNIT測試類,這些測試類在C:\project\classes里已被分別存儲. 

    Test thread safety
    安全的線程測試 
    You'll want to guarantee the status of supposedly thread-safe classes by testing them. Such tests prove difficult using Junit 3.2's existing set of facilities. You can use junit.extensions.ActiveTest to run a test case in a different thread. However, TestSuite assumes that a test case is complete when it returns from run(); with junit.extensions.ActiveTest, it is not. We could work hard to define a properly working ActiveTestSuite; instead, let's look at a simpler solution: MultiThreadedTestCase. First, I'll show how MultiThreadedTestCase assists with multithreaded testing. Then I'll show how MultiThreadedTestCase is implemented.
    通過測試,你想確保這些被測試的線程處于安全狀態.但是用Junit 3.2's提供的現成的工具完成這些測試是困難的.你可以使用junit.extensions.ActiveTest在不同的線程中運行一個測試實例,但是測試集只能確保在run()函數里一個測試實例是可以正常完成的,而用junit.extensions.ActiveTest不可以.我們可以盡力定義一個精確的工作集ActiveTestSuite(動態測試集)來替換它,讓我們看一個簡單的解決方法: MultiThreadedTestCase(多線程測試集).首先我將展示如何用多線程測試來輔助多線程測試實例.然后將展示多線程實例是怎么樣運行的. 

    To use MultiThreadedTestCase, we implement the standard elements of a TestCase, but we derive from MultiThreadedTestCase. The standard elements are the class declaration, the constructor, and since we're using TestAll, the definition of the test type:
    為使用MultiThreadedTestCase,我們使用一個標準元素TestCase,但我們從MultiThreadedTestCase里創建. 這個標準的元素是一個類的說明,是創建者,既然我們使用TestAll,下面是測試類的定義: 

    public class MTTest extends MultiThreadedTestCase { 
    /** 
    * Basic constructor - called by the test runners. 
    */ 
    public MTTest(String s) { 
    super (s); 


    public static final String TEST_ALL_TEST_TYPE = "UNIT"; 


    A multithreaded test case needs to spawn a number of threads that perform some operation. We need to start those threads, wait until they've executed, and then return the results to JUnit -- all done in the code below. The code is trivial; in practice, this code would spawn multiple threads that performed different operations on the class under test. After each operation the class invariants and post-conditions would be tested to ensure that the class was behaving properly.
    一個多線程的測試實例需要產生許多的線程.我們必須執行這些線程,并等待他們完成將結果返回到JUnit--這一切在下面的例子中可以完成.這些代碼是微不足道的.實際上,這些代碼在下面測試的類中能夠產生許多的執行不同動作的線程.在每一個動作后,這些類的變量和后續的條件將會被測試以確保這個類是被正常的執行的. 

    public void testMTExample () 

    // Create 100 threads containing the test case. 
    TestCaseRunnable tct [] = new TestCaseRunnable [100]; 
    for (int i = 0; i < tct.length; i++) 

    tct[i] = new TestCaseRunnable () { 
    public void runTestCase () { 
    assert (true); 

    }; 

    // Run the 100 threads, wait for them to complete and return the results to JUnit. 
    runTestCaseRunnables (tct); 




    Now that I've shown how to use MultiThreadedTestCase, I'll examine the implementation. First, we declare the class and add an array where the running threads will be stored:
    既然我已經演示了怎么使用多線程測試實例,接下來我將檢查一下執行情況.首先,我們定義一個類,并在已運行線程里添加一個數組: 

    public class MultiThreadedTestCase extends TestCase { 
    /** 
    * The threads that are executing. 
    */ 
    private Thread threads[] = null; 


    testResult, seen below, holds the testResult that declares that the test case's run() will be passed. We override run() so we can store the testResult for later population by the test threads:
    從下面我們可以看到,testResult類所顯示的run()函數的測試結果是能夠順利通過的.為了方便觀察我們可以屏蔽run()函數保存測試線程testResult中的結果 

    /** 
    * The tests TestResult. 
    */ 
    private TestResult testResult = null; 
    /** 
    * Simple constructor. 
    */ 
    public MultiThreadedTestCase(final String s) { 
    super(s); 

    /** 
    * Override run so we can save the test result. 
    */ 
    public void run(final TestResult result) { 
    testResult = result; 
    super.run(result); 
    testResult = null; 


    runTestCaseRunnables() runs each TestCaseRunnable in a seperate thread. All the threads are created and then started at the same time. The method waits until every thread has finished and then returns:
    在分離的線程里運行runTestCaseRunnables()都會運行一次TestCaseRunnable.所有的這些線程都是都是同時被創建和運行的.而runTestCaseRunnable()則要等每一個線程運行完并返回: 

    protected void runTestCaseRunnables (final TestCaseRunnable[] runnables) { 
    if(runnables == null) { 
    throw new IllegalArgumentException("runnables is null"); 

    threads = new Thread[runnables.length]; 
    for(int i = 0;i < threads.length;i++) { 
    threads[i] = new Thread(runnables[i]); 

    for(int i = 0;i < threads.length;i++) { 
    threads[i].start(); 

    try { 
    for(int i = 0;i < threads.length;i++) { 
    threads[i].join(); 


    catch(InterruptedException ignore) { 
    System.out.println("Thread join interrupted."); 

    threads = null; 



    Exceptions caught in the test threads must be propagated into the testResult instance we saved from the run() method. handleException(), below, does just that:
    在測試線程里面溢出情況的捕捉必須能夠被傳送到我們從run()函數testResult實例里面.下面是溢出情況處理的代碼: 

    /** 
    * Handle an exception. Since multiple threads won't have their 
    * exceptions caught the threads must manually catch them and call 
    * handleException(). 
    * @param t Exception to handle.*/ 

    private void handleException(final Throwable t) { 
    synchronized(te, stResult) { 
    if(t instanceof AssertionFailedError) { 
    testResult.addFailure(this, (AssertionFailedError)t); 

    else { 
    testResult.addError(this, t); 





    Finally, we define the class that each test thread extends. The purpose of this class is to provide an environment (runTestCase()) where thrown exceptions will be caught and passed to JUnit. The implementation of this class is:
    最后,我們可以為每一個測試的線程定義一個擴展的類.這個類可以用來提供一種環境 (runTestCase())以捕捉線程中的溢出情況并返回到JUnit.這個類的例子如下: 

    /** 
    * A test case thread. Override runTestCase () and define 
    * behaviour of test in there.*/ 
    protected abstract class TestCaseRunnable implements Runnable { 
    /** 
    * Override this to define the test*/ 

    public abstract void runTestCase() 
    throws Throwable; 
    /** 
    * Run the test in an environment where 
    * we can handle the exceptions generated by the test method.*/ 

    public void run() { 
    try { 
    runTestCase(); 

    catch(Throwable t) /* Any other exception we handle and then we interrupt the other threads.*/ { 
    handleException(t); 
    interruptThreads(); 






    The implementation above helps to develop multithreaded test cases. It handles exceptions thrown in the multiple testing threads and passes them back to JUnit. JUnit only sees a test case that behaves like a single-threaded test. The unit test developer can extend that test case to develop multithreaded tests, without spending much time developing thread-handling code.
    以上的implementation能可以用來解決多線程測試中的一些問題.它可以處理在多線程測試中的例外溢出情況,并且可以通過他們返回到JUnit.在不耗費更過的時間改善多線程代碼的同時, 單元測試的開發者可以增加測試的條件來進一步改善多線程的測試. 

    Conclusion
    結論: 
    Using JUnit to develop robust tests takes some practice (as does writing tests). This article contains a number of techniques for improving your tests' usefulness. Those techniques range from avoiding basic mistakes (such as not using setUp()) to more design-level issues (avoiding intertest coupling). I've covered some basic ideas to help you use JUnit to test parts of your UI or Web application. I've also shown how to build an automated test suite that removes the overhead of maintaining hand-coded test suites and a mechanism for reducing the effort of developing multithreaded JUnit test cases.
    運用JUnit來進行強壯的測試需要做多個方面的練習(比如寫一些測試程序).這篇文章為你提高測試的有用性提供了許多技巧.這些技巧的范圍包括避免一些基本的錯誤(比如不會使用 setup()這樣的代碼)直到更到層次的一些設計問題(如避免intertest coupling) .在這里我已提供了許多基本的思想來幫助你用JUnit測試UI或者網頁應用程序.而且我也已經說明怎么構建一個可以自動的清除那些留在手寫代碼中測試語句的測試集和可以自動減化在運用多線程的JUnit測試時的機制. 

    JUnit is an excellent framework for unit-testing Java applications. One final thought: If you just started using JUnit to produce unit tests, stick at it. For the first few weeks, you may not see any real reward for your labors. In fact, you may feel that the whole process slows you down. However, after a few weeks, you'll begin to enhance existing code. Then you'll run your tests, pick up new bugs, and fix them. You'll be far more confident in your code base and you will see the value of unit testing.
    JUnit 是一個用在Java 單元測試應用的優秀的平臺.它的終極目標就是要實現這樣的一種理念: 如果你使用JUnit 來進行單元測試,你就會喜歡它,并會堅持下去.在剛開始的幾個星期里,你也許不能明白你已為你的勞動做了多少真正有意思的貢獻.甚至會感覺到在進度上反而慢了下來.但是,在過幾個星期,你就會完善已完成的編碼工作.然后進行調試,找出程序中的新的漏洞,并修補他們.漸漸的你會對你的編碼功底越來越自信,而且會進一步認識到進行單元測試的價值! 

    About the author 
    Andy Schneider is a technical architect for BJSS. He has been using object technology since 1988 to build both large- and small-scale systems. Schneider has been using xUnit in projects for over 18 months. His interests include distributed architectures and development processes.
    作者簡介: 
    Andy schneider :是一個BJSS的技術架構人員,他自從1988年以來一直在用面向對象的技術架構大型的和小型的系統,Schneider 從18個月前就把xUnit用在了工程方面.他的研究方向主要在分布式系統及其系統的發展方面 



    (c) Copyright 2000 ITworld.com, Inc., an IDG Communications company 

    Resources 

    The complete source code for this article can be downloaded at: 
    http://www.javaworld.com/jw-12-2000/junit/jw-1221-junit.zip 
    For more great articles on Java tools, visit the Development Tools section of JavaWorld's Topical Index: 
    http://www.javaworld.com/javaworld/topicalindex/jw-ti-tools.html 
    For a freewheeling discussion of programming theory, visit JavaWorld's Programming Theory & Practice discussion, moderated by the intrepid Allen Holub: 
    http://forums.itworld.com/webx?14@@.ee6b806 
    The free Java Tutor newsletter gives weekly hands-on guidance and tips for Java programmers: 
    http://www.itworld.com/cgi-bin/subcontent12.cgi 
    The JUnit Website: 
    http://www.junit.org/ 
    A directory of xUnit implementations for different technologies: 
    http://www.xprogramming.com/software.htm 
    Unit-test-related discussion of Wiki: 
    http://c2.com/cgi-bin/wiki?UnitTests 
    Extreme Programming roadmap on Wiki: 
    http://c2.com/cgi-bin/wiki?ExtremeProgrammingRoadmap 
    A worked example of testing a GUI with JUnit: 
    http://users.vnet.net/wwake/xp/xp0001/index.shtml 
    Feedback: http://www.javaworld.com/javaworld/cgi-bin/jw-mailto.cgi?jweditors@javaworld.com+/javaworld/jw-12-2000/jw-1221-junit.html+jweditors
    Technical difficulties: http://www.javaworld.com/javaworld/cgi-bin/jw-mailto.cgi?webmaster@javaworld.com+/javaworld/jw-12-2000/jw-1221-junit.html+webmaster
    URL: http://www.javaworld.com/jw-12-2000/jw-1221-junit.html 
    Last modified: Tuesday, May 01, 2001

     



    |----------------------------------------------------------------------------------------|
                               版權聲明  版權所有 @zhyiwww
                引用請注明來源 http://www.tkk7.com/zhyiwww   
    |----------------------------------------------------------------------------------------|
    posted on 2006-06-07 17:49 zhyiwww 閱讀(1501) 評論(0)  編輯  收藏 所屬分類: 軟件測試
    主站蜘蛛池模板: 久久精品国产精品亚洲蜜月| 国产精品亚洲天堂| 国内外成人免费视频| 午夜亚洲乱码伦小说区69堂| 亚洲AV无码精品色午夜果冻不卡 | 亚洲精品乱码久久久久久久久久久久| 中文无码成人免费视频在线观看| 亚洲国产激情在线一区| 亚洲人妻av伦理| 久久午夜免费视频| a级片在线免费看| 亚洲熟妇丰满xxxxx| 国产AV无码专区亚洲AV手机麻豆| 成年网站免费视频A在线双飞| 国产激情久久久久影院老熟女免费| 亚洲一区二区三区91| 中文字幕人成人乱码亚洲电影| 欧洲黑大粗无码免费| 你懂得的在线观看免费视频| 亚洲精品理论电影在线观看| 色婷婷亚洲十月十月色天| 亚洲第一网站男人都懂| 成全高清视频免费观看| 日韩免费高清大片在线| kk4kk免费视频毛片| 日韩欧美亚洲国产精品字幕久久久 | 69式互添免费视频| 精品国产免费一区二区三区| 亚洲欧洲av综合色无码| 亚洲视频免费播放| 亚洲成亚洲乱码一二三四区软件| 国产成人免费福利网站| 国产精品成人免费一区二区| 特级无码毛片免费视频尤物| 久香草视频在线观看免费| 亚洲GV天堂无码男同在线观看| 亚洲一级毛片视频| 亚洲欧洲日本天天堂在线观看| 人人狠狠综合久久亚洲88| 亚洲精品色婷婷在线影院 | 亚洲综合丁香婷婷六月香|