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

时间:2022-09-11 18:18:01

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.

我正处于使用ASP.Net MVC的初学阶段,我正在使用Steven Sanderson的体育用品店和Scott Gu的书呆子晚餐作为例子。我刚才遇到的一个小问题是如何在做假货时使用只读属性。我正在使用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.

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

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.

我还没有决定解决这个问题。我不喜欢为了创造假货而修改我的Domain类的概念。对我来说,为了进行测试而向域类添加标记是一种额外的耦合形式 - 与测试的实现相结合。我现在正在研究另一种可能性,即创建一个继承自Person的FakePerson类,但是使用新的读写属性隐藏属性。

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.

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

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.

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

5 个解决方案

#1


6  

Consider mocking the Person type in your test. Example using Moq:

考虑模拟测试中的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;

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

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

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.

编辑:如果您尝试上述操作并且Moq在SetupGet调用中抛出ArgumentException,并显示消息“在非可覆盖成员上设置无效:p => p.id”,则需要将该属性标记为虚拟。对于要覆盖其getter的每个属性,都需要执行此操作。

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.

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

#2


1  

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.

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

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.

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

#3


1  

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.

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

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).

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

#4


0  

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.

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

#5


0  

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.

我也用Moq。我喜欢它并且效果很好。但是,在我开始使用Moq之前,我写了许多假货。这就是我如何用假货解决问题的方法。

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

}

#1


6  

Consider mocking the Person type in your test. Example using Moq:

考虑模拟测试中的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;

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

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

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.

编辑:如果您尝试上述操作并且Moq在SetupGet调用中抛出ArgumentException,并显示消息“在非可覆盖成员上设置无效:p => p.id”,则需要将该属性标记为虚拟。对于要覆盖其getter的每个属性,都需要执行此操作。

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.

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

#2


1  

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.

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

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.

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

#3


1  

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.

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

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).

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

#4


0  

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.

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

#5


0  

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.

我也用Moq。我喜欢它并且效果很好。但是,在我开始使用Moq之前,我写了许多假货。这就是我如何用假货解决问题的方法。

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

}