プロジェクトで式を適用できる場所、またはテストの作成を最適化できる場所を知っていますか

0.歌詞



単体テストについて話しましょう。 大規模で年齢に関連したプロジェクトの場合、「厚い」サービスの問題は非常に重要です。 コンストラクターに渡される多数の依存関係について話しています。 これにテストする必要のある数十のメソッドを追加すると、不要な部分を濡らすのに多くの時間が費やされることが明らかになります。 自動化は問題の解決に役立ちます。 つまり 必要なタイプのインスタンスを作成し、実行中に未使用の依存関係を濡らします。



必要なことがわかりました



var myService = new MyService(A.Fake<ISevice1>(), new Sevice2(), A.Fake<ISevice3>(), A.Fake<ISevice4>(), A.Fake<ISevice5>(), A.Fake<ISevice6>())
      
      





同様のものに置き換えます。 ビルダーパターンを思い出しますか?



  var myService = GetInstance<MyService>().With(new Sevice2()).Subject;
      
      





主なことは、自動化でやり過ぎないことです。 特にプロジェクトにローカルおよび構成されたCIの両方で実行される数万のテストがある場合、パフォーマンスも重要です。



もちろん、私たちは反省せずにはできません。



1.型に関する必要な情報をすべて取得します



まず、インスタンスの作成時に使用するmokaは、次のロジックを使用して_overriddenTypesフィールドに格納されることを明確にします。



  public ObjectBuilder<T> With<TParam>(TParam param) { _overriddenTypes.Add(typeof(TParam), param); return this; }
      
      





次に、作成者向けの情報を準備するコードを検討します。 ここでは、反射が便利になります。



 private T Build() { var type = typeof(T); var constructors = type.GetConstructors().Where(x => x.IsPublic).ToList(); var parameterizedConstructors = constructors.Where(x => x.GetParameters().Any()).ToList(); if (!parameterizedConstructors.Any()) { //     } var constructor = parameterizedConstructors.Single(); var parametersType = constructor.GetParameters().Select(x => x.ParameterType).ToList(); var arguments = parametersType.Select(x => _overriddenTypes.ContainsKey(x) ? _overriddenTypes[x] : Create.Fake(x)).ToArray(); return GetObject(constructor, parametersType, arguments); }
      
      





上記のコードは、モーキングにFakeItEasyライブラリを使用したことを示しています。



2.キャッシングシステム



 private T GetObject(ConstructorInfo constructor, List<Type> constructorParametersType, object[] arguments) { if (_objectCreatorCache != null) { return _objectCreatorCache(arguments); } var creator = GetObjectCreator(constructor, constructorParametersType); _objectCreatorCache = creator; return creator(arguments); }
      
      





ここでは、これがどのように機能するのか、また、どのような理由で同じクラスのフィールドをキャッシュに使用するのかが完全に明確ではない場合があります。 クラス定義は次のとおりであることを考慮します。



 public class ObjectBuilder<T> {...}
      
      





また、静的フィールドもキャッシュに使用されます。



 private static Func<object[], T> _objectCreatorCache;
      
      





ジェネリッククラスの静的フィールドには1つの機能があります(一見明らかではありません)。一意の型で閉じられた新しいジェネリックオブジェクトごとに、独自の静的メンバーが存在します。



3.ランタイムでのインスタンスの作成



頭に浮かぶ最初のオプションは(実際、しばらくの間だけでした)、ActivatorクラスとそのCreateInstance()メソッドを使用することです。



 Activator.CreateInstance<T>();
      
      





パフォーマンスの観点では、これは良い解決策ではなく、キャッシュは問題を引き起こす可能性があります。 新しいテストのたびに、前のテストで使用したものとは異なるサービスの依存関係をモックする必要があると仮定した場合、このオプションは私たちに合わないことがわかります。



式をプラットフォームに導入すると、実行時に型インスタンスを作成する別の、場合によってはより膨大な方法が登場しました。 適用します。



3.1式オブジェクト作成者



なぜこのアプローチは良いのですか? オブジェクトのインスタンスではなく、コンパイルされたラムダになります。 これにより、キャッシュが可能になります。 このアプローチを適用することはお勧めしません;オブジェクトのインスタンスを一度だけ受信する必要があります; Activatorはこのタスクにはるかに速く対処できます。



 private Func<object[], T> GetObjectCreator(ConstructorInfo constructor, List<Type> constructorParametersType) { var param = Expression.Parameter(typeof(object[]), "parameters"); var argsExpressions = new Expression[constructorParametersType.Count]; for (var index = 0; index < constructorParametersType.Count; index++) { var constantIndex = Expression.Constant(index); var paramAccessorExp = Expression.ArrayIndex(param, constantIndex); var paramCastExp = Expression.Convert(paramAccessorExp, constructorParametersType[index]); argsExpressions[index] = paramCastExp; } var newExpression = Expression.New(constructor, argsExpressions); var lambda = Expression.Lambda(typeof(Func<object[], T>), newExpression, param); return (Func<object[], T>)lambda.Compile(); }
      
      





PS興味深い場合は、パフォーマンスの比較を行い、表現を使用して作業を詳細に説明できます。



PSS以下は、このbuilder'aの完全なコードです。



 public class ObjectBuilder<T> { private static Func<object[], T> _objectCreatorCache; private readonly Dictionary<Type, object> _overriddenTypes; private readonly Lazy<T> _subject; public ObjectBuilder() { _overriddenTypes = new Dictionary<Type, object>(); _subject = new Lazy<T>(Build); } public T Subject => _subject.Value; public ObjectBuilder<T> With<TParam>(TParam param) { if (_subject.IsValueCreated) { throw new Exception("Can't change builder options after first call to Object. Please create new one"); } _overriddenTypes.Add(typeof(TParam), param); return this; } private T Build() { var type = typeof(T); var constructors = type.GetConstructors().Where(x => x.IsPublic).ToList(); var parameterizedConstructors = constructors.Where(x => x.GetParameters().Any()).ToList(); if (!parameterizedConstructors.Any()) { //      } var constructor = parameterizedConstructors.Single(); var constructorParametersType = constructor.GetParameters().Select(x => x.ParameterType).ToList(); var arguments = constructorParametersType.Select(x => _overriddenTypes.ContainsKey(x) ? _overriddenTypes[x] : Create.Fake(x)).ToArray(); return GetObject(constructor, constructorParametersType, arguments); } private T GetObject(ConstructorInfo constructor, List<Type> constructorParametersType, object[] arguments) { if (_objectCreatorCache != null) { return _objectCreatorCache(arguments); } var creator = GetObjectCreator(constructor, constructorParametersType); _objectCreatorCache = creator; return creator(arguments); } private Func<object[], T> GetObjectCreator(ConstructorInfo constructor, List<Type> constructorParametersType) { var param = Expression.Parameter(typeof(object[]), "parameters"); var argsExpressions = new Expression[constructorParametersType.Count]; for (var index = 0; index < constructorParametersType.Count; index++) { var constantIndex = Expression.Constant(index); var paramAccessorExp = Expression.ArrayIndex(param, constantIndex); var paramCastExp = Expression.Convert(paramAccessorExp, constructorParametersType[index]); argsExpressions[index] = paramCastExp; } var newExpression = Expression.New(constructor, argsExpressions); var lambda = Expression.Lambda(typeof(Func<object[], T>), newExpression, param); return (Func<object[], T>)lambda.Compile(); } }
      
      








All Articles