【问题标题】:In TDD and DDD, how do you handle read-only properties in fakes?在 TDD 和 DDD 中,如何处理 fake 中的只读属性?
【发布时间】:2009-06-08 15:38:00
【问题描述】:

问题

在创建假货时如何处理只读字段?

背景

我正处于使用 ASP.Net MVC 的初学者阶段,我正在使用 Steven Sanderson 的 Sports Store 和 Scott Gu 的 Nerd Dinner 作为示例。我刚刚遇到的一个小问题是如何在进行伪造时使用只读属性。我正在使用 LINQToSQL。

我的界面是:

public interface IPersonRespository
{   
    Person GetPerson(int id);
}

我的假货变成了

public class FakePersonRepository
{
    public Person GetPerson(int id)
    {
        return new Person {id="EMP12345", name="John Doe", age=47, ssn=123-45-6789, totalDrWhoEpisodesWatched=42};
    }
}

这是我的问题。字段 id、ssn 和 totalDrWhoEpisodesWatched 是只读的,所以上面的代码实际上不起作用。但是,我不知道如何创建一个假的新人并设置一个只读属性。我确信有一个解决方案,但我在搜索中还没有遇到它。

更新:继承 + 属性隐藏是一种潜在的解决方案?

我还没有决定解决这个问题的可靠方法。我不喜欢为了制造假货而修改我的域类的想法。对我来说,为域类添加标记以进行测试是一种增加耦合的形式——耦合到测试的实现。我现在正在研究另一种可能性,即创建一个 FakePerson 类,该类继承自 Person,但使用新的读写属性隐藏属性。

public class FakePerson: Person
{
    public new int age { get; set; }
    public new string ssn { get; set; }
    public new int totalDrWhoEpisodesWatched { get; set; }
}

到目前为止,这个解决方案是我的倾向。它确实违反了 Liskov 替换原则,但是在测试项目中并没有给我带来太多困扰。我很高兴听到对此作为解决方案的任何批评和/或反馈。

获胜者:模拟框架

起订量似乎可以胜任。实际上,我最后一个通过继承隐藏属性的解决方案确实有效,但是通过使用 Moq,我获得了一组更易于维护的标准化功能。我假设其他模拟框架有这个功能,但我没有检查。据说起订量对于开始模拟写作来说更直接,我现在肯定是这样。

【问题讨论】:

  • 为什么设置为只读?
  • 设置为只读,因为该域的任何用户都无权修改属性。这可能有多种原因,但通常是因为应用程序无权修改该字段,因为它是一个计算/自动生成的字段,或者是由于数据库安全策略。
  • 在一个稍微不相关的主题上,我建议将您的属性名称更改为 PascalCase 以遵守 .NET 命名约定。 msdn.microsoft.com/en-us/library/ms229043(loband).aspx

标签: asp.net-mvc tdd domain-driven-design nerddinner


【解决方案1】:

考虑在您的测试中模拟 Person 类型。使用Moq 的示例:

var mock = new Mock<Person>();
mock.SetupGet(p => p.id).Returns("EMP12345");
mock.SetupGet(p => p.ssn).Returns("123-45-6789");
mock.SetupGet(p => p.totalDrWhoEpisodesWatched).Returns(42);
return mock.Object;

否则,请尝试找出 LINQ to SQL 如何设置这些只读属性。

编辑:如果您尝试上述操作并且 Moq 在 SetupGet 调用中抛出 ArgumentException 并显示消息 “不可覆盖成员上的无效设置:p => p .id",那么您需要将该属性标记为虚拟。这需要为您希望覆盖其 getter 的每个属性完成。

在 LINQ to SQL 中,这可以通过选择属性在 OR 设计器中完成,然后在属性窗口中将 Inheritance Modifier 设置为 virtual

