2013-01-12 113 views
1

我有一個相當簡單的類,我試圖單元測試。一般來說,我對單元測試非常陌生,我不確定我應該在這裏測試什麼。單元測試亞馬遜S3

我可以弄清楚如何編碼的唯一測試用例是stream的空參數。除此之外,我不知道如何測試PutObjectRequest或其他的結果。如果我在這裏使用嘲笑,怎麼樣?

public class AmazonS3Service : IAmazonS3Service 
{ 
    private readonly Uri baseImageUrl; 
    private readonly Uri s3BaseUrl; 
    private readonly string imageBucket; 

    public AmazonS3Service() 
    { 
     imageBucket = ConfigurationManager.AppSettings["S3.Buckets.Images"]; 

     s3BaseUrl = new Uri(ConfigurationManager.AppSettings["S3.BaseAddress"]); 
     baseImageUrl = new Uri(s3BaseUrl, imageBucket); 
    } 

    public Image UploadImage(Stream stream) 
    { 
     if (stream == null) throw new ArgumentNullException("stream"); 
     var key = string.Format("{0}.jpg", Guid.NewGuid()); 

     var request = new PutObjectRequest 
     { 
      CannedACL = S3CannedACL.PublicRead, 
      Timeout = -1, 
      ReadWriteTimeout = 600000, // 10 minutes * 60 seconds * 1000 milliseconds 
      InputStream = stream, 
      BucketName = imageBucket, 
      Key = key 
     }; 

     using (var client = new AmazonS3Client()) 
     { 
      using (client.PutObject(request)) 
      { 
      } 
     } 

     return new Image 
     { 
      UriString = Path.Combine(baseImageUrl.AbsoluteUri, key) 
     }; 
    } 
} 

回答

1

事情我想看看:

  • 模擬配置管理器爲桶和URL返回無效數據。 (空,無效的URL,無效的桶)

  • S3是否支持https?如果是這樣,嘲笑它,如果沒有,嘲笑它,並驗證你得到一個有效的錯誤。

  • 在(內存,文件,其他類型)中傳遞不同種類的流。

  • 通在不同狀態下的流(空流,已經被讀取到 最終流,...)

  • 我將允許超時被設置爲參數,這樣你就可以測試真的很低 超時,看看你回來了什麼錯誤。

  • 我也會測試重複鍵,只是爲了驗證錯誤信息。儘管你使用的是GUID,但你正在存儲到一個亞馬遜服務器,其他人可以使用S3 API來存儲文件,並且理論上可以創建一個似乎是GUID的文件,但可能會在路上產生衝突(不太可能,但可能的話)

+0

其中大部分將被視爲集成測試,對吧?另外,我不確定嘲笑https的意思。 –

+0

通過嘲笑https我的意思是嘲笑配置管理器返回HTTPS和HTTP網址。 –

+0

是的,你說的很多都是集成測試,但那是因爲你的代碼是如何耦合的。 Mike Z的建議提供了一些方法來幫助遏制這種情況。 –

1

你有麻煩的單元測試UploadImage,因爲它被連接到許多其他的外部服務和狀態。靜態調用包括(new)將代碼緊密地耦合到特定實現。你的目標應該是重構這些,這樣你可以更容易地進行單元測試。此外,請記住,在對此課程進行單元測試後,您仍然需要執行涉及實際使用Amazon S3服務的大型測試,並確保上載正確無誤地發生或按預期失敗。通過徹底的單元測試,希望你可以減少這些大的和可能昂貴的測試的數量。

刪除耦合到AmazonS3Client實現可能會給你測試降壓最大的爆炸。我們需要通過撥出new AmazonS3Client來重構。如果沒有這個類的接口,那麼我會創建一個包裝它。然後你需要決定如何注入實現。有許多選項,包括作爲方法參數,構造函數參數,屬性或工廠。

讓我們使用工廠方法,因爲它比其他人更有趣,這很直接。爲了清晰和可讀性,我省略了一些細節。

interface IClientFactory 
{ 
    IAmazonS3Client CreateAmazonClient(); 
} 

interface IAmazonS3Client 
{ 
    PutObjectResponse PutObject(PutObjectRequest request); // I'm guessing here for the signature. 
} 

public class AmazonS3Service : IAmazonS3Service 
{ 
    // snip 
    private IClientFactory factory; 

    public AmazonS3Service(IClientFactory factory) 
    { 
     // snip 
     this.factory = factory; 
    } 

    public Image UploadImage(Stream stream) 
    { 
     if (stream == null) throw new ArgumentNullException("stream"); 
     var key = string.Format("{0}.jpg", Guid.NewGuid()); 

     var request = new PutObjectRequest 
     { 
      CannedACL = S3CannedACL.PublicRead, 
      Timeout = -1, 
      ReadWriteTimeout = 600000, // 10 minutes * 60 seconds * 1000 milliseconds 
      InputStream = stream, 
      BucketName = imageBucket, 
      Key = key 
     }; 

     // call the factory to provide us with a client. 
     using (var client = factory.CreateAmazonClient()) 
     { 
      using (client.PutObject(request)) 
      { 
      } 
     } 

     return new Image 
     { 
      UriString = Path.Combine(baseImageUrl.AbsoluteUri, key) 
     }; 
    } 
} 

單元測試可能是這樣的MSTest的:

[TestMethod] 
public void InputStreamSetOnPutObjectRequest() 
{ 
    var factory = new TestFactory(); 
    var service = new AmazonS3Service(factory); 
    using (var stream = new MemoryStream()) 
    { 
     service.UploadImage(stream); 
     Assert.AreEqual(stream, factory.TestClient.Request.InputStream); 
    } 
} 

class TestFactory : IClientFactory 
{ 
    public TestClient TestClient = new TestClient(); 

    public IAmazonS3Client CreateClient() 
    { 
    return TestClient; 
    } 
} 

class TestClient : IAmazonS3Client 
{ 
    public PutObjectRequest Request; 
    public PutObjectResponse Response; 

    public PutObjectResponse PutObject(PutObjectRequest request) 
    { 
    Request = request; 
    return Response; 
    } 
} 

現在,我們有一個測試驗證正確的輸入流在請求對象送了過來。顯然,一個模擬框架將有助於減少很多樣板代碼來測試這種行爲。您可以通過開始爲請求對象上的其他屬性編寫測試來擴展此功能。錯誤情況是單元測試可以真正發揮作用的地方,因爲在生產實現類中它們往往難以或不可能產生。

要完全測試此方法/類的其他場景,還需要傳入或模擬其他外部依賴項。 ConfigurationManager直接訪問配置文件。這些設置應該被傳入。Guid.NewGuid基本上是不受控制的隨機性的來源,這對單元測試也是不利的。你可以定義一個IKeySource作爲各種服務的關鍵值提供者,並模擬它或者只是從外部傳遞密鑰。

最後,您應該權衡測試/重構所花費的所有時間,以及它爲您提供多少價值。總是可以添加更多圖層來隔離越來越多的組件,但每個添加圖層的收益遞減。

+0

你給出了很多很好的建議。我喜歡ConfigurationManager分離和IKeySource。我正在使用Ninject而不是工廠方法。我明白你的意思了 - 爲TestClient添加一個新層將會呈指數級疊加。相反,我認爲我可以在這裏使用集成測試。 –