日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

教你如何更好的编写JAVA单元测试

發(fā)布時(shí)間:2023/12/29 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 教你如何更好的编写JAVA单元测试 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

如何更好的編寫JAVA單元測(cè)試

如各位希望轉(zhuǎn)載或引用,請(qǐng)注明出處,尊重原創(chuàng),謝謝。如有疑問(wèn)或錯(cuò)誤,歡迎郵件溝通。gitHub地址:https://github.com/thinkingfioa 郵箱地址:thinking_fioa@163.com 博客地址: https://blog.csdn.net/thinking_fioa gitHub項(xiàng)目地址:https://github.com/thinkingfioa/tech-summary gitHub項(xiàng)目代碼地址:https://github.com/thinkingfioa/tech-summary-code

本文重點(diǎn)

單元測(cè)試覆蓋率往往是檢驗(yàn)一個(gè)系統(tǒng)的可靠性指標(biāo),優(yōu)秀的單元測(cè)試能幫助系統(tǒng)提早發(fā)現(xiàn)問(wèn)題。JAVA語(yǔ)言提供了非常好的單元測(cè)試框架,本文將重點(diǎn)介紹: 如何編寫單元測(cè)試Mock+PowerMock使用

1. 背景

開(kāi)發(fā)過(guò)程中,很多重要的業(yè)務(wù)和關(guān)鍵性代碼,都需要單元測(cè)試覆蓋,這樣能保證質(zhì)量。

好的單元測(cè)試用例,能有效提高軟件的質(zhì)量,也方便后期的代碼重構(gòu)和維護(hù)。但是一般來(lái)說(shuō)編寫單元測(cè)試工作量很大,單元測(cè)試代碼維護(hù)成本也很高,因?yàn)槟銟I(yè)務(wù)邏輯發(fā)生了改變,代碼結(jié)構(gòu)發(fā)生了改變,不可能不會(huì)修改單元測(cè)試。

通常單元測(cè)試的代碼,閱讀難度也很高,需要理解具體的業(yè)務(wù)邏輯。建議編寫單元測(cè)試時(shí),當(dāng)需要使用其他類的時(shí)候,盡量使用Mock方法,做到單元測(cè)試依賴越少,后續(xù)修改和理解就更簡(jiǎn)單。

2. 編寫單元測(cè)試的原則

2.1 單元測(cè)試類包路徑管理

建議單元測(cè)試中的test包路徑目錄結(jié)構(gòu)與main包中的目錄結(jié)構(gòu)保持一致。

2.2 單元測(cè)試類名稱管理

建議每個(gè)單元測(cè)試類測(cè)試功能單一,僅針對(duì)性測(cè)試指定類的方法。比如文件Father.java中類名稱為Father,那么我們?cè)趖est新建一個(gè)相同的包結(jié)構(gòu)目錄,并在新建后的目錄下新建FatherTest.java文件,類名為FatherTest。

單元測(cè)試中每個(gè)測(cè)試方法以testXXX()開(kāi)頭

Father.java
package org.thinking.fioa;public class Father {public void growUp() throws Exception {} }
FatherTest.java
package org.thinking.fioa;public class FatherTest {public void testGrowUp() throws Exception {} }

3. POM依賴

JAVA語(yǔ)言下單元測(cè)試比不可少三個(gè)依賴包,需要配置到pom.xml下。powermock + mockito + junit。

<!-- unit --><dependency><groupId>org.powermock</groupId><artifactId>powermock-module-junit4</artifactId><version>2.0.2</version><scope>test</scope></dependency><dependency><groupId>org.powermock</groupId><artifactId>powermock-api-mockito2</artifactId><version>2.0.2</version><scope>test</scope></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><dependency><groupId>org.mockito</groupId><artifactId>mockito-core</artifactId><version>2.20.1</version><scope>test</scope></dependency> <!-- unit end -->

4. 基礎(chǔ)知識(shí)

Java使用的Junit4常用的annotation

4.1 靜態(tài)方法

@BeforeClass ----- 針對(duì)所有測(cè)試,只執(zhí)行一次。方法簽名必須是static void
@AfterClass ----- 針對(duì)所有測(cè)試,只執(zhí)行一次。方法簽名必須是static void

4.2 非靜態(tài)方法

