Autofixture: リクエスト:クローンメソッドをIFixtureに追加

作成日 2020年09月24日  ·  9コメント  ·  ソース: AutoFixture/AutoFixture

序章

IFixture Cloneメソッドなど、フィクスチャのコピーを作成する方法があればいいのですが。

現時点では、これを行うための拡張メソッドを作成できます。

public static IFixture Clone( this IFixture fixture)
{
   var cloneFixture = new Fixture();

   cloneFixture.Behaviors.Clear();
   foreach ( var behavior in fixture.Behaviors)
   {
      cloneFixture.Behaviors.Add( behavior );
   }

   cloneFixture.Customizations.Clear();
   foreach ( var customization in fixture.Customizations )
   {
      cloneFixture.Customizations.Add( customization );
   }

   cloneFixture.OmitAutoProperties = fixture.OmitAutoProperties;
   cloneFixture.RepeatCount = fixture.RepeatCount;

   cloneFixture.ResidueCollectors.Clear();
   foreach ( var residueCollector in fixture.ResidueCollectors )
   {
      cloneFixture.ResidueCollectors.Add( residueCollector );
   }

   return cloneFixture;
}

しかし、そのようなコピーを作成する能力は、プロジェクト自体にあるべきだと感じています。

詳細

私のシナリオは、テストで多くのパラメーターを使用してクラスのインスタンスをいくつか作成することです。 さらに、コンストラクターパラメーターを1つのインスタンスの特定の値にし、別のインスタンスの別のコンストラクター値にしたいです。 これを行うにはいくつかの方法があります。 私がやってきた方法は次のとおりです。

fixture.Customizations.Add( new FilteringSpecimenBuilder(
            new FixedBuilder( mediaId ),
            new ParameterSpecification( 
                typeof( ObjectId ), 
                "mediaId" ) ) );
var mediaVM = fixture.Build<MediaViewModel>()   
                     .With( mvm => mvm.ParentMixerId, mixerId )
                     .With( mvm => mvm.TrackId, trackId )
                     .Create();

_ = fixture.Customizations.Remove( fixture.Customizations.Last() );

//...

カスタマイズを削除する理由は、最後に追加されたカスタマイズの方が優先されて使用される可能性があるとは思わずに試したためです。 しかし、そうではありませんでした。

このような拡張メソッドを使用してこれを単純化しようとすると、次のようになります。

public static IFixture AddCustomization<T>( this IFixture fixture, T value, string name )
{
   fixture.Customizations.Add( new FilteringSpecimenBuilder(
         new FixedBuilder( value ),
         new ParameterSpecification(
            typeof( T ),
            name ) ) );
   return fixture;
}

一部の場所では機能するかもしれませんが、他の場所では機能しない可能性があります。これは、オブジェクトがビルドされた後、フィクスチャに不要になったカスタマイズが含まれているため、削除する必要があるためです。

追加されたカスタマイズを削除する行がないようにしたいと思います。 したがって、代わりに、拡張メソッドがフィクスチャのコピーを作成し、そのコピーに変更を追加した場合、作成は期待どおりに機能し、元のフィクスチャは変更されません。

また、オブジェクトの作成後に自動的に削除されるカスタマイズを追加する機能も機能します。

これがすべて理にかなっていることを願っています。

私の問題を検討していただきありがとうございます:smiley:

feature request

最も参考になるコメント

@ajorians活動がないために問題を解決しようとしていましたが、その巨大なコンストラクターのそばを歩くことができませんでした。
デザインを変えることを考えたことがありますか?

あなたのコンストラクターを見ることによって、私はあなたの問題に対するいくつかの問題と潜在的な解決策を見ることができます。

まず第一に、あなたは新しいものと注射可能なものの概念を混ぜ合わせています。
最も簡単な解決策は、注入可能な依存関係(別名サービスインターフェイス)をプロパティに移動し、プロパティ注入を使用してそれらを注入することです。
これにより、 Fixtureをコピーする必要があるという当面の問題が解決され、コンストラクターがより管理しやすくなります。

2つ目は、過剰注入コードの臭いに対処する
これは、最も一般的に一緒に使用されるサービスを別々の抽象化に移動することを意味します。
これは、設計がSRPを尊重するのに役立ち、拡張によって、テストで現時点で実行する必要のある膨大な量のセットアップを削減する必要があります。

