Service 还有一个关于嘲笑的问题

Service 还有一个关于嘲笑的问题,service,mocking,encapsulation,Service,Mocking,Encapsulation,首先让我声明,尽管我是TDD的一个相当新的实践者,但我对它的好处非常满意。我觉得我已经有足够的进步去考虑使用嘲讽,并且在理解MOOK与OOP相适应时,打了一个真正的砖墙。 我已经阅读了尽可能多的关于这个主题的相关帖子/文章(,),但仍然不完全清楚如何或何时嘲笑 让我举一个具体的例子。我的应用程序有一个服务层类(有人称之为应用层?),其中的方法大致映射到特定用例。这些类可以与持久层、域层甚至其他服务类协作。我一直是一个很好的DI小男孩,并且已经正确地考虑了我的依赖性,所以它们可以作为测试目的的子平

首先让我声明,尽管我是TDD的一个相当新的实践者,但我对它的好处非常满意。我觉得我已经有足够的进步去考虑使用嘲讽,并且在理解MOOK与OOP相适应时,打了一个真正的砖墙。

我已经阅读了尽可能多的关于这个主题的相关帖子/文章(,),但仍然不完全清楚如何或何时嘲笑

让我举一个具体的例子。我的应用程序有一个服务层类(有人称之为应用层?),其中的方法大致映射到特定用例。这些类可以与持久层、域层甚至其他服务类协作。我一直是一个很好的DI小男孩,并且已经正确地考虑了我的依赖性,所以它们可以作为测试目的的子平台

示例服务类可能如下所示:

public class AddDocumentEventService : IAddDocumentEventService
{
    public IDocumentDao DocumentDao
    {
        get { return _documentDao; }
        set { _documentDao = value; }
    }
    public IPatientSnapshotService PatientSnapshotService
    {
        get { return _patientSnapshotService; }
        set { _patientSnapshotService = value; }
    }

    public TransactionResponse AddEvent(EventSection eventSection)
    {
        TransactionResponse response = new TransactionResponse();
        response.Successful = false;

        if (eventSection.IsValid(response.ValidationErrors))
        {

            DocumentDao.SaveNewEvent( eventSection,  docDataID);

            int patientAccountId = DocumentDao.GetPatientAccountIdForDocument(docDataID);
            int patientSnapshotId =PatientSnapshotService.SaveEventSnapshot(patientAccountId, eventSection.EventId);

            if (patientSnapshotId == 0)
            {
                throw new Exception("Unable to save Patient Snapshot!");
            }

            response.Successful = true;
        }
        return response;
    }
}

我使用NMock在隔离依赖项(DocumentDao、PatientSnapshotService)的情况下测试了这个方法。下面是测试结果

 [Test]
 public void AddEvent()
    {
        Mockery mocks = new Mockery();
        IAddDocumentEventService service = new AddDocumentEventService();
        IDocumentDao mockDocumentDao = mocks.NewMock<IDocumentDao>();
        IPatientSnapshotService mockPatientSnapshot = mocks.NewMock<IPatientSnapshotService>();

        EventSection eventSection = new EventSection();

        //set up our mock expectations
        Expect.Once.On(mockDocumentDao).Method("GetPatientAccountIdForDocument").WithAnyArguments();
        Expect.Once.On(mockPatientSnapshot).Method("SaveEventSnapshot").WithAnyArguments();
        Expect.Once.On(mockDocumentDao).Method("SaveNewEvent").WithAnyArguments();

        //pass in our mocks as dependencies to the class under test
        ((AddDocumentEventService)service).DocumentDao = mockDocumentDao;
        ((AddDocumentEventService)service).PatientSnapshotService = mockPatientSnapshot;

        //call the method under test
        service.AddEvent(eventSection);

        //verify that all expectations have been met
        mocks.VerifyAllExpectationsHaveBeenMet();
    }
