【问题标题】:Inject/Mock external dll abstract classes' static methods注入/模拟外部 dll 抽象类的静态方法
【发布时间】:2015-05-17 19:47:48
【问题描述】:

我有这种情况:使用外部 DLL 并进行 API 调用的天蓝色云服务。这个 DLL 有一个抽象类,它有一个静态方法来返回我需要用来进行 API 调用的子类引用。

现在出于测试目的,我们在模拟器中运行云服务并运行我们的单元测试。但是我们不想对外部系统进行 API 调用。我们需要以某种方式拦截它。我昨天大部分时间都在尝试看看我是否可以做一些依赖注入(统一)来做到这一点,但不用说,没有运气。

抽象类公开静态方法以获取子类的实例以实际进行 API 调用可能是最具限制性的场景。

下面是一些反编译和清理的代码,以显示相关部分。

public abstract class EntityManager : System.Object
{

    private static object lockObject;
    private static Dictionary<System.Type, EntityManager> entityManagers;
    private bool isSingleton;

    public enum EntityManagerInstanceType : int
    {
        SingletonInstance = 0,
        NewInstance = 1,
    }

    static EntityManager() { }

    protected EntityManager() { }

    public static T GetEntityManager<T>(EntityManagerInstanceType instanceType) where T : EntityManager
    {
        T item;
        System.Type type = typeof(T);
        T t = default(T);
        lock (EntityManager.lockObject)
        {
            if (instanceType != EntityManagerInstanceType.SingletonInstance || !EntityManager.entityManagers.ContainsKey(type))
            {
                t = (T)System.Activator.CreateInstance(type, true);
                try
                {                        
                    t.isSingleton = instanceType == EntityManagerInstanceType.SingletonInstance;                        
                }
                catch (Exception adapterException)
                {                        
                    throw;
                }
                if (instanceType == EntityManagerInstanceType.SingletonInstance)
                {
                    EntityManager.entityManagers[type] = t;
                }
                return t;
            }
            else
            {
                item = (T)EntityManager.entityManagers[type];
            }
        }
        return item;
    }

    protected object ProcessRequest(string methodName, object request) { return new object(); }
}

public class PersonaEntityManager : EntityManager
{
    protected PersonaEntityManager() { }

    public PersonaResponseData UpdatePersona(PersonaUpdateRequestData requestData)
    {
        return (PersonaResponseData)base.ProcessRequest("Mdm.UpdatePersona", requestData);
    }
}

public class PublisherWorkerRole : RoleEntryPoint
{
    public bool UpdatePersona(PersonaUpdateRequestData contact, string MessageId)
    {
        PersonaEntityManager mgr = EntityManager.GetEntityManager<PersonaEntityManager>(EntityManager.EntityManagerInstanceType.NewInstance);
        var resp = mgr.UpdatePersona(contact);
        return resp != null;
    }
}

在这种情况下,理想的方法是什么?如果不设置我们自己的模拟 API 并更改应用程序配置以进行测试以调用我们的模拟 API,这甚至可以测试吗?

如果您需要我进一步详细说明,请告诉我。

【问题讨论】:

    标签: c# unit-testing dependency-injection inversion-of-control


    【解决方案1】:

    一种方法是使用ms shimstypemock 之类的东西来模拟静态调用。这将减少对您的生产代码的影响,但如果您还没有使用它们可能需要财务投资。这些库能够拦截其他模拟框架无法拦截的调用,除了允许您模拟静态调用之外,它们还允许您创建您也需要的 PersonaEntityManager 的模拟版本。

    正如您在下面的评论中提到的,以下方法不起作用,因为您需要能够模拟 PersonaEntityManager 类,以便您可以拦截对 UpdatePersona 的调用,因为它不是虚拟的标准的模拟框架做不到。为了完整起见,我保留了下面的方法,因为这是我通常用来隔离静态依赖项的方法。

    如果您不介意修改生产代码,则可以隔离包装类背后的依赖关系。然后可以以正常方式将此包装类注入到您的代码中。

    所以你最终会得到一些类似这样的包装代码:

    public interface IEntityManagerWrapper {
        T GetEntityManager<T>(EntityManager.EntityManagerInstanceType instanceType) where T : EntityManager;
    }
    
    public class EntityManagerWrapper : IEntityManagerWrapper {
        public T GetEntityManager<T>(EntityManager.EntityManagerInstanceType instanceType) where T : EntityManager {
            return EntityManager.GetEntityManager<T>(instanceType);
        }
    }
    

    IEntityWrapper 可以设置为使用 Unity 注入,然后使用您选择的模拟框架进行模拟,以返回您所依赖的其他类的模拟实例,例如 PesonaEntityManager

    因此,您的生产代码将如下所示:

    public class MyProductionCode{
        private IEntityManagerWrapper _entityManager;
    
        public MyProductionCode(IEntityManagerWrapper entityManager) {
            _entityManager = entityManager;
        }
    
        public void DoStuff() {
            PersonaEntityManager pem = _entityManager.GetEntityManager<PersonaEntityManager>(EntityManager.EntityManagerInstanceType.NewInstance);
    
            var response = pem.UpdatePersona(new PersonaUpdateRequestData());
        }
    }
    

    测试代码应该是这样的(假设您使用的是最小起订量):

    [Test]
    public void TestSomeStuff() {
        var em = new Mock<IEntityManagerWrapper>();
        var pe = new Mock<PersonaEntityManager>();
        pe.Setup(x => x.UpdatePersona(It.IsAny<PersonaUpdateRequestData>())).Returns(new PersonaResponseData());
        em.Setup(x=>x.GetEntityManager<PersonaEntityManager>(It.IsAny<EntityManager.EntityManagerInstanceType>())).Returns(pe.Object);
    
        var sut = new MyProductionCode(em.Object);
    
        sut.DoStuff();
    }
    

    EntityWrapper 类本身非常简单,所以我倾向于将它作为一个集成点进行测试,因此使用集成级别测试来确保它在编写时和更改时都能正常工作。

    【讨论】:

    • 为延迟响应道歉,因喉咙错误而下降。无论如何,我相信@user2184057 也指的是类似的方法。我仍然不清楚如何为模拟类注入 EntityManagerWrapper,因为我需要用具体类型调用它的 GetEntityManager - PersonaEntityManager 或 MockedEntityManager 意味着我需要在我的生产代码中进行切换,这就是我的我试图避免。抱歉,如果我的理解不正确。我确实尝试过使用通用接口,但这会导致容器解析问题。需要深入挖掘。
    • @Shani 我已经更新了我的答案以完成我的示例,但是你是对的,因为你不能模拟对UpdatePersona 的调用,包装器方法可能对你不起作用.如果您可以在 Visual Studio 中访问 Shims/Fakes(我认为这取决于您使用的版本),那么您可以使用它们来创建这些难以模拟的类的假货。或者,TypeMock Isolator 是一个非常强大的模拟工具,如果您有预算,它非常适合此类场景。
    • 感谢更新的回复。在我的例子中,服务要么在模拟器中运行,要么在 azure 本身中运行,并且测试将向服务发送一些数据并查看响应。所以测试本身不会嘲笑角色实体。因此,我选择了 ServiceLocator 路线,注册了 Prod 和 Mock 实例,并根据标志解析了要在运行时使用的实际对象。我试图避免的事情是注册两个实例并让统一根据标志为我实例化对象,但这是我在这个特定场景中无法实现的。 HTH。
    【解决方案2】:

    嗯,如何为该服务创建代理。通过代理公开必要的接口并向其注入提供者(模拟或原始)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-03
      • 2016-12-05
      • 1970-01-01
      相关资源
      最近更新 更多