0

So I have 2 classes. ItemWithAttributes and ItemWithNameAndAttributes (derives from 1st).

using System;
using System.Collections.Generic;
using System.Linq;
using Helpers;

namespace Objects.Base
{
    public class ItemWithAttributes
    {
        public IEnumerable<string> Attributes { get; }

        public ItemWithAttributes(IEnumerable<string> attributes)
        {
            if (attributes == null)
                throw new ArgumentNullException(nameof(attributes), "Parameter can not be mull.");

            Attributes = attributes.ToArray();
        }

        public virtual string AttributesToParenthesesString(bool prependSpace)
        {
            return $"{Attributes.ToParenthesesString(prependSpace)}";
        }

        public override string ToString()
        {
            return AttributesToParenthesesString(false);
        }
    }
}
using System;
using System.Collections.Generic;

namespace Objects.Base
{
    public class ItemWithNameAndAttributes : ItemWithAttributes
    {
        public string Name { get; }

        public ItemWithNameAndAttributes(string name, IEnumerable<string> attributes) : base(attributes)
        {
            if (name == null)
                throw new ArgumentNullException(nameof(name), "Parameter can not be null.");

            if (name.Length == 0)
                throw new ArgumentException("Parameter can not be empty.", nameof(name));

            Name = name;
        }

        public override string ToString()
        {
            return $"{Name}{AttributesToParenthesesString(true)}";
        }
    }
}

I want to get into unit testing so this is what I got so far for both my classes. I did some reading on naming convention for test method naming but I feel kind of stuck now.

using System;
using System.Collections.Generic;
using System.Linq;
using Objects.Base;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace UnitTests.Objects.Base
{
    [TestClass]
    public class ItemWithAttributesTests
    {
        [TestMethod]
        public void Constructor_AttributesWithElements_PropertyEqualToInput()
        {
            var attributes = new List<string>() { "Item 1", "Item 2", "Item 3", "Item 4", "Item 5" };
            var itemWithAttributes = new ItemWithAttributes(attributes);

            CollectionAssert.AreEqual(attributes, itemWithAttributes.Attributes.ToList());
        }

        [TestMethod]
        public void Constructor_AttributesWithoutElements_PropertyEqualToInput()
        {
            var attributes = new List<string>() { };
            var itemWithAttributes = new ItemWithAttributes(attributes);

            CollectionAssert.AreEqual(attributes, itemWithAttributes.Attributes.ToList());
        }

        [TestMethod]
        public void Constructor_AttributesNull_ThrowArgumentNullException()
        {
            Assert.ThrowsException<ArgumentNullException>(() => new ItemWithAttributes(null));
        }

        [TestMethod]
        public void AttributesToParenthesesString_AttributesWithElementsAndPrependFalse_ReturnsEqualString()
        {
            var attributes = new List<string>() { "Item 1", "Item 2", "Item 3", "Item 4", "Item 5" };
            var itemWithAttributes = new ItemWithAttributes(attributes);

            var result = itemWithAttributes.AttributesToParenthesesString(false);

            Assert.AreEqual("(Item 1) (Item 2) (Item 3) (Item 4) (Item 5)", result);
        }

        [TestMethod]
        public void AttributesToParenthesesString_AttributesWithElementsAndPrependTrue_ReturnsEqualString()
        {
            var attributes = new List<string>() { "Item 1", "Item 2", "Item 3", "Item 4", "Item 5" };
            var itemWithAttributes = new ItemWithAttributes(attributes);

            var result = itemWithAttributes.AttributesToParenthesesString(true);

            Assert.AreEqual(" (Item 1) (Item 2) (Item 3) (Item 4) (Item 5)", result);
        }

        [TestMethod]
        public void AttributesToParenthesesString_AttributesWithoutElementsAndPrependFalse_ReturnsEqualString()
        {
            var attributes = new List<string>() {  };
            var itemWithAttributes = new ItemWithAttributes(attributes);

            var result = itemWithAttributes.AttributesToParenthesesString(false);

            Assert.AreEqual("", result);
        }

        [TestMethod]
        public void AttributesToParenthesesString_AttributesWithoutElementsAndPrependTrue_ReturnsEqualString()
        {
            var attributes = new List<string>() {  };
            var itemWithAttributes = new ItemWithAttributes(attributes);

            var result = itemWithAttributes.AttributesToParenthesesString(true);

            Assert.AreEqual("", result);
        }

        [TestMethod]
        public void ToString_AttributesWithElements_ReturnsEqualString()
        {
            var attributes = new List<string>() { "Item 1", "Item 2", "Item 3", "Item 4", "Item 5" };
            var itemWithAttributes = new ItemWithAttributes(attributes);

            var result = itemWithAttributes.ToString();

            Assert.AreEqual("(Item 1) (Item 2) (Item 3) (Item 4) (Item 5)", result);
        }

        [TestMethod]
        public void ToString_AttributesWithoutElements_ReturnsEqualString()
        {
            var attributes = new List<string>() { };
            var itemWithAttributes = new ItemWithAttributes(attributes);

            var result = itemWithAttributes.ToString();

            Assert.AreEqual("", result);
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using Objects.Base;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace UnitTests.Objects.Base
{
    [TestClass]
    public class ItemWithNameAndAttributesTests
    {
        [TestMethod]
        public void Constructor_NameAndAttributesWithElements_PropertiesAreEqualToInput()
        {
            var name = "Name";
            var attributes = new List<string>() { "Item 1", "Item 2", "Item 3", "Item 4", "Item 5" };
            var itemWithNameAndAttributes = new ItemWithNameAndAttributes(name, attributes);

            Assert.AreEqual(name, itemWithNameAndAttributes.Name);
            CollectionAssert.AreEqual(attributes, itemWithNameAndAttributes.Attributes.ToList());
        }

        [TestMethod]
        public void Constructor_NameAndAttributesWithoutElements_PropertiesAreEqualToInput()
        {
            var name = "Name";
            var attributes = new List<string>() { };
            var itemWithNameAndAttributes = new ItemWithNameAndAttributes(name, attributes);

            Assert.AreEqual(name, itemWithNameAndAttributes.Name);
            CollectionAssert.AreEqual(attributes, itemWithNameAndAttributes.Attributes.ToList());
        }


        [TestMethod]
        public void Constructor_NameEmpty_ThrowArgumentException()
        {
            var name = string.Empty;
            var attributes = new List<string>() { "Item 1", "Item 2", "Item 3", "Item 4", "Item 5" };
            Assert.ThrowsException<ArgumentException>(() => new ItemWithNameAndAttributes(name, attributes));
        }

        [TestMethod]
        public void Constructor_NameNull_ThrowArgumentNullException()
        {
            var attributes = new List<string>() { "Item 1", "Item 2", "Item 3", "Item 4", "Item 5" };
            Assert.ThrowsException<ArgumentNullException>(() => new ItemWithNameAndAttributes(null, attributes));
        }

        [TestMethod]
        public void Constructor_AttributesNull_ThrowArgumentNullException()
        {
            var name = "Name";
            Assert.ThrowsException<ArgumentNullException>(() => new ItemWithNameAndAttributes(name, null));
        }

        [TestMethod]
        public void AttributesToParenthesesString_AttributesWithElementsAndPrependFalse_ReturnsEqualString()
        {
            var name = "Name";
            var attributes = new List<string>() { "Item 1", "Item 2", "Item 3", "Item 4", "Item 5" };
            var itemWithNameAndAttributes = new ItemWithNameAndAttributes(name, attributes);

            var result = itemWithNameAndAttributes.AttributesToParenthesesString(false);

            Assert.AreEqual("(Item 1) (Item 2) (Item 3) (Item 4) (Item 5)", result);
        }

        [TestMethod]
        public void AttributesToParenthesesString_AttributesWithElementsAndPrependTrue_ReturnsEqualString()
        {
            var name = "Name";
            var attributes = new List<string>() { "Item 1", "Item 2", "Item 3", "Item 4", "Item 5" };
            var itemWithNameAndAttributes = new ItemWithNameAndAttributes(name, attributes);

            var result = itemWithNameAndAttributes.AttributesToParenthesesString(true);

            Assert.AreEqual(" (Item 1) (Item 2) (Item 3) (Item 4) (Item 5)", result);
        }

        [TestMethod]
        public void AttributesToParenthesesString_AttributesWithoutElementsAndPrependFalse_ReturnsEqualString()
        {
            var name = "Name";
            var attributes = new List<string>() { };
            var itemWithNameAndAttributes = new ItemWithNameAndAttributes(name, attributes);

            var result = itemWithNameAndAttributes.AttributesToParenthesesString(false);

            Assert.AreEqual("", result);
        }

        [TestMethod]
        public void AttributesToParenthesesString_AttributesWithoutElementsAndPrependTrue_ReturnsEqualString()
        {
            var name = "Name";
            var attributes = new List<string>() { };
            var itemWithNameAndAttributes = new ItemWithNameAndAttributes(name, attributes);

            var result = itemWithNameAndAttributes.AttributesToParenthesesString(true);

            Assert.AreEqual("", result);
        }

        [TestMethod]
        public void ToString_AttributesArgumentIsListWithElements_ReturnsEqualString()
        {
            var name = "Name";
            var attributes = new List<string>() { "Item 1", "Item 2", "Item 3", "Item 4", "Item 5" };
            var itemWithNameAndAttributes = new ItemWithNameAndAttributes(name, attributes);

            var result = itemWithNameAndAttributes.ToString();

            Assert.AreEqual("Name (Item 1) (Item 2) (Item 3) (Item 4) (Item 5)", result);
        }

        [TestMethod]
        public void ToString_AttributesArgumentIsListWithoutElements_ReturnsEqualString()
        {
            var name = "Name";
            var attributes = new List<string>() { };
            var itemWithNameAndAttributes = new ItemWithNameAndAttributes(name, attributes);

            var result = itemWithNameAndAttributes.ToString();

            Assert.AreEqual("Name", result);
        }
    }
}

What I notice is that the unit tests for my derived class ItemWithNameAndAttributes are almost the same as for my base class ItemWithAttributes but just a little more and different because I also need to validate for the name argument. I'm not sure if this is the correct way to go or if there is some method to reuse the tests from my base class in my derived class. If I keep this current pattern up, when I derive from ItemWithNameAndAttributes I would have even more tests that are the same and retest the base classes. I got the feeling it got very complex very quickly for the small classes I have.

Krowi
  • 1,565
  • 3
  • 20
  • 40
  • hello, this might be helpful https://stackoverflow.com/questions/60023968/unit-test-for-update-method-in-c-sharp-using-mock/60029608#60029608 – Clint Feb 08 '20 at 16:37
  • **General Tip:** For different `Methods` of same `Class` if you notice you are instantiating same code multiple times for each `Method`, then you can use `Setup` attribute to instantiate the types in a group, that way you will have less repeated code – Clint Feb 08 '20 at 16:43
  • Maybe also helpful: https://stackoverflow.com/questions/59312507/should-i-use-inherited-tests – Dirk Herrmann Feb 11 '20 at 11:48

1 Answers1

1

Based on the example code, I don't see a good way to consolidate through inheritance. The tests do follow a nearly-identical structure (instantiate an object, do something with that object, verify the outcome--ie, Arrange, Act, Assert), but the implementation details are meaningfully different (the "act" step is the same, but the constructors and expected outcomes are different). The test and assertion frameworks have already done as much de-duplication as is reasonable in this case.

Note: With some unit test frameworks (I mostly use xUnit.net), it is possible to create an inheritance hierarchy of unit tests in which all of the base fixture's tests run as part of each inherited fixture, but I don't think that's helpful for the example problem. Here is another answer describing inherited tests.

If you are open to using xUnit as your test framework, you could consolidate some the tests by using "theories":

public class ItemWithNameAndAttributesTests_Theories
{
    [Theory]
    [InlineData("Name", new [] { "Item 1", "Item 2", "Item 3", "Item 4", "Item 5" })]
    [InlineData("Name", new string[0])]
    public void Verify_Constructor_PropertiesAreEqualToInput(string name, string[] attributes)
    {
        var itemWithNameAndAttributes = new ItemWithNameAndAttributes(name, new List<string>(attributes));

        Assert.Equal(name, itemWithNameAndAttributes.Name);
        Assert.Equal(attributes, itemWithNameAndAttributes.Attributes);
    }

    [Theory]
    [InlineData("", new [] { "Item 1", "Item 2", "Item 3", "Item 4", "Item 5" }, typeof(ArgumentException))]
    [InlineData(null, new [] { "Item 1", "Item 2", "Item 3", "Item 4", "Item 5" }, typeof(ArgumentNullException))]
    [InlineData("Name", null, typeof(ArgumentNullException))]
    public void Verify_Constructor_ThrowException(string name, string[] attributes, Type exceptionType)
    {
        Assert.Throws(exceptionType, () => new ItemWithNameAndAttributes(name, attributes));
    }

    [Theory]
    [InlineData("Name", new [] { "Item 1", "Item 2", "Item 3", "Item 4", "Item 5" }, false, "(Item 1) (Item 2) (Item 3) (Item 4) (Item 5)")]
    [InlineData("Name", new [] { "Item 1", "Item 2", "Item 3", "Item 4", "Item 5" }, true, " (Item 1) (Item 2) (Item 3) (Item 4) (Item 5)")]
    [InlineData("Name", new string[0], false, "")]
    [InlineData("Name", new string[0], true, "")]
    public void Verify_AttributesToParenthesesString(string name, string[] attributes, bool prependSpace, string expected)
    {
        var itemWithNameAndAttributes = new ItemWithNameAndAttributes(name, attributes);

        var result = itemWithNameAndAttributes.AttributesToParenthesesString(prependSpace);

        Assert.Equal(expected, result);
    }

    [Theory]
    [InlineData("Name", new [] { "Item 1", "Item 2", "Item 3", "Item 4", "Item 5" }, "Name (Item 1) (Item 2) (Item 3) (Item 4) (Item 5)")]
    [InlineData("Name", new string[0], "Name")]
    public void Verify_ToString(string name, string[] attributes, string expected)
    {
        var itemWithNameAndAttributes = new ItemWithNameAndAttributes(name, attributes);

        var result = itemWithNameAndAttributes.ToString();

        Assert.Equal(expected, result);
    }
}

While writing that example, I noticed that Verify_AttributesToParenthesesString is somewhat redundant with Verify_ToString--testing ToString implicitly tests AttributesToParenthesesString. If AttributesToParenthesesString exists only to support ToString, then it's an implementation detail. The code can be simplified by deleting Verify_AttributesToParenthesesString and changing AttributesToParenthesesString's access to protected.

xander
  • 1,689
  • 10
  • 18