如何 mock/unit 测试来自 amazon s3 的下载?

how to mock/unit test downloads from amazon s3?

我是单元 testing/moq 的新手,想知道如何做到这一点。我有一个将文件从 s3 下载到本地的功能。我如何模拟它,使其实际上不使用 transferUtility 从 s3 下载任何内容?

bool downloadFileFromS3(string localPathToSave, string filenameToDownloadAs, string bucketName, string objectKey)
{
    try
    {
        string accessKey = "xxx";
        string secretKey = "xxx";
        // do stuff before
        TransferUtility transferUtility = new TransferUtility(accessKey, secretKey);
        transferUtility.Download( Path.Combine(localPathToSave, filenameToDownloadAs), bucketName, objectKey );
        // do stuff after
        return true;
    }

    catch (Exception e)
    {
        // stuff
    }
}

我已经创建了模拟,但我不知道如何使用它来测试我编写的函数。

[Test]
public void testDownloadFromS3()
{
    string filePath = null;
    string bucketName = null;
    string key = null;

    Mock<Amazon.S3.Transfer.TransferUtility> mock = new Mock<Amazon.S3.Transfer.TransferUtility>();
    //mock.Setup(x => x.Download(filePath, bucketName, key)).Verifiable();
    //Mock.Verify();
}

根据 documentationTransferUtility class 实现了 ITransferUtility 接口。

如果您需要测试您的 downloadFileFromS3,那么您的代码 应该依赖抽象,而不是 SOLID's DIP 所说的具体

private readonly ITransferUtility transferUtility; //should be set via constructor injection

bool downloadFileFromS3(string localPathToSave, string filenameToDownloadAs, string bucketName, string objectKey)
{
    try
    {
        // do stuff before

        transferUtility.Download( Path.Combine(localPathToSave, filenameToDownloadAs), bucketName, objectKey );

        // do stuff after
        return true;
    }

    catch (Exception e)
    {
        // stuff
    }
}

因为 Download method 没有 return 任何你需要做的就是在快乐路径的情况下设置可验证性

[Test]
public void testDownloadFromS3_HappyPath()
{
    //Arrange
    const string localPathToSave = "C:/tmp/";
    const string filenameToDownloadAs = "test.txt";
    const string bucketName = "x";
    const string objectKey = "y";

    Mock<Amazon.S3.Transfer.ITransferUtility> transferUtilMock = new Mock<Amazon.S3.Transfer.ITransferUtility>();
    transferUtilMock
        .Setup(util => util.Download(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
        .Verifiable();

    var sut = new ClassThatContainsTheDownloadFileFromS3(transferUtilMock.Object);

    //Act
    var result = sut.downloadFileFromS3(localPathToSave, filenameToDownloadAs, bucketName, objectKey);

    //Assert
    Assert.IsTrue(result);
    transferUtilMock.Verify(util => util.Download(
            It.Is<string>(filePath => string.Equals(filePath, localPathToSave +filenameToDownloadAs, StringComparison.InvariantCultureIgnoreCase)),
            It.Is<string>(bucket => string.Equals(bucket, bucketName, StringComparison.InvariantCultureIgnoreCase)),
            It.Is<string>(key => string.Equals(key, objectKey, StringComparison.InvariantCultureIgnoreCase)),
        Times.Once);
}
  • 我们已将模拟设置为接受任何参数 (It.IsAny)
  • 我们已经将模拟实例的 Object 属性 传递给包含 downloadFileFromS3
  • 的 class
  • 我用了一个名字sut,指的是System Under Test
  • 我们已经使用 It.Is 来验证 Download 方法是否以我们期望的方式被调用
  • 我们还 pass Times.OnceVerify 以确保 Download 只被调用一次。

这是测试不愉快路径的方法:

[Test]
public void testDownloadFromS3_UnhappyPath()
{
    //Arrange
    const string localPathToSave = "C:/tmp/";
    const string filenameToDownloadAs = "test.txt";
    const string bucketName = "x";
    const string objectKey = "y";

    Mock<Amazon.S3.Transfer.ITransferUtility> transferUtilMock = new Mock<Amazon.S3.Transfer.ITransferUtility>();
    transferUtilMock
        .Setup(util => util.Download(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
        .Throws<System.IO.IOException>();;

    var sut = new ClassThatContainsTheDownloadFileFromS3(transferUtilMock.Object);

    //Act
    var result = sut.downloadFileFromS3(localPathToSave, filenameToDownloadAs, bucketName, objectKey);

    //Assert
    Assert.IsFalse(result);
    transferUtilMock.Verify(util => util.Download(
            It.Is<string>(filePath => string.Equals(filePath, localPathToSave +filenameToDownloadAs, StringComparison.InvariantCultureIgnoreCase)),
            It.Is<string>(bucket => string.Equals(bucket, bucketName, StringComparison.InvariantCultureIgnoreCase)),
            It.Is<string>(key => string.Equals(key, objectKey, StringComparison.InvariantCultureIgnoreCase)),
        Times.Once);
}
  • 如您所见,我们已将 Verifiable 调用替换为 Throws,以便在调用时抛出异常
  • 我也将 Assert.IsTrue 替换为 Assert.IsFalse
    • 我假设在异常情况下 // stuff returns false