简体   繁体   English

单元测试中的依赖注入

[英]Dependency Injection in Unit Tests

I have implemented array based Stack Data Structure along with corresponding Unit Tests. 我已经实现了基于数组的堆栈数据结构以及相应的单元测试。 This Stack implements my IStack interface. 该堆栈实现了我的IStack接口。 So at the moment, my UT class looks something like that: 因此,目前,我的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);
    }

Now, I am about to implement Linked List based Stack. 现在,我要实现基于链接列表的堆栈。 This Stack will inherit from the same IStack interface. 该堆栈将从相同的IStack接口继承。

I would like to Unit Test the linked list Stack as well. 我也想对链接列表堆栈进行单元测试。 Since both are inheriting from the same interface, I should be able to take advantage of already implemented Unit Test, in order to prevent from unnecessary code duplication. 由于两者都是从同一个接口继承的,因此我应该能够利用已经实现的单元测试,以防止不必要的代码重复。

What would be the best way to create two separate Unit Test classes, one for Array based Stack, and another for Linked List based Stack, that would use the same Unit Test methods? 创建两个单独的单元测试类的最佳方法是什么,一个用于基于数组的堆栈,另一个用于基于链表的堆栈,它们将使用相同的单元测试方法? I assume Dependency Injection would be an answer, but how I would go about it? 我认为依赖注入将是一个答案,但是我将如何处理呢?

Dependency injection is never the answer when it comes to tests. 当涉及到测试时,依赖注入永远不是答案。

You are not testing abstractions, that's impossible, you test concrete implementations. 您不是在测试抽象,那是不可能的,您在测试具体的实现。 You can however mock abstractions, interfaces, abstract classes. 但是,您可以模拟抽象,接口,抽象类。

You can create some class with the sole purpose of reusing code and you call that class from your test methods, that's ok and totally doable. 您可以创建某个类,其唯一目的是重用代码,然后从测试方法中调用该类,这是可以的并且完全可行。

You will still need two test classes one for each of your concrete implementations and have both call this new class you created. 您仍将需要两个测试类,每个具体的实现一个,并且都调用了您创建的这个新类。 This avoid code duplication. 这样可以避免代码重复。

You can separate logic in another method. 您可以使用另一种方法来分离逻辑。

[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);
}

Say we have the following 说我们有以下

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; }
}

And we wish to implement the IsEmpty_OnCreation() test from the OP. 我们希望从OP中实现IsEmpty_OnCreation()测试。 We can make a common test and add multiple invokers (one for each implementation to be tested). 我们可以进行一个通用测试,并添加多个调用程序(每个要测试的实现一个)。 The problem is scaling. 问题在扩展。

For each new piece of functionality to be tested we need to add 对于每个要测试的新功能,我们需要添加

1) the test implementation 1)测试执行
2) an invoker for each implementation to be tested. 2)每个要测试的实现的调用程序。

For each new implementation we introduce, we need to add an invoker for each existing test. 对于我们介绍的每个新实现,我们需要为每个现有测试添加一个调用程序。

It is possible to use inheritance to do most of the work for us 可以使用继承为我们完成大部分工作

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);
  }
}

The tests are generated in each derived fixture. 测试在每个派生的夹具中生成。

If we want to add a new test, we add it to the StackTest class and it is automatically included in each derived fixture. 如果要添加新测试,则将其添加到StackTest类中,并自动将其包含在每个派生的夹具中。

If we add a third implementation of IStack , we simply add a new test fixture deriving from StackTest and overriding the create method. 如果添加IStack的第三个实现,则只需添加一个从StackTest并覆盖create方法的新测试装置。

Note: 注意:
If the classes under test have default constructors, the same shape can be used with a Generic StackTest as the base 如果要测试的类具有默认构造函数,则可以将通用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>
{
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM