天天看點

JAVA自動化之Junit單元測試架構詳解

JAVA自動化之Junit單元測試架構詳解

一、JUnit概述&配置

1、Junit是什麼?

Junit是一個Java 程式設計語言的開源測試架構,用于編寫和運作測試。官網 位址:

https://junit.org/junit4/

2、Maven配置

?xml version="1.0" encoding="UTF-8"?>

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>junit</groupId>
<artifactId>junitTest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>           

二、Assertions 斷言

JUnit提供了一些輔助的函數,用來判斷被測試的方法是否如我們預期的效果一樣正常執行。這些輔助函數被稱之為斷言。

常用斷言:

方法 示例 功能

assertArrayEquals assertArrayEquals("message", expected, actual); 判斷兩個數組是否相等

assertEquals assertEquals("message", "text", "text"); 判斷兩個對象是否相等

assertFalse assertFalse("failure - should be false", false); 判斷表達式是否為false

testAssertTrue assertTrue("failure - should be true", true); 判斷表達式是否為true

assertNotNull assertNotNull("should not be null", new Object()); 判斷是否非空

assertNull assertNull("should be null", null); 判斷是否為空

assertNotSame assertNotSame("should not be same Object", new Object(), new Object()); 判斷是否為不同對象

assertSame assertSame("should be same", aNumber, aNumber); 判斷是否為同一對象

...... ...... ......

三、Test Runners 測試運作器

JUnit中所有的測試方法都是由測試運作器負責執行。當一個類被@RunWith注釋或拓展了一個@RunWith注釋的類,JUnit将會使用引用的類來執行測試,而不是使用JUnit内置的運作器。

org.junit.runner.JUnitCore.runClasses(TestClass1.class, ...);

Specialized Runners:

Suite:Suite是一個标準的運作器,允許手動建構包含來自許多類的測試集。

Parameterized:Parameterized是一個實作參數化測試的标準運作器。運作參數化測試類時,測試方法和測試資料進行合并來建立測試執行個體。

Categories:Categories運作器來制定分類,定義測試被包含或排除。

四、Aggregating tests in suites 套件

測試套件用于捆綁幾個單元測試用例并且一起執行他們,使用@RunWith 和 @Suite 注解。

@RunWith(Suite.class)

@Suite.SuiteClasses({AssertTests.class, CalculatorTest.class})

public class SuiteTest {

// the class remains empty, used only as a holder for the above annotations           

}

五、Test execution order 執行順序

要改變測試執行的順序隻需要在測試類上使用 @FixMethodOrder注解,并指定一個可用的MethodSorter即可:

@FixMethodOrder(MethodSorters.DEFAULT):JUnit預設使用一個确定的,但不可預測的順序

@FixMethodOrder(MethodSorters.JVM): 保留測試方法的執行順序為JVM傳回的順序,每次測試的執行順序有可能會所不同

@FixMethodOrder(MethodSorters.NAME_ASCENDING):根據測試方法的方法名排序,按照詞典排序規則(ASC從小到大遞增)

@FixMethodOrder(MethodSorters.NAME_ASCENDING)

