C# 模拟一个稍复杂的对象

huangapple go评论52阅读模式
英文:

C# Mocking a bit complex Object

问题

I can provide the translated code snippet for your question:

我有一个关于 Mocking 的问题。

有以下接口及其类表示:

public interface ICourses
{
    public ICourse CurrentCourse { get; }
    public IEnumerable<ICourse> PreviousCourses { get; }
}

其中 `ICourse` 如下:

public interface ICourse
{
    public int Id { get; set; }
    public string CourseName { get; set; }
    public int DurationInMinutes { get; set; }
}

`ICourses` 的类是:

public class Courses : ICourses
{
    private readonly IEnumerable<ICourse> _courses;
    private readonly int _selectedIdAsCurrent;

    public Courses(IEnumerable<ICourse> courses,
        int selectedAsCurrent)
    {
        _courses = courses;
        _selectedIdAsCurrent = selectedIdAsCurrent;
    }

    public Courses(int selectedIdAsCurrent, IEnumerable<ICourse> courses)
    {
        _courses = courses;
        _selectedIdAsCurrent = selectedIdAsCurrent;
    }

    public ICourse CurrentCourse
    {
        get { return _courses.FirstOrDefault(p => p.Id == _selectedIdAsCurrent); }
    }

    public IEnumerable<ICourse> PreviousCourses
    {
        get { return _courses.Where(p => p.Id != _selectedIdAsCurrent); }
    }
}

我的问题是,如何模拟 `Mock<ICourses>`?
我尝试了以下代码:

public Mock<ICourses> CreateCoursesMock(List<ICourse> coursesData)
{
    var coursesMock = new Mock<ICourses>();

    var coursesMockList = new List<Mock<ICourse>>();
    foreach (var course in coursesData)
    {
        var courseMock = CreateCourseMock(course);
        coursesMockList.Add(courseMock);
    }
    coursesMock.Setup(p => p.PreviousCourses).Returns(???);
}

请注意,代码中的 Returns(???) 部分需要您提供模拟 PreviousCourses 属性的返回值,这取决于您的测试场景和需求。这部分需要您根据您的具体情况进行填充。

英文:

I have a Mocking related question please.

There is the following interface with it's class representation

public interface ICourses
{
    public ICourse CurrentCourse { get; }
    public IEnumerable&lt;ICourse&gt; PreviousCourses { get; }
}

Where the ICourse is the following:

public interface ICourse
{
    public int Id { get; set; }
    public string CourseName { get; set; }
    public int DurationInMinutes { get; set; }
}

The class of the ICourses is:

public class Courses: ICourses
{
private readonly IEnumerable<ICourse> _courses;
private readonly int _selectedIdAsCurrent;

public Courses(IEnumerable&lt;ICourse&gt; courses,
    int selectedAsCurrent)
{
    _courses = courses;
    _selectedIdAsCurrent = selectedIdAsCurrent;
    
    public Courses(int selectedIdAsCurrent, IEnumerable&lt;ICourse&gt; courses) 
    {
        _courses = courses;
        _selectedIdAsCurrent = selectedIdAsCurrent;
    }
    public ICourse CurrentCourse
    {
        get { return _courses.FirstOrDefault(p =&gt; p.Id == _selectedIdAsCurrent); }
    }

    public IEnumerable&lt;ICourse &gt; PreviousCourses 
    {
        get { return _courses.Where(p =&gt; p.Id != _selectedIdAsCurrent); }
    }
}

}

My question is, how to mocking Mock&lt;ICourses&gt; ?
I tried the following:

public Mock&lt;ICourses&gt; CreateCoursesMock(List&lt;ICourse&gt; coursesData)
{
    var coursesMock = new Mock&lt;ICourses&gt;();

    var coursesMockList = new List&lt;Mock&lt;ICourse&gt;&gt;();
    foreach (var course in coursesData)
    {
        var courseMock = CreateCourseMock(course);
        coursesMockList.Add(courseMock);
    }
    coursesMock Setup(p =&gt; p.PreviousCourses).Returns(???);
}

答案1

得分: 2

首先,你需要模拟它吗?尽管单独测试组件有一些好处,但同时测试组件也有一些好处。如果你只有一个 ICourses 的实现,尤其是真的只有一个实现,你甚至可以考虑移除接口,并将该类视为数据传输对象,或者作为你正在测试的模块的一部分。

你应该针对某个模块的接口进行测试,但一个 '模块' 可能比一个单独的类要大得多。而且接口很可能包括复杂的类型。为每个类型都使用接口很可能只会导致代码变得更难阅读和理解。

其次,你需要使用模拟框架吗?虽然模拟框架在某些情况下确实很有用,但我不认为它们是所有类型模拟的灵丹妙药。在某些情况下,只需编写接口的简单实现可能更有用。如果你在设置模拟时遇到问题,你能在一年后阅读它的操作吗?你的同事们呢?还是你会切换模拟框架并需要重新编写所有模拟代码?

如果你正在使用 Moq,你需要将你的列表转换为一个 ICourse 列表,通过使用 .Object 属性:

