【问题标题】:same unit test for different implementations不同实现的相同单元测试
【发布时间】:2013-03-22 11:57:01
【问题描述】:

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

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

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

【问题讨论】:

  • 在您询问 MSTest 的问题中,但在您的标签中您指定了 NUnit。你想要哪一个答案?

标签: java mstest


【解决方案1】:

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

【讨论】:

    【解决方案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] 中,每个都只测试一个实现:这样,失败的测试将始终正确地指出哪个实现出错了。

      【讨论】:

      • 您的两个 [TestMethod] 方法可以简单地是一行方法,它们都调用实际包含测试代码的相同方法。
      • 我拥有的搜索实现有大约 10 种不同的测试方法。您基本上建议将这些方法复制粘贴为同一类中的 20 个方法(或两个不同测试类中的 10 个)
      • @ShellShock,嗯,这基本上是我现在实现它的方式,但我正在寻找一些内置或现成的功能。
      • 如果你有更多的实现(例如 10 个)呢?这会变得非常失控。
      【解决方案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 中没有等效项,但是您可以按照此处的说明对其进行一些修改: Does MSTest have an equivalent to NUnit's TestCase?

      【讨论】:

        【解决方案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 还使编写速度更快……我只需键入、“覆盖”并获得一个选项列表。自动完成会为我编写其余部分。

        【讨论】:

          【解决方案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 不会。

          【讨论】:

            猜你喜欢
            • 2018-06-22
            • 1970-01-01
            • 2011-01-28
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-07-24
            • 1970-01-01
            相关资源
            最近更新 更多