[英]how to properly test an abstract class
我目前正在为一个名为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
类。
在每个测试方法中,都有这一行:
Component target = CreateComponent(); // TODO: Initialize to an appropriate value
如何将其初始化为适当的值? 或者,我如何实例化一个适当的具体类,如上面通过CreateComponent
和CreateComponent_Accessor
方法所述?
这是抽象类的构造函数,以获取更多信息:
protected Component(eVtCompId inComponentId, eLayer inLayerId, IF_SystemMessageHandler inMessageHandler)
您不能实例化抽象类。 因此,您可以在单元测试项目中编写此抽象类的模拟实现(您应该在其中实现抽象成员),然后调用您尝试测试的方法。 你可以有不同的模拟实现来测试你的类的各种方法。
作为编写模拟实现的替代方法,您可以使用模拟框架,例如 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);
我就是这样做的。 在 UnitTest 类上使用内部嵌套类。
请注意“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 };
}
}
}
}
和下面的“真实”抽象类
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();
}
这个问题比较老,但原理是一样的。
这是我的 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.