簡體   English   中英

單元測試的最佳實踐 class 主要負責調用依賴項的方法,但也包含邏輯

[英]Best practice for Unit Testing class which is mostly responsible to call methods of dependencies, but contains logic as well

假設我有StartCommandHandler負責創建一些包含所需文件的文件。 但是為了做到這一點,我必須給他一組子職責,比如:

  • 檢查文件是否存在於 FTP
  • If Not 從多個來源下載文件到臨時文件夾
  • 然后執行文件夾中的一些腳本
  • 然后在腳本執行后讀取生成的文件
  • 然后從該文件夾創建 zip
  • 然后刪除該文件夾
  • 然后更新數據庫

作為該命令處理程序的結果,我們正在創建包含所有必需文件的文件夾。 現在該文件夾已准備好進行另一項操作。

我剛剛閱讀"Art of the Unit testing" 並開始添加單元測試。 我也遵循SOLID原則。 特別是SRPDIP ,我認為它們是單元測試的先決條件。 所以,我上面提到的大部分事情都是通過特定接口完成的。 因此,該命令處理程序 90% 的工作是調用依賴項的方法。 10% 是這樣的邏輯:

if(!_dependency1.IsAnySomething())
{
     _dependency2.Download();

      var isScriptNeeded = _dependency2.IsScriptNeeded();

      if(isScriptNeeded)
      {
          var res = _dependency3.ExecuteScript();
         _dependency4.SetScriptResult(res.Info, res.Date, res.State);
      }

     _dependency3.Archive();

     _dependency5.DeleteTemp();
}

我已經測試了該命令處理程序的所有依賴項。 但是,hat 命令處理程序還包括一些小邏輯,如是否需要下載文件,或刪除或不刪除臨時文件等等......

我腦子里有很多問題,比如:

  1. 單元測試可能對這些單元沒有意義嗎? 集成測試來拯救? 因為,測試是否檢查所有調用似乎是錯誤的,例如下載后是否調用DeleteTemp ,或者腳本是否執行,或者腳本結果是否以正確的方式傳遞SetScriptResult方法。 它是好的單元測試嗎?
  2. 有什么方法可以重構 class 以使其可測試嗎?

單元測試應該測試代碼的行為,而不是代碼的實現。

考慮單元測試如何增加價值是有幫助的:它們傳達代碼的預期行為,並驗證預期行為是否由實現生成。 它們在您的項目生命周期中增加了兩次價值:第一次是代碼最初實現時,第二次是代碼重構時。

但是,如果單元測試與特定實現緊密相關,那么在重構時它們就無法增加價值。

這從來都不是一門完美的科學,但是了解您是在測試行為還是在執行實現的一種方法是問“如果我重構,這個單元測試會中斷嗎?” 如果重構會破壞測試,那么它就不是一個好的單元測試。

編寫單元測試以簡單地確保調用方法 A,然后是方法 B,然后是方法 C(或其他)通常沒有幫助。 那只是要測試你的實現是不是你的實現,它很可能會阻礙而不是幫助下一個想要重構代碼的開發人員。

相反,請考慮行為以及您的代碼如何與其他對象交互。 嘗試將這些行為中的每一個梳理成單獨的對象,並分別測試這些對象。

例如,您可以將上述代碼分解為三種不同的行為:

  1. 緩存 object 檢查值是否不存在然后調用工廠創建它,
  2. 一個工廠 object 創建一個空目錄,調用構建器 object 來填充它,然后壓縮並刪除它
  3. 一個構建器 object,它將文件下載到一個目錄並運行它在其中找到的腳本。

這些對象中的每一個都有單獨的可測試行為:

class Cache {
    Cache(ValueStore store, ValueFactory factory) { ... }

    object GetValue(object key) {
        if (!store.HasValue(key))
            factory.CreateValue(key);
        return store.GetValue(key);
    }
}

class CacheTest {
   void GetValue_CallsFactory_WhenValueNotInStore() {
      // arrange
      var store = Mock.Of<VaueStore>(_ => _.HasValue() == false);
      var factory = Mock.Of<ValueFactory>();
      var cache = new Cache(store, factory);

      // act
      cache.getValue();

      // assert
      Mock.Get(factory).Verify(_ => _.CreateValue(), Times.Once());
   }
}

您可以對工廠和建造者進行類似的分解,並分別測試他們的行為。

它是好的單元測試嗎?

我擔心你必須對這個話題做出自己的決定。

據我所知,並不是每個人都同意 UT 應該涵蓋哪些內容。 我想說這也取決於你的團隊、你的公司以及你真正想要實現的目標。

  • 有些人盲目地測試一切,不管怎樣,因為你永遠不知道下一個錯誤會是什么。 他們可能是對的。 在您的情況下,他們將為每個依賴項進行多個模擬,以測試通過和未通過的案例。

  • 有人爭辯說,它太昂貴了,無法證明對高級協調類進行統一測試,就像您在這里指出的那樣。 他們可能也是對的。

