關於軟體驗證中的單元測試

SpaceLion發表於2021-06-02
為什麼使用單元測試

一般來說,驗證的兩個思路是證明和證偽,分別對應著軟體驗證的形式化驗證和測試。

證明適用於在有明確的邏輯範疇內通過演繹推理(如三段論、假言推理等)進行;證偽可通過舉出反例的方式進行。

軟體領域的性質導致證明的困難及收效甚微,絕大部分都採用測試來驗證。

根據測試目的的不同有功能測試、效能測試等;根據是否瞭解測試物件分為黑盒測試與白盒測試,單元測試屬於白盒測試。

一般來說,開發人員瞭解程式碼的結構,自己走一遍流程或CodeReview等形式基本可以發現bug,但為了團隊中其他人員及日後的維護,單元測試有一定的存在的合理性。

因此需要保持單元測試中方法的獨立性,隔離測試依賴,呼叫其他方法時需要模擬;給定輸入,執行測試,驗證輸出,需要用到斷言。

JAVA常用測試框架junit、mockito、powermock
junit常用註解
@Ignore                                該註解標記的測試方法在測試中會被忽略
@Test(expected=xxxException.class)     斷言該方法會丟擲異常
@Test(timeout=1000)                    執行時間超過設定的值該案例會失敗
@RunWith(JUnit4.class)                 預設執行器
@RunWith(PowerMockRunner.class)        PowerMockRunner執行器
@RunWith(Parameterized.class)          引數化執行器
@Rule
public class ExpectedExceptionsTest {
    @Rule
    public ExpectedException thrown = ExpectedException.none();
    @Test
    public void verifiesTypeAndMessage() {
        thrown.expect(RuntimeException.class);
        thrown.expectMessage("Runtime exception occurred");
        throw new RuntimeException("Runtime exception occurred");
    }
}
引數化測試
@RunWith(Parameterized.class)
public class Testa {
    @Parameterized.Parameters
    public static Collection<?> data() {
        return Arrays.asList(new Object[][] { { "1+2", 3 }, { "1+2+5", 8 }});
    }
    @InjectMocks
    Calculator calc;
    @Parameterized.Parameter(0)
    public String input;
    @Parameterized.Parameter(1)
    public int expected;
    @Test
    public void testCalculate() {
        int r = calc.calculate(this.input);
        assertEquals(this.expected, r);
    }
}
斷言
assertEquals(a, b)    測試a是否等於b(a和b是原始型別數值或者必須為實現比較而具有equal方法)
assertFalse(a)        測試a是否為false(假),a是一個Boolean數值。
assertTrue(a)         測試a是否為true(真),a是一個Boolean數值
assertNotNull(a)      測試a是否非空,a是一個物件或者null。
assertNull(a)         測試a是否為null,a是一個物件或者null。
assertNotSame(a, b)   測試a和b是否沒有都引用同一個物件。
assertSame(a, b)      測試a和b是否都引用同一個物件。
fail(string)          Fail讓測試失敗,並給出指定資訊。
mockito使用介紹
通過程式碼建立
public class UserServiceTest {  
    private UserService userService;  
    private UserDao mockUserDao;  
    @Before
    public void setUp() {
    	mockUserDao = mock(UserDao.class);  
    	userService = new UserServiceImpl();  
    	userService.setUserDao(mockUserDao);  
    }
通過註解建立
public class UserServiceTest {  
    @InjectMocks
    private UserServiceImpl userService;  
    @Mock
    private UserDao mockUserDao;   
}
常用方法
verify
    verify(mock, never()).add();       		  驗證add方法沒有被呼叫
    verify(mock, times(2)).add();      		  驗證add方法被呼叫了2次
    verify(mock, atLeast(n)).someMethod();        方法至少被呼叫n次
    verify(mock, atMost(n)).someMethod();         方法最多被呼叫n次
when
    when(mock.someMethod()).thenReturn(value1).thenReturn(value2);
    when(mock.someMethod()).thenThrow(new RuntimeException());
spy
    List spy = spy(new LinkedList());
    when(spy.get(0)).thenReturn(“foo");
    doReturn("foo").when(spy).get(0);
使用powermock 測試private方法
@RunWith(PowerMockRunner.class)
//類上加註解 @PrepareForTest({xxx.class})
@PrepareForTest({RpaRightsConfService.class})
public class RpaRightsConfServiceTest {
    @InjectMocks
    private RpaRightsConfService rpaRightsConfService;
    @Test
    public void getCellValue() throws Exception{
        Row row = Mockito.mock(Row.class);
        Cell cell = Mockito.mock(Cell.class);
        cell.setCellType(Cell.CELL_TYPE_STRING);
        Mockito.when(row.getCell(Mockito.anyInt())).thenReturn(cell);
        //通過反射
        Method method1 = rpaRightsConfService.getClass().getDeclaredMethod("getCellValue", Row.class,int.class);
        method1.setAccessible(true);// 抑制訪問修飾符,使得私有方法變為可以訪問的
        method1.invoke(rpaRightsConfService,row,3);
        Mockito.verify(row, Mockito.times(1)).getCell(Mockito.anyInt());
        //或者通過PowerMockito.method
        Method method2 = PowerMockito.method(RpaRightsConfService.class, "getCellValue", Row.class,int.class);
        Object x2 = method2.invoke(rpaRightsConfService,row,3);
        assertEquals(Integer.parseInt(x2.toString()),0);
    }
}
使用powermock mock靜態方法/final方法/new
@RunWith(PowerMockRunner.class)
//類上加註解 @PrepareForTest({xxx.class})
@PrepareForTest({JSON.class,HSSFWorkbook.class})
public class RpaRightsConfServiceTest {
    @InjectMocks
    private RpaRightsConfService rpaRightsConfService;
    @Test
    public void save() {
        //mock 靜態
        PowerMockito.mockStatic(JSON.class);
        List<RpaRightsConfVo> rpaRightsConfVoList =new ArrayList<RpaRightsConfVo>();
        rpaRightsConfVoList.add(new RpaRightsConfVo());
        PowerMockito.when(JSON.parseArray(formListJson, RpaRightsConfVo.class)).thenReturn(rpaRightsConfVoList);
        rpaRightsConfService.save();
    }
    @Test
    public void importExcel() throws Exception{
        RpaRightsConfForm form = Mockito.mock(RpaRightsConfForm.class);
        InputStream is = Mockito.mock(InputStream.class);
        FormFile file = Mockito.mock(FormFile.class);
        HSSFWorkbook wb = PowerMockito.mock(HSSFWorkbook.class);
        Mockito.when(form.getFile()).thenReturn(file);
        Mockito.when(file.getInputStream()).thenReturn(is);
        //mock new
        PowerMockito.whenNew(HSSFWorkbook.class).withArguments(is).thenReturn(wb);
        rpaRightsConfService.importExcel(form);
    }
}

相關文章