Как правильно тестировать абстрактный класс

В настоящее время я нахожусь в процессе создания модульного теста для абстрактного класса, называемого 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>  
Другие вопросы по тегам