java程式員如何編寫更好的單元測試的7個技巧,java單元測試

來源:互聯網
上載者:User

java程式員如何編寫更好的單元測試的7個技巧,java單元測試

測試是開發的一個非常重要的方面,可以在很大程度上決定一個應用程式的命運。良好的測試可以在早期捕獲導致應用程式崩潰的問題,但較差的測試往往總是導致故障和停機。
雖然有三種主要類型的軟體測試:單元測試,功能測試和整合測試,但是在這篇博文中,我們將討論開發人員級單元測試。在我深入講述具體細節之前,讓我們先來回顧一下這三種測試的詳細內容。
一.軟體開發測試的類型
單元測試用於測試各個程式碼群組件,並確保代碼按照預期的方式工作。單元測試由開發人員編寫和執行。大多數情況下,使用JUnit或TestNG之類的測試架構。測試案例通常是在方法層級寫入並通過自動化執行。
整合測試檢查系統是否作為一個整體而工作。整合測試也由開發人員完成,但不是測試單個組件,而是旨在跨組件測試。系統由許多單獨的組件組成,如代碼,資料庫,Web伺服器等。整合測試能夠發現如組件布線,網路訪問,資料庫問題等問題。
功能測試通過將給定輸入的結果與規範進行比較來檢查每個功能是否正確實現。通常,這不是在開發人員層級的。功能測試由單獨的測試團隊執行。測試案例基於規範編寫,並且實際結果與預期結果進行比較。有若干工具可用於自動化的功能測試,如Selenium和QTP。
如前所述,單元測試可協助開發人員確定代碼是否正常工作。在這篇博文中,我將提供在Java中單元測試的有用提示。
二.使用架構來用於單元測試
Java提供了若干用於單元測試的架構。TestNG和JUnit是最流行的測試架構。JUnit和TestNG的一些重要功能:
    易於設定和運行。
    支援注釋。
    允許忽略或分組並一起執行某些測試。
    支援參數化測試,即通過在運行時指定不同的值來運行單元測試。
    通過與構建工具,如Ant,Maven和Gradle整合來支援自動化的測試執行。
EasyMock是一個類比架構,是單元測試架構,如JUnit和TestNG的補充。EasyMock本身不是一個完整的架構。它只是添加了建立類比對象以便於測試的能力。例如,我們想要測試的一個方法可以調用從資料庫擷取資料的DAO類。在這種情況下,EasyMock可用於建立返回寫入程式碼資料的MockDAO。這使我們能夠輕鬆地測試我們意向的方法,而不必擔心資料庫訪問。
三.謹慎使用測試驅動開發
測試驅動開發(TDD)是一個軟體開發過程,在這過程中,在開始任何編碼之前,我們基於需求來編寫測試。由於還沒有編碼,測試最初會失敗。然後寫入最小量的代碼以通過測試。然後重構代碼,直到被最佳化。
目標是編寫覆蓋所有需求的測試,而不是一開始就寫代碼,卻可能甚至都不能滿足需求。TDD是偉大的,因為它導致簡單的模組化代碼,且易於維護。總體開發速度加快,容易發現缺陷。此外,單元測試被建立作為TDD方法的副產品。
然而,TDD可能不適合所有的情況。在設計複雜的項目中,專註於最簡單的設計以便於通過測試案例,而不提前思考可能會導致巨大的代碼更改。此外,TDD方法難以用於與遺留系統,GUI應用程式或與資料庫一起工作的應用程式互動的系統。另外,測試需要隨著代碼的改變而更新。
因此,在決定採用TDD方法之前,應考慮上述因素,並應根據項目的性質採取措施。
四.測量程式碼涵蓋範圍
程式碼涵蓋範圍衡量(以百分比表示)了在運行單元測試時執行的代碼量。通常,高覆蓋率的程式碼封裝含未檢測到的錯誤的幾率要低,因為其更多的原始碼在測試過程中被執行。測量程式碼涵蓋範圍的一些最佳做法包括:
    使用代碼覆蓋工具,如Clover,Corbetura,JaCoCo或Sonar。使用工具可以提高測試品質,因為這些工具可以指出未經測試的代碼地區,讓你能夠開發開發額外的測試來覆蓋這些領域。
    每當寫入新功能時,立即寫新的測試覆蓋。
    確保有測試案例覆蓋代碼的所有分支,即if / else語句。
高代碼覆蓋不能保證測試是完美的,所以要小心!
下面的 concat 方法接受布爾值作為輸入,並且僅當布爾值為true時附加傳遞兩個字串:
public String concat(boolean append, String a,String b) {
        String result = null;
        If (append) {
            result = a + b;
                            }
        return result.toLowerCase();
}
以下是上述方法的測試案例:
@Test
public void testStringUtil() {
     String result = stringUtil.concat(true, "Hello ", "World");
     System.out.println("Result is "+result);
}
在這種情況下,執行測試的值為true。當測試執行時,它將通過。當程式碼涵蓋範圍工具運行時,它將顯示100%的程式碼涵蓋範圍,因為 concat 方法中的所有代碼都被執行。但是,如果測試執行的值為false,則將拋出 NullPointerException 。所以100%的程式碼涵蓋範圍並不真正表明測試覆蓋了所有情境,也不能說明測試良好。
五.儘可能將測試資料外部化
在JUnit4之前,測試案例要啟動並執行資料必須寫入程式碼到測試案例中。這導致了限制,為了使用不同的資料運行測試,測試案例代碼必須修改。但是,JUnit4以及TestNG支援外部化測試資料,以便可以針對不同的資料集運行測試案例,而無需更改原始碼。
下面的 MathChecker 類有方法可以檢查一個數字是否是奇數:
public class MathChecker {
        public Boolean isOdd(int n) {
            if (n%2 != 0) {
                return true;
            } else {
                return false;
            }
        }
    }
