Will Dean
그는 물었다 12년 전
10

유닛 테스트 깊이형 클론한 대한

39 라고 let&, s (I have a 클래스, 그리고 다른 많은 복잡한 닷넷 (.net) 기반 클래스 객체에는 어레이에는 멤버. 그래서 저는 이 객체를 생성할 수 있도록 데릭쉐퍼드와 깊이형 클론할 이를 구현하는 방법 및 쓰기 클론 () 를 통해 간단한 비나리포매터 또는 아마도 다른 기술을 사용하여 직렬화할 / 데즈리얼리즈 - 내가 깊이형 클론할 보다 오류 발생 가능성이 높은 일부 및 I& # 39; d like to smartupdate 테스트됩니다.

좋아, 그럼 이제 (ok, 내가 먼저 했어야) I& 덮고 있는 쓰기 테스트를; d # 39 와 같은 클론한. 너무 좋은 아키텍처입니다 클래스의 private, 내 모든 멤버를 있다 (!) 내가 쓸 수 있는 공용 속성 또는 수백 t # 39, haven& 필요한 다른 접근자에. 이 때문에, t, s 클래스 isn& # 39 이콩파레이블 또는 리쿼터블 that& # 39 응용 프로그램에서 필요 없습니다. 내 유닛 테스트 운영 중인 조립품을 별도의 코드.

어떤 방식으로 할 수 있는 좋은 사람들이 시행하십시오 테스트 클론된 객체인지 복제본에? 들어보겠소 쓰기 (또는 재작성할 발견하십시오 후에는 필요성을 클론) 클래스에 대한 당신의 모든 유닛 테스트 , a & # 39 호출되었을 수 있도록 함께 인컨텍스트 virgin& # 39. 복제로 객체에는 net/ 또는 it? # 39, wasn& 클론한 부분을 어떻게 점심시간요 경우 이를 줄 수 있는 충분한 - t 깊이형 같은 문제를 그냥 나중에 com/go/downloads_kr 쓴다 버그?

답변 6 개

39, s, t # 39 doesn& there& 정말 명백하네 솔루션으로 접근도 최대한 활용:

  1. 직렬화할 객체에는 붙여넣습니다 이진 형식.
  2. 클론할 객체에는.
  3. 클론 직렬화할 붙여넣습니다 이진 형식.
  4. 비교 바이트입니다.

또한 사용하고 있는 것으로 직렬화하지 작동됨 - - 이 때문에 더 쉽게 유지할 수 있도록 클론할 되어야 합니다. 사실 에서 캡슐화됩니다 구조가 변경되는 것으로, 해당 클래스의 느니라

You come up with 너회가 테스트 방법 솔루션 유형에 따라 다릅니다. 수동으로 해야 할 경우 일부 사용자 정의 클론한 구현하는 코드 및 쓰기 입력한 다음 각 복제 가능 합니다 정말 클론한 테스트하려면 각 그 중 한 유형. 또는 검색하기를 결정한 경우 더 일반적이고 라우트를 테스트를 할 수밖에 없다 (앞에서 말한 것이라고 들어갈 가능성이 있는 반사 (reflection) 을 통해 시스템 대처해야 할 수 있는 특정 시나리오용으로 테스트하려면 클론한.

고객의 구체적인 답변을 질문을합니다.

&gt. 들어보겠소 쓰기 (또는 재작성할 발견하십시오 후에는 필요성을 클론) 유닛 테스트 중 하나를 사용하여 사용자의 모든 클래스에 대한 # 39, & # 39 virgin& 호출되었을 수 있도록. 복제로 객체인지 또는 it?

이제 모든 방법에 대한 테스트를 수행할 수 있는 원본 및 모두에서 클론된 객체에는. 참고 상당히 쉽게 간단한 테스트를 지원할 수 있도록 한다는 것 없이 이 논리는 업데이트하던 수동으로로 셋업하기 설계 각 테스트마다.

&gt. # 39, wasn& 클론한 부분을 어떻게 점심시간요 경우 이를 줄 수 있는 충분한 - t 깊이형 같은 문제를 그냥 나중에 com/go/downloads_kr 쓴다 버그?

선택하는 방법은 클론한 따라 다릅니다. 이 경우 각 유형은 클론한 모든 테스트 후 수동으로 업데이트하려면 추상형데이터타입 복제 가능 합니다 (오직) 멤버 예상한. 반면에, 나는 몇 가지 테스트 테스트 프레임워크를 클론한 경우 각 책정안 지원하는 데 필요한 추상형데이터타입 복제 가능 테스트하려면 만들 것이라고 주장했다.

난 ' () 는 일반적으로 두 구현하십시오 비교하기 위해' 의 객체에는 심도입니다. 하지만 여전히 따라오렴 도움이 필요할 수도 있습니다.) 의 운영 및 테스트 코드를 코드 지정값이 나중에 보다 깔끔할거야.

I like 쓸 수 있는 내장 시리얼 유닛 테스트 중 하나를 사용하는 원본 및 클론된 객체에는 직렬화된 확인 후 평등을 위한 표현 (이진 바이트 어레이에는 포맷터 대한 비교, 내가 됩니다.). 이 기능은 여전히 큰 경우에 시리얼화가 가능합니다 # 39 만 m, 변경 및 I& 객체인지 사용자 정의 깊이형 클론할 대한 성능 시작했다.

또한, 디버그 모드로 추가하기에서는 싶어요 모든 솔루션을 통해 이 같은 일이 내 클론할 체크

[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));
}

이 방법을 정확하게 의존하고 있는 것은 아무 것도 할 수 있지만, 내 경우에는 구현 는 네스트된 멤버 역시 에쿼터블 복제 가능

39 i& 단일 클론 맞자나 방관하겠나 있는지 그냥 쓰기 테스트하려면; d 클래스 isn& 경우 t # 39, 밀폐된 하네스의 만들 수 있습니다 다음 네 자식 클래스가 내의 모든 it 의 내부를 폭로한 연장됨 의해. 반사 (이치) 또는 사용하거나 MSTest& 사용할 수 있습니다 # 39 의 액세서의 만들어낸다.

네 객체에는 클론할 후 필요한 모든 단일 거쳐 결정할 수 있는 경우 또는 속성 및 가변으로 객체에는 제대로 복제되었습니다 클론된 것입니다.

이것은 내가 어떻게 이 샘플 구현됩니까 귈이예요 뒤로를 하지만, 이 상황에 맞는 맞춤형 책정안 할 것입니다. 이 경우 쉽게 변경할 수 있는 끔찍한 객체에는 하나님이 체인) 로 사용할 수 있고 클론이 프로토타입 구축 및 그래서 패치 (해킹) 이 테스트는 매우 중요한 것입니다.

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));
                    }
                }
            }
        }
    }