12

問題の説明

私たちはかなり大きなシステムを持っています。これは、プライベートセッターを使用してデータをプロパティにロードするために使用されていました。特定のシナリオのテストを使用するために、私はプライベートセッターを使用してそれらのプロパティにデータを書き込むために使用しました。

ただし、システムの速度が低下し、不要なものを読み込んでいたため、Lazyクラスを使用して特定のものを遅延読み込みに変更しました。ただし、これらのプロパティにデータを書き込むことができなくなったため、多くの単体テストが実行されなくなりました。

私たちが持っていたもの

テストするオブジェクト:

public class ComplexClass
{
    public DateTime Date { get; private set; }

    public ComplexClass()
    {
        // Sample data, eager loading data into variable
        Date = DateTime.Now;
    }
    public string GetDay()
    {
        if (Date.Day == 1 && Date.Month == 1)
        {
            return "New year!";
        }
        return string.Empty;
    }
}

テストはどのように見えますか:

[Test]
public void TestNewyear()
{
    var complexClass = new ComplexClass();
    var newYear = new DateTime(2014, 1, 1);
    ReflectionHelper.SetProperty(complexClass, "Date", newYear);

    Assert.AreEqual("New year!", complexClass.GetDay());
}

上記のサンプルで使用されているReflectionHelperの実装。

public static class ReflectionHelper
{
    public static void SetProperty(object instance, string properyName, object value)
    {
        var type = instance.GetType();

        var propertyInfo = type.GetProperty(properyName);
        propertyInfo.SetValue(instance, Convert.ChangeType(value, propertyInfo.PropertyType), null);
    }
}

私たちが今持っているもの

テストするオブジェクト:

public class ComplexClass
{
    private readonly Lazy<DateTime> _date;

    public DateTime Date
    {
        get
        {
            return _date.Value;
        }
    }

    public ComplexClass()
    {
        // Sample data, lazy loading data into variable
        _date = new Lazy<DateTime>(() => DateTime.Now);
    }
    public string GetDay()
    {
        if (Date.Day == 1 && Date.Month == 1)
        {
            return "New year!";
        }
        return string.Empty;
    }
}

それを解決しよう

これは1つのサンプルにすぎないことに注意してください。積極的な読み込みから遅延読み込みへのコードの変更は、さまざまな場所で変更されています。すべてのテストのコードを変更したくないので、最良のオプションは仲介者を変更することであるように思われました。ReflectionHelper

これが現在の状態ですReflectionHelper

ところで、この奇妙なコードについて事前に謝罪したいと思います

public static class ReflectionHelper
{
    public static void SetProperty(object instance, string properyName, object value)
    {
        var type = instance.GetType();

        try
        {
            var propertyInfo = type.GetProperty(properyName);
            propertyInfo.SetValue(instance, Convert.ChangeType(value, propertyInfo.PropertyType), null);
        }
        catch (ArgumentException e)
        {
            if (e.Message == "Property set method not found.")
            {
                // it does not have a setter. Maybe it has a backing field
                var fieldName = PropertyToField(properyName);
                var field = type.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);

                // Create a new lazy at runtime, of the type value.GetType(), for comparing reasons
                var lazyGeneric = typeof(Lazy<>);
                var lazyGenericOfType = lazyGeneric.MakeGenericType(value.GetType());
                
                // If the field is indeed a lazy, we can attempt to set the lazy
                if (field.FieldType == lazyGenericOfType)
                {
                    var lazyInstance = Activator.CreateInstance(lazyGenericOfType);
                    var lazyValuefield = lazyGenericOfType.GetField("m_boxed", BindingFlags.NonPublic | BindingFlags.Instance);
                    lazyValuefield.SetValue(lazyInstance, Convert.ChangeType(value, lazyValuefield.FieldType));

                    field.SetValue(instance, Convert.ChangeType(lazyInstance, lazyValuefield.FieldType));
                }

                field.SetValue(instance, Convert.ChangeType(value, field.FieldType));
            }
        }
    }

    private static string PropertyToField(string propertyName)
    {
        return "_" + Char.ToLowerInvariant(propertyName[0]) + propertyName.Substring(1);
    }
}

これを実行しようとして最初に遭遇した問題は、実行時に不明なタイプのデリゲートを作成できなかったため、代わりにの内部値を設定することでそれを回避しようとしましたLazy<T>

