单元测试流利的NHibernate。需要帮助了解测试期间发生的异常

时间:2010-12-15 22:26:59

标签: unit-testing nhibernate fluent-nhibernate nhibernate-mapping

我有以下测试支持类。

    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:For   财产“AssociatedStudent”预计   'Pats.DataTransfer.Student'的类型   'Pats.DataTransfer.Student'但得到了''   类型'Pats.DataTransfer.Student'

一些研究表明,这种错误通常会发生,因为当您未能在DTO中覆盖对象相等时,我目前正在基于我的DTO基类中的id实现非常基本的对象相等。

    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 Associations部分包含测试。

底线,是什么导致

  

System.ApplicationException:For   财产“AssociatedStudent”预计   'Pats.DataTransfer.Student'的类型   'Pats.DataTransfer.Student'但得到了''   类型'Pats.DataTransfer.Student'

我的测试期间会抛出异常,我应采取什么策略来纠正它。

修改


这是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 个答案:

答案 0 :(得分:2)

尝试使用重载的PersistenceSpecification构造函数,该构造函数使用IEqualityComparer来比较子对象(AssociatedGuardian,AssociatedStudent)。持久性规范测试比较两个不同的引用(原始和检索),因此它们将与您的实现具有不同的哈希码。据我了解,使用IEqualityComparer首次比较第一次比较哈希码,然后检查等于哈希码是否匹配。我的猜测是PersistenceSpecification在IEqualityComparer实现中包装了Equals调用。

我使用实用程序类来简化这一过程:

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

编辑:

我想我看到了问题:监护人和学生没有坚持在会话中,并且没有自动执行的级联。在运行PersistenceSpecification之前保存这些对象应该修复它。您可以在this answer中查看CheckValue方法的代码。在再次查看您的问题时,错误消息指出“但得到''类型'Pats.DataTransfer.Student'”表示该值为空。

假设这是解决方案,我很好奇你是否还需要IEqualityComparer。使用诸如NHProf之类的工具进行性能分析会很快发现这一点,因为您会在插入中看到空值。

答案 1 :(得分:1)

我最终解决了这个问题,事实证明代码不是我的问题,而是使用了32位版本的System.Data.Sqlite.dll并将我的测试程序集编译为x86让它正常工作,我的其他程序集作为AnyCPU,在我的系统上意味着64位。出于某种原因,这适用于某些事情,但开始在CheckValue调用中表现出来,这让我有点失去了气味。

我能够为Sqlite找到一个64位的dll http://sqlite.phxsoftware.com/ 这很美妙。

现在生活很好,没有自定义的IEqualityComparer,或者在会话之前显式持久保存对象。