20

Let's say I have two implementations of a search algorithm that return the same result for the same input. They both implement the same interface.

How can I use a single [TestClass] for testing both implementations, rather then create two test files with eventually the same logic ?

Can I tell MSUnit to launch one of the tests twice with different constructor parameter?
Perhaps I should (n)inject it somehow ?

Sylhare
  • 5,907
  • 8
  • 64
  • 80
Alex
  • 11,479
  • 6
  • 28
  • 50
  • In your question you ask about MSTest, but in your tags you specify NUnit. Which one do you want answers for? – Joe White Mar 22 '13 at 12:42

6 Answers6

18

Use an 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();
         }
    }
}
tallseth
  • 3,635
  • 1
  • 23
  • 24
3

You've tagged your question with NUnit, but you ask about MSTest. What you are asking about can be achieved with parameterized test fixtures in NUnit. I am not familiar enough with MSTest to suggest an equivalent approach there, and a quick search indicates that MSTest may not have this feature.

In NUnit you parameterize the test fixture by applying multiple [TestFixture(...)] attributes to the fixture class with different parameters. These parameters will be passed to the fixture constructor.

Since there are limits on the types of parameter that can be passed, you'll probably need to pass a string in specifying the algorithm, then in the constructor assign the delegate or object that provides the search algorithm to a member field which is used in the tests.

For example:

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 }));
        }
    }
}
Ergwun
  • 12,579
  • 7
  • 56
  • 83
1

I'd rather have two different [TestMethod] in one [TestClass] each testing only one implementation: this way a failing test will always correctly point you which implementation went wrong.

Alexander Tsvetkov
  • 1,649
  • 14
  • 24
  • 2
    And your two [TestMethod] methods can simply be one line methods that both call the same method that actually contains the test code. – Polyfun Mar 22 '13 at 12:02
  • There are ~10 different testmethods for the search implementation I have. You basically suggest copy-pasting those methods to be 20 methods in the same class (or 10 in two different testclasses) – Alex Mar 22 '13 at 12:03
  • @ShellShock, well, that's basically how I implement it now, but I'm looking for some built-in or ready functionality for that. – Alex Mar 22 '13 at 12:03
  • What if you had more implementations (e.g. 10)? That'd get pretty out of control. – byxor Dec 05 '16 at 05:33
1

If you are using NUnit you can pass through a variable declared in an attribute http://www.nunit.org/index.php?p=testCase&r=2.5.6

if you use something like:

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

if will run once for 1, once for 2, uses the same setup/teardown too :)

There isn't an equivalent in MSTest however you can fudge it somewhat as explained here: Does MSTest have an equivalent to NUnit's TestCase?

Community
  • 1
  • 1
finman
  • 545
  • 6
  • 16
0

I can't say I'm extremely happy with this approach, but here's what I ended up doing. I then went to look for a better approach and found this question. This approach meets the criteria, 1) I'm using MS Test, 2) I write the test logic only 1 time, 3) I can tell which implementation failed (and double clicking on the test will take me to the right test class). This approach uses a base class to contain all the actual test logic, and then a derived class for each implementation (I have 3) that sets the specific implementation on the base interface and overrides the base test methods.

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

So what I don't like about this approach is that every time I want to add a test I need to go to each of the test files and override the new test method. What I do like are the 3 requirements you had. If I need to change a test, I change the logic in just one place. The advantage I see to this solution over the similar one of having a single method called by two tests is that I don't have to repeat the code for setting up the right implementation. In this solution you have a single line that calls the base.TestName(), and not two lines, one to set the Searcher and another to call the test. The Visual Studio also makes writing this much faster... I just type, "override" and get a list of choices. Auto complete writes the rest for me.

KarlZ
  • 170
  • 9
0

Clarifications based on my testing.

The accepted answer (to use an abstract class) works as long as the abstract class and concrete classes are in the same assembly.

If you desire to have the abstract class and concrete classes in different assemblies, the approach mentioned by KarlZ, unfortunately seems to be necessary. Not sure why this is the case. In this scenario, the TestExplorer will not show TestMethod.

Also, the accepted answer uses concrete classes nested within the abstract class. This does not appear to be a requirement.

Test with MSTestV2 (1.1.17), VS2017. Here are sample classes used.

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
    {
    }

using these, the test for SampleA and SampleB will execute (and fail by design), but SampleC & SampleD will not.

Steve R.
  • 165
  • 2
  • 9