【问题标题】:Unit Testing abstract classes and or interfaces单元测试抽象类和/或接口
【发布时间】:2010-09-16 22:08:10
【问题描述】:

我正在尝试在 Visual Studio 2010 中对我当前的项目使用单元测试。但是,我的类结构包含许多接口和抽象类继承关系。

如果两个类派生自同一个抽象类或接口,我希望能够在它们之间共享测试代码。我不确定该怎么做。我正在考虑为每个要测试的接口创建一个测试类,但我不确定将具体类提供给适用的单元测试的正确方法。


更新

好的,这是一个例子。假设我有一个接口 IEmployee ,它由抽象类 Employee 实现,然后由两个具体类 Worker 和 Employee 继承。 (代码如下)

现在假设我想创建适用于所有 IEmployee 或员工的测试。或者,为特定类型的员工创建特定测试。例如,我可能想要断言,对于任何 IEmployee 的实现,将 IEmployee.Number 设置为小于零的数字都会引发异常。我更愿意从任何 IEmployee 的角度编写测试,然后能够在 IEmployee 的任何实现上使用测试。

这是另一个例子。我可能还想断言,将任何员工的休假时间设置为小于零的值会引发错误。然而,我可能还希望有不同的测试适用于特定的具体版本的 Employee。假设我想测试 Worker 如果提供超过 14 天的假期会引发异常,但经理最多可以提供 36 天。

public interface IEmployee
{
    string Name {get; set;}
    int Number {get; set;}
}


public abstract class Employee:IEmploee
{
    string Name {get; set;}
    int Number {get;set;}
    public abstract int VacationTime(get; set;)
}


public abstract class Worker:IEmployee
{
    private int v;

    private int vTime;
    public abstract int VacationTime
    {
       get
       {
         return VTime;
       }
       set
      { 
         if(value>36) throw new ArgumentException("Exceeded allowed vaction");
         if(value<0)throw new ArgumentException("Vacation time must be >0");
         vTime= value;
       }
    }


    public void DoSomWork()
    {
      //Work
    }
}


public abstract class Manager:IEmployee
{
    public abstract int VacationTime
    {
       get
       {
         return VTime;
       }
       set
      { 
         if(value>14) throw new ArgumentException("Exceeded allowed vaction");
         if(value<0)throw new ArgumentException("Vacation time must be >0");
         vTime= value;
       }
    }

  public void DoSomeManaging()
  {
      //manage
  }

}

所以我想我正在寻找的是一个允许我嵌套单元测试的工作流程。因此,例如,当我测试 Manager 类时,我想首先测试它是否通过了 Employee 和 IEmployee 测试,然后测试特定成员,例如 DoSomeManaging()

【问题讨论】:

  • 不清楚你想做什么。您谈论单元测试接口,但这没有意义。你到底想测试什么?您能否提供一个简短的对象层次结构示例,并指出您想在此层次结构中测试什么?
  • 我不想对它自己的接口进行单元测试,但我希望能够将所有适用于接口的测试分组,以便我可以轻松地将所有这些测试应用于任何类实现接口。请参阅我添加到问题中的示例。

标签: unit-testing visual-studio-2010 inheritance interface


【解决方案1】:

我想我知道你的意思。我有同样的问题。

我的解决方案是创建一个层次结构也用于测试。我将使用您展示的相同示例。

首先,为基础 IEmployee 创建一个抽象测试类。

主要有两点: 一世。您想要的所有测试方法。 ii.返回所需 IEmployee 实例的抽象方法。

[TestClass()]
public abstract class IEmployeeTests
{
    protected abstract GetIEmployeeInstance();

    [TestMethod()]
    public void TestMethod1()
    {
    IEmployee target = GetIEmployeeInstance();
    // do your IEmployee test here
    }

}

其次,您有一个针对每个 IEmployee 实现的测试类,实现抽象方法并提供适当的 IEmployee 实例。

[TestClass()]
public class WorkerTests : IEmployeeTests
{

    protected override GetIEmployeeInstance()
    {
        return new Worker();
    }

}

[TestClass()]
public class ManagerTests : IEmployeeTests
{

    protected override GetIEmployeeInstance()
    {
        return new Manager();
    }

}

您可以看到一切都按预期工作,并且 VS 为您在 TestView 窗口中为每个 WorkerTests 和 ManagerTests 类提供了预期的测试方法。 您可以运行它们并获得 IEmployee 接口的每个实现的测试结果,只需在基 IEmployeeTests 类中创建测试。

您始终可以为派生的 WorkerTests 和 ManagerTests 类添加特定测试。

现在的问题是,实现多个接口的类(比如 EmployedProgrammer)呢?

public EmployedProgrammer : IEmployee, IProgrammer
{
}

我们在 C# 中没有多重继承,所以这不是一个选项:

[TestClass()]
public EmployedProgrammerIEmployeeTests : IEmployeeTests, IProgrammerTests
{
     // this doesn't compile as IEmployeeTests, IProgrammerTests are classes, not interfaces
}

对于这种情况,解决方案是使用以下测试类:

[TestClass()]
public EmployedProgrammerIEmployeeTests : IEmployeeTests
{
    protected override GetIEmployeeInstance()
    {
        return new EmployedProgrammer();
    }
}

[TestClass()]
public EmployedProgrammerIProgrammerTests : IProgrammerTests
{
    protected override GetIProgrammerInstance()
    {
        return new EmployedProgrammer();
    }
}

[TestClass()]
public abstract class IProgrammerTests
{
    protected abstract GetIProgrammerInstance();

    [TestMethod()]
    public void TestMethod1()
    {
        IProgrammer target = GetIProgrammerInstance();
        // do your IProgrammerTest test here
    }

}

我用这个效果很好。 希望对您有所帮助。

问候, 何塞

【讨论】:

  • 这称为“合同测试”。当以 TDD 方式工作时,您应该在重构步骤期间在代码中提取 Employee 类时提取了抽象测试类。
【解决方案2】:

我认为您想要做的是为抽象类中的方法创建单元测试。

我不确定要在抽象类上测试受保护的方法是否有意义,但如果您坚持只需在专门用于单元测试的类中扩展该类。这样,您可以通过扩展类上的公共方法公开要测试的抽象类上的受保护方法,这些方法只需调用抽象类上的方法。

如果您希望对抽象类中的方法进行单元测试,我建议将它们重构为单独的类,并将它们简单地公开为公共方法并进行测试。尝试从“测试优先”的角度查看您的继承树,我很确定您也会想出那个解决方案(或类似的解决方案)。

【讨论】:

    【解决方案3】:

    您似乎描述了 Visual Studio 2010 单元测试不支持的“复合单元测试”。这样的事情可以根据这个article在MbUnit中完成。可以在 Visual Studio 2010 中创建 abstract tests,这可能不是您想要的。 Here 描述了如何在 VS 中实现抽象测试(继承示例部分)。

    【讨论】:

      【解决方案4】:

      使用 microsoft moles 进行更好的测试。所以你可以轻松地模拟抽象基类/静态方法等。更多信息请参考以下帖子

      detouring-abstract-base-classes-using-moles

      BenzCar benzCar = new BenzCar();    
      new MCar(benzCar)
          {
                  Drive= () => "Testing"
          }.InstanceBehavior = MoleBehaviors.Fallthrough;
      
      var hello = child.Drive();
      
      Assert.AreEqual("Benz Car driving. Testing", hello);
      

      【讨论】:

        【解决方案5】:

        对多个类运行相同测试的愿望通常意味着您有机会将要测试的行为提取到单个类中(无论是基类还是您组合到现有类中的全新类)。

        考虑您的示例:不要在WorkerManager 中实施假期限制,而是向Employee 添加一个新成员变量“MaximumVacationDays”,在员工类设置器中实施限制,然后检查那里的限制:

        abstract class Employee {
            private int maximumVacationDays;
            protected Employee(int maximumVacationDays) {
                this.maximumVacationDays = maximumVacationDays
            }
            public int VacationDays {
                set { 
                    if (value > maximumVacationDays)
                        throw new ArgumentException("Exceeded maximum vacation");
                    }
            }
        }
        
        class Worker: Employee {
            public Worker(): Employee(14) {}
        }
        
        class Manager: Employee {
            public Manager(): Employee(36) {}
        }
        

        现在您只需测试一种方法,需要维护的代码更少。

        【讨论】:

          猜你喜欢
          • 2022-10-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-04-13
          • 2022-11-26
          • 2012-11-06
          • 1970-01-01
          • 2017-02-19
          相关资源
          最近更新 更多