针对不同实现的相同单元测试

时间:2013-03-22 11:57:02

标签: java mstest

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

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

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

6 个答案:

答案 0 :(得分:12)

使用abstract test class

[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();
         }
    }
}

答案 1 :(得分:2)

您已使用NUnit标记了您的问题,但您询问MSTest。您可以通过NUnit中的参数化测试夹具实现您的要求。我不熟悉MSTest建议在那里使用等效方法,快速搜索表明MSTest可能没有此功能。

在NUnit中,通过将多个[TestFixture(...)]属性应用于具有不同参数的fixture类来参数化测试夹具。这些参数将传递给fixture构造函数。

由于可以传递的参数类型有限制,您可能需要在指定算法时传递字符串,然后在构造函数中将提供搜索算法的委托或对象分配给成员字段,用于测试。

例如:

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 }));
        }
    }
}

答案 2 :(得分:1)

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

答案 3 :(得分: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中没有相应的东西,但是你可以稍微捏造它,如下所述: Does MSTest have an equivalent to NUnit's TestCase?

答案 4 :(得分:0)

我不能说我对这种做法非常满意,但这就是我最终做的事情。然后我去寻找更好的方法并找到了这个问题。这种方法符合标准,1)我使用MS测试,2)我只编写测试逻辑一次,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也使写入速度更快......我只需输入,&#34;覆盖&#34;并获得一个选择列表。自动完成为我写下剩下的部分。

答案 5 :(得分: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&amp; SampleD不会。