Подтвердить что ты не робот

Такой же unit test для разных реализаций

Скажем, у меня есть две реализации алгоритма поиска, которые возвращают один и тот же результат для одного и того же ввода. Они оба реализуют один и тот же интерфейс.

Как я могу использовать одиночный [TestClass] для тестирования обеих реализаций, а не создавать два тестовых файла, в конечном итоге, одну и ту же логику?

Могу ли я сообщить MSUnit о запуске одного из тестов дважды с использованием другого параметра конструктора?
Возможно, мне нужно (n) ввести его каким-то образом?

4b9b3361

Ответ 1

Используйте абстрактный класс теста:

[TestClass]
public abstract class SearchTests
{
    private ISearcher _searcherUnderTest;

    [TestSetup]
    public void Setup()
    {
        _searcherUnderTest = CreateSearcher();
    }

    protected abstract ISearcher CreateSearcher();

    [TestMethod]
    public void Test1(){/*do stuff to _searcherUnderTest*/ }

    // more tests...

    [TestClass]
    public class CoolSearcherTests : SearcherTests
    {
         protected override ISearcher CreateSearcher()
         {
             return new CoolSearcher();
         }
    }

    [TestClass]
    public class LameSearcherTests : SearcherTests
    {
         protected override ISearcher CreateSearcher()
         {
             return new LameSearcher();
         }
    }
}

Ответ 2

Вы отметили свой вопрос с помощью NUnit, но вы спрашиваете о MSTest. То, о чем вы просите, может быть достигнуто с помощью параметризованных тестовых приборов в NUnit. Я не достаточно хорошо знаком с MSTest, чтобы предложить эквивалентный подход там, и быстрый поиск указывает, что MSTest может не иметь этой функции.

В NUnit вы параметризуете тестовое устройство, применяя несколько атрибутов [TestFixture(...)] к классу прибора с различными параметрами. Эти параметры будут переданы конструктору прибора.

Поскольку существуют ограничения на типы параметров, которые могут быть переданы, вам, вероятно, потребуется передать строку при указании алгоритма, а затем в конструкторе назначить делегат или объект, который предоставляет алгоритм поиска в поле члена, которое используется в тестах.

Например:

using System;
using System.Collections.Generic;
using NUnit.Framework;

namespace MyTests
{
    public static class SearchAlgorithms
    {
        public static int DefaultSearch(int target, IList<int> data)
        {
            return data.IndexOf(target);
        }

        public static int BrokenSearch(int target, IList<int> data)
        {
            return 789;
        }
    }

    [TestFixture("forward")]
    [TestFixture("broken")]
    public class SearchTests
    {
        private Func<int, IList<int>, int> searchMethod;

        public SearchTests(string algorithmName)
        {
            if (algorithmName == "forward")
            {
                this.searchMethod = SearchAlgorithms.DefaultSearch;
                return;
            }

            if (algorithmName == "broken")
            {
                this.searchMethod = SearchAlgorithms.BrokenSearch;
            }
        }

        [Test]
        public void SearchFindsCorrectIndex()
        {
            Assert.AreEqual(
                1, this.searchMethod(2, new List<int> { 1, 2, 3 }));
        }

        [Test]
        public void SearchReturnsMinusOneWhenTargetNotPresent()
        {
            Assert.AreEqual(
                -1, this.searchMethod(4, new List<int> { 1, 2, 3 }));
        }
    }
}

Ответ 3

Я бы предпочел иметь два разных [TestMethod] в одном [TestClass], каждый из которых тестирует только одну реализацию: таким образом, неудачный тест всегда будет правильно указывать на то, какая реализация поступила не так.

Ответ 4

Если вы используете NUnit, вы можете передать переменную, объявленную в атрибуте http://www.nunit.org/index.php?p=testCase&r=2.5.6

если вы используете что-то вроде:

[TestCase(1)]
[TestCase(2)]
public void Test(int algorithm)
{
//..dostuff
}

если он будет выполняться один раз для 1, один раз для 2, использует ту же настройку/отключение:)

В MSTest нет эквивалента, но вы можете выдумать его несколько, как описано здесь: Имеет ли MSTest эквивалент TestCase NUnit?

Ответ 5

Я не могу сказать, что я очень доволен этим подходом, но вот что я сделал. Затем я пошел искать лучший подход и нашел этот вопрос. Этот подход соответствует критериям: 1) Я использую MS Test, 2) Я пишу тестовую логику только 1 раз, 3) Я могу сказать, какая реализация завершилась неудачно (и двойной щелчок по тесту приведет меня к правильному тестовому классу), Этот подход использует базовый класс, который содержит всю фактическую тестовую логику, а затем производный класс для каждой реализации (у меня есть 3), который устанавливает конкретную реализацию на базовом интерфейсе и переопределяет базовые методы тестирования.

[TestClass]
public abstract class SearchTestBase
{
    protected ISearcher Searcher { get; set; }

    [TestMethod]
    public virtual void Find_Results_Correct()
    {
        // Arrange (code here)
        // Act (single line here)
        var actual = Searcher.Results(input);
        // Assert
    }
}

(different file...)
[TestClass]
public class FastSearcherTest : SearcherTestBase
{
    [TestInitialize]
    public void TestInitialize()
    {
        Searcher = new FastSearcher();
    }

    [TestMethod]
    public override void Find_Results_Correct()
    {
        base.Find_Results_Correct();
    }
}

(different file...)
[TestClass]
public class ThoroughSearcherTest : SearcherTestBase
{
    [TestInitialize]
    public void TestInitialize()
    {
        Searcher = new ThoroughSearcher();
    }

    [TestMethod]
    public override void Find_Results_Correct()
    {
        base.Find_Results_Correct();
    }
}

Итак, что мне не нравится в этом подходе, так это то, что каждый раз, когда я хочу добавить тест, мне нужно перейти к каждому из тестовых файлов и переопределить новый метод тестирования. Мне нравятся 3 требования, которые у вас были. Если мне нужно изменить тест, я изменю логику только в одном месте. Преимущество, которое я вижу в этом решении по сравнению с аналогичным методом одного метода, называемого двумя тестами, заключается в том, что мне не нужно повторять код для правильной реализации. В этом решении у вас есть одна строка, которая вызывает base.TestName(), а не две строки, одну для установки Searcher и другую для вызова теста. Visual Studio также делает запись намного быстрее... Я просто набираю "переопределить" и получаю список вариантов. Auto complete записывает остальное для меня.

Ответ 6

Разъяснения, основанные на моем тестировании.

Принятый ответ (использовать абстрактный класс) работает до тех пор, пока абстрактный класс и конкретные классы находятся в одной и той же сборке.

Если вы хотите иметь абстрактный класс и конкретные классы в разных сборках, подход, упомянутый KarlZ, к сожалению, кажется необходимым. Не знаю, почему это так. В этом случае TestExplorer не будет показывать TestMethod.

Кроме того, принятый ответ использует конкретные классы, вложенные в абстрактный класс. Это не является требованием.

Тест с MSTestV2 (1.1.17), VS2017. Здесь используются образцы классов.

Assembly 1
    [TestClass]
    public abstract class SampleExternal
    {
        [TestMethod]
        public void SampleTest01()
        {
            Assert.IsTrue(false, this.GetType().Name);
        }
    }

Assembly 2
    [TestClass]
    public abstract class Sample
    {
        [TestMethod]
        public void SampleTest01()
        {
            Assert.IsTrue(false, this.GetType().Name);
        }

        [TestClass]
        public class SampleA : Sample
        {
        }
    }

    [TestClass]
    public class SampleB : Sample
    {
    }

    [TestClass]
    public class SampleC : SampleExternal
    {
    }

    [TestClass]
    public class SampleD : SampleExternal
    {
    }

с их использованием, тест SampleA и SampleB будет выполняться (и сбой по дизайну), но SampleC и SampleD не будут.