使用Junit進行單元測試教程

軟件測試的目的

什麼是軟件測試?

軟件測試是    執行的軟件以驗證代碼狀態(state testing)或事件序列(behavior testing)符合預期    。
html

軟件測試的做用

軟件單元測試幫助開發人員驗證程序的部分邏輯是正確的。    
java

測試術語

被測代碼(或應用)

被測試的代碼一般稱被測代碼。若是您正在測試的應用程序,則爲被測試應用程序。
python

測試夾具(fixture)

測試夾具是代碼的固定狀態,一般爲測試輸入,也能夠爲預置條件。    
android

單元測試

單元測試用代碼測試代碼,關注狀態和行爲測試。單元測試所測試的百分比一般被稱爲測試覆蓋率。
單元測試一般針對一小段代碼,例如方法或類。經過測試實現或mock排除外部依賴。
單元測試一般不適合測試複雜用戶界面或組件集成。
git

集成測試

集成測試關注測試組件的行爲或一組組件之間的集成。功能測試有時等同於集成測試。一般須要把user story轉化爲test suite。github

性能測試

性能測試使用基準重複測試軟件組件。其目的是爲了確保測試的代碼即便是在高負載下也運行速度夠快。
web

行爲與狀態測試

行爲測試(也叫交互做用測試)不驗證的方法調用的結果,但檢查方法是否爲用正確的輸入參數調用。狀態測試驗證的結果。

若是您正在測試的算法和系統的功能,你想測試在大多數狀況下的狀態,而不是交互。典型的測試裝置使用嘲笑或相關類的存根,以抽象與這些其餘類路程,測試狀態中的對象,被測試的相互做用。
算法

測試組織

測試代碼與實際代碼分開。簡單的getter和setter之類代碼一般不須要測試。JVM相關內容一般能夠認爲是可靠的。對已有代碼的測試,一般從出錯最多的地方開始。Java的主流測試框架是JUnit和TestNG。前者用戶最多,對mock框架兼容好。後者可讀性更好。

數據庫

Junit快速入門


JUnit的4.x版使用註解來指定測試。JUnit的主頁:http://junit.org/,代碼託管:https://github.com/junit-team/junit。JUnit測試是類中用於測試的方法。使用註解@org.junit.Test。方法中使用的斷言方法(JUnit或其餘斷言框架提供),檢查代碼的執行的實際結果與預期。

下面的代碼演示JUnit測試。數組

建立java工程first,並在src目錄下建立test目錄。

建立類MyClass:

package first;

public class MyClass {
    
    public int multiply(int x, int y) {
        
        // the following is just an example
        if (x > 999) {
          throw new IllegalArgumentException("X should be less than 1000");
        }
        return x / y;
  }
}

建立測試類MyClassTest,建立方法參見下面的安裝配置部分。

Selecting the methods to test

Eclipse prompt for adding JUnit to the project classpath

package first;

import static org.junit.Assert.assertEquals;
import org.junit.Test;

public class MyClassTest {
  
  @Test(expected = IllegalArgumentException.class)
  public void testExceptionIsThrown() {
    MyClass tester = new MyClass();
    tester.multiply(1000, 5);
  }
  
  @Test
  public void testMultiply() {
    MyClass tester = new MyClass();
    assertEquals("10 x 5 must be 50", 50, tester.multiply(10, 5));
  }
}

選中測試類, 右鍵選擇Run-As → JUnit Test.          

Result of running a unit test

                         


在普遍使用的Junit命名方法是類名稱下測試和「測試」加Test後綴。should經常使用於測試方法名,如ordersShouldBeCreated,menuShouldGetActive,以加強可讀性。Maven經過surfire插件自動生成Tests後綴的測試類。


多個測試類能夠組合成test suite。運行test suite將在該suite按照指定的順序執行全部測試類。下面演示包含兩個測試類 (MyClassTest和MySecondClassTest) 的測試集,經過@Suite.SuiteClasses statement能夠增長測試類。

import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;

@RunWith(Suite.class)
@SuiteClasses({ MyClassTest.class, MySecondClassTest.class })
public class AllTests {

}

test suite中也能夠包含test suite。