クラスはViewModelと呼ばれるので、MVVMアプリケーションがあると思います。 メッセージングモデルを導入することで、アプリケーションをさらに分離できる可能性があります。 おそらくイベントアグリゲーター。 ほとんどのMVVMフレームワークにはそれらが組み込まれているため、それらを利用するために多くのセットアップ作業を行う必要はありません。

これが役立つかどうか教えてください。

全てのコメント9件

@ajoriansこんにちは! 私があなたの例をどのように理解したかから、私たちはあなたが必要とするものをすでに持っているように思えます。 フィクスチャのBuild()メソッドは、実際には内部グラフの不変のコピーを作成しているため、AutoFixtureを永続化せずに「1回限り」のカスタマイズを適用できます。

この遊び場のデモを確認してください。

`` `c#
パブリッククラスモデル
{{
public int Id {get; 設定; }
パブリック文字列名前{取得; 設定; }
}

[事実]
public void Demo()
{{
varfixture = new Fixture();
フィクスチャ。カスタマイズ(c => c.With(m => m.Id、42));

var value1 = fixture.Create<Model>();
Assert.Equal(42, value1.Id);
Assert.NotNull(value1.Name);

var value2 = fixture.Build<Model>()
    .With(m => m.Id, (int genInt) => genInt * 2 /* make it always even */)
    .Without(x => x.Name)
    .Create();
Assert.NotEqual(42, value2.Id);
Assert.Equal(value2.Id % 2, 0);
Assert.Null(value2.Name);

var value3 = fixture.Create<Model>();
Assert.Equal(42, value3.Id);
Assert.NotNull(value3.Name);

}
`` `

それがあなたのニーズを解決するかどうか私たちに知らせてください。 そうでない場合は、それがうまくいかない理由をより具体的に説明してください。

ありがとう!

こんにちは@zvirja

良い一日!

よろしければ; Modelクラスにはsetがなく、コンストラクターを介してのみ設定可能です。 したがって、これに置き換えるとしたら、次のようになります。

public class Model
{
   public Model( int id, string name )
   {
      Id = id;
      Name = name;
   }

   public int Id { get; }
   public string Name { get; }
}

元々指定していなくてすみません。

ですから、私は物事を理解しているので、コンストラクターパラメーターを実行するためにカスタマイズを追加する必要があります。 そして、これらのカスタマイズはBuild()ます。

コンストラクターでセッターなしで多くのことを行う理由のいくつかに興味がある場合は、 readonlyキーワードを使用して、メンバー変数が設定され、メソッドが設定されるまでに設定を解除できないことを確認してください。と呼ばれる。

それがより理にかなっていることを願っています。 これを手伝ってくれてありがとう! :スマイリー:

@ajorians大量の入力引数を持つモデルがあるので、明示的に心配することなく、そのうちの1つだけをカスタマイズしたいという問題を正しく理解していますか。 右?

そうでなければ、あなたはいつでも次のようなものを書くことができます
c# var value2 = fixture.Build<Model>() .FromFactory((int id, string name) => new Model(id, name)) .Create();

残念ながら、パラメーターの数が増えると、スケーリングはあまり良くありません😟

@ajorians大量の入力引数を持つモデルがあるので、明示的に心配することなく、そのうちの1つだけをカスタマイズしたいという問題を正しく理解していますか。 右?

はい、その通りです。

残念ながら、パラメーターの数が増えると、スケーリングはあまり良くありません😟

幸い、 Freezeは、パラメータの数が増えている多くのクラスで機能します。 しかし、はい、コンストラクターパラメーターを実行するためにカスタマイズを使用している場所で私が念頭に置いていたクラスには、現在30超えるパラメーターがあります。
image

そして、おそらく今年の終わりまでにさらに4かそこらを成長させるでしょう。

これは珍しいかもしれません。

しかし、はい、入力コンストラクター引数が大量に増えているので、残りの引数について考えずに、そのうちの1つ(またはいくつか)だけをカスタマイズしたいと思います。 テスト全体でそのタイプを登録または凍結する必要はありません。 そして、カスタマイズを追加するのは約5行のコードであるため、簡潔に言えば。

私は幸運にもそのすべてを行うことができます。 しかし、私がそれを行った方法は、クライアント側のコードでclone拡張メソッドを作成することを含みました。

これがすべて理にかなっていることを願っています。 私と一緒にこれを見てくれてありがとう:smiley:

@ajorians活動がないために問題を解決しようとしていましたが、その巨大なコンストラクターのそばを歩くことができませんでした。
デザインを変えることを考えたことがありますか?

