1

単体テストしようとしているかなり単純なクラスがあります。私は一般的に単体テストに非常に慣れていないため、ここで何をテストすべきかわかりません。

コーディング方法を理解できる唯一のテスト ケースは、 の null 引数ですstream。それに加えて、 a などの結果をテストする方法がわかりませ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)
        };
    }
}
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;
  }
}

ここで、正しい入力ストリームがリクエスト オブジェクトで送信されることを確認する 1 つのテストがあります。明らかに、モッキング フレームワークは、この動作をテストするための定型コードの多くを削減するのに役立ちます。リクエスト オブジェクトの他のプロパティのテストを書き始めることで、これを拡張できます。エラー ケースは、多くの場合、本番環境の実装クラスで誘発することが困難または不可能な場合があるため、単体テストが真価を発揮できる場所です。

このメソッド/クラスの他のシナリオを完全に単体テストするには、ここに渡すかモックする必要がある他の外部依存関係があります。はConfigurationManager構成ファイルに直接アクセスします。これらの設定を渡す必要があります。Guid.NewGuid基本的には、制御されていないランダム性のソースであり、単体テストにも適していません。をさまざまなサービスへのキー値のプロバイダーとして定義し、IKeySourceそれをモックするか、単にキーを外部から渡すことができます。

最後に、テスト/リファクタリングにかかる​​すべての時間を、それがもたらす価値と比べて比較検討する必要があります。より多くのコンポーネントを分離するために、いつでもレイヤーを追加できますが、レイヤーを追加するたびに利益が減少します。

于 2013-01-12T21:34:00.343 に答える
1

私が見るもの:

  • 構成マネージャーをモックして、バケットとURLの無効なデータを返します。(null、無効なURL、無効なバケット)

  • S3はhttpsをサポートしていますか?その場合はモックし、そうでない場合はモックして、有効なエラーが発生することを確認します。

  • さまざまな種類のストリーム(メモリ、ファイル、その他の種類)を渡します。

  • さまざまな状態のストリームを渡します(空のストリーム、最後まで読み取られたストリーム、...)

  • タイムアウトをパラメーターとして設定できるようにするので、非常に低いタイムアウトでテストして、どのようなエラーが返されるかを確認できます。

  • また、エラーメッセージを確認するために、重複したキーを使用してテストします。GUIDを使用している場合でも、他の誰かがS3 APIを使用してドキュメントを保存し、理論的にはGUIDのように見えるファイルを作成できるが、将来的に競合が発生する可能性があるAmazonサーバーに保存しています(可能性は低いですが、可能)

于 2013-01-12T21:32:20.393 に答える