在 TDD 和 DDD 中,如何处理 fakes 中的只读属性?

发布于 2024-07-23 08:16:08 字数 1356 浏览 2 评论 0原文

问题

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

背景

我正处于使用 ASP.Net MVC 的初级阶段,并使用 Steven Sanderson 的 Sports Store 和 Scott Gu 的 Nerddinner 作为示例。 我刚刚遇到的一个小问题是在进行假操作时如何使用只读属性。 我正在使用 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; }
}

到目前为止,这个解决方案就是我的倾向。 它确实打破了里氏替换原则,但是在测试项目中这并没有让我那么烦恼。 我很高兴听到对此作为解决方案的任何批评和/或反馈。

获胜者:Mock Frameworks

Moq 似乎可以完成这项工作。 事实上,我通过继承隐藏属性的最后一个解决方案确实有效,但是通过使用 Moq,我获得了一组更易于维护的标准化功能。 我假设其他模拟框架具有此功能,但我尚未检查。 据说 Moq 对于刚开始的模拟写作来说更简单,我现在确实是这样。

Question

How do you handle read-only fields when creating fakes?

Background

I'm in the beginner stages of using ASP.Net MVC and am using Steven Sanderson's Sports Store and Scott Gu's Nerd Dinner as examples. One small problem that I've just hit is how to work with read-only properties when doing fakes. I'm using LINQToSQL.

My interface is:

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

and my fake becomes

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

Here's my problem. The fields id, ssn and totalDrWhoEpisodesWatched are read-only, so the above code won't actually work. However, I don't recognize how to create a fake new person and set a read-only property. I'm sure there is a solution, but I haven't come across it yet in my searches.

Update: Inheritance + Property Hiding as a Potential Solution?

I haven't yet decided upon a firm solution to the problem. I dislike the notion of modifying my Domain classes for the purposes of creating fakes. To me, adding markup to the domain classes in order to do testing is a form of added coupling -- coupling to the implementation of your test. I'm now investigating another possibility, which is to create a FakePerson class, which inherits from Person, but hides the properties with new read-write properties.

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

So far, this solution is how I am leaning. It does break the Liskov Substitution Principle, however that doesn't bug me as much in a test project. I'd be glad to hear any criticism and/or feedback on this as a solution.

Winner: Mock Frameworks

Moq appears to do the job. My last solution of hiding the property through inheritance does, in fact, work, however by using Moq, I get a standardized set of functionality that is more maintainable. I assume that other mock frameworks have this functionality, but I haven't checked. Moq is said to be more straightforward for the beginning mock writing, which I definitely am right now.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

谢绝鈎搭 2024-07-30 08:16:08

考虑在测试中模拟 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 设计器中通过选择属性来完成此操作,然后在“属性”窗口中将继承修饰符设置为虚拟

Consider mocking the Person type in your test. Example using 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;

Otherwise, try finding out how LINQ to SQL sets those read only properties.

EDIT: If you attempt the above and Moq throws an ArgumentException in the SetupGet call with the message "Invalid setup on a non-overridable member: p => p.id", then you need to mark the property as virtual. This will need to be done for each property whose getter you wish to override.

In LINQ to SQL, this can be done in the OR designer by selecting the property, then in the Properties window set Inheritance Modifier to virtual.

清音悠歌 2024-07-30 08:16:08

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

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

You can only set readonly properties in the constructor of the class. The Person object should have a constructor that accepts id, ssn, and totalDrWhoEpisodesWatched. Of course, if this is a linqtosql generated object, you might have issues modifying that as the code is auto-generated.

You could consider using a mapped object to expose in your repository ... so you'd never actually have to use your linqtosql object as your model.

场罚期间 2024-07-30 08:16:08

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

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

In .NET, you could mark your setters as "internal" and use the InternalsVisibleTo assembly attribute to make internals visible to your test assembly. That way your setters won't be public, but you can still access them.

note: even though the question isn't tagged .NET, I assumed it was based on your usage of object initializer syntax. If my assumption was wrong, this suggestion does not apply (unless the language you're using has an equivalent feature, of course).

勿忘初心 2024-07-30 08:16:08

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

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

If it's for tests - consider using reflection. That wouldn't involve messing around your domain model.

For example - i got FactoryBase class, which uses reflection to set needed prop by lambda expression through parameters (like this). Works like a charm - creating new factory is simple as defining repository type and default entity data.

眼波传意 2024-07-30 08:16:08

我也用起订量。 我喜欢它,而且效果很好。 但是,在我开始使用 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);
    }

}

I also use Moq. I love it and it works great. But, before I started using Moq, I wrote many fakes. Here's how I would have solved the problem using fakes.

Since a fake can have additional methods that the "production" implementation doesn't have, I would add a few extra methods to my fake implementation to handle setting the read-only portion.

Like this:

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

}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文