1

我有一个相当简单的类,我正在尝试对其进行单元测试。一般来说,我对单元测试很陌生,我不确定我应该在这里测试什么。

我能弄清楚如何编码的唯一测试用例是stream. 除此之外,我不确定如何测试 aPutObjectRequest或其他什么的结果。如果我应该在这里使用模拟,如何?

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)
        };
    }
}
4

2 回答 2

2

您在单元测试中遇到了麻烦,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基本上是不受控制的随机性的来源,这对单元测试也是不利的。您可以将 an 定义IKeySource为各种服务的键值提供者并对其进行模拟,或者只是将键从外部传递。

最后,您应该权衡测试/重构所花费的所有时间与它给您带来的价值。总是可以添加更多的层来隔离越来越多的组件,但是每增加一层的回报就会递减。

于 2013-01-12T21:34:00.343 回答
1

我会看的东西:

  • 模拟您的配置管理器以返回存储桶和 URL 的无效数据。(空,无效的网址,无效的桶)

  • S3 支持 https 吗?如果是这样模拟它,如果不是,模拟它并验证你得到一个有效的错误。

  • 在(内存、文件、其他类型)中传递不同类型的流。

  • 传入不同状态的流(空流,已读到最后的流,...)

  • 我会允许将超时设置为参数,因此您可以使用非常低的超时进行测试,看看您会得到什么错误。

  • 我还会使用重复的密钥进行测试,只是为了验证错误消息。即使您使用的是 guid,您也将存储到亚马逊服务器,其他人可以使用 S3 API 来存储文档,理论上可以创建一个看似 guid 的文件,但可能会在未来产生冲突(不太可能,但可能的)

于 2013-01-12T21:32:20.393 回答