【问题标题】:C# Unit Testing (Xunit and Moq) - Test an interface and its implementations separatelyC# 单元测试(Xunit 和 Moq) - 分别测试接口及其实现
【发布时间】:2017-05-18 12:47:18
【问题描述】:

我想将用于测试接口行为本身的单元测试与实现的附加业务行为分开。

public interface IIncrementor
{
    void Increment();

    int Count { get; }
}

public class AIncrementor : IIncrementor { /* Implementation */ }
public class BIncrementor : IIncrementor { /* Implementation */ }

为了确保接口业务行为的正确性,我需要根据我定义的单元测试检查所有相应的实现。当然,每个实现都有自己的额外行为要测试,但我不想一遍又一遍地重复自己。

我当前的解决方案是创建一个带有抽象属性的抽象测试类,供子类实现。

public abstract class IIncrementorTest
{
    protected abstract IIncrementor Incrementor { get; }

    [Fact]
    public void WhenIncremented_ThenCounterHasCorrectValue()
    {
        var oldCount = Incrementor.Count;

        Incrementor.Increment();

        Assert.Equal(oldCount + 1, Incrementor.Count);
    }
}

public class AIncremetorTest : IIncrementorTest
{
    protected override IIncrementor Incrementor => new AIncrementor();

    [Fact]
    public void WhenSomeAdditional_ThenCheckSomething() { /* More test */ }
}

public class BIncremetorTest : IIncrementorTest
{
    protected override IIncrementor Incrementor => new BIncrementor();

    [Fact]
    public void WhenSomeAdditional_ThenCheckSomething() { /* More test */ }
}

但这会导致一些问题,例如当一个测试用例需要以不同的方式构建一个实例时,这很可能会发生。您是否建议为这些情况创建抽象属性?

protected abstract IIncrementor IncrementorForCheckingSomething { get; }

或者是否有一个最佳实践方法可以全面解决这个接口测试问题?

【问题讨论】:

  • 你为什么要测试一个模拟?
  • 这个问题不清楚。
  • @Nkosi 我想根据单元测试检查接口的所有实现;如果我的业务规则暗示商店中的商品数量必须增加,因为我调用 Save-method 我想在接口“本身”上测试这种行为,这意味着我必须收集所有实现类反射方式(模拟) 并让他们通过单元测试。
  • 不清楚的是mock the implementation-types。那些不会是模拟,它们将是实现实例。只需使用反射并获取所有实现并为每个实现创建一个实例来运行您的测试。另外这个问题有点宽泛,因为没有迹象表明实现是否具有也需要模拟的依赖项。

标签: c# unit-testing mocking moq xunit


【解决方案1】:

如果我理解正确,您想为您的接口编写一组测试,并将这些测试应用于实现该接口的任何类。

您可以使用反射来查找程序集中所有实现接口的类型,如下所示:

var interfaceType = typeof(IIncrementor);
var types = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a => a.GetTypes())
.Where(t => t.IsClass && interfaceType.IsAssignableFrom(t));

并实例化所有这些类型。如果它们不共享一个共同的构造函数签名,这可能会很棘手;但可以使用默认构造函数来解决问题中的示例...

foreach(Type t in types)
{
    yield return (IIncrementor)Activator.CreateInstance(t,null);
}

将该代码放入返回类型为IEnumerable<IIncrementor> 的方法中,并为该方法提供属性MemberData 以将这些实例传递给每个测试。

【讨论】:

  • 我不认为这就是他们所追求的。似乎他们想编写一次每个测试,然后在测试资源管理器中看到它们显示一次,每个实施者都有自己的通过/失败状态。不幸的是,反射不能创建测试。他们尝试使用派生的测试类,但这使得为各种实现者的测试提供自定义模拟变得困难。 IE。如果实现者 B 的测试应该对测试中涉及的其他一些接口使用模拟 B,那么如果没有一些额外的机制,派生的测试类将无法提供。
  • @Daniel Reflection 无法创建测试,这不是我的建议。 MemberData 方法允许您动态参数化测试,我使用反射作为这些参数的来源。使用这种方法,我希望每个实例以单独的通过/失败状态单独显示在测试资源管理器中,而无需编写单独的测试。显示名称可能没有帮助,因此也许可以使用提供基于文本的值的“MemberData”方法实现更好的拆分;但这个概念是合理的。
猜你喜欢
  • 2018-01-26
  • 2012-11-27
  • 2021-04-12
  • 1970-01-01
  • 2018-04-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-23
相关资源
最近更新 更多