【讨论】:

  • 起订量可以绕过只读道具吗?我记得我在使用 Rhino 模拟时遇到了问题。 ://
  • 您是指只读的“字段”,还是没有可公开访问的设置器的属性?在前一种情况下,我认为 Moq 无济于事。在后一种情况下,setter 无关紧要 - Moq 只是覆盖 getter。
  • 只读属性(即带有集合的属性)。 Linq to SQL 在 Person 类中设置私有变量,在本例中为 _id、_ssn 和 _totalDrWhoEpisodesWatched。
  • mvr,这是模拟框架的标准功能还是 Moq 特有的功能?
  • 我没有使用其他模拟框架的经验,所以我害怕无法回答。
【解决方案2】:

您只能在类的构造函数中设置只读属性。 Person 对象应该有一个接受 id、ssn 和 totalDrWhoEpisodesWatched 的构造函数。当然,如果这是一个 linqtosql 生成的对象,由于代码是自动生成的,您可能会在修改它时遇到问题。

您可以考虑使用映射对象在您的存储库中公开......因此您实际上不必使用您的 linqtosql 对象作为您的模型。

【讨论】:

  • @Joel- 你觉得使用完全 POCO 模型的开销值得双重映射所需的额外努力吗?
  • 我不喜欢将 ssn 和 totalDrWhoEpisodesWatched 添加到构造函数的想法。添加在运行程序中实际上不需要的额外参数只是为了执行测试似乎与良好的 API 设计背道而驰,因为这意味着公开一个不应被消费者(测试消费者除外)使用的方法。 “映射对象”是什么意思?
  • @RichardOD,有时 :-) @John,通过映射对象,我的意思是使用您自己编码的对象(而不是依赖 linq2sql 提供的自动生成的类)。因此,当您从数据存储中进行选择时,您将执行 (select new MyPerson() { ... }) 并在那里设置您的属性。这可以让您避免依赖自动生成的类,该类在未来可能会发生变化,也可能不会发生变化。
  • 乔尔。谢谢,我去看看。
【解决方案3】:

在 .NET 中,您可以将 setter 标记为“内部”,并使用 InternalsVisibleTo 程序集属性使内部对您的测试程序集可见。这样你的 setter 就不会公开,但你仍然可以访问它们。

注意:即使问题没有标记为 .NET,我认为它是基于您对对象初始化器语法的使用。如果我的假设是错误的,则此建议不适用(当然,除非您使用的语言具有等效功能)。

【讨论】:

  • 我没有将其标记为 .Net,但我认为提及 ASP.net 和 LinqToSQL 会放弃这一点。谢谢。
【解决方案4】:

如果是用于测试 - 考虑使用反射。这不会涉及弄乱您的域模型。

例如 - 我有 FactoryBase 类,它使用反射通过参数通过 lambda 表达式设置所需的道具(如this)。工作就像一个魅力 - 创建新工厂就像定义存储库类型和默认实体数据一样简单。

【讨论】:

  • 答案很耐人寻味,但我不明白它是如何解决我所询问的问题的,即如何处理假货中的只读属性。 Foo 具有所有 3 个属性(Bar、Baz 和 Bling)的设置器。如果 BAZ 只有一个吸气剂,你会怎么做?
  • 反射可以处理这个问题。也有一些带有受保护和私有设置器的域对象。
【解决方案5】:

我也使用最小起订量。我喜欢它,而且效果很好。但是,在我开始使用 Moq 之前,我写了很多假货。以下是我使用假货解决问题的方法。

由于假的可以有“生产”实现没有的其他方法,我会在我的假实现中添加一些额外的方法来处理设置只读部分。

像这样:

public class FakePersonRepository : IPersonRespository
{
    private IDictionary<int, Person> _people = new Dictionary<int, Person>();

    public Person GetPerson(int id)  // Interface Implementation
    {
        return _people(id);
    }

    public void SetPerson(int id, Person person)  // Not part of interface
    {
         _people.Add(id, person);
    }

}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-11-11
    • 1970-01-01
    • 2010-09-22
    • 2011-02-16
    • 1970-01-01
    • 2010-09-14
    • 2016-03-04
    相关资源
    最近更新 更多