經過標準的Java代碼能夠在Eclipse以外運行JUnit測試。Apache Maven的或Gradle框架之類的構建框架一般與持續集成服務器(如Hudson或Jenkins)組合按期自動執行測試。
org.junit.runner.JUnitCore類的runClasses()方法容許運行測試類,返回爲org.junit.runner.Result對象,包含測試結果信息。

下面類演示如何運行MyClassTest。這個類將執行測試類,輸出潛在錯誤到控制檯。

import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;

public class MyTestRunner {

  public static void main(String[] args) {
    Result result = JUnitCore.runClasses(MyClassTest.class);
    for (Failure failure : result.getFailures()) {
      System.out.println(failure.toString());
    }
  }
}

以上類能夠像Java類同樣從命令行運行,你只須要添加JUnit Jar到classpath便可。

Junit基礎

註解

Junit4.*使用註解(annotation)標識測試方法和配置測試,如下是經常使用相關annotation的簡要說明。特別重要的註解以下:

 

註解 描述
@Test  
public void method()
標識方法爲測試方法
@Test  (expected = Exception.class) 標識拋出指定的異常
@Test(timeout=100) 超時,單位ms
@Before 
public void method()
每一個測試執行以前的準備工做。用來準備測試環境,如讀取輸入數據,初始化類等。
@After  
public void method()
每一個測試執行以後的清理工做。如刪除臨時文件,恢復初始設置,釋放內存等。
@BeforeClass 
public static void method()
每一個測試集執行以前的準備工做。用來執行費時任務,如鏈接數據庫。必須是Static方法。
@AfterClass 
public static void method()
每一個測試集執行以後的清理工做。用來清理,如斷開數據庫鏈接,必須是Static方法。
@Ignore or @Ignore("Why disabled") 忽略指定測試方法,用於測試類還沒有準備好等狀況,使用時最好標明忽略的緣由。

 @RunWith會替代默認的org.junit.runner.Runner類,好比:

@RunWith(Suite.class)
public class MySuite {
}

Mock也須要使用註解。

測試用例組織

import org.junit.runner.RunWith;
import org.junit.runners.Suite;


@RunWith(Suite.class)
@Suite.SuiteClasses({ AssertTest.class, TestExecutionOrder.class, Assumption.class })
public class TestSuite {
}

斷言


JUnit會假定全部測試方法按任意順序執行,也就是說,一個測試不該該依賴其它測試。

Junit 4.11容許你用Annoation以字母順序對測試方法進行排序,使用方法爲用@FixMethodOrder(MethodSorters.NAME_ASCENDING)。默認使用固定但不可預期的順序,對應參數爲`MethodSorters.DEFAULT`,也可使用`MethodSorters.JVM`,表明JVM的默認方式,每次運行順序會不一樣。

自定義的實例:
package org.hamcrest.examples.tutorial;

import org.hamcrest.Description;
import org.hamcrest.Factory;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;

public class IsNotANumber extends TypeSafeMatcher<Double> {

 
@Override
 
public boolean matchesSafely(Double number) {
   
return number.isNaN();
 
}

 
public void describeTo(Description description) {
    description
.appendText("not a number");
 
}

 
@Factory
 
public static <T> Matcher<Double> notANumber() {
   
return new IsNotANumber();
 
}

}


使用:

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;

import static org.hamcrest.examples.tutorial.IsNotANumber.notANumber;

import junit.framework.TestCase;

public class NumberTest extends TestCase {

 
public void testSquareRootOfMinusOneIsNotANumber() {
    assertThat
(Math.sqrt(-1), is(notANumber()));
 
}
}

切記,要靜態導入notANumber, 參考資料:https://code.google.com/p/hamcrest/wiki/Tutorial。

稍微複雜點的實例:

package com.example;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Factory;
import org.hamcrest.Matcher;

public class LessThanOrEqual<T extends Comparable<T>> extends BaseMatcher<Comparable<T>> {
    
    private final Comparable<T> expValue;
    
    public LessThanOrEqual(T expValue) {
        this.expValue= expValue;
    }
    
    @Override
    public void describeTo(Description desc) {
        desc.appendText(" less than or equal(<=)"
        +expValue);
    }
    
    @Override
    public boolean matches(Object t) {
        int compareTo = expValue.compareTo((T)t);
        return compareTo > -1;
    }
    