@Before ----- 初始化方法。每個(gè)@Test測(cè)試方法前都會(huì)執(zhí)行一次
@Test ----- 測(cè)試方法。每個(gè)@Test測(cè)試方法都會(huì)創(chuàng)建一個(gè)實(shí)例對(duì)象
@After ----- 釋放資源。每個(gè)@Test測(cè)試方法后都會(huì)執(zhí)行一次

4.3 執(zhí)行順序

@BeforeClass -> {類的構(gòu)造函數(shù) -> @Before -> @Test -> @After} , 類的構(gòu)造函數(shù) -> {@Before -> @Test -> @After} … -> @AfterClass

其中每個(gè)@Test方法執(zhí)行前會(huì)創(chuàng)建新的XxxTest實(shí)例, 單個(gè)@Test方法執(zhí)行前后會(huì)執(zhí)行@Before和@After方法

4.4 斷言的常方法

assertEquals(100, x) ----- 相等
assertArrayEquals(100, x) ----- 數(shù)組相等
assertNull(x) ----- 斷言為null
assertTrue(x) ----- 斷言為true
assertNotEquals ----- 斷言不相等
expected = Exception.class ----- 異常測(cè)試
timeout=1000 ----- 超時(shí)時(shí)間

5. 單元測(cè)試編寫參考用例

詳細(xì)代碼可參考tech-summary-code

下面舉例介紹四大類單元測(cè)試方法,這四類單元測(cè)試用例能基本滿足大家日常編寫單元測(cè)試的功能

序號(hào)名稱說(shuō)明
1基礎(chǔ)單元測(cè)試基礎(chǔ)用例
2使用單例設(shè)計(jì)模式單例是開(kāi)發(fā)中最長(zhǎng)使用的設(shè)計(jì)模式
3依賴其他類面向?qū)ο笳Z(yǔ)言,封裝是一大特性
4類對(duì)象都是私有屬性類的屬性都是私有,或者方法是私有的,可通過(guò)反射方法來(lái)編寫單元測(cè)試

5.1 基礎(chǔ)單元測(cè)試用例

基礎(chǔ)單元測(cè)試是最被經(jīng)常使用的

5.1.1 類CommonMethod.java

測(cè)試類CommonMethod.java有如下的對(duì)象公開(kāi)方法,為每個(gè)方法編寫單元測(cè)試

public class CommonMethod {public boolean success() {return true;}public int age() {return 100;}public int[] arrayOfInt() {return new int[]{1, 2, 3, 4};}public Object isNull() {return null;}public void throwException() {throw new NullPointerException();}public void timeout() throws InterruptedException {Thread.sleep(500L);} }

5.1.2 測(cè)試類CommonMethodTest.java

import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue;import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test;public class CommonMethodTest {private CommonMethod method;/*** 僅執(zhí)行一次*/@BeforeClasspublic static void setUpBeforeClass() {System.out.println("Before Class.");}/*** 僅執(zhí)行一次*/@AfterClasspublic static void setUpAfterClass() {System.out.println("After Class.");}@Beforepublic void setUpBefore() {System.out.println("Before.");method = new CommonMethod();}@Afterpublic void setUpAfter() {System.out.println("After.");}@Testpublic void testSuccess() {System.out.println("testSuccess.");assertTrue(method.success());}@Testpublic void testAge() {System.out.println("testAge.");assertEquals(100, method.age());}@Testpublic void testArrayOfInt() {System.out.println("testArrayOfInt.");int[] copyArray = {1, 2, 3, 4};assertArrayEquals(copyArray, method.arrayOfInt());}@Testpublic void testIsNull() {System.out.println("testIsNull.");assertNull(method.isNull());}@Test(expected = NullPointerException.class)public void testThrowException() {System.out.println("testThrowException.");method.throwException();}@Test(timeout = 1000)public void testTimeout() throws InterruptedException {System.out.println("testTimeout.");method.timeout();} }

5.2 Mock一個(gè)單例對(duì)象

項(xiàng)目中最常使用的設(shè)計(jì)模式就是單例模式,開(kāi)發(fā)某個(gè)類時(shí)可能需要依賴這些單例模式。編寫該類單元測(cè)試時(shí),建議使用Mock方法構(gòu)建另一個(gè)單例對(duì)象供單元測(cè)試使用,而不是直接使用代碼中的單例對(duì)象。