以下是MathChecker類的TestNG測試案例:
public class MathCheckerTest {
        private MathChecker checker;
        @BeforeMethod
        public void beforeMethod() {
          checker = new MathChecker();
        }
        @Test
        @Parameters("num")
        public void isOdd(int num) {
          System.out.println("Running test for "+num);
          Boolean result = checker.isOdd(num);
          Assert.assertEquals(result, new Boolean(true));
        }
    }
TestNG
以下是testng.xml(用於TestNG的設定檔),它具有要為其執行測試的資料:
<?xml version="1.0" encoding="UTF-8"?>
    <suite name="ParameterExampleSuite" parallel="false">
    <test name="MathCheckerTest">
    <classes>
      <parameter name="num" value="3"></parameter>
      <class name="com.stormpath.demo.MathCheckerTest"/>
    </classes>
     </test>
     <test name="MathCheckerTest1">
    <classes>
      <parameter name="num" value="7"></parameter>
      <class name="com.stormpath.demo.MathCheckerTest"/>
    </classes>
     </test>
    </suite>
可以看出,在這種情況下,測試將執行兩次,值3和7各一次。除了通過XML設定檔指定測試資料之外,還可以通過DataProvider注釋在類中提供測試資料。
六.JUnit
與TestNG類似,測試資料也可以外部化用於JUnit。以下是與上述相同MathChecker類的JUnit測試案例:
@RunWith(Parameterized.class)
    public class MathCheckerTest {
     private int inputNumber;
     private Boolean expected;
     private MathChecker mathChecker;
     @Before
     public void setup(){
         mathChecker = new MathChecker();
     }
        // Inject via constructor
        public MathCheckerTest(int inputNumber, Boolean expected) {
            this.inputNumber = inputNumber;
            this.expected = expected;
        }
        @Parameterized.Parameters
        public static Collection<Object[]> getTestData() {
            return Arrays.asList(new Object[][]{
                    {1, true},
                    {2, false},
                    {3, true},
                    {4, false},
                    {5, true}
            });
        }
        @Test
        public void testisOdd() {
            System.out.println("Running test for:"+inputNumber);
            assertEquals(mathChecker.isOdd(inputNumber), expected);
        }
    }
可以看出,要對其執行測試的測試資料由getTestData()方法指定。此方法可以輕鬆地修改為從外部檔案讀取資料,而不是寫入程式碼資料。
七.使用斷言而不是Print語句
許多新手開發人員習慣於在每行代碼之後編寫System.out.println語句來驗證代碼是否正確執行。這種做法常常擴充到單元測試,從而導致測試代碼變得雜亂。除了混亂,這需要開發人員手動幹預去驗證控制台上列印的輸出,以檢查測試是否成功運行。更好的方法是使用自動指示測試結果的斷言。
下面的 StringUti 類是一個簡單類,有一個串連兩個輸入字串並返回結果的方法:
public class StringUtil {
        public String concat(String a,String b) {
            return a + b;
        }
    }
以下是上述方法的兩個單元測試:
@Test
    public void testStringUtil_Bad() {
         String result = stringUtil.concat("Hello ", "World");
         System.out.println("Result is "+result);
    }
    @Test
    public void testStringUtil_Good() {
         String result = stringUtil.concat("Hello ", "World");
         assertEquals("Hello World", result);
    }
testStringUtil\_Bad將始終傳遞,因為它沒有斷言。開發人員需要手動地在控制台驗證測試的輸出。如果方法返回錯誤的結果並且不需要開發人員幹預,則testStringUtil\_Good將失敗。
八.構建具有確定性結果的測試
一些方法不具有確定性結果,即該方法的輸出不是預Crowdsourced Security Testing道的,並且每一次都可以改變。例如,考慮以下代碼,它有一個複雜的函數和一個計算執行複雜函數所需時間(以毫秒為單位)的方法:
public class DemoLogic {
    private void veryComplexFunction(){
        //This is a complex function that has a lot of database access and is time consuming
        //To demo this method, I am going to add a Thread.sleep for a random number of milliseconds
        try {
            int time = (int) (Math.random()*100);
            Thread.sleep(time);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    public long calculateTime(){
        long time = 0;
        long before = System.currentTimeMillis();
        veryComplexFunction();
        long after = System.currentTimeMillis();
        time = after - before;
        return time;
    }
    }
在這種情況下,每次執行 calculateTime 方法時,它將返回一個不同的值。為該方法編寫測試案例不會有任何用處,因為該方法的輸出是可變的。因此,測試方法將不能驗證任何特定執行的輸出。
九.除了正面情景外,還要測試負面情景和邊緣情況
通常,開發人員會花費大量的時間和精力編寫測試案例,以確保應用程式按預期工作。然而,測試負面測試案例也很重要。負面測試案例指的是測試系統是否可以處理無效資料的測試案例。例如,考慮一個簡單的函數,它能讀取長度為8的字母數字值,由使用者鍵入。除了字母數字值,應測試以下負面測試案例:
    1.使用者指定非字母數字值,如特殊字元。
    2.使用者指定空值。
    3.使用者指定大於或小於8個字元的值。
類似地,邊界測試案例測試系統是否適用於極端值。例如,如果使用者希望輸入從1到100的數字值,則1和100是邊界值,對這些值進行測試系統是非常重要的。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.