【问题标题】:unit test c# mock interface composed inside another interface单元测试 c# 模拟接口在另一个接口内部组成
【发布时间】:2016-07-25 00:00:54
【问题描述】:

我是编码新手。

我有课,A:

public class A 
{
    public InterfaceB _b;   

    public A() 
    {  
        _b = new B(); 
    } 

    public string functionA() 
    {
        if(String.IsNullOrEmpty(_b.GetId())) 
            return String.Empty;
        else if(String.IsNullOrEmpty(_b.GetKey())) 
            return String.Empty;
        else  
            return _b.GetToken(); 
    } 
} 

public interface InterfaceB 
{    
    string GetId();   
    string GetKey(); 
    string GetToken();
}

我想测试functionA,在那里我可以渗透到interfaceB的所有三个方法。在我的单元测试中,我创建了一个 A 类的实例,当我调用它时,我无法设置 B 类的行为。

它一直在访问数据库,但是我需要它用于其他测试用例。

我如何完全模拟这个以便我可以测试整个逻辑?

【问题讨论】:

  • 您将需要在A 上使用InterfaceB 作为参数的构造函数,然后您可以直接将模拟对象InterfaceB 传递给该构造函数,或者将其设置为注入正在使用 DI,即使仅用于该测试。

标签: c# .net unit-testing xunit.net


【解决方案1】:

为了能够测试A 和模拟接口InterfaceB,您必须编写A,以便它不负责创建InterfaceB 的实例。相反,它通过其构造函数接收InterfaceB 的实例。

你会一遍又一遍地看到这种模式:

public A() 
{  
    private readonly InterfaceB _b;

    public A(InterfaceB b)
    {
        _b = b;
    }

    public string functionA() 
    {
        if(String.IsNullOrEmpty(_b.GetId())) return String.Empty;
        else if(String.IsNullOrEmpty(_b.GetKey())) return String.Empty;
        else  return _b.GetToken(); 
     } 
} 

这称为依赖注入。这意味着一个类的依赖被“注入”到其中,而不是创建它的那个类。当我们像这样注入构造函数时,我们也称其为“构造函数注入”,但通常只是“依赖注入”。能够像您所询问的那样模拟接口是我们使用它的原因之一。

几个关键细节:

  • 因为InterfaceB 被传递给构造函数,所以A 中的任何内容都不会“知道”实际的实现是什么。它可以是任何东西。因此,A 永远不会绑定到任何具体的实现。 (这就是为什么你可以“模拟”InterfaceB。)
  • 字段_breadonly。这不是绝对必要的,但这意味着 _b 只能从构造函数中设置并且永远不会再次更改。这强调了A 只有接收这个并使用它。这个类从不控制_b 是什么。无论创造什么A 决定了这个价值是什么。

现在,当您编写单元测试时,您可以创建 InterfaceB 的模拟实现,这些实现完全符合您的要求,例如

public class MockedInterfaceB : InterfaceB
{
    private string _id;
    private string _key;
    private string _token;

    public MockedInterfaceB(string id, string key, string token);
    {
       _id = id;
       _key = key;
       _token = token;
    }
    public string GetId() {return _id};   
    public string GetKey() {return _key}; 
    public string GetToken() {return _token};
}

然后在您的单元测试中,您可以使用该实现:

var testA = new A(new MockedInterfaceB("myid","mykey","mytoken"));

您还可以使用Moq 等工具更轻松地创建这些模拟。

当您听说依赖注入时,它通常是在诸如 Castle Windsor、Autofac 或 Unity 之类的依赖注入容器的上下文中。这些是帮助您进行依赖注入工作的有用工具。他们值得学习。但是依赖注入实际上只是关于你如何编写类,就像在上面的例子中,我们将依赖(InterfaceB)“注入”到类A中。

【讨论】:

  • 太棒了斯科特! Id、key 和 Token 存在于 sql 的表 B 中。我想模拟几个场景,如空、非零和零 id。 64位键..但是我不想用InterfaceB作为参数编写一个额外的构造函数,因为我是依赖注入它并且不想接触源代码我也想在CI的一部分中检查它。使用 Moq 时,我可以创建两个单独的模拟 IA 和 IB 并设置期望,当 B 在 A 中组成时将一个 moq 注入另一个 moq?如果是这样,如何实现?
  • 如果您需要这种向后兼容性,则将构造函数参数设为可选并提供默认值。如果需要,您可以“手动”编写类以进行测试。创建一个对象并将您想要的对象插入其中以进行一项测试,并注入另一个对象以进行另一项测试。您可以编写方法来为您编写测试对象。
【解决方案2】:

实际上,通过使用Typemock,可以在不更改源代码的情况下测试您的方法。 在创建 A 的实例之前,您将能够将 B 模拟为未来的实例,然后您可以修改 B 的方法行为。

例如:

[TestMethod,Isolated]
public void AllBMethodReturnStrings_WillReturnSuccess()
{
    // Arrange
    // Mocking future B's instance
    var fakeIB = Isolate.Fake.NextInstance<B>();
    var realA = new A();

    Isolate.WhenCalled(()=> fakeIB.GetId()).WillReturn("fakeID");
    Isolate.WhenCalled(() => fakeIB.GetKey()).WillReturn("fakeKey");
    Isolate.WhenCalled(() => fakeIB.GetToken()).WillReturn("success");

    // Act
    var result = realA.functionA();

    // Assert
    Assert.AreEqual("success", result);
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-03-14
    • 1970-01-01
    • 1970-01-01
    • 2021-06-11
    • 2011-11-13
    • 1970-01-01
    • 1970-01-01
    • 2021-04-09
    相关资源
    最近更新 更多