5.2.1 編寫的類

類Line.java方法midPoint()方法使用到了單例對(duì)象MathInstance.getInstance()。我們使用Mock方式創(chuàng)建單例對(duì)象MathInstance。

public class Line {private final Point p1;private final Point p2;public Line(Point p1, Point p2) {this.p1 = p1;this.p2 = p2;}public Point midPoint() {if (p1 == null || p2 == null) {throw new NullPointerException("p1 or p2 is null");}// 使用到單例模式return MathInstance.getInstance().midPoint(p1, p2);} }public class Point {private int x;private int y;public Point(int x, int y) {this.x = x;this.y = y;}public int getX() {return x;}public int getY() {return y;} }public class MathInstance {public static MathInstance getInstance() {return SingleInstanceHolder.INSTANCE;}public Point midPoint(Point p1, Point p2) {throw new UnsupportedOperationException("HelloWorld not supported");}private static class SingleInstanceHolder {private static final MathInstance INSTANCE = new MathInstance();} }

5.2.2 單元測(cè)試類

import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.powermock.api.mockito.PowerMockito.mockStatic;import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.powermock.modules.junit4.PowerMockRunner;@RunWith(PowerMockRunner.class) @PrepareForTest({MathInstance.class}) @PowerMockIgnore({"javax.management.*", "javax.script.*"}) public class LineTest {private Line line;@Mockprivate Point p1;@Mockprivate Point p2;@Mockprivate Point mid;/*** Mock一個(gè)單例對(duì)象**/@Mockprivate MathInstance mathInstance;@Beforepublic void setUp() {when(mid.getX()).thenReturn(5);when(mid.getX()).thenReturn(50);line = new Line(p1, p2);// 單例靜態(tài)方法進(jìn)行打樁,供單元測(cè)試使用mockStatic(MathInstance.class);when(MathInstance.getInstance()).thenReturn(mathInstance);when(mathInstance.midPoint(p1, p2)).thenReturn(mid);}@Test(expected = NullPointerException.class)public void testMidPointOfNull() {Line localLine = new Line(null, null);localLine.midPoint();}@Testpublic void testMidPoint() {assertEquals(mid, line.midPoint());verify(mathInstance, times(1)).midPoint(p1, p2);} }

5.3 依賴別的類的單元測(cè)試

我們編寫類A時(shí)候,可能會(huì)使用到類B的對(duì)象,也就是類A會(huì)將類B封裝到自己的內(nèi)部,作為自己的私有屬性。

通常有兩種方式來(lái)實(shí)現(xiàn)這樣的封裝:

  • 類A的構(gòu)造函數(shù)中有一個(gè)參數(shù)是類B,通過(guò)傳參傳入
  • 類A中直接創(chuàng)建類B的對(duì)象
  • 5.3.1 通過(guò)構(gòu)造函數(shù)傳參數(shù)

    通過(guò)構(gòu)造函數(shù)傳參,建議直接Mock類B的對(duì)象。這樣我們可以非常方便的為類B對(duì)象打樁。

    5.3.1.1 編寫的類

    類PowerController通過(guò)構(gòu)造函數(shù)參入?yún)?shù)PowerService對(duì)象

    public class PowerController {private final PowerService service;// 參數(shù)傳入public PowerController(PowerService service) {this.service = service;}public void saveUser(List<String> userList) {if (null == userList || userList.isEmpty()) {throw new IllegalArgumentException("userList is empty");}service.saveUser(userList);}public int deleteUser() {return service.deleteUser();} }public class PowerService {public void saveUser(List<String> userList) {throw new UnsupportedOperationException("not supported");}public int deleteUser() {throw new UnsupportedOperationException("not supported");} }
    5.3.1.2 單元的類
    import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when;import java.util.ArrayList; import java.util.List; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.powermock.modules.junit4.PowerMockRunner;@RunWith(PowerMockRunner.class) public class PowerControllerTest {private static final String USER_NAME = "thinking_fioa";/*** Mock一個(gè)參數(shù)**/@Mockprivate PowerService service;private PowerController controller;@Beforepublic void setUp() {controller = new PowerController(service);}@Testpublic void testSaveUser() {List<String> userList = new ArrayList<>();userList.add(USER_NAME);controller.saveUser(userList);verify(service, times(1)).saveUser(anyList());}@Test(expected = IllegalArgumentException.class)public void testSaveUserOfEmpty() {List<String> userList = new ArrayList<>();controller.saveUser(userList);}@Testpublic void testDeleteUser() {// 為Mock的對(duì)象打樁when(service.deleteUser()).thenReturn(987);assertEquals(987, controller.deleteUser());} }

    5.3.2 對(duì)象內(nèi)部直接創(chuàng)建類B

    如果是在類A中直接創(chuàng)建出類B的對(duì)象,而不是通過(guò)構(gòu)造函數(shù)傳參。我們需要使用PowerMockito.whenNew來(lái)實(shí)現(xiàn)打樁。

    5.3.2.1 編寫的類
    public class ConstructorMethod {private final InnerService service;public ConstructorMethod() {// 內(nèi)部直接創(chuàng)建service = new InnerService();}public void sayWords(List<String> words) {if (null == words || words.isEmpty()) {throw new IllegalArgumentException("words is empty");}for (String word : words) {service.sayWord(word);}}public int removeWords() {return service.removeWords();} }public class InnerService {public void sayWord(String word) {throw new UnsupportedOperationException("not supported");}public int removeWords() {throw new UnsupportedOperationException("not supported");} }
    5.3.2.2 單元測(cè)試的類
    import static org.junit.Assert.assertNotEquals; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.powermock.api.mockito.PowerMockito.whenNew;import java.util.ArrayList; import java.util.List; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.powermock.core.classloader.annotations.PowerMockIgnore; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner;@RunWith(PowerMockRunner.class) @PrepareForTest({ConstructorMethod.class}) @PowerMockIgnore({"javax.management.*", "javax.script.*"}) public class ConstructorMethodTest {private static final String WORD = "ppp";private static final String WORD2 = "courage";private ConstructorMethod method;@Mockprivate InnerService service;@Beforepublic void setUp() throws Exception {// 打樁類InnerService的構(gòu)造函數(shù)方法whenNew(InnerService.class).withAnyArguments().thenReturn(service);method = new ConstructorMethod();}@Testpublic void testSayWords() {List<String> words = new ArrayList<>();words.add(WORD);words.add(WORD2);method.sayWords(words);verify(service, times(words.size())).sayWord(anyString());}@Test(expected = IllegalArgumentException.class)public void testSaveUserOfEmpty() {List<String> words = new ArrayList<>();method.sayWords(words);}@Testpublic void testDeleteUser() {// 打樁when(service.removeWords()).thenReturn(1987);assertNotEquals(987, method.removeWords());} }

    5.4 基于反射的單元測(cè)試

    Java語(yǔ)言開(kāi)發(fā)時(shí),通常會(huì)將屬性設(shè)置為private,這種情況下,可以通過(guò)反射方式來(lái)實(shí)現(xiàn)賦值,供單元測(cè)試使用。

    5.4.1 編寫的類

    public class ReflectMethod {private String name;private int age;private ReflectMethod(String name, int age) {throw new UnsupportedOperationException("not ready");}public ReflectMethod() {}/*** 成年人** @return*/public boolean isAdult() {return age >= 18 && null != name;} }

    5.4.2 單元測(cè)試的類

    import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue;import org.junit.Test; import org.powermock.reflect.Whitebox;public class ReflectMethodTest {private static final String NAME = "thinking_fioa";private static final int AGE = 19;@Test(expected = UnsupportedOperationException.class)public void testConstructor() throws Exception {Whitebox.invokeConstructor(ReflectMethod.class, "ppp", 12);}@Testpublic void testAdult() {ReflectMethod method = new ReflectMethod();// 反射賦值Whitebox.setInternalState(method, "name", NAME);Whitebox.setInternalState(method, "age", AGE);assertTrue(method.isAdult());}@Testpublic void testNotAdult() {ReflectMethod method = new ReflectMethod();Whitebox.setInternalState(method, "name", NAME);Whitebox.setInternalState(method, "age", 14);assertFalse(method.isAdult());} }

    參考資料

    無(wú)

    總結(jié)

    以上是生活随笔為你收集整理的教你如何更好的编写JAVA单元测试的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。