public class ExecutionOrderTest {

@Test
public void testB() {
    System.out.println("second");
}
@Test
public void testA() {
    System.out.println("first");
}
@Test
public void testC() {
    System.out.println("third");
}           

運作結果:

first

second

third

六、Expected Exceptions 異常測試

用于測試某一方法是否抛出了正确的異常。

1、@Test(expected=xxx)方式:當抛出的異常與expected參數指定的異常相同時,測試通過。

2、try...fail...catch...方式:捕捉具體待測語句的異常資訊并斷言,當沒有異常被抛出的時候fail方法會被調用,輸出測試失敗的資訊。

3、ExpectedException Rule方式:使用Rule标記來指定一個ExpectedException,并在測試相應操作之前指定期望的Exception類型。

public class ExpectedExceptionsTest {

//方法一:@Test(expected=xxx)
@Test(expected = IndexOutOfBoundsException.class)
public void empty() {
    new ArrayList<Object>().get(0);
}

//方法二:try...fail...catch...  當沒有異常被抛出的時候fail方法會被調用,輸出測試失敗的資訊。
@Test
public void testExceptionMessage() {
    try {
        new ArrayList<Object>().get(0);
        fail("Expected an IndexOutOfBoundsException to be thrown");
    } catch (IndexOutOfBoundsException anIndexOutOfBoundsException) {
        assertThat(anIndexOutOfBoundsException.getMessage(), is("Index: 0, Size: 0"));
    }
}

//方法三:在測試之前使用Rule标記來指定一個ExpectedException,并在測試相應操作之前指定期望的Exception類型(如IndexOutOfBoundException.class)
@Rule
public ExpectedException thrown = ExpectedException.none();

@Test
public void shouldTestExceptionMessage() throws IndexOutOfBoundsException {
    List<Object> list = new ArrayList<Object>();
    thrown.expect(IndexOutOfBoundsException.class);
    thrown.expectMessage("Index: 0, Size: 0");
    list.get(0);
}           

七、Matchers and assertThat

JUnit4.4引入了Hamcrest架構,Hamcest提供了一套比對符Matcher,這些比對符更接近自然語言,可讀性高,更加靈活。并且使用全新的斷言文法assertThat,結合Hamcrest提供的比對符,隻用這一個方法,就可以實作所有的測試。

assertThat文法:

assertThat(T actual, Matcher matcher);

assertThat(String reason, T actual, Matcher matcher);

其中reason為斷言失敗時的輸出資訊,actual為斷言的值或對象,matcher為斷言的比對器,裡面的邏輯決定了給定的actual對象滿不滿足斷言。

Matchers詳見:

http://hamcrest.org/JavaHamcrest/javadoc/1.3/org/hamcrest/Matchers.html

八、Ignoring tests 忽略測試

方法用 @Ignore 注解了将不會被執行

類用 @Ignore 注解後,其下所有測試方法将不會被執行

public class IgnoreTest {

@Ignore("Test is ignored as a demonstration")
@Test
public void testSame() {
    assertThat(1, is(1));
}           

九、Timeout for tests 逾時測試

@Timeout 注解用來測試特定方法的執行時間。如果測試方法的執行時間大于指定的逾時參數,測試方法将抛出異常,測試結果為失敗。指定的逾時參數機關為毫秒。

1、@Test注解上的timeout參數,作用域為方法,機關毫秒

@Test(timeout = 2000)

public void testSleepForTooLong() throws Exception {
    log += "ran1";
    TimeUnit.SECONDS.sleep(100); // sleep for 100 seconds
}
           

2、Timeout Rule,作用域為測試類

public class TimeoutTests {

public static String log;
private final CountDownLatch latch = new CountDownLatch(1);

@Rule
public Timeout globalTimeout = Timeout.seconds(3); // 3 seconds max per method tested

@Test
public void testSleepForTooLong() throws Exception {
    log += "ran1";
    TimeUnit.SECONDS.sleep(100); // sleep for 100 seconds
}

@Test
public void testBlockForever() throws Exception {
    log += "ran2";
    latch.await(); // will block
}           

十、Parameterized tests 參數化測試

參數化測試允許開發人員使用不同的值反複運作同一個測試。建立參數化測試步驟:

用 @RunWith(Parameterized.class) 來注釋 test 類。

建立一個由 @Parameters 注釋的公共的靜态方法,它傳回一個對象的集合(數組)來作為測試資料集合。

建立一個公共的構造函數,接受測試資料。

為每一列測試資料建立一個執行個體變量。

用執行個體變量作為測試資料的來源來建立測試用例。

1、Constructor方式

@RunWith(Parameterized.class)

public class FibonacciTest {

@Parameters(name = "{index}: fib({0})={1}")
public static Collection<Object[]> data() {
    return Arrays.asList(new Object[][] {
            { 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 }
    });
}
private int fInput;
private int fExpected;
public FibonacciTest(int input, int expected) {
    this.fInput = input;
    this.fExpected = expected;
}
@Test
public void test() {
    assertEquals(fExpected, Fibonacci.compute(fInput));
}           

2、Field injection方式

@Parameters(name = "{index}: fib({0})={1}")
public static Collection<Object[]> data() {
    return Arrays.asList(new Object[][] {
            { 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 }
    });
}
@Parameter // first data value (0) is default
public /* NOT private */ int fInput;
@Parameter(1)
public /* NOT private */ int fExpected;
@Test
public void test() {
    assertEquals(fExpected, Fibonacci.compute(fInput));
}           

十一、Assumptions with assume 假定測試

使用Assumptions類中的假設方法時,當假設不成立時會報錯,但是測試會顯示被ignore忽略執行。也就是當一個類中有多個測試方法時,其中一個假設測試方法假設失敗,其他的測試方法全部成功,那麼該測試類也會顯示測試成功。

假設方法适用于:在不影響測試是否成功的結果的情況下根據不同情況執行相關代碼。

public class AssumptionsTest {

@Test
public void testAssumTrue() {
    System.out.println("test");
    assumeTrue(3>5);
    //該方法中下面所有的代碼在上面假設的條件成立後執行
    //如果上述假設不成立,則會忽略執行該行下面的代碼,并報錯
    System.out.println("assume is true!");
}
@Test
public void testAssumFalse(){
    assumeFalse(3>5);
    System.out.println("assume is true!");
}           

以下文法JUnit5支援:

@Test

public void testAssumTrueMessage() {
    assumeTrue(3<5,
            //第二個參數為當第一個參數不成立時,輸出的自定義錯誤資訊
            () -> "Aborting test: not on developer workstation");
    System.out.println("assume is true!");
}
@Test
public void testAssumeTrueLambda(){
    //這個方法的第一個參數為函數式接口,無參數傳回值為boolean
    assumeTrue(()->{
        System.out.println("in assumeTrue");
        boolean flag = false;
        return flag;
    });
    System.out.println("out assumeTrue");
}
@Test
public void testAssumThat() {
    assumingThat(3>5,
            () -> {
                //與上述方法不同的是,僅目前面假設成立時,才會執行這裡面的語句
                //且隻會影響到該lambda表達式中的代碼
                assertEquals(2, 2);
            });
    //此處的斷言不受上述assumingThat限制,在所有情況下都會執行
    System.out.println("no effect");
    assertEquals("a string", "a string");
}
           

十二、Rules 規則

一個JUnit Rule就是一個實作了TestRule的類,用來在每個測試方法的執行前後執行一些代碼。

1、架構自帶的Rule

JUnit自帶很多已經實作過好了的JUnit Rule,比如Timeout,ExpectedException等等。

2、自定義Rule

自定義一個Rule就是implement一個TestRule interface,實作一個叫apply()的方法。

例:在測試方法運作之前,記錄測試方法所在的類名和方法名,然後在測試方法運作之後列印出來。

public class MethodNameExample implements TestRule {

@Override
public Statement apply(final Statement base, final Description description) {
    return new Statement() {
        @Override
        public void evaluate() throws Throwable {
            //base.evaluate()之前為測試方法運作之前所做操作
            String className = description.getClassName();
            String methodName = description.getMethodName();
            //運作測試方法
            base.evaluate();
            //base.evaluate()之後為測試方法運作之後所做操作
            System.out.println("Class name: "+className +", method name: "+methodName);
        }
    };
}           

public class RuleTest2 {

@Rule
public MethodNameExample methodNameExample = new MethodNameExample();
@Test
public void addition_isCorrect() throws Exception {
    assertEquals(4, 2 + 2);
}
@Test
public void mulitiplication_isCorrect() throws Exception {
    assertEquals(4, 2 * 2);
}           

十三、Theories

在參數化測試中,我們需要給定所有具體的測試資料組。而在Theories測試中,使用者隻需給定了一些資料,JUnit自動利用這些資料組合出各種各種可能的組合來執行測試。

1、内置實作

(1)@DataPoints注解靜态變量方式

@RunWith(Theories.class)

public class TheoryTest {

//允許的最大誤差
private static final double DELTA = 0.01;
/*@DataPoints注解靜态變量*/
@DataPoint
public static int ZERO = 0;
@DataPoint
public static int TWO = 2;
@DataPoint
public static int EIGHT = 8;
//标志這個測試為Theory測試
@Theory
public void testDivide(int dividend, int divisor) {
    //跳過除數為0的case
    assumeThat(divisor, not(0));
    //Calculator.divide(dividend, divisor)方法傳回他們相除的結果
    assertEquals(dividend / divisor, Calculator.divide(dividend, divisor), DELTA);
    System.out.println("Passed with: dividend=" + dividend + ", divisor=" + divisor);
}           

(2)@DataPoints注解靜态方法方式

//允許的最大誤差
private static final double DELTA = 0.01;
/*@DataPoints注解一個靜态方法*/
@DataPoints
public static int[] getTestData() {
    return new int[]{0, 2, 8};
}
//标志這個測試為Theory測試
@Theory
public void testDivide(int dividend, int divisor) {
    //跳過除數為0的case
    assumeThat(divisor, not(0));
    //Calculator.divide(dividend, divisor)方法傳回他們相除的結果
    assertEquals(dividend / divisor, Calculator.divide(dividend, divisor), DELTA);
    System.out.println("Passed with: dividend=" + dividend + ", divisor=" + divisor);
}           

@DataPoint用于注解靜态變量(或靜态方法),表示這個變量是個資料點。當執行testDivide這個Theory測試時,JUnit會把所有的DataPoint資料兩兩組合,形成一組組的測試資料,并用這些資料分别執行測試。執行上面的測試會輸出以下結果:

Passed with: dividend=0, divisor=2

Passed with: dividend=0, divisor=8

Passed with: dividend=2, divisor=2

Passed with: dividend=2, divisor=8

Passed with: dividend=8, divisor=2

Passed with: dividend=8, divisor=8

(3)如果需要限定某個參數,可以使用@TestOn注解

import org.junit.experimental.theories.Theories;

import org.junit.experimental.theories.Theory;

import org.junit.experimental.theories.suppliers.TestedOn;

import org.junit.runner.RunWith;

import static org.junit.Assert.assertEquals;

//允許的最大誤差
private static final double DELTA = 0.01;
//如果需要限定某個參數,可以使用@TestOn注解
@Theory
public void testDivide2(
        @TestedOn(ints = {0, 2, 8}) int dividend,
        @TestedOn(ints = {2, 8}) int divisor
) {
    assertEquals(dividend / divisor, Calculator.divide(dividend, divisor), DELTA);
    System.out.println("Passed with: dividend=" + dividend + ", divisor=" + divisor);
}           

2、自定義實作

JUnit預設隻提供了一個int型的簡單 Parameter Supplier 實作,而Theory機制真正的價值在于,能參考@TestedOn的做法,相對簡單的完全自定義出可重用 Parameter Supplier,适應于各種複雜要求的限定範圍參數值測試場景,滿足開發者所需的高度動态自定義範圍取值自動化測試,同時保留與一般@Test相同的強大相容性。

例:

(1)定義annotation注解接口Between

@Retention(RetentionPolicy.RUNTIME)

// 聲明注解接口所使用的委托處理類

@ParametersSuppliedBy(BetweenSupplier.class)

public @interface Between{

// 聲明所有可用參數,效果為 @Between([first = int,] last = int)
int first() default 0;  // 聲明預設值
int last();           

(2)定義委托處理類 BetweenSupplier

public class BetweenSupplier extends ParameterSupplier {

@Override
public List<PotentialAssignment> getValueSources(ParameterSignature sig) {
    // 自定義實參值清單
    List<PotentialAssignment> list = new ArrayList<PotentialAssignment>();
    // 擷取注解變量
    Between between = sig.getAnnotation(Between.class);
    // 擷取通過注解@Between傳入的first值
    int first = between.first();
    // 擷取通過注解@Between傳入的last值
    int last = between.last();
    for (int i = first; i <= last; i++) {
        // PotentialAssignment.forValue(String name, Object value)
        // name為value的描述标記,沒實際作用
        // value為實參可選值
        list.add(PotentialAssignment.forValue("name", i));
    }
    return list;
}           

(3)調用方式

public class TheoryDefinedTest {

@Theory
public final void test(@Between(last = 0) int i, @Between(first = 3, last= 10) int j) {
    // i 取值為 0(first預設=0,last=0),j 取值為 3-10
    System.out.println("i="+i+"  j="+j);
}           

(4)運作結果

i=0 j=3

i=0 j=4

i=0 j=5

i=0 j=6

i=0 j=7

i=0 j=8

i=0 j=9

i=0 j=10

十四、Test fixtures

Test Fixture是指一個測試運作所需的固定環境,也是就是測試運作之前所需的穩定的、公共的可重複的運作環境,這個“環境”不僅可以是資料,也可以指對被測軟體的準備,例如執行個體化被測方法所依賴的類、加載資料庫等等。

@Before - 在每個@Test方法之前運作

@After - 在每個@Test方法之後運作

@BeforeClass - 在所有的@Test方法之前運作一次

@AfterClass - 在所有的@Test方之後運作一次

注:

1、如果建立一個子類繼承有fixture注解的父類,那麼子類中的@Before方法會在測試方法之前、父類的@Before執行之後執行。

2、如果@Before方法裡抛出了異常,@Test方法會跳過,但是@After還是會執行

3、每個測試方法都會在單獨的測試類的執行個體裡面運作,@BeforeClass在測試執行個體建立之前執行

public class FixtureTest {

private static int quantity = 0;
public FixtureTest() {
    quantity++;
}
@BeforeClass
public static void breforeTestOnlyOnce() throws Exception {
    System.out.println("Run before all test only once..."+ quantity);
}
@AfterClass
public static void afterTestOnlyOnce() throws Exception {
    System.out.println("Run after all test only once..."+ quantity);
}
@Before
public void beforePerTest() {
    System.out.println("Run before per test ..."+ quantity);
}
@After
public void afterPerTest() {
    System.out.println("Run after per test ..."+ quantity);
}
//Test Method
@Test
public void testOne() {
    System.out.println("testOne Start..."+ quantity);
}
@Test
public void testTwo() {
    System.out.println("testTwo Start..."+ quantity);
}           

Run before all test only once...0

Run before per test ...1

testOne Start...1

Run after per test ...1

Run before per test ...2

testTwo Start...2

Run after per test ...2

Run after all test only once...2

十五、Categories 用例分類

category 和 testSuite的比較:testSuite是類級分組(xx.class),category是用例級分組(@Test),category是testSuite的更新

category使用步驟:

1、建立好測試類,及測試類中的測試用例

2、建立接口:按用例的分組來建立

3、@Category注解:将用例用@Category注解分組

4、建立類來執行這些分組的類

public interface FastTests { / category marker / }

public interface SlowTests { / category marker / }

public class A {

@Test

public void a() {

fail();           

@Category(SlowTests.class)

public void b() {

}

@Category({SlowTests.class, FastTests.class})

public class B {

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

@ExcludeCategory(FastTests.class)

// Will run A.b, but not A.a or B.c

十六、總結

如果你覺得此文對你有幫助,如果你對軟體測試、接口測試、自動化測試、面試經驗交流感興趣歡迎加入軟體測試技術群:695458161,群裡發放的免費資料都是筆者十多年測試生涯的精華。還有同行大神一起交流技術哦。

獨行踽近,衆行緻遠!歡迎加入《軟體測試技術交流群:695458161》各種軟體測試資料,WEB自動化,接口自動化,APP自動化,jenkins持續內建下的不要太爽! 還可以定期免費領取測試進階資料哦!

作者:諸葛

原文位址

https://www.cnblogs.com/csmashang/p/12696717.html