あなたのコンストラクターを見ることによって、私はあなたの問題に対するいくつかの問題と潜在的な解決策を見ることができます。

まず第一に、あなたは新しいものと注射可能なものの概念を混ぜ合わせています。
最も簡単な解決策は、注入可能な依存関係(別名サービスインターフェイス)をプロパティに移動し、プロパティ注入を使用してそれらを注入することです。
これにより、 Fixtureをコピーする必要があるという当面の問題が解決され、コンストラクターがより管理しやすくなります。

2つ目は、過剰注入コードの臭いに対処する
これは、最も一般的に一緒に使用されるサービスを別々の抽象化に移動することを意味します。
これは、設計がSRPを尊重するのに役立ち、拡張によって、テストで現時点で実行する必要のある膨大な量のセットアップを削減する必要があります。

クラスはViewModelと呼ばれるので、MVVMアプリケーションがあると思います。 メッセージングモデルを導入することで、アプリケーションをさらに分離できる可能性があります。 おそらくイベントアグリゲーター。 ほとんどのMVVMフレームワークにはそれらが組み込まれているため、それらを利用するために多くのセットアップ作業を行う必要はありません。

これが役立つかどうか教えてください。

こんにちは@aivascu

@ajorians活動がないため、問題を解決しようとしていました

何か私にできることがあれば、私に知らせてください。 機能リクエストであるため、実装されるまではかなりの量の非アクティブ状態が発生する可能性があります。

デザインを変えることを考えたことがありますか?

現時点では、プリズムアプリケーションになることを検討しています。 それはあなたが言及した項目のいくつかに役立つでしょう。 しかし悲しいことに、 null一度チェックしてreadonlyなり、常に非nullなるように、すべてをコンストラクターに入れるという哲学を変えることはありません。クラスの存続期間を通じて

これが役立つかどうか教えてください。

これはすべて役に立ちます。 世界の人々でさえ、私たちが変更を加えることを検討する必要があると考えていることをチームに知らせます。

しかし、私が尋ねるなら、 IFixtureCloneまたは同様の方法を持つことは合理的ですか? または、どういうわけか、人々がカスタマイズを追加してから、最後に追加されたカスタマイズをpopオフにするか、カスタマイズを追加する前の状態に戻ることを許可しますか? 私は、この望ましい行動を促すシナリオを理解することが重要であり、確かにシナリオにはそもそも多くの問題があることに同意します。 そして、良いユースケースがない限り、複雑さを増したくないと思います。 さて、あなたは次に何が起こるかを選ぶことができます、しかし私が助けることができるならば、ただ私に知らせてください。

読んで検討してくれてありがとう:smiley:

@ajorians私の意見では、 .Clone()メソッドをFixtureクラスに追加するべきではありません。 私が見ているように、1つのテストに対して常に1つのテストフィクスチャのみが存在する必要があります。

過去に、フリーズ解除/取り出し/フリーズワンス機能を実装するようにいくつかのリクエストがありましたが、誰もそれを実装する必要がありませんでした。
その理由は、どのビルダーがフリーズした値を返すのか、または最後に注入された値を確実に識別する簡単な方法がないためだと思います。

あなたができることは、以下のように、成功した解決の特定のカウントの後に要求を無視するリレーを実装することです。 実装を更新して、特定の量のリクエストをスキップすることもできます。 残りは、元の投稿で使用したカスタマイズでカバーする必要があります。

public class CountingRelay : ISpecimenBuilder
{
    public CountingRelay(ISpecimenBuilder builder, int maxCount)
    {
        this.MaxCount = maxCount;
        this.Builder = builder;
    }

    public int Count { get; private set; }
    public int MaxCount { get; }
    public ISpecimenBuilder Builder { get; }

    public object Create(object request, ISpecimenContext context)
    {
        if (this.Count == this.MaxCount) return new NoSpecimen();
        var result = this.Builder.Create(request, context);
        if (!(result is NoSpecimen)) this.Count++;
        return result;
    }
}

こんにちは@aivascu

@ajorians私の意見では、 .Clone()メソッドをFixtureクラスに追加するべきではありません。

わかった。 よろしくお願いします。

あなたができることは、成功した解決の特定のカウントの後に要求を無視するリレーを実装することです

見てみます。

再度、感謝します! :スマイリー:

@ajorians明示的かつ自動のカスタマイズリセットのためのより正式な機能リクエスト#1214を作成しました。 そこで、この機能の進行状況を追跡できます。

このページは役に立ちましたか?
0 / 5 - 0 評価