C# 单元测试中的依赖注入

C# 单元测试中的依赖注入,c#,unit-testing,dependency-injection,C#,Unit Testing,Dependency Injection,我已经实现了基于数组的堆栈数据结构以及相应的单元测试。这个堆栈实现了我的IStack接口。所以现在,我的UT课程看起来是这样的: [TestClass] public class Stack_ArrayBased_Tests { //check against empty collection [TestMethod] public void IsEmpty_NoElements() { var myStack = new Stack_

我已经实现了基于数组的堆栈数据结构以及相应的单元测试。这个堆栈实现了我的IStack接口。所以现在,我的UT课程看起来是这样的:

[TestClass]
public class Stack_ArrayBased_Tests
{      
    //check against empty collection
    [TestMethod]
    public void IsEmpty_NoElements()
    {
        var myStack = new Stack_ArrayBased_Example1(10);

        var exp = true;
        var act = myStack.IsEmpty();
        Assert.AreEqual(exp, act);
    }
现在,我将实现基于链表的堆栈。此堆栈将从同一IStack接口继承

我还想对链表堆栈进行单元测试。由于两者都是从同一接口继承的,我应该能够利用已经实现的单元测试,以防止不必要的代码重复


创建两个单独的单元测试类(一个用于基于数组的堆栈,另一个用于基于链表的堆栈)的最佳方法是什么,它们将使用相同的单元测试方法?我假设依赖注入是一个答案,但我会怎么做呢?

您可以用另一种方法分离逻辑

[TestMethod]
public void IsEmpty_NoElements_ArrayBased()
{
    var myStack = new Stack_ArrayBased_Example1(10);
    IsEmpty_NoElements(myStack)
}

[TestMethod]
public void IsEmpty_NoElements_LinkedListBased()
{
    var myStack = new Stack_LinkedListBased_Example1(10);
    IsEmpty_NoElements(myStack)
}

public void IsEmpty_NoElements(IStack myStack)
{
    var exp = true;
    var act = myStack.IsEmpty();
    Assert.AreEqual(exp, act);
}

当涉及到测试时,依赖注入永远不是答案

你不是在测试抽象,这是不可能的,你是在测试具体的实现。但是,您可以模拟抽象、接口和抽象类

您可以创建一些类,其唯一目的是重用代码,然后从测试方法中调用该类,这是完全可行的


您仍然需要两个测试类,每个测试类一个用于您的具体实现,并让它们都调用您创建的这个新类。这样可以避免代码重复。

假设我们有以下几点

public interface IStack
{
  bool IsEmpty { get; }
}

public class StackImpl1 : IStack
{
  public StackImpl1(int size)
  {
     IsEmpty = true;
  }

  public bool IsEmpty { get; }
}

public class StackImpl2 : IStack
{

  public StackImpl2(int size)
  {
     IsEmpty = true;
  }

  public bool IsEmpty { get; }
}
我们希望从OP中实现
IsEmpty\u OnCreation()
测试。我们可以进行一个公共测试并添加多个调用程序(每个要测试的实现一个调用程序)。问题在于可伸缩性

对于要测试的每个新功能,我们需要添加

1) 测试实现
2) 每个要测试的实现的调用程序

对于我们引入的每个新实现,我们需要为每个现有测试添加一个调用程序

使用继承为我们完成大部分工作是可能的

public abstract class StackTest
{
  protected abstract IStack Create(int size);

  [TestMethod]
  public void IsEmpty_NoElements()
  {
     var myStack = Create(10);

     var exp = true;
     var act = myStack.IsEmpty;
     Assert.AreEqual(exp, act);

  }
}

[TestClass]
public class StackImp1Fixture : StackTest
{
  protected override IStack Create(int size)
  {
     return new StackImpl1(size);
  }
}

[TestClass]
public class StackImp2Fixture : StackTest
{
  protected override IStack Create(int size)
  {
     return new StackImpl2(size);
  }
}
在每个衍生夹具中生成测试

如果我们想添加一个新的测试,我们将它添加到
StackTest
类中,它将自动包含在每个派生的fixture中

如果我们添加
IStack
的第三个实现,我们只需添加一个从
StackTest
派生并覆盖create方法的新测试夹具

注:
如果被测试的类具有默认构造函数,那么可以将相同的形状与通用StackTest一起用作基

public class GenStackTest<TStack> where TStack : IStack, new()
{

  [TestMethod]
  public void IsEmpty_NoElements()
  {
     var myStack = new TStack();

     var exp = true;
     var act = myStack.IsEmpty;
     Assert.AreEqual(exp, act);

  }
}

[TestClass]
public class GenStack1Fixture : GenStackTest<StackImpl1>
{
}

[TestClass]
public class GenStack2Fixture : GenStackTest<StackImpl2>
{
}
公共类测试,其中TStack:IStack,new()
{
[测试方法]
公共无效是空的元素()
{
var myStack=new TStack();
var exp=真;
var act=myStack.IsEmpty;
主张平等(exp,act);
}
}
[测试类]
公共类Genstack1混合:GenStackTest
{
}
[测试类]
公共类Genstack2混合:GenStackTest
{
}

与任何其他代码一样,您应该将属于这两种场景的代码重构为自己的类,以便在不同的用例中使用。因此,当您已经有了测试用例1的实现时,将行为重构到它自己的类中,并在两个测试中使用实例。