Unit testy pro hluboké klonování

hlasů
10

Řekněme, že mám komplex .NET třídy, se spoustou polí a jiných členů třídy objektů. Musím být schopen generovat hluboké klon tohoto objektu - takže píšu metodu clone () a provádět je s jednoduchým BinaryFormatter serializaci / rekonstruovat - nebo možná dělám hluboké klon pomocí nějakou jinou techniku, která je více náchylné k chybám a rád bych, aby se ujistil je testován.

OK, takže teď (ok, jsem měl udělat to první) bych chtěl psát testy, které pokrývají klonování. Všichni členové skupiny jsou soukromé, a moje architektura je tak dobrý (!), Že jsem potřeboval napsat stovky společných vlastností nebo jiné přístupové. Třída není IComparable nebo IEquatable, protože to není potřeba aplikací. Mé unit testy jsou v samostatné sestavy k výrobním kódem.

Jaké postupy se lidé berou na testování, které klonovaný objekt je dobrá kopie? Píšete (nebo přepsat , jakmile zjistíte, že je třeba pro klon) všechny jednotkové testy pro třídu tak, aby mohly být vyvolán buď za ‚panenský‘ objektu nebo s klonem to? Jak byste vyzkoušet, zda část klonování nebylo dost hluboko - protože to je přesně ten druh problému, který může dát odporná-k-najít chyby později?

Položena 14/08/2008 v 13:49
zdroj uživatelem
V jiných jazycích...                            


6 odpovědí

hlasů
2

Tam je opravdu jasné řešení, které nebere zdaleka tolik práce:

  1. Serializaci objektu do binárního formátu.
  2. Naklonovat objekt.
  3. Serializaci klon do binárního formátu.
  4. Porovnejte bajtů.

Za předpokladu, že serializace funguje - a to lepší, protože jej používáte ke klonování - to by mělo být snadno udržovatelné. Ve skutečnosti bude zapouzdřen ze změn ve struktuře vaší třídy úplně.

Odpovězeno 23/03/2010 v 05:22
zdroj uživatelem

hlasů
2

Ty metoda zkoušení bude záviset na typu řešení vám přijít s. Máte-li psát nějaký zvyk klonování kód a je nutné ručně provést, že v každém cloneable typ, pak byste měli opravdu vyzkoušet klonování každého z těchto typů. Případně, pokud jste se rozhodli jít obecnější trasu (pokud by výše uvedené úvahy by se vešly v), vaše testy by potřebovat pouze k testování konkrétní scénáře, které klonovacím systémem, se bude muset vypořádat s.

Chcete-li odpovědět na vaše konkrétní otázky:

Píšete (nebo přepsat, jakmile zjistíte, že je třeba pro klon) všechny jednotkové testy pro třídu tak, aby mohly být vyvolán buď virgin "objekt nebo klon to?

Měli byste mít zkoušky pro všechny metody, které lze provádět na obou původních a klonovaných objektů. Všimněte si, že by to mělo být docela snadné vytvořit jednoduchý testovací návrh podpořit, aniž ruční aktualizace logiku pro každou zkoušku.

Jak byste vyzkoušet, zda část klonování nebylo dost hluboko - protože to je přesně ten druh problému, který může dát odporná-k-najít chyby později?

Záleží na způsobu klonování, kterou si vyberete. Máte-li ručně aktualizovat cloneable typy, pak byste měli vyzkoušet, že každý typ je klonování všechny (a jediný) členy, které očekáváte. Vzhledem k tomu, pokud se testuje rámec klonovací bych vytvořit několik testovacích cloneable typy zkoušet každý scénář, který potřebují k podpoře.

Odpovězeno 14/08/2008 v 15:01
zdroj uživatelem

hlasů
1

I by obvykle provést Equals()pro porovnávání dvou objektů do hloubky. Možná se vám nebude potřebovat ve svém výrobním kódu, ale to by mohlo ještě hodit později a test kód je mnohem čistší.

Odpovězeno 18/09/2012 v 14:04
zdroj uživatelem

hlasů
1

Líbí se mi psát unit testy, které používají některý z vestavěných Serializézy na originálu a klonovaného objektu a poté zkontrolovat serializovaných reprezentace pro rovnost (pro binární formátovací, mohu jen porovnat bajt matice). To funguje skvěle v případech, kdy objekt je stále serializovatelné a měním jen do vlastního hluboké klon pro Perf důvodů.

Dále bych rád dodal přezkoušení režim ladění, aby všechny mé implementace Clone pomocí něco takového

[Conditional("DEBUG")]
public static void DebugAssertValueEquality<T>(T current, T other, bool expected, 
                                               params string[] ignoredFields) {
    if (null == current) 
    { throw new ArgumentNullException("current"); }
    if (null == ignoredFields)
    { ignoredFields = new string[] { }; }

    FieldInfo lastField = null;
    bool test;
    if (object.ReferenceEquals(other, null))
    { Debug.Assert(false == expected, "The other object was null"); return; }
    test = true;
    foreach (FieldInfo fi in current.GetType().GetFields(BindingFlags.Instance)) {
        if (test = false) { break; }
        if (0 <= Array.IndexOf<string>(ignoredFields, fi.Name))
        { continue; }
        lastField = fi;
        object leftValue = fi.GetValue(current);
        object rightValue = fi.GetValue(other);
        if (object.ReferenceEquals(null, leftValue)) {
            if (!object.ReferenceEquals(null, rightValue))
            { test = false; }
        }
        else if (object.ReferenceEquals(null, rightValue))
        { test = false; }
        else {
            if (!leftValue.Equals(rightValue))
            { test = false; }
        }
    }
    Debug.Assert(test == expected, string.Format("field: {0}", lastField));
}

Tato metoda je založena na přesném plnění rovnými o případných vložených členů, ale v mém případě je také equatable cokoliv, co je cloneable

Odpovězeno 23/08/2008 v 13:03
zdroj uživatelem

hlasů
1

Jen bych napsat jediný test, který určí, zda klon byl správný nebo ne. V případě, že třída není uzavřené, můžete vytvořit svazek pro ni tím, že rozšíří to, a pak vystavovat všechny své vestavbami ve třídě dětí. Případně můžete použít odraz (yech), nebo použít accessor generátory MSTest je.

Musíte klonovat objekt a pak jít přes každého jednotlivého majetku a proměnné, že váš objekt a zjistit, zda byl zkopírován správně nebo správně klonován.

Odpovězeno 14/08/2008 v 13:56
zdroj uživatelem

hlasů
0

Zde je příklad toho, jak jsem implementoval tuto chvíli zpět, i když to bude muset být přizpůsobena scénáři. V tomto případě jsme měli ošklivou objekt řetěz, který by se snadno změnit a klon byl používán jako provádění velmi kritickém prototypu a tak jsem musel opravit (hack) tento test spolu.

public static class TestDeepClone
    {
        private static readonly List<long> objectIDs = new List<long>();
        private static readonly ObjectIDGenerator objectIdGenerator = new ObjectIDGenerator();

        public static bool DefaultCloneExclusionsCheck(Object obj)
        {
            return
                obj is ValueType ||
                obj is string ||
                obj is Delegate ||
                obj is IEnumerable;
        }

        /// <summary>
        /// Executes various assertions to ensure the validity of a deep copy for any object including its compositions
        /// </summary>
        /// <param name="original">The original object</param>
        /// <param name="copy">The cloned object</param>
        /// <param name="checkExclude">A predicate for any exclusions to be done, i.e not to expect IPolicy items to be cloned</param>
        public static void AssertDeepClone(this Object original, Object copy, Predicate<object> checkExclude)
        {
            bool isKnown;
            if (original == null) return;
            if (copy == null) Assert.Fail("Copy is null while original is not", original, copy);

            var id = objectIdGenerator.GetId(original, out isKnown); //Avoid checking the same object more than once
            if (!objectIDs.Contains(id))
            {
                objectIDs.Add(id);
            }
            else
            {
                return;
            }

            if (!checkExclude(original))
            {
                Assert.That(ReferenceEquals(original, copy) == false);
            }

            Type type = original.GetType();
            PropertyInfo[] propertyInfos = type.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);
            FieldInfo[] fieldInfos = type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);

            foreach (PropertyInfo memberInfo in propertyInfos)
            {
                var getmethod = memberInfo.GetGetMethod();
                if (getmethod == null) continue;
                var originalValue = getmethod.Invoke(original, new object[] { });
                var copyValue = getmethod.Invoke(copy, new object[] { });
                if (originalValue == null) continue;
                if (!checkExclude(originalValue))
                {
                    Assert.That(ReferenceEquals(originalValue, copyValue) == false);
                }

                if (originalValue is IEnumerable && !(originalValue is string))
                {
                    var originalValueEnumerable = originalValue as IEnumerable;
                    var copyValueEnumerable = copyValue as IEnumerable;
                    if (copyValueEnumerable == null) Assert.Fail("Copy is null while original is not", new[] { original, copy });
                    int count = 0;
                    List<object> items = copyValueEnumerable.Cast<object>().ToList();
                    foreach (object o in originalValueEnumerable)
                    {
                        AssertDeepClone(o, items[count], checkExclude);
                        count++;
                    }
                }
                else
                {
                    //Recurse over reference types to check deep clone success
                    if (!checkExclude(originalValue))
                    {
                        AssertDeepClone(originalValue, copyValue, checkExclude);
                    }

                    if (originalValue is ValueType && !(originalValue is Guid))
                    {
                        //check value of non reference type
                        Assert.That(originalValue.Equals(copyValue));
                    }
                }

            }

            foreach (FieldInfo fieldInfo in fieldInfos)
            {
                var originalValue = fieldInfo.GetValue(original);
                var copyValue = fieldInfo.GetValue(copy);
                if (originalValue == null) continue;
                if (!checkExclude(originalValue))
                {
                    Assert.That(ReferenceEquals(originalValue, copyValue) == false);
                }

                if (originalValue is IEnumerable && !(originalValue is string))
                {
                    var originalValueEnumerable = originalValue as IEnumerable;
                    var copyValueEnumerable = copyValue as IEnumerable;
                    if (copyValueEnumerable == null) Assert.Fail("Copy is null while original is not", new[] { original, copy });
                    int count = 0;
                    List<object> items = copyValueEnumerable.Cast<object>().ToList();
                    foreach (object o in originalValueEnumerable)
                    {
                        AssertDeepClone(o, items[count], checkExclude);
                        count++;
                    }
                }
                else
                {
                    //Recurse over reference types to check deep clone success
                    if (!checkExclude(originalValue))
                    {
                        AssertDeepClone(originalValue, copyValue, checkExclude);
                    }
                    if (originalValue is ValueType && !(originalValue is Guid))
                    {
                        //check value of non reference type
                        Assert.That(originalValue.Equals(copyValue));
                    }
                }
            }
        }
    }
Odpovězeno 30/06/2015 v 13:03
zdroj uživatelem

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more