如何为CRM 2011插件正确设置C#单元测试?

Dar*_*ryl 8 c# unit-testing dynamics-crm-2011

尝试在CRM 2011中调试插件非常困难.将.pdb文件放在服务器上的正确位置不仅存在问题,而且每次进行编码更改时,都会遇到部署和重新注册插件的麻烦.由于触发器是在CRM本身,因此很难为它创建单元测试.

我目前为一个全新的插件编写单元测试的过程相当缓慢而且错误,但是这样的事情:

  1. 使用SDK插件注册工具注册新插件
  2. 将调试器附加到w3wp.exe,在插件代码中添加一个断点.
  3. 通过注册运行的任何操作触发插件.
  4. 当断点被击中时,将管道的preimage,postimage和目标值序列化为XML文件,然后这将成为我的单元测试的输入.
  5. 停止调试并创建一个新的单元测试,使用RhinoMocks模拟PluginExecutionContext和ServiceProvider,使用序列化的XML文件作为输入参数的存根.
  6. 创建在每个单元测试的开始和结束时运行的方法,这些方法重置(首先尝试删除,然后添加)要处理的单元测试的虚拟数据,然后在测试结束时删除虚拟数据.
  7. 编辑序列化文件以引用虚拟数据,这样我就可以确保插件在每次运行时都能对抗完全相同的数据.
  8. 在单元测试中声明并实例化插件,传入模拟对象
  9. 执行插件,运行其他查询以确保插件执行我期望的工作,断言失败.

这是一件痛苦的事.从得到的图片正确,创造虚拟的数据,并且每个测试运行时将其复位,似乎有需要改进的很多地区.

如何在不必从CRM实际触发插件的情况下对插件进行单元测试,或者首先运行在CRM中调试它的所有箍,并为每个测试创建独特的虚拟数据?如何使用注入来消除在CRM中为每个单元测试删除,创建,测试,验证和删除数据的需要?

2016年更新

这个问题仍然有很多点击,所以我想我会添加两个(我所知道的)开源项目,为单元测试提供Fake CRM实例:

  • FakeXrmEasy - 由Jordi创建(见下面的答案)
    • 主要是伪造的CRM服务
    • 支持插件/工作流程伪造
    • 对FakeItEasy的依赖
    • 伟大的文档
  • XrmUnitTest - 由我自己创建
    • 假CRM服务+更多(假设,实体建设者等)
    • 流畅支持插件/工作流程伪造
    • 没有对任何模拟框架的依赖
    • Sucky文档(我正在研究它)

查看我创建的视频,比较和对比差异.

Ben*_*on1 2

如何对插件进行单元测试,而无需从 CRM 实际触发它,或者首先在 CRM 中进行调试,并为每个测试创建唯一的虚拟数据?

带着嘲讽。请参阅此链接了解要使用 RhinoMocks 模拟哪些类。听起来你在这方面正在路上。

如何使用注入来消除在每个单元测试中删除、创建、测试、验证和删除 CRM 中的数据的需要?

可以通过在要操作的实体的手摇实例中存根来完成输入参数的值注入:

// Add the target entity     
Entity myStubbedEntity = new Entity("account");
// set properties on myStubbedEntity specific for this test...
ParameterCollection inputParameters = new ParameterCollection();     
inputParameters.Add("Target", myStubbedEntity);     
pipelineContext.Stub(x => x.InputParameters).Return(inputParameters); 
Run Code Online (Sandbox Code Playgroud)

这不是比捕获 xml 数据并重新水化整个输入参数集合更容易吗?


编辑:对于数据访问,通常的建议是将数据访问包装到类中。存储库模式很流行,但对于我们这里的需要来说太过分了。对于您的插件执行类,您可以在创建时“注入”您的模拟类。一个初始化默认存储库的空白构造函数,以及一个采用 IRepository 的第二个构造函数。

public class MyPluginStep
{
    ITaskRepository taskRepository;
    public MyPluginStep(ITaskRepository repo)
    {
        taskRepository = repo;
    }
    public MyPluginStep()
    {
        taskRepository = new DefaultTaskRepositoryImplementation();
    }
    public MyExecuteMethod(mypluginstepparams){
        Task task = taskRepository.GetTaskByContact(...);
    }
Run Code Online (Sandbox Code Playgroud)

根据插件步骤的复杂性,这可能会演变成将许多存储库传递给每个类,并且可能会变得繁重,但这是您可以根据需要增加复杂性的基础知识。