18

假设我有两个搜索算法的实现,它们为相同的输入返回相同的结果。它们都实现了相同的接口。

如何使用单个[TestClass]测试两个实现,而不是创建两个最终具有相同逻辑的测试文件?

我可以告诉 MSUnit 使用不同的构造函数参数两次启动其中一个测试吗?
也许我应该(n)以某种方式注入它?

4

6 回答 6

16

使用抽象测试类

[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();
         }
    }
}
于 2013-03-24T21:18:33.873 回答
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 }));
        }
    }
}
于 2013-03-22T12:39:57.847 回答
1

我宁愿有两个不同[TestMethod]的一个,[TestClass]每个测试一个实现:这样一个失败的测试总是会正确地指出你哪个实现出错了。

于 2013-03-22T11:59:53.023 回答
1

如果您使用的是 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 是否具有与 NUnit 的 TestCase 的等价物?

于 2013-03-22T12:02:10.413 回答
0

我不能说我对这种方法非常满意,但这就是我最终要做的。然后我去寻找更好的方法并发现了这个问题。这种方法符合标准,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(),而不是两行,一行用于设置搜索器,另一行用于调用测试。Visual Studio 还使编写速度更快……我只需键入、“覆盖”并获得一个选项列表。自动完成为我写了剩下的。

于 2017-01-08T18:01:07.047 回答
0

根据我的测试进行澄清。

只要抽象类和具体类在同一个程序集中,接受的答案(使用抽象类)就可以工作。

如果您希望在不同的程序集中拥有抽象类和具体类,不幸的是,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 不会。

于 2017-05-19T18:36:08.330 回答