レイジーの内部値を設定した後、実際に設定されていることがわかりました。しかし、私がそれに遭遇した問題は、aの内部フィールドがでLazy<T>はなく<T>実際にはであることがわかったということでしたLazy<T>.BoxedLazy<T>.Boxed怠惰な内部クラスなので、どういうわけかそれをインスタンス化する必要があります...

解決策が指数関数的に複雑になっているため、間違った方向からこの問題に取り組んでいる可能性があることに気付きました。多くの人が「ReflectionHelper」の奇妙なメタプログラミングを理解するとは思えません。

これを解決するための最良のアプローチは何でしょうか?これを解決することはできますか、ReflectionHelperそれともすべての単体テストを実行してそれらを変更する必要がありますか?

答えを得た後に編集する

dasblinkenlightから、SetPropertyを汎用にするための回答を得ました。私はコードに変更しました、そしてこれは他の誰かがそれを必要とする場合に備えて最終結果です

ソリューション

public static class ReflectionHelper
{
    public static void SetProperty<T>(object instance, string properyName, T value)
    {
        var type = instance.GetType();

        var propertyInfo = type.GetProperty(properyName);
        var accessors = propertyInfo.GetAccessors(true);

        // There is a setter, lets use that
        if (accessors.Any(x => x.Name.StartsWith("set_")))
        {
            propertyInfo.SetValue(instance, Convert.ChangeType(value, propertyInfo.PropertyType), null);
        }
        else
        {
            // Try to find the backing field
            var fieldName = PropertyToField(properyName);
            var fieldInfo = type.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);

            // Cant find a field
            if (fieldInfo == null)
            {
                throw new ArgumentException("Cannot find anything to set.");
            }

            // Its a normal backing field
            if (fieldInfo.FieldType == typeof(T))
            {
                throw new NotImplementedException();
            } 
            
            // if its a field of type lazy
            if (fieldInfo.FieldType == typeof(Lazy<T>))
            {
                var lazyValue = new Lazy<T>(() => value);
                fieldInfo.SetValue(instance, lazyValue);
            }
            else
            {
                throw new NotImplementedException();
            }
        }
    }

    private static string PropertyToField(string propertyName)
    {
        return "_" + Char.ToLowerInvariant(propertyName[0]) + propertyName.Substring(1);
    }
}

これの重大な変更

変数をnullに設定すると、明示的に型を指定しないと機能しなくなります。

ReflectionHelper.SetProperty(instance, "parameter", null);

になる必要があります

ReflectionHelper.SetProperty<object>(instance, "parameter", null);
4

2 に答える 2

4

SetProperty一般的なメソッドを作成してみてください。

public static void SetProperty<T>(object instance, string properyName, T value)

これにより、のタイプをキャプチャできるはずですvalue。適切に配置すると、リフレクションを実行するのではなく、通常のC#構文でオブジェクトをT構築できます。Lazy<T>

...
Lazy<T> lazyValue = new Lazy<T>(() => value);
...

lazyValueこれで、呼び出しを使用してプロパティ/フィールドにを書き込むことができsetValueます。

これは、すべてではないにしても、多くの単体テストに十分なはずです。

于 2013-03-13T11:28:56.630 に答える
0

クラスをユニットテスト可能にし、関心の分離を促進するには、依存性注入の使用を検討してください。

あなたが持っているべきもの:

public class ComplexClass
{
    private readonly Lazy<DateTime> _date;

    public DateTime Date
    {
        get
        {
            return _date.Value;
        }
    }

    public ComplexClass(Lazy<DateTime> date)
    {
        // Allow your DI framework to determine where dates come from.
        // This separates the concern of date resolution from this class,
        // whose responsibility is mostly around determining information
        // based on this date.
        _date = date;
    }
    public string GetDay()
    {
        if (Date.Day == 1 && Date.Month == 1)
        {
            return "New year!";
        }
        return string.Empty;
    }
}

テストはどのように見えるべきか:

[Test]
public void TestNewyear()
{
    var newYear = new DateTime(2014, 1, 1);
    var complexClass = new ComplexClass(new Lazy<DateTime>(() => newYear));

    Assert.AreEqual("New year!", complexClass.GetDay());
}
于 2013-03-13T18:10:34.197 に答える