[测试]
公共事务
{
mockry mocks=新mockry();
IADDocumentEventService服务=新的AddDocumentEventService();
IDocumentDao mockDocumentDao=mocks.NewMock();
IPatientSnapshotService mockPatientSnapshot=mocks.NewMock();
EventSection EventSection=新的EventSection();
//设定我们的模拟期望
Expect.Once.On(mockDocumentDao.Method)(“GetPatientAccountIdForDocument”).WithAnyArguments();
Expect.Once.On(mockPatientSnapshot.Method(“SaveEventSnapshot”).WithAnyArguments();
Expect.Once.On(mockDocumentDao.Method(“SaveNewEvent”).WithAnyArguments();
//将模拟作为依赖项传递给被测试的类
((AddDocumentEventService)服务).DocumentDao=mockDocumentDao;
((AddDocumentEventService)服务).PatientSnapshotService=mockPatientSnapshot;
//调用被测试的方法
服务.附录(事件部分);
//验证是否满足了所有期望
mocks.VerifyAllExpectationsHaveBeenMet();
}
我对这场小小的嘲弄之旅的想法如下:

  • 这个测试似乎打破了许多基本的OO规则,其中最重要的是封装:我的测试完全了解被测类的具体实现细节(即方法调用)。每当类内部发生变化时,我都会看到大量非生产性的时间花在更新测试上
  • 也许是因为我的服务类目前相当简单,但我不太明白这些测试增加了什么价值。我是否保证按照特定用例的要求调用协作对象?为了这么小的好处,代码复制似乎高得离谱
    我遗漏了什么?

    你提到了马丁·福勒关于这个主题的一篇非常好的帖子。他提到的一点是,嘲弄者喜欢测试行为,并孤立事物

    “经典的TDD风格是在可能的情况下使用真实对象,如果使用真实对象有困难,则使用双精度。因此,经典的TDD将使用真实仓库和双精度邮件服务。双精度的类型实际上并不重要

    然而,mockist TDD实践者总是对任何具有有趣行为的对象使用mock。在这种情况下,对仓库和邮件服务都使用mock。”

    如果你不喜欢这类东西,你可能是一个典型的TDDer,只有在不方便的时候才应该使用Mock(比如邮件服务,或者信用卡收费)否则,您将创建自己的翻倍(就像创建内存中的数据库一样)

    特别是,我是一个mockist,但我不会验证是否调用了特定的方法(除非它不返回值)。在任何情况下,我都会测试接口。当函数返回某些内容时,我使用模拟框架创建存根

    最后,所有内容都包括您想要测试的内容和方式。您认为检查这些方法是否真的被调用(使用mock)很重要吗?您是否只想检查通话前后的状态(使用假货)<强>选择足以考虑它正在工作,然后建立你的测试来精确地检查它!<强>

    关于测试的价值,我有一些看法:

    • 短期而言,当您进行TDD时,通常会得到更好的设计,尽管您可能需要更长的时间
    • 从长远来看,您不会太害怕在以后更改和维护此代码(当您记不清详细信息时),您会立即收到红色,几乎是即时反馈

    顺便说一下,测试代码大小与生产代码大小一样大是正常的。

    您提到了martin fowler关于这个主题的一篇非常好的帖子。他提到的一点是,嘲弄者喜欢测试行为,并孤立事物

    “经典的TDD风格是在可能的情况下使用真实对象,如果使用真实对象有困难,则使用双精度。因此,经典的TDD将使用真实仓库和双精度邮件服务。双精度的类型实际上并不重要

    然而,mockist TDD实践者总是对任何具有有趣行为的对象使用mock。在这种情况下,对仓库和邮件服务都使用mock。”

    如果你不喜欢这类东西,你可能是一个典型的TDDer,只有在不方便的时候才应该使用Mock(比如邮件服务,或者信用卡收费)否则,您将创建自己的翻倍(就像创建内存中的数据库一样)

    特别是,我是一个mockist,但我不会验证是否调用了特定的方法(除非它不返回值)。在任何情况下,我都会测试接口。当函数返回某些内容时,我使用模拟框架创建存根

    最后,
    Instantiate the fake repositories.
    Run your test method.
    Check the fake repository to see if the new elements exist in it.
    
    public void AddEventSavesSnapshot(object eventSnaphot)
    {
        Mock<IDocumentDao> mockDocumentDao = new Mock<IDocumentDao>();
        Mock<IPatientSnapshotService> mockPatientSnapshot = new Mock<IPatientSnapshotService>();
    
        string eventSample = Some.String();
        EventSection eventSection = new EventSection(eventSample);
    
        mockPatientSnapshot.Setup(r => r.SaveEventSnapshot(eventSample));
    
        AddDocumentEventService sut = new AddDocumentEventService();
        sut.DocumentDao = mockDocumentDao;
        sut.PatientSnapshotService = mockPatientSnapshot;
    
        sut.AddEvent(eventSection);
    
        mockPatientSnapshot.Verify();
    }