Row Tests or Paramerterized Tests (MsTest) – CSV

In a previous post, I discussed Parameter Value Coverage (PVC). One of the easiest ways to add PVC to your test suite is to use Row Tests, sometimes called Parameterized Tests. Row test is the idea of writing a single unit test, but passing many different values in.

Different testing frameworks implement row tests differently.

MsTest uses an attribute called DataResourceAttribute. This supports anything from a csv, Excel file or xml, to a full-blown database.

For a single Unit Test, NUnit’s Row tests are far superior. However, for a larger test project, you will see that DataSourceAttribute with external data sources, scales nicely and compares to NUnits TestCaseSource. However, it is not as easy to get it right the first time. It needs a step by step tutorial.

Step 1 – Create a the Project

  1. In Visual Studio click File | New | Project.
  2. Select Unit Test Project for C#.
  3. Give it a name and a path and click OK.

Step 2 – Add a csv file

  1. Create a folder in your visual studio project called Data.
  2. Right-click on the data folder and choose Add | New Item.
  3. Create new text file named: Data.csv
  4. Add the following to the csv.
Value1, Value2, ExpectedMinValue, Message
1,10, 1, 1 is less th an 10.
192, 134, 134, 134 is less than 192.
101, 99, 99, 99 is less than 101.
77, 108, 77, 77 is less than 108.
45, 37, 37, 37 is less than 34.
12, 18, 12, 12 is less than 18.

Step 3 – Save the CSV file with the correct encoding

Note: The CSV file needs to be saved with an encoding that doesn’t add junk characters.

  1. Click File | Advanced Save Options.
  2. Choose this Encoding option: Unicode (UTF-8 without signature) – Codepage 65001
  3. Click OK and then click save.

Note: VS 2017 doesn’t have an Advance Save Options menu item.

  1. Click File | Save Data.csv as.
  2. Click the drop down on the Save button and choose Save with Encoding.
  3. Choose this Encoding option: Unicode (UTF-8 without signature) – Codepage 65001
  4. Click OK and then click save.

Step 4 – Set CSV File Properties

The CSV file needs to be copied on build.

  1. Right-click on the CSV file and choose Properties.
  2. Change the Copy to output directory to: Copy if newer

Step 5 – Add a Unit Test method

Note: We won’t have a real project to test. We will just test Math.Min() for an example.

  1. Add a reference to System.Data.
  2. Add the TestContext property. (See line 9 below)
  3. Add a DataSource attribute to the test method. (See line 12 below)
  4. Use TestContext.DataRow[0] to get the first column. (See line 16 below)
    Note: You can also access the column by the column name we used: TestContext.DataRow[Value1]

  5. Assign variables for the rest of the columns. (See lines 16-19 below)
  6. Add the test and the assert. (See lines 22 and 25)
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace MsTestRowTestExample
{
    [TestClass]
    public class UnitTest1
    {
        public TestContext TestContext { get; set; }

        [TestMethod]
        [DataSource("Microsoft.VisualStudio.TestTools.DataSource.CSV", @"Data\Data.csv", "Data#csv", DataAccessMethod.Sequential)]
        public void TestMethod1()
        {
            // Arrange
            int a = Convert.ToInt32(TestContext.DataRow[0]);
            int b = Convert.ToInt32(TestContext.DataRow[1]);
            int expected = Convert.ToInt32(TestContext.DataRow[2]);
            string message = TestContext.DataRow[3].ToString();

            // Act
            var actual = Math.Min(a, b);

            // Assert
            Assert.AreEqual(expected, actual, message);
        }
    }
}

You finished! Good job!.

Use Common Csv files for Parameter Value Coverage

While this method involved way more steps than anything with NUnit to set up, tt can actually be a bit quicker for subsequent tests and quite useful when testing larger projects. Once you have the csv files setup, you can reuse them for many methods. For example, for every method that takes a string, you could use the same DataSource to get Parameter Value Coverage (PVC).

null, A null string
"", An empty string, String.Empty, or ""
" ", One or more spaces " "
"	", One or more tabs "	"
"
", A new line or Environment.NewLine
"Hello, world!", A valid string.
"&*^I#UYLdk1-KNnS1.,Dv0Hhfwelfnzsdase", An invalid or junk string
গঘ, Double-byte Unicode characters

3 Comments

  1. Rhyous says:

    If you used a Base64 string in the csv, you could then use the below extension method. But as I said before, it means your test code needs tests. 🙂
    http://stackoverflow.com/a/36592239/375727

    using System;
    
    namespace Service.Support
    {
        public static class Base64
        {
            public static string ToBase64(this System.Text.Encoding encoding, string text)
            {
                if (text == null)
                {
                    return null;
                }
    
                byte[] textAsBytes = encoding.GetBytes(text);
                return Convert.ToBase64String(textAsBytes);
            }
    
            public static bool TryParseBase64(this System.Text.Encoding encoding, string encodedText, out string decodedText)
            {
                if (encodedText == null)
                {
                    decodedText = null;
                    return false;
                }
    
                try
                {
                    byte[] textAsBytes = Convert.FromBase64String(encodedText);
                    decodedText = encoding.GetString(textAsBytes);
                    return true;
                }
                catch (Exception)
                {
                    decodedText = null;
                    return false;   
                }
            }
        }
    }
    
  2. Marco de Freitas says:

    Hello,

    I really like your posts and it's helping start on unit tests!
    But I have a small question. While maintaining one test per method and having to perform Parameterized Tests using a CSV file is it ok if I include an if inside the test?

    /// 
    /// A test for EncryptStringToBytes(string plainText, byte[] Key, byte[] IV) where
    /// plainText value is variable and Key and IV have dummy data
    /// 
    [TestMethod]
    [DataSource("Microsoft.VisualStudio.TestTools.DataSource.CSV", @"Data\Data.csv", "Data#csv", DataAccessMethod.Sequential)]
    public void EncryptStringToBytes_Test()
    {
      //Dummy data for key
      byte[] key = new byte[16];
      //Dummy data for IV
      byte[] IV = new byte[16];
      //Gets test data
      string plainText = TestContext.DataRow[0].ToString();
      //Gets message associated with the test
      string message = TestContext.DataRow[1].ToString();
    
      if (string.IsNullOrEmpty(plainText))
      {
        byte[] expected = null;
        byte[] actual = Rijndael.EncryptStringToBytes(plainText, key, IV);
        Assert.AreEqual(expected, actual, message);
      }
      else
      {
        byte[] notExpected = null;
        byte[] actual = Rijndael.EncryptStringToBytes(plainText, key, IV);
        Assert.AreNotEqual(notExpected, actual, message);
      }
    }
    

    Including the if on the test method performs the tests as it should but I want to make sure I am following a good procedure!
    Best regards!

    • Rhyous says:

      Marco,

      Well, I would normally recommend that you add an 'Expected' column to your data set, but your expected type is byte[], which would be hard to do in CSV. You would have to convert the string to and from a byte array, which may add too much complexity for a test, causing your test to need its own code and unit tests to function. Not to mention the expected value is an encryption, which means you won't know the expected values before hand.

      This is a perfectly acceptable work around that doesn't break any rules and won't hide failures. The additional complexity of the if statement is minor.

Leave a Reply

How to post code in comments?