    @Factory
    public static<T extends Comparable<T>> Matcher<T> lessThanOrEqual(T t) {
        return new LessThanOrEqual(t);
    }
}

使用:

    @Test
    public void lessthanOrEquals_matcher() throws Exception
    {
        int actualGoalScored = 2;
        int expGoalScored= 4;
        assertThat(actualGoalScored, lessThanOrEqual(expGoalScored));
        expGoalScored =2;
        assertThat(actualGoalScored, lessThanOrEqual(expGoalScored ));
        
        double actualDouble = 3.14;
        double expDouble = 9.00;
        assertThat(actualDouble, lessThanOrEqual(expDouble));
        String authorName = "Sujoy";
        String expAuthName = "Zachary";
        assertThat(authorName, lessThanOrEqual(expAuthName));
    }

安裝配置

在Gradle編譯時使用Junit:

apply plugin: 'java'
dependencies {
  testCompile 'junit:junit:4.12'
}

Maven:

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>


Eclipse 等IDE自帶Junit。


Eclipse對Junit的支持

Eclipse有建立JUnit測試的嚮導。例如,要爲現有類建立JUnit測試或測試類,在類單擊鼠標右鍵,New → JUnit Test Case。File → New → Other... → Java→ JUnit也可打開相似窗口。


執行:在類單擊鼠標右鍵Run-as →JUnit Test,執行類中全部測試。Alt+Shift+X, ,T,若是光標在測試裏面,只會執行當前測試。


 

只看失敗的測試:

測試失敗時才彈出:

 

拷貝錯誤信息

Copy failed tests into clipboard

 JUnit的靜態導入
靜態導入容許在類中定義的public static字段和方法不指定類就可使用。

// without static imports you have to write the following statement
Assert.assertEquals("10 x 5 must be 50", 50, tester.multiply(10, 5));

// alternatively define assertEquals as static import
import static org.junit.Assert.assertEquals;

// more code
// use assertEquals directly because of the static import
assertEquals("10 x 5 must be 50", 50, tester.multiply(10, 5));

Eclipse中能夠配置自動靜態導入:Window → Preferences and select Java → Editor → Content Assist → Favorites.

  • org.junit.Assert

  • org.hamcrest.CoreMatchers

  • org.hamcrest.Matchers

Adding static imports to the preferences

這樣就可使用Content Assist(快捷方式Ctrl+Space)添加方法導入。

異常測試

註解:@Test (expected = Exception.class)能夠測試單個異常。

測試多個異常的方法:

try {
   mustThrowException(); 
   fail();
} catch (Exception e) {
   // expected
   // could also check for message of exception, etc.
}

插件測試

JUnit的插件測試爲插件書寫單元測試。這些測試運行特殊的測試執行器,在單獨的虛擬機中生成Eclipse實例。


高級用法

參數化(數據驅動)

使用註解@RunWith(Parameterized.class)便可。

測試類必須包含@Parameters註解的靜態方法返回數組的集合,用於做爲測試方法的參數。public域使用@parameter註解能夠取得測試注入測試值。

package first;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

import java.util.Arrays;
import java.util.Collection;
import static org.junit.Assert.assertEquals;
import static org.junit.runners.Parameterized.*;

@RunWith(Parameterized.class)
public class ParameterizedTestFields {

    // fields used together with @Parameter must be public
    @Parameter
    public int m1;
    @Parameter (value = 1)
    public int m2;
    
    // creates the test data
    @Parameters
    public static Collection<Object[]> data() {
        Object[][] data = new Object[][] { { 1 , 2 }, { 5, 3 }, { 121, 4 } };
        return Arrays.asList(data);
    }
    
    @Test
    public void testMultiplyException() {
        MyClass tester = new MyClass();
        assertEquals("Result", m1 * m2, tester.multiply(m1, m2));
    }
    
    // class to be tested
    class MyClass {
        public int multiply(int i, int j) {
            return i *j;
        }
    }
}


使用構造方法也能夠實現相似的效果:

package first;

import static org.junit.Assert.assertEquals;

import java.util.Arrays;
import java.util.Collection;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

@RunWith(Parameterized.class)
public class ParameterizedTestUsingConstructor {

