简体   繁体   English

如何正确测试抽象类

[英]how to properly test an abstract class

I'm currently in the process of creating a unit test for an abstract class, called Component .我目前正在为一个名为Component的抽象类创建单元测试。 VS2008 compiled my program no problems so I was able to create a unit test project within the solution. VS2008 编译我的程序没有问题,所以我能够在解决方案中创建一个单元测试项目。 One thing I've noticed, though, is that when the test file has been created, there are these methods which I've never seen before:不过,我注意到的一件事是,当创建测试文件时,有一些我以前从未见过的方法:

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

I presume these are for creating a concrete Component class.我认为这些是为了创建一个具体的Component类。

Within each Test method, there is this line:在每个测试方法中,都有这一行:

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

how do I initialize this to an appropriate value?如何将其初始化为适当的值? Or, how do I instantiate an appropriate concrete class, as stated above by the CreateComponent and the CreateComponent_Accessor methods?或者,我如何实例化一个适当的具体类,如上面通过CreateComponentCreateComponent_Accessor方法所述?

here is the constructor of the abstract class, for additional info:这是抽象类的构造函数,以获取更多信息:

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

You cannot instantiate an abstract class.您不能实例化抽象类。 So you could write a mock implementation of this abstract class (where you should implement the abstract members) in your unit test project and then call the methods you are trying to test.因此,您可以在单元测试项目中编写此抽象类的模拟实现(您应该在其中实现抽象成员),然后调用您尝试测试的方法。 You could have different mock implementations in order to test various methods of your class.你可以有不同的模拟实现来测试你的类的各种方法。

As an alternative to writing a mock implementation you could use a mock framework such as Rhino Mocks, Moq, NSubstitute, ... which could simplify this task and allow you to define expectations for the abstract members of the class.作为编写模拟实现的替代方法,您可以使用模拟框架,例如 Rhino Mocks、Moq、NSubstitute 等,它们可以简化此任务并允许您定义对类的抽象成员的期望。


UPDATE:更新:

As requested in the comments section here's an example.根据评论部分的要求,这里有一个示例。

Let's suppose that you have the following abstract class that you want to unit test:假设您有以下要进行单元测试的抽象类:

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

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

Now in your unit test project you could implement it by writing a derived class implementing the abstract members with mocked values:现在,在您的单元测试项目中,您可以通过编写实现具有模拟值的抽象成员的派生类来实现它:

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

and then you could write your unit test against the GetTheFoo method:然后您可以针对GetTheFoo方法编写单元测试:

// arrange
var sut = new FooBarMock();

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

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

and with a mock framework (Moq in my example) you do not need to implement this abstract class in the unit test but you could directly use the mocking framework to define expectations of the abstract members that the method under test is relying upon:并且使用模拟框架(在我的示例中为 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);

This is how I do it.我就是这样做的。 With a inner-nested-class on the UnitTest class.在 UnitTest 类上使用内部嵌套类。

Please notice the "MyAbstractClass.GetABoolean" method.. to point out how the abstract class can depend upon the implementation of the subclass.请注意“MyAbstractClass.GetABoolean”方法.. 指出抽象类如何依赖于子类的实现。

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

and the "real" abstract class below和下面的“真实”抽象类

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

}

This question is older, but the principals are the same.这个问题比较老,但原理是一样的。

Here are my VS2019, year 2020 .. package imports.这是我的 VS2019,2020 年 .. 包导入。

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

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

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