單元測試的一些分享

背景

最近在給一個客戶作技術諮詢,而後發現了客戶對於單元測試的一個有意思的現象。分享出來,你們一塊兒學習探討一下。html

現狀分析

這裏以java後端項目例,發現客戶寫的測試長下面的樣子。(代碼已經脫敏處理過。)java

@Autowired
    private SampleJob handler;

    @Test
    public void testStart() throws Exception {
        SampleParamVo paramVo = new SampleParamVo();
        paramVo.setStartTime("2021-03-18");
        paramVo.setEndTime("2021-03-18");
        handler.execute(paramVo);
    }
@Autowired
    private SampleHandler handler;

    @Test
    public void testHandler() {
        handler.doHandler(new DateTime("2021-11-26"), null);
    }

那麼這樣的測試代碼有什麼問題呢?編程

  1. 別人看不懂這個測試是在作什麼。首先測試的方法名沒有任何意義,其次測試代碼也只是調用了某個函數。
  2. 沒法運行。這類測試代碼運行每每須要啓動其餘服務或者須要一些特殊的設置。沒法運行就意味着它不能成爲CI跑測試的一部分。
  3. 沒有斷言。沒有斷言就沒法知道測試的代碼的正確性。
  4. 使用了@Autowired這樣的代碼,增長了測試的耦合以及編寫成本。

和客戶深聊了以後發現,原來客戶不一樣的人對單元測試的理解也不同。後端

  • 寫這個代碼的開發人員說,「這些代碼是在開發完成以後作一些自測的輔助腳本。」
  • 有的開發人員說,「咱們是微服務,單元測試須要調用其餘服務,寫起來很麻煩,並且若是其餘服務不可用時,測試也跑不過。」
  • 測試人員說:「單元測試咱們有的,我天天都在寫測試用例,到單元測試的時候我就會把個人用例所有過一遍。」

因此咱們能夠發現,有的開發人員口中的單元測試其實應該屬於集成測試或者E2E測試,有的開發人員徹底沒有寫過單元測試,而測試人員理解單元測試是本身手動測試的時候用的測試用例。框架

那咱們就先來講說什麼是單元測試。ide

什麼是單元測試?

單元測試(unit testing),是指對軟件中的最小可測試單元進行檢查和驗證。

一般在java的世界裏面,單元測試就是指對一個public的方法編寫檢查和驗證的代碼。函數

爲何要寫單元測試?

寫單元測試主要有兩大目的:微服務

  1. 驗證功能實現。
  2. 保護已有功能不被破壞。

當咱們寫完一個方法,咱們如何知道本身寫的方法是定期望工做的呢?這個時候就能夠添加單元測試來驗證咱們的代碼是定期望工做的。即當咱們給定指定的輸入,咱們得到指望的輸出,則咱們說這個功能是符合指望的。單元測試

其次,代碼不是寫了就永遠不變的,當需求變動時,新增需求時,修復bug時,都會修改代碼,而單元測試則能保護咱們已有的功能不被破壞。保護已有功能不會被本身破壞,被新人破壞,被新功能破壞。學習

如何寫單元測試?

下面是一個單元測試的例子

@Test
    public void should_return_fizz_given_input_can_be_divided_by_3() {
        FizzBuzz fizzBuzz = new FizzBuzz(); // Given
        String actual = fizzBuzz.sayIt(6); // When
        Assertions.assertEquals("Fizz", actual); // Then
    }

一個標準的單元測試包含如下幾個部分:

  1. 能描述清楚作了什麼的測試名(方法名)
  2. 單元測試的Given、When、Then具體內容。

    1. Given:初始狀態或前置條件
    2. When:行爲發生
    3. Then:斷言結果

寫好單元測試要主要幾個要點:

  • 由於測試代碼並不會進入生產環境,同時咱們指望測試即文檔,所以測試的名稱寫很長也沒有關係,重要的是能清晰的表達咱們這個測試所覆蓋的用例是什麼。
  • 一個測試只測一種case。
  • 單元測試一般須要覆蓋大量的case來保證咱們的代碼在絕大多數場景下都是定期望工做的。所以要作到這一點能夠參考下面兩大原則。這裏就不詳細講解這兩個原則,具體內容能夠Google。

    • CORRECT原則
    • Right-BICEP原則
  • 單元測試有一個考覈的標準就是測試覆蓋率,指的是咱們的代碼有百分之多少被單元測試測到了。

    • 測試覆蓋率分幾種:行覆蓋率,分支覆蓋率,路徑覆蓋率,條件覆蓋率等。每種均可以單獨設置百分比。一般咱們會看中行覆蓋率和分支覆蓋率。
    • 一般行業裏面常設置測試覆蓋率在85%以上。
    • 爲何不是100%?由於不是全部代碼都能被測到的,好比private的構造函數是沒法被測到的,這種就會下降覆蓋率。
  • 一般全部的自動化測試都是開發人員來寫,好比單元測試,集成測試等。

測試金字塔

說到單元測試,就不得不提測試金字塔,以下圖,最底層是單元測試,最頂層是UI測試。(測試金字塔有好幾種,但道理都是相通的)

看左邊的箭頭,越往下越快,越往上越慢,它主要包括編寫越快,運行越快,定位問題越快等。

看右邊的箭頭,越往下成本越低,越往上成本越高,包括時間成本,金錢成本,人員成本,維護成本等。

測試金字塔

什麼是mock?

咱們在作單元測試的時候,經常可能訪問外部系統或者外部類,這些外部的不可控性會讓咱們的單元測試成本變得很高。

常見的外部不可控性有:HTTP訪問,增刪文件,隨機性,時間相關性,接口類等。

因而開發者便開始探索更廉價的方式來寫單元測試,mock就是其中的解決方案。

mock 對象運行在本地徹底可控環境內,利用 mock 對象模擬被依賴的資源,使開發者能夠輕易的建立一個穩定的測試環境。

mock是Test double理論中的一種,若是對test double理論感興趣,能夠到這裏瞭解更多,這裏就不展開說了。

如何用mock?

仍是以java爲例,java的世界中經常使用的mock框架好比mockito。

下面是一個mock的例子。

@Test
    void should_return_100_when_get_list_size() {
        List map = mock(List.class);
        //當調用list.size()方法時候,返回100
        when(map.size()).thenReturn(100);
        Assert.assertEquals(100, map.size());
    }

單元測試是咱們測試的最小單位,所以咱們只測當前這個public的方法中的實現,而方法中調用第三方類的東西,咱們都應該mock掉。

這樣的好處有兩個:

  1. 不會由於其餘類的不可控性而致使這個測試方法變得難寫。
  2. 其餘類的修改不會致使這個測試方法掛掉。全部的變化都被隔離出去了。

什麼是TDD?

最後再昇華一下,簡單說一說TDD,TDD的全稱是Test driven development,即測試驅動開發。它是極限編程XP中的一個標準實踐。

TDD要求在編寫某個功能的代碼以前先編寫測試代碼,而後只編寫使測試經過的功能代碼,經過測試來推進整個開發的進行。

這樣作有四大好處:

  1. TDD是一個很好的契機,可讓你在考慮解決方案以前先考慮問題。
  2. 首先考慮測試會迫使你首先考慮與代碼的接口。先思考接口能夠幫助你將接口與實現分開。
  3. 簡單設計。
  4. 幾乎100%的測試覆蓋率。

這裏我就不詳細敘述TDD相關的話題了,由於TDD是一個比較大的話題,若是感興趣,下次專門開一個新話題來聊TDD。

相關文章
相關標籤/搜索