我非常有信心不這樣做的唯一理由是成本。 或者至少我不知道任何其他的。 如果您的高級 UT 維護成本較低,則沒有理由不這樣做。

不幸的是,有很多情況(在我看來,但我也同意它並不適用於所有地方)是 mocking 難以維護並使 UT 變得毫無價值。

例如(因為我不想被誤解),如果您的依賴項使用低級對象,如 sockets 或文件,這會導致競爭條件和/或意外延遲(在多線程環境中),您不能模擬它們以某種方式幫助您檢測錯誤:您應該模擬那些競爭條件和延遲。

如果您嘗試模仿它們,您將在模擬開發和維護上花費大量精力,您最好增加集成測試以檢測真實環境中的錯誤。

而且,如果您不模擬它們,您的 UT 就不會提供任何安全性,因為可以肯定的是,這些錯誤將來自您沒有模擬的那些競爭條件。

最后,我要指出,這個問題也有一個“教學”方面。 您可能想要實現那些高級 UT,即使它們不能阻止任何事情,也可以使用模擬……只是因為您希望您的同事在允許他們自己破壞它之前了解 UT 的一般規則是如何工作的。

我希望我有更多的線索。 祝你好運。

您也可以對此方法使用單元測試。 這可以通過 mocking 依賴項來完成。 原理如下:

您使用預定義的結果創建依賴項的模擬(通常通過實現依賴項的接口)。 例如,您為dependency2提供一個始終為IsScriptNeeded()返回true的實現。

然后您監視dependency3並檢查它是否被調用(應該被調用!)以及dependency4.SetScriptResult(...)的參數是否與結果匹配。

對於 C#,有 FakeItEasy 和 Moq 這樣的庫,這讓 mocking 變得輕而易舉。 使用 xUnit 和 FakeItEasy 的示例代碼片段:

using System;
using FakeItEasy;
using Xunit;

public class MyTest
{
    [Fact]
    public void Test() 
    {
        // Create the mock
        var mock = A.Fake<IMyDependency>();
        A.CallTo(() => mock.DoSomething()).Returns(true);

        // Create the class to test
        var sut = new SystemUnderTest(mock);
        sut.DoSomethingElse();

        // Assert that mock.DoSomething() was called
        A.CallTo(() => mock.DoSomething()).MustHaveHappened();
    }
}

更新;

由於它負責調用和編排其他東西,您可能會破壞所有依賴項並驗證它們是否以正確的順序被調用,並且根據需要多次調用。 類似於 Moq 在模擬類上提供的 Verify 方法。

        // Explicitly verify each expectation...
        mockSomeClass.Verify(s => s. SetScriptResult(It.IsAny<YourType>()/* or whatever value parameters you may setup snd ecpect it to be called*/), Times.Once());

https://stackoverflow.com/a/9136721/2663492

我相信你在這里所做的和提到的對於單元測試來說已經足夠了,因為單元測試的職責是在假設其他互連單元正常工作的情況下測試一個單元,此外單元測試應該在幾分之一秒內完成,這樣當你或者你的隊友做了一些改變,可以快速驗證這個改變是否沒有破壞現有的功能,所以快走是對 UT 的一個嚴重要求。

話雖如此,我認為你想做的是確保所有這些單元都能很好地協同工作。 這暗示您可能需要一些集成測試來檢查此功能是否正常工作。

取決於系統的復雜性、並行用戶的數量、要求等等,您可以決定是否需要某種負載測試。

我應該強調我在談論單元測試作為一種技術,您可以使用單元測試工具作為工具/媒介來編寫集成和負載測試。 我建議看一下像 Specflow 這樣的 BDD 框架,恕我直言,這將有助於更好地描述和演示實際中的問題和解決方案。 (再次將 BDD 框架作為媒體而不是過程。)

你有兩個if語句。 您需要根據if語句測試您的代碼是否調用了正確的依賴方法。

測試順序方法調用似乎是錯誤的。 對於那種情況,我建議你涵蓋更糟糕的情況。 例如,如果_dependency2.Download(); 拋出異常? 然后

_dependency3.Archive();
_dependency5.DeleteTemp();

方法不應該被調用對嗎? 這是您需要測試的案例。

這些異常的回滾操作是什么? 如果您有回滾操作,那么您需要測試它們是否被調用,以防拋出異常。

關於參數,你不應該在這里測試它們。 您需要測試_dependency3.ExecuteScript()方法是否返回正確的參數。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM