最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c# - "No suitable constructor was found" even when I use a base class as basis for parametrized unit test - St

programmeradmin0浏览0评论

I want to have parametrized tests in NUnit for tests running in two different scenarios. Most/Some tests are the same, but some are not. So I thought, I want to have a base class for running common tests in two scenarios.

Here is the full MVE:

using FluentAssertions;
using NUnit.Framework;

public class SystemUnderTest
{
    public bool? CrucialSettingsForSut { get; }

    public SystemUnderTest(bool crucialSettingsForSut)
    {
        // does something with crucialSettingsForSut that inheritly changes the logic, this is just an example
        this.CrucialSettingsForSut = crucialSettingsForSut;
    }
}

public class ConstructorTest
{
    public class BaseTestClass
    {
        protected readonly SystemUnderTest sut;

        public BaseTestClass(bool crucialSettingsForSut)
        {
            this.sut = new SystemUnderTest(crucialSettingsForSut);
        }

        [Test]
        public void TestApplicableToBothCases()
        {
            this.sut.CrucialSettingsForSut.Should().NotBeNull();
        }
    }
    
    [TestFixture]
    public class ActualTestFixtureFalse : BaseTestClass
    {
        public ActualTestFixtureFalse() : base(false) { }

        [Test]
        public void TestOnlyApplyingToFalse()
        {
            this.sut.CrucialSettingsForSut.Should().BeFalse();
        }
    }

    [TestFixture]
    public class ActualTestFixtureTrue : BaseTestClass
    {
        public ActualTestFixtureTrue() : base(true) { }

        [Test]
        public void TestOnlyApplyingToTrue()
        {
            this.sut.CrucialSettingsForSut.Should().BeTrue();
        }
    }
}

This is a simplified example, of course the real code is more complex.

The important thing is that TestOnlyApplyingToTrue and TestOnlyApplyingToFalse are contradictory, i.e. I cannot place them in the same class and have both tests run in a TestFixture with the parameter for the constructor.

Problem

Unfortunately NUnit tries to instantiate BaseTestClass even though I do not actually want this. After all, this just contains the set of "base tests":

This results in this error for TestApplicableToBothCases:

OneTimeSetUp: No suitable constructor was found
  Exception doesn't have a stacktrace



-----

No suitable constructor was found
  Exception doesn't have a stacktrace

Tries

I saw No suitable constructor was found in NUnit Parameterised tests, but in my case BaseTestClass should be there and has a purpose, as explained. I cannot solve this with TestFixture.

An alternative may be to have a custom Assert.Ignore in TestOnlyApplyingToTrue and TestOnlyApplyingToFalse only triggered if the respective other test case is run, but this is IMHO ugly code design and causes an "ignored test" entry. The way with inheritage I used here makes much more sense. Also, obviously this hardly scales as I not have only one test for each scenario as in this simplified example.

Design patterns

You may wonder about that design, but IMHO it is good for simple cases. Actually, it is featured as abstract test infrastructure class pattern in The Art of Unit Testing Second Edition (with examples in C#) by Roy Osherove. (ISBN: 9781617290893)

For what it matters: At least in the version I have, they actually have the same error in there! BaseTestsClass (in Listing 7.4, page 139) is not abstract. (I wrote this after knowing the solution.) Just it does not matter, because no actual test is included in the (non-abstract) base class there.

Listing 7.6 etc. then uses a real abstract base class as the solution for this one and as far as I read also includes a test in the base class, then.

Question

So how can I solve this in the most elegant way?

I want to have parametrized tests in NUnit for tests running in two different scenarios. Most/Some tests are the same, but some are not. So I thought, I want to have a base class for running common tests in two scenarios.

Here is the full MVE:

using FluentAssertions;
using NUnit.Framework;

public class SystemUnderTest
{
    public bool? CrucialSettingsForSut { get; }

    public SystemUnderTest(bool crucialSettingsForSut)
    {
        // does something with crucialSettingsForSut that inheritly changes the logic, this is just an example
        this.CrucialSettingsForSut = crucialSettingsForSut;
    }
}

public class ConstructorTest
{
    public class BaseTestClass
    {
        protected readonly SystemUnderTest sut;

        public BaseTestClass(bool crucialSettingsForSut)
        {
            this.sut = new SystemUnderTest(crucialSettingsForSut);
        }

        [Test]
        public void TestApplicableToBothCases()
        {
            this.sut.CrucialSettingsForSut.Should().NotBeNull();
        }
    }
    
    [TestFixture]
    public class ActualTestFixtureFalse : BaseTestClass
    {
        public ActualTestFixtureFalse() : base(false) { }

        [Test]
        public void TestOnlyApplyingToFalse()
        {
            this.sut.CrucialSettingsForSut.Should().BeFalse();
        }
    }

    [TestFixture]
    public class ActualTestFixtureTrue : BaseTestClass
    {
        public ActualTestFixtureTrue() : base(true) { }

        [Test]
        public void TestOnlyApplyingToTrue()
        {
            this.sut.CrucialSettingsForSut.Should().BeTrue();
        }
    }
}

This is a simplified example, of course the real code is more complex.

The important thing is that TestOnlyApplyingToTrue and TestOnlyApplyingToFalse are contradictory, i.e. I cannot place them in the same class and have both tests run in a TestFixture with the parameter for the constructor.

Problem

Unfortunately NUnit tries to instantiate BaseTestClass even though I do not actually want this. After all, this just contains the set of "base tests":

This results in this error for TestApplicableToBothCases:

OneTimeSetUp: No suitable constructor was found
  Exception doesn't have a stacktrace



-----

No suitable constructor was found
  Exception doesn't have a stacktrace

Tries

I saw No suitable constructor was found in NUnit Parameterised tests, but in my case BaseTestClass should be there and has a purpose, as explained. I cannot solve this with TestFixture.

An alternative may be to have a custom Assert.Ignore in TestOnlyApplyingToTrue and TestOnlyApplyingToFalse only triggered if the respective other test case is run, but this is IMHO ugly code design and causes an "ignored test" entry. The way with inheritage I used here makes much more sense. Also, obviously this hardly scales as I not have only one test for each scenario as in this simplified example.

Design patterns

You may wonder about that design, but IMHO it is good for simple cases. Actually, it is featured as abstract test infrastructure class pattern in The Art of Unit Testing Second Edition (with examples in C#) by Roy Osherove. (ISBN: 9781617290893)

For what it matters: At least in the version I have, they actually have the same error in there! BaseTestsClass (in Listing 7.4, page 139) is not abstract. (I wrote this after knowing the solution.) Just it does not matter, because no actual test is included in the (non-abstract) base class there.

Listing 7.6 etc. then uses a real abstract base class as the solution for this one and as far as I read also includes a test in the base class, then.

Question

So how can I solve this in the most elegant way?

Share Improve this question edited Feb 6 at 17:25 rklec asked Feb 6 at 13:27 rklecrklec 3191 silver badge19 bronze badges
Add a comment  | 

3 Answers 3

Reset to default 2

@Klaus gave you the write answer for your case, but I wanted to spell it out in more detail beyond what's possible in a comment.

When you mark a non-abstact class as a test fixture either explicitly with [TestFixture] or implicitly by including tests in the class, you are telling the NUnit framework to instantiate it and run those tests. Of course, that's impossible without a proper constructor, which gives rise to the error.

OTOH, an abstract class is never treated that way. It really isn't a TestFixture, merely a base class for your real test fixtures. "Abstract TestFixture" is the standard pattern for including tests in multiple fixtures. It works like this...

  1. Create an abstract class to use as a base. Ensure that it has whatever constructors you need for your purpose. Don't include [TestFixture] on the base. If you do, it will still work, but the attribute tends to confuse those who read your code. Include [Test], [TestCase], etc. methods in the base class.
  2. Create as many derived classes as you need. Make sure that each one has whatever constructors it needs and that each one calls the proper constructor in the base class. Of course, if the base has only a default constructor, this happens automatically.

Sometimes, folks ask "But couldn't NUnit just figure all that out instead of giving an error?" Maybe... but we designed the framework with the philosophy that it would attempt to do whatever you told it and give an error if that was not possible. Nowadays, it's more common to use analyzers that give you warnings when you do something suspicious, like inheriting from a non-abstract test fixture. The latest NUnit.Analyzers might have given you some kind of warning in this case.

UPDATE: As @rklec commented, there is no rule for this in NUnit.Analyzers. I created this issue to add a rule.

If you stick to your design, you can make the base test class abstract. This will avoid that the NUnit test engine or test runners try to instantiate the class.

I tested with MsTest and Resharper runners and it seems to work fine.

As with any other class, it's usually better to favour composition over inheritance, so instead of deriving from your class containing the common stuff, just use it:

public class ConstructorTest 
{
    public class BaseTestClass
    {
        private readonly SystemUnderTest sut;

        public BaseTestClass(bool crucialSettingsForSut)
        {
            this.sut = new SystemUnderTest(crucialSettingsForSut);
        }

        public void TestApplicableToBothCases()
        {
            this.sut.CrucialSettingsForSut.Should().NotBeNull();
        }
    }
    
    [TestFixture]
    public class ActualTestFixtureFalse
    {
        BaseTestClass baseTest = new BaseTestClass(false);

        [Test]
        public void TestOnlyApplyingToFalse()
        {
            this.sut.CrucialSettingsForSut.Should().BeFalse();
        }
        [Test]
        public void TestApplicableToBothCases()
        {
            this.baseTest.TestApplicableToBothCases(); // call the "base"-class function here
        }
    }

    [TestFixture]
    public class ActualTestFixtureTrue
    {
        BaseTestClass baseTest = new BaseTestClass(true);

        [Test]
        public void TestOnlyApplyingToTrue()
        {
            this.sut.CrucialSettingsForSut.Should().BeTrue();
        }
        
        [Test]
        public void TestApplicableToBothCases()
        {
            this.baseTest.TestApplicableToBothCases(); // call the "base"-class function here
        }
    }
}

Of course you're doubling some lines of code. But this saves you headaches if your BaseTestClass sometime changes. E.g. when someone adds a new function to it that shouldn't actually be used by all tests, you'd need some way to disable that function - and kill the developer who introduced that new function.

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论