var coursesMockList = new List<ICourse>();
foreach (var course in coursesData)
{
   var courseMock = CreateCourseMock(course);
   coursesMockList.Add(courseMock.Object);
}
coursesMock.Setup(p => p.PreviousCourses).Returns(coursesMockList);

(注意:代码中的角括号已经进行了HTML编码以确保显示正确的文本。)

英文:

First of all, do you need to mock it? While there is some benefit to test components individually, there is also some benefit at testing components together. This is especially true if you only have one implementation of ICourses, you might even consider removing the interface and view the class as a data transfer object, or as a part of the module you are testing.

You should test against the interface of some module, but a 'module' can be much larger than a single class. And the interface might very well include complex types. Using interfaces for every single type will likely just result in code that is more difficult to read and understand.

Second, do you need to use a mocking framework? While mocking frameworks certainly can be useful in some cases, I do not view them as a panacea for all types of mocking. In some cases it is more useful to just write a simple implementation of your interface. If you have problems setting up your mock, will you be able to read what it does in a year? will your colleges? Or will you switch mocking framework and need to rewrite all your mocking code?

If you are using Moq you need to convert your list to a list of ICourse, by using the .Object property

var coursesMockList = new List&lt;ICourse&gt;();
foreach (var course in coursesData)
{
   var courseMock = CreateCourseMock(course);
   coursesMockList.Add(courseMock.Object);
}
coursesMock.Setup(p =&gt; p.PreviousCourses).Returns(coursesMockList);

答案2

得分: 0

让我们一步一步来:首先,让我们模拟一个单独的 ICourse,例如(让我们为它提取一个方法)

private static Mock<ICourse> MockedCourse(int id, string name, int duration) {
  Mock<ICourse> mockedCourse = new Mock<ICourse>();

  // 这是如何模拟 "get" 的方式
  mockedCourse
    .SetupGet(item => item.Id)
    .Returns(id);

  // 这是如何模拟 "set" 的方式;
  // 让我们不允许更改 id
  mockedCourse
    .SetupSet(item => item.Id = It.IsAny<int>())
    .Callback<int>(value => throw new InvalidOperationException("Don't change my ID!"));

  mockedCourse
    .SetupGet(item => item.CourseName)
    .Returns(name);

  mockedCourse
    .SetupGet(item => item.DurationInMinutes)
    .Returns(duration);

  return mockedCourse;
}

现在我们准备好处理 ICourses 了:

// 模拟的课程
Mock<ICourses> mockedCourses = new Mock<ICourses>();

// 使用 Math 作为当前课程
// 注意 ".Object" - 我们想要模拟的对象,而不是模拟本身
mockedCourses
  .SetupGet(item => item.CurrentCourse)
  .Returns(MockedCourse(123, "Math", 60).Object);

// 并要求 Arithmetic 和 Geometry 作为先前的课程
mockedCourses
  .SetupGet(item => item.PreviousCourses)
  .Returns(new ICourse[] {
     MockedCourse(700, "Arithmetic", 90).Object,
     MockedCourse(700, "Geometry", 90).Object,
   });

// 准备好在测试中使用的模拟接口
ICourses courses = mockedCourses.Object;
...
英文:

Let's go step by step: first, let's mock a single ICourse, e.g. (let's extract a method for it)

private static Mock&lt;ICourse&gt; MockedCourse(int id, string name, int duration) {
  Mock&lt;ICourse&gt; mockedCourse = new Mock&lt;ICourse&gt;();

  // That&#39;s how &quot;get&quot; can be mocked
  mockedCourse
    .SetupGet(item =&gt; item.Id)
    .Returns(id);

  // That&#39;s how &quot;set&quot; can be mocked;
  // let&#39;s not allow id changing
  mockedCourse
    .SetupSet(item =&gt; item.Id = It.IsAny&lt;int&gt;())
    .Callback&lt;int&gt;(value =&gt; throw new InvalidOperationException(&quot;Don&#39;t change my ID!&quot;));

  mockedCourse
    .SetupGet(item =&gt; item.CourseName)
    .Returns(name);

  mockedCourse
    .SetupGet(item =&gt; item.DurationInMinutes)
    .Returns(duration);

  return mockedCourse;
}

Now we are ready for ICourses:

// A mocked courses
Mock&lt;ICourses&gt; mockedCourses = new Mock&lt;ICourses&gt;();

// uses Math as a current course
// note &quot;.Object&quot; - we want mocked object, not mock itself
mockedCourses
  .SetupGet(item =&gt; item.CurrentCourse)
  .Returns(MockedCourse(123, &quot;Math&quot;, 60).Object);

// And requires Arithmetic and Geometry as previous courses
mockedCourses
  .SetupGet(item =&gt; item.PreviousCourses)
  .Returns(new ICourse[] {
     MockedCourse(700, &quot;Arithmetic&quot;, 90).Object,
     MockedCourse(700, &quot;Geometry&quot;, 90).Object,
   });

// Mocked interface ready to be used in the test
ICourses courses = mockedCourses.Object;
...

huangapple
  • 本文由 发表于 2023年5月29日 19:33:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/76356976.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定