    private int m1;
    private int m2;
    
    public ParameterizedTestUsingConstructor(int p1, int p2) {
        m1 = p1;
        m2 = p2;
    }
    
    // creates the test data
    @Parameters
    public static Collection<Object[]> data() {
        Object[][] data = new Object[][] { { 1 , 2 }, { 5, 3 }, { 121, 4 } };
        return Arrays.asList(data);
    }
    
    @Test
    public void testMultiplyException() {
        MyClass tester = new MyClass();
        assertEquals("Result", m1 * m2, tester.multiply(m1, m2));
    }
    
    // class to be tested
    class MyClass {
        public int multiply(int i, int j) {
            return i *j;
        }
    }
}


Rule

Rule能夠靈活的增長或者重定義測試類每一個測試方法的行爲。經過@Rule註解能夠建立和配置在測試方法使用的對象。好比靈活地指定異常:

package first;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

public class RuleExceptionTesterExample {

  @Rule
  public ExpectedException exception = ExpectedException.none();
  
  @Test
  public void throwsIllegalArgumentExceptionIfIconIsNull() {
    exception.expect(IllegalArgumentException.class);
    exception.expectMessage("Negative value not allowed");
    ClassToBeTested t = new ClassToBeTested();
    t.methodToBeTest(-1);
  }
}


注意上述代碼只作演示,不能實際執行。

JUnit的已經提供了規則的幾個有用的實現。例如, TemporaryFolder類在測試執行完畢後會刪除文件。

package first;

import static org.junit.Assert.assertTrue;

import java.io.File;
import java.io.IOException;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

public class RuleTester {

  @Rule
  public TemporaryFolder folder = new TemporaryFolder();
  
  @Test
  public void testUsingTempFolder() throws IOException {
    File createdFolder = folder.newFolder("newfolder");
    File createdFile = folder.newFile("myfilefile.txt");
    assertTrue(createdFile.exists());
  }
}


實現TestRule接口可自定義Rule。這個接口的apply(Statement, Description)方法返回Statement實例。Statement即JUnit運行時中的測試,Statement#evaluate()運行它們。Description 描述了單個測試。它經過反射閱讀測試信息。


下例子添加日誌語句到Android應用。

import android.util.Log;

import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

public class MyCustomRule implements TestRule {

    private Statement base;
    private Description description;
    
    @Override
    public Statement apply(Statement base, Description description) {
        this.base = base;
        this.description = description;
        return new MyStatement(base);
    }
    
    public class MyStatement extends Statement {
    
        private final Statement base;
        public MyStatement(Statement base) {
            this.base = base;
        }
        
        @Override
        public void evaluate() throws Throwable {
            Log.w("MyCustomRule",description.getMethodName() + "Started");
            try {
                base.evaluate();
            } finally {
                Log.w("MyCustomRule",description.getMethodName() + "Finished");
            }
        }
    }
}


使用Rule:

@Rule
public MyCustomRule myRule = new MyCustomRule();


更多關於Rule的資料:https://github.com/junit-team/junit/wiki/Rules

分類

public interface FastTests { /* category marker */
}

public interface SlowTests { /* category marker */
}

public class A {
  @Test
  public void a() {
    fail();
  }
  
  @Category(SlowTests.class)
  @Test
  public void b() {
  }
}

@Category({ SlowTests.class, FastTests.class })
public class B {
  @Test
  public void c() {
  }
}

@RunWith(Categories.class)
@IncludeCategory(SlowTests.class)
@SuiteClasses({ A.class, B.class })
// Note that Categories is a kind of Suite
public class SlowTestSuite {
  // Will run A.b and B.c, but not A.a
}

@RunWith(Categories.class)
@IncludeCategory(SlowTests.class)
@ExcludeCategory(FastTests.class)
@SuiteClasses({ A.class, B.class })
// Note that Categories is a kind of Suite
public class SlowTestSuite {
  // Will run A.b, but not A.a or B.c
}

  本部分參考資料:https://github.com/junit-team/junit/blob/master/doc/ReleaseNotes4.8.md


參考資料

python開發自動化測試羣291184506 PythonJava單元白盒測試羣144081101

Unit Testing with JUnit - Tutorial

相關文章
相關標籤/搜索