1

我有以下代码:

public class Foo
{
    public string Name { get; set; }
}

//query object pattern
public class FooQuery
{
    private string _startsWith;

    public FooQuery NameStartsWith(string startsWith)
    {
        _startsWith = startsWith;
        return this;
    }

    public List<Foo> Execute(IQueryable<Foo> someContext)
    {
        if (!string.IsNullOrWhiteSpace(_startsWith))
            someContext = someContext.Where(f => f.Name.StartsWith(_startsWith));
        return someContext.ToList();
    }
}

public interface IFooService
{
    List<Foo> FindByNameStartsWith(string startsWith);
}

public class FooService : IFooService
{
    private readonly IFooRepository _fooRepository;

    public FooService(IFooRepository fooRepository)
    {
        _fooRepository = fooRepository;
    }

    public List<Foo> FindByNameStartsWith(string startsWith)
    {
        var query = new FooQuery().NameStartsWith(startsWith);
        return _fooRepository.Find(query);
    }
}

public interface IFooRepository
{
    List<Foo> Find(FooQuery query);
}

public class FooRepository : IFooRepository
{
    public List<Foo> Find(FooQuery query)
    {
        var someContext = new List<Foo>().AsQueryable(); //would be EF/Mongo, etc
        return query.Execute(someContext);

    }
}

基本上,我有一个服务“FooService”,它更新一个查询对象“FooQuery”并根据传递给它的方法参数设置它的状态。然后,该服务将查询传递给存储库“FooRepository”,在该存储库中进行数据访问。FooQuery 故意不通过属性公开其状态。相反,它公开了用于更好控制的方法。我需要对 FooService 已正确创建查询对象进行单元测试。

这是一个挑战,因为 FooQuery 的状态对于单元测试是不可见的。我看到了几个选项,但似乎都闻起来:

  1. 将 FooQuery 的状态公开为只读属性,并在单元测试中检查这些属性在传递到存储库时是否有效。(检查查询的状态在技术上可以通过带有回调的模拟框架。)我不喜欢这样,因为我们现在必须打开状态并修改代码只是为了测试目的。

  2. 保持代码不变,并测试服务方法生成的结果与查询对象的结果是否相同。我不喜欢这样,因为它使单元测试变得更大、更无定论且更冗余(在检查结果时,我必须对查询对象本身进行非常相似的测试)

  3. 将 FooQuery 包装在一个接口中并创建一个要注入 foo 服务的工厂。然后我可以测试在模拟查询上调用的正确方法。但是,这仍然给我留下了对工厂本身的挑战性测试。

任何有关测试/重构此代码以使其更易于测试的建议将不胜感激。

4

2 回答 2

1

你可以实现Equals然后FooQuery验证我FooQuery通过一个 mock'ed得到了预期IFooRepository。这是您的选项#1 的有限形式。

于 2013-02-01T11:24:53.847 回答
1

您似乎有两个职责:从数据源获取 IQueryable,并对其执行查询。也许更简单的设计有意义?

public interface IFooRepository
{
     IQueryable<Foo> GetFoo();
}

public FooService : IFooService
{
     public List<Foo> FindByNameStartsWith(string startsWith)
     {
          return new FooQuery().StartsWith(startsWith).Execute(_fooRepo.GetFoo());
     }
}

现在 FooService 易于测试,并且数据库特定逻辑与所有其他逻辑分离。

[Test]
public void StartsWithFiltersFooFromRepository()
{
    var fooFromRepository = new List<Foo> { new Foo {Name="yes1"}, new Foo {Name="no"}, new Foo {Name="yes2"} };
    _fooRepMock.Setup(r=>r.GetFoo()).Returns(fooFromRepository);

    var actual = _fooService.FindByNameStartsWith("yes");

    Assert.That(actual, Is.EquivalentTo(new [] { fooFromRepository[0], fooFromRepository[2] }));
}
于 2013-02-02T18:40:08.320 回答