Модульное тестирование Свободный NHibernate. Нужна помощь в понимании исключения, возникающего во время теста - PullRequest
2 голосов
/ 16 декабря 2010

У меня есть следующие классы поддержки тестирования.

    public class FixtureBase
    {
        protected SessionSource SessionSource { get; set; }
        protected ISession Session { get; private set; }

        [TestFixtureSetUp]
        public void SetupFixture()
        {
            var cfg = Fluently.Configure().Database(SQLiteConfiguration.Standard.ShowSql().InMemory);
            SessionSource = new SessionSource(cfg.BuildConfiguration().Properties, new TestModel());
        }

        [SetUp]
        public void SetupContext()
        {
            Session = SessionSource.CreateSession();
            SessionSource.BuildSchema(Session);
        }

        [TearDown]
        public void TearDownContext()
        {
            Session.Close();
            Session.Dispose();
        }
    }

    public TestModel()
    {
        this.AddMappingsFromAssembly(typeof(StudentMap).Assembly);
    }

И очень простой тестовый класс, который должен проверять связующую карту классов во многих отношениях.

[TestFixture]
public class StudentGuardianAssociationMap_Fixture : FixtureBase
{
    [Test]
    public void can_correctly_map_studentguardianassociation()
    {
        Guardian guardian = new Guardian
                                {
                                    FirstName = "GuardianFName",
                                    LastName = "GuardianLName",
                                    NameSuffix = "GuardianSuffix",
                                    MiddleName = "GuardianMiddleName"
                                };

        Student student = new Student
                              {
                                  FirstName = "StudentFName",
                                  LastName = "StudentLName",
                                  MiddleName = "StudentMiddleName",
                                  Address1 = "StudentAddress1",
                                  Address2 = "StudentAddress2",
                                  City = "StudentCity",
                                  State = "MO",
                                  PostalCode = "12345-2342"
                              };   

        new PersistenceSpecification<StudentGuardianAssociation>(Session)
            .CheckProperty(x => x.RelationShipToStudent, 1)
            .CheckReference(x => x.AssociatedStudent, student)
            .CheckReference(x => x.AssociatedGuardian, guardian)
            .VerifyTheMappings();
    }
}

При выполнении этого теста я получаю следующее исключение.

System.ApplicationException: для ожидается свойство 'AssociatedStudent' Pats.DataTransfer.Student типа 'Pats.DataTransfer.Student', но получил '' типа 'Pats.DataTransfer.Student'

Некоторые исследования показывают, что такого рода ошибки обычно происходят, потому что, когда вы не можете переопределить равенство объектов в ваших DTO, я в настоящее время реализую очень элементарное равенство объектов на основе идентификатора в моем базовом классе DTO.

    public class DataTranfserObject<TDto> where TDto : DataTranfserObject<TDto>
    {
        private int? _oldHashCode;

        public virtual int Id { get; set; }
        public virtual int Version { get; set; }

        public override int GetHashCode()
        {
            // Once we have a hash code we'll never change it
            if (_oldHashCode.HasValue)
            {
                return _oldHashCode.Value;
            }

            var thisIsTransient = Equals(Id, 0);

            // When this instance is transient, we use the base GetHashCode()
            // and remember it, so an instance can NEVER change its hash code.
            if (thisIsTransient)
            {
                _oldHashCode = base.GetHashCode();
                return _oldHashCode.Value;
            }
            return Id.GetHashCode();
        }

        public override bool Equals(object obj)
        {
            var other = obj as TDto;
            if (other == null)
            {
                return false;
            }

            // handle the case of comparing two NEW objects
            var otherIsTransient = Equals(other.Id, 0);
            var thisIsTransient = Equals(Id, 0);

            if (otherIsTransient && thisIsTransient)
            {
                return ReferenceEquals(other, this);
            }
            return other.Id.Equals(Id);
        }
    }

И представление объекта моей таблицы сопоставления.

    public class StudentGuardianAssociation : DataTranfserObject<StudentGuardianAssociation>
    {
        public virtual int RelationShipToStudent { get; set; }

        public virtual Student AssociatedStudent { get; set; }
        public virtual Guardian AssociatedGuardian { get; set; }
    }

И, наконец, моя карта для хорошей меры.

public StudentGuardianAssociationMap() 
    {
        LazyLoad();

    this.Table("StudentGuardians");

    this.Id(x => x.Id).GeneratedBy.HiLo("100").UnsavedValue(0);

    this.Version(x => x.Version).Column("Version");

    Map(x => x.RelationShipToStudent).Column("RelationshipToStudent").Not.Nullable();

    References(x => x.AssociatedGuardian).Not.Nullable();
    References(x => x.AssociatedStudent).Not.Nullable();
}

Я все еще довольно новичок в nhibernate и в свободном API, но я успешно получил свои карты учеников и опекунов, чтобы пройти их тесты. Хотя я еще не включил тесты для их части Ассоциации HasMany.

Итог, что вызывает

System.ApplicationException: для ожидается свойство 'AssociatedStudent' Pats.DataTransfer.Student типа 'Pats.DataTransfer.Student', но получил '' типа 'Pats.DataTransfer.Student'

исключение, которое будет выдвинуто во время моего теста, и какую стратегию я должен использовать, чтобы исправить его.

EDIT


Это информация трассировки стека, возвращаемая nunit.

at FluentNHibernate.Testing.Values.Property`2.CheckValue(Object target) in d:\Builds\FluentNH\src\FluentNHibernate\Testing\Values\Property.cs: line 91
at FluentNHibernate.Testing.PersistenceSpecification`1.c__DisplayClass2.b__1(Property`1 p) in d:\Builds\FluentNH\src\FluentNHibernate\Testing\PersistenceSpecification.cs: line 64
at System.Collections.Generic.List`1.ForEach(Action`1 action)
at FluentNHibernate.Testing.PersistenceSpecification`1.VerifyTheMappings(T first) in d:\Builds\FluentNH\src\FluentNHibernate\Testing\PersistenceSpecification.cs: line 64
at FluentNHibernate.Testing.PersistenceSpecification`1.VerifyTheMappings() in d:\Builds\FluentNH\src\FluentNHibernate\Testing\PersistenceSpecification.cs: line 40
at Pats.DataAccessTest.StudentGuardianAssociationMap_Fixture.can_correctly_map_studentguardianassociation() in StudentGuardianAssociationMap_Fixture.cs: line 37 

И sql, выполненный в случае, если есть какие-либо идеи там.

NHibernate: select next_hi from hibernate_unique_key
NHibernate: update hibernate_unique_key set next_hi = @p0 where next_hi = @p1;@p0 = 2, @p1 = 1
NHibernate: INSERT INTO Students (Version, FirstName, LastName, MiddleName, NameSuffix, Address1, Address2, City, State, PostalCode, Id) VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9, @p10);@p0 = 1, @p1 = 'StudentFName', @p2 = 'StudentLName', @p3 = 'StudentMiddleName', @p4 = NULL, @p5 = 'StudentAddress1', @p6 = 'StudentAddress2', @p7 = 'StudentCity', @p8 = 'MO', @p9 = '12345-2342', @p10 = 1001
NHibernate: select next_hi from hibernate_unique_key
NHibernate: update hibernate_unique_key set next_hi = @p0 where next_hi = @p1;@p0 = 3, @p1 = 2
NHibernate: INSERT INTO Guardians (Version, FirstName, LastName, MiddleName, NameSuffix, Id) VALUES (@p0, @p1, @p2, @p3, @p4, @p5);@p0 = 1, @p1 = 'GuardianFName', @p2 = 'GuardianLName', @p3 = 'GuardianMiddleName', @p4 = 'GuardianSuffix', @p5 = 2002
NHibernate: select next_hi from hibernate_unique_key
NHibernate: update hibernate_unique_key set next_hi = @p0 where next_hi = @p1;@p0 = 4, @p1 = 3
NHibernate: INSERT INTO StudentGuardians (Version, RelationshipToStudent, Id) VALUES (@p0, @p1, @p2);@p0 = 1, @p1 = 1, @p2 = 3003
NHibernate: SELECT studentgua0_.Id as Id1_2_, studentgua0_.Version as Version1_2_, studentgua0_.RelationshipToStudent as Relation3_1_2_, student1_.Id as Id2_0_, student1_.Version as Version2_0_, student1_.FirstName as FirstName2_0_, student1_.LastName as LastName2_0_, student1_.MiddleName as MiddleName2_0_, student1_.NameSuffix as NameSuffix2_0_, student1_.Address1 as Address7_2_0_, student1_.Address2 as Address8_2_0_, student1_.City as City2_0_, student1_.State as State2_0_, student1_.PostalCode as PostalCode2_0_, guardian2_.Id as Id0_1_, guardian2_.Version as Version0_1_, guardian2_.FirstName as FirstName0_1_, guardian2_.LastName as LastName0_1_, guardian2_.MiddleName as MiddleName0_1_, guardian2_.NameSuffix as NameSuffix0_1_ FROM StudentGuardians studentgua0_ left outer join Students student1_ on studentgua0_.Id=student1_.Id left outer join Guardians guardian2_ on studentgua0_.Id=guardian2_.Id WHERE studentgua0_.Id=@p0;@p0 = 3003

Спасибо, что пробирались сквозь мою стену текста, я просто хотел быть внимательным к тому, что я пытаюсь достичь, и что я сделал до сих пор.

Ответы [ 2 ]

2 голосов
/ 16 декабря 2010

Попробуйте использовать перегруженный конструктор PersistenceSpecification, который принимает IEqualityComparer для сравнения дочерних объектов (AssociatedGuardian, AssociatedStudent). Тест спецификации постоянства сравнивает две разные ссылки (исходную и извлеченную), поэтому они будут иметь разные хеш-коды в вашей реализации. Насколько я понимаю, сравнения, выполненные с использованием первых IEqualityComparer, сравнивают хэш-коды, а затем проверяют Equals, если хеш-код совпадает. Я предполагаю, что PersistenceSpecification обертывает вызов Equals в реализации IEqualityComparer.

Я использую служебный класс, чтобы сделать это проще:

public class PersistenceSpecificationEqualityComparer : IEqualityComparer
{
    private readonly Dictionary<Type, Delegate> _comparers = new Dictionary<Type, Delegate>();

    public void RegisterComparer<T>(Func<T, object> comparer)
    {
        _comparers.Add(typeof(T), comparer);
    }

    public bool Equals(object x, object y)
    {
        if (x == null || y == null)
        {
            return false;
        }
        var xType = x.GetType();
        var yType = y.GetType();
        // check subclass to handle proxies
        if (_comparers.ContainsKey(xType) && (xType == yType || yType.IsSubclassOf(xType)))
        {
            var comparer = _comparers[xType];
            var xValue = comparer.DynamicInvoke(new[] {x});
            var yValue = comparer.DynamicInvoke(new[] {y});
            return xValue.Equals(yValue);
        }
        return x.Equals(y);
    }

    public int GetHashCode(object obj)
    {
        throw new NotImplementedException();
    }
}

Использование:

var comparer = new PersistenceSpecificationEqualityComparer();
comparer.RegisterComparer((Guardian x) => x.Id);
comparer.RegisterComparer((Student x) => x.Id);
// etc.

new PersistenceSpecification<StudentGuardianAssociation>(Session, comparer)
    .CheckProperty(x => x.RelationShipToStudent, 1)
    .CheckReference(x => x.AssociatedStudent, student)
    .CheckReference(x => x.AssociatedGuardian, guardian)
    .VerifyTheMappings();

EDIT:

Мне кажется, я вижу проблему: опекун и ученик не сохраняются на сессии, и нет каскада, который бы делал это автоматически. Сохранение этих объектов перед запуском PersistenceSpecification должно исправить это. Вы можете увидеть код для метода CheckValue в этот ответ . При повторном рассмотрении вашего вопроса в сообщении об ошибке указывается «но есть» типа «Pats.DataTransfer.Student», указывающее, что значение равно нулю.

Предполагая, что это решение, мне любопытно, если вам все еще нужен IEqualityComparer. Профилирование с использованием такого инструмента, как NHProf , быстро это поймало бы, потому что вы видите нулевые значения во вставке.

1 голос
/ 16 декабря 2010

Я наконец-то решил эту проблему, оказалось, что код не является проблемой в моем случае, но вместо этого я использовал 32-битную версию System.Data.Sqlite.dll и скомпилировал мою тестовую сборку как x86 длязаставить его работать правильно, и мои другие сборки как AnyCPU, что в моей системе означало 64 бит.По некоторым причинам это сработало для некоторых вещей, но начало проявляться в вызове CheckValue, который немного сбивал меня с толку.

Мне удалось найти 64-битную DLL для Sqlite на http://sqlite.phxsoftware.com/, которая прекрасно работает.

И теперь жизнь хороша без специального IEqualityComparer или явного сохранения объектов перед началом сеанса.

...