如何正确测试抽象类

时间:2013-03-04 08:09:36

标签: c# unit-testing visual-studio-2008

我目前正在为一个名为Component的抽象类创建单元测试。 VS2008编译我的程序没有问题所以我能够在解决方案中创建一个单元测试项目。但是,我注意到的一件事是,当创建测试文件时,有一些我以前从未见过的方法:

internal virtual Component CreateComponent()
        {
            // TODO: Instantiate an appropriate concrete class.
            Component target = null;
            return target;
        }


internal virtual Component_Accessor CreateComponent_Accessor()
        {
            // TODO: Instantiate an appropriate concrete class.
            Component_Accessor target = null;
            return target;
        }

我认为这些是用于创建具体的Component类。

在每个Test方法中,都有这一行:

Component target = CreateComponent(); // TODO: Initialize to an appropriate value

如何将其初始化为适当的值?或者,如何通过CreateComponentCreateComponent_Accessor方法如上所述实例化适当的具体类?

这是抽象类的构造函数,有关其他信息:

protected Component(eVtCompId inComponentId, eLayer inLayerId, IF_SystemMessageHandler inMessageHandler)

2 个答案:

答案 0 :(得分:13)

您无法实例化抽象类。因此,您可以在单元测试项目中编写此抽象类的模拟实现(您应该在其中实现抽象成员),然后调用您要测试的方法。您可以使用不同的模拟实现来测试类的各种方法。

作为编写模拟实现的替代方法,您可以使用模拟框架,例如Rhino Mocks,Moq,NSubstitute,......这可以简化此任务,并允许您定义对类的抽象成员的期望。


更新:

根据评论部分的要求,这是一个例子。

假设您有以下要进行单元测试的抽象类:

public abstract class FooBar
{
    public abstract string Foo { get; }

    public string GetTheFoo()
    {
        return "Here's the foo " + Foo;
    }
}

现在在您的单元测试项目中,您可以通过编写实现具有模拟值的抽象成员的派生类来实现它:

public class FooBarMock : FooBar
{
    public override string Foo 
    { 
        get { return "bar" } 
    }
}

然后你可以用GetTheFoo方法编写单元测试:

// arrange
var sut = new FooBarMock();

// act
var actual = sut.GetTheFoo();

// assert
Assert.AreEqual("Here's the foo bar", actual);

并且使用模拟框架(在我的示例中为Moq),您不需要在单元测试中实现此抽象类,但您可以直接使用模拟框架来定义抽象成员对所测试方法所依赖的期望:

// arrange
var sut = new Mock<FooBar>();
sut.Setup(x => x.Foo).Returns("bar");

// act
var actual = sut.Object.GetTheFoo();

// assert
Assert.AreEqual("Here's the foo bar", actual);

答案 1 :(得分:0)

这就是我的方法。在UnitTest类上带有一个内部嵌套类。

namespace MyCompany.MyProject.UnitTests
{
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Threading.Tasks;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using FluentAssertions;

    [TestClass]
    [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
    public class MyAbstractClassTests
    {
        [TestMethod]
        public void ConstructorILoggerFactoryIsNullTest()
        {
            Action a = () => new MyUnitTestConcreteClass(null);
            a.Should().Throw<ArgumentNullException>().WithMessage(MyAbstractClass<int>.ErrorMessageILoggerFactoryIsNull);

        }

        [TestMethod]
        public void GetABooleanIsTrueTest()
        {
            /* here is more likely what you want to test..an implemented method on the abstract class */
            Mock<ILoggerFactory> iloggerFactoryMock = this.GetDefaultILoggerFactoryMock();
            MyUnitTestConcreteClass testItem = new MyUnitTestConcreteClass(iloggerFactoryMock.Object);
            Assert.IsTrue(testItem.GetABoolean());
        }   

        [TestMethod]
        public void GetSomeIntsIsNotNullTest()
        {
            /* you may not want to test the abstract methods, but you can */
            Mock<ILoggerFactory> iloggerFactoryMock = this.GetDefaultILoggerFactoryMock();
            MyUnitTestConcreteClass testItem = new MyUnitTestConcreteClass(iloggerFactoryMock.Object);
            Assert.IsNotNull(testItem.GetSomeInts());
        }       


        private Mock<ILoggerFactory> GetDefaultILoggerFactoryMock()
        {
            Mock<ILoggerFactory> returnMock = new Mock<ILoggerFactory>(MockBehavior.Strict);
            ////returnMock.Setup(x => x.SomeBooleanMethod()).Returns(true);
            return returnMock;
        }       



        internal class MyUnitTestConcreteClass : MyAbstractClass<int>
        {
            internal MyUnitTestConcreteClass(ILoggerFactory loggerFactory) : base(loggerFactory)
            {
            }

            public override ICollection<int> GetSomeInts()
            {
                return new List<int> { 111, 222, 333 };
            }
        }
    }
}

和下面的“真实”抽象类

public abstract class MyAbstractClass<T> : where T : struct
{

    public const string ErrorMessageILoggerFactoryIsNull = "ILoggerFactory is null";

    public WhiteListStepBodyAsyncBase(ILoggerFactory loggerFactory)
    {
        if (null == loggerFactory)
        {
            throw new ArgumentNullException(ErrorMessageILoggerFactoryIsNull, (Exception)null);
        }

    }           

    public bool GetABoolean()
    {
          /* note , this is important factor (sometimes), here this implemented method DEPENDS on an abstract method , and why I have the code "return new List<int> { 111, 222, 333 };" above .. see the connection ?? */
                    return this.GetSomeInts().Count > 0;
    }

    public abstract ICollection<int> GetSomeInts();


}

这个问题比较老,但是原理相同。

这是我的2020年VS2019,..包进口。

  <ItemGroup>
    <PackageReference Include="coverlet.msbuild" Version="2.8.0">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="FluentAssertions" Version="5.10.0" />
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
    <PackageReference Include="Moq" Version="4.13.1" />
    <PackageReference Include="MSTest.TestAdapter" Version="2.0.0" />
    <PackageReference Include="MSTest.TestFramework" Version="2.0.0" />
    <PackageReference Include="coverlet.collector" Version="1.0.1" />
  </ItemGroup>