单元测试应该知道NHibernate吗?

时间:2010-08-29 18:05:04

标签: unit-testing nhibernate abstraction

我正在追踪a previous question。我接受的答案涉及使用通用IRepository来处理基本CRUD,其中包含委托给通用设置的特定域IMovieRepository。进一步的细节涉及在通用WrapQueryInSession上使用IRepository方法:

IEnumerable<T> WrapQueryInSession(Func<ISession, IEnumerable<T>> query);

当我意识到这会将NHibernate ISession暴露给通用存储库的使用者时,我开始实现。 NHibernate完全包含在IRepository实现中,但是对于该方法签名。

当我想要MovieRepository时,通过IRepository实现RepositoryFake,传递给MovieRepository构造函数

protected override void BeforeEachTest()
{
    _fixture = new MovieRepository(new RepositoryFake());
}

我的测试类有一个私有虚假存储库实现:

private class RepositoryFake : IRepository<Movie>
{
    ...
    public IEnumerable<Movie> WrapQueryInSession(Func<ISession, IEnumerable<Movie>> query)
    {
        ...
    }
    ...
}

设置它的方式,测试类以及IRepository实现的任何其他使用者,都知道来自NHibernate的ISession,因此NHibernate本身。这似乎是抽象漏洞的情况。

是否有更好的方法可以在IRepository实现中完全包含NHibernate的使用?

2 个答案:

答案 0 :(得分:2)

来自my answer to your previous question的想法是,通用IRepository仅在您的基础架构层中已知 - 它不会在此之外发布。当您将ISession发布到非通用存储库时,它们获得了一个非常通用的接口,因为它们可以访问ISession进行查询。不暴露ISession的问题是您的通用存储库将:

  1. 限制您的查询功能或
  2. 有许多不同的查询方法(基本上是复制了ISession的界面。
  3. 看起来有点浪费,NHibernate的查询界面隐藏在一个立面内(通用存储库将被限制)。

    IMO,如果您选择nHibernate,您应该利用它为您提供的功能,并在整个基础架构dll(包括测试)中依赖。将通用IRepository接口视为NHibernate的辅助接口,以减少存储库中重复代码的数量。

答案 1 :(得分:2)

我同意NHibernate不应该被完全抽象的原则,但是我发现NHibernate的查询界面可以隐藏起来而不会有太多的麻烦,并且可以通过使用Query对象来实现。

每个查询对象应该利用NHibernate提供的功能(ISession,ICriteria,IQuery等),并且可以通过IRepository的实现来执行。

在您的存储库中使用Query对象而不是方法可以提供更好的可测试性,并且在测试类中不需要对NHibernate进行任何引用。

以下是整个事情的样子:

public interface IRepository
{
        ISession Session { get; }
        TResult Query<TResult>(IQuery<TResult> query);
}

public class Repository : IRepository
{
    public ISession Session
    {
        get { return /* Call the session factory to return an ISession */; }
    }

    public TResult Query<TResult>(IQuery<TResult> query)
    {
        return query.Execute(Session));
    }
}

public interface IQuery<TResult>
{
    TResult Execute(QueryContext context);
}

public abstract class Query<TResult> : IQuery<TResult>
{
    public abstract TResult Execute(ISession session);
}

public class GetPeopleByName: IQuery<Person>
{
    private readonly string _name;

    public GetPeopleByName(string name)
    {
        _name = name;
    }

    public override IList<Person> Execute(ISeesion session)
    {
        var query = context.Session.CreateCriteria(typeof(Person))
            .Add(Restrictions.Eq("Name", _name));

        return query.List<Person>();
    }
}

然后您可以使用以上内容:

IRepository repository = /* Get somehow the implementation */
IList<Person> people = repository.Execute(new GetPeopleByName("Anna"));