Как правильно тестировать абстрактный класс
В настоящее время я нахожусь в процессе создания модульного теста для абстрактного класса, называемого 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
как я инициализирую это к соответствующему значению? Или, как мне создать соответствующий конкретный класс, как указано выше CreateComponent
и CreateComponent_Accessor
методы?
вот конструктор абстрактного класса, для дополнительной информации:
protected Component(eVtCompId inComponentId, eLayer inLayerId, IF_SystemMessageHandler inMessageHandler)
1 ответ
Вы не можете создать экземпляр абстрактного класса. Таким образом, вы можете написать фиктивную реализацию этого абстрактного класса (где вы должны реализовать абстрактные члены) в своем проекте модульного теста, а затем вызвать методы, которые вы пытаетесь протестировать. У вас могут быть разные импровизированные реализации для тестирования различных методов вашего класса.
В качестве альтернативы написанию фиктивной реализации вы могли бы использовать фиктивную среду, такую как 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.
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>