Такой же unit test для разных реализаций
Скажем, у меня есть две реализации алгоритма поиска, которые возвращают один и тот же результат для одного и того же ввода. Они оба реализуют один и тот же интерфейс.
Как я могу использовать одиночный [TestClass]
для тестирования обеих реализаций, а не создавать два тестовых файла, в конечном итоге, одну и ту же логику?
Могу ли я сообщить MSUnit о запуске одного из тестов дважды с использованием другого параметра конструктора?
Возможно, мне нужно (n) ввести его каким-то образом?
Ответы
Ответ 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 не будут.