How do test all combinations of parameters in DataRows

Viewed 271

Suppose I have a test method like so:

[TestMethod]
[DataRow( 0, 0, 0 )]
[DataRow( 0, 0, 1 )]
[DataRow( 0, 1, 0 )]
[DataRow( 0, 1, 1 )]
[DataRow( 1, 0, 0 )]
[DataRow( 1, 0, 1 )]
[DataRow( 1, 1, 0 )]
[DataRow( 1, 1, 1 )]
public void ReallyCoolTest( int a, int b, int c )
{
    // some really cool test code that uses a, b, and c
}

This tests against all combinations of a, b, & c equaling 0 or 1. Just two values in three parameters and it takes 8 DataRow lines!

What I'd really love to do is something like this:

[TestMethod]
[DataMatrix( {0,1}, {0,1}, {0,1} )]
public void ReallyCoolTest( int a, int b, int c )
{
    // some really cool test code that uses a, b, and c
}

I'd like to specify some values for each parameter and have all combinations tested. Is there anything like that using MS test?

Note: I am aware of the DataSource attribute. However, I'd like to have the values in-code and not in an external file.

2 Answers

You can also generate your test parameters during runtime with the DynamicData attribute. You can give the name of a method that returns an IEnumerable of object[]. These are used as the parameters for your test.

[DataTestMethod]
[DynamicData(nameof(GetData), DynamicDataSourceType.Method)]
public void ReallyCoolTest(int a, int b, int expected)
{
   // ...
}

public static IEnumerable<object[]> GetData()
{
  for(int i = 0; i <= 1; i++)
  {
     for(int j = 0; j <= 1; j++)
     {
        for(int k = 0; k <=1; k++)
        {
           yield return new object[] {i,j,k};
        }
     }
   }
}

Best I could come up with is this:

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;

namespace UnitTestProject
{
    [TestClass]
    public class TestThing
    {
        [CombinatorialTestMethod]
        [DataRow( 0 , 9 )]
        [DataRow( 1 , 2 )]
        [DataRow( 3 , 4 , 5 , 7)]
        public void TestMe( int a , int b , int c )
        {
            if ( b * 2 == c )
                throw new Exception( "blah!" );
        }
    }

    public class CombinatorialTestMethodAttribute : TestMethodAttribute
    {
        private object[][] argsArrays = new object[][] { };

        private List<object[]> argumentsList = new( );

        public override TestResult[] Execute( ITestMethod testMethod )
        {
            // Get arrays of arguments, then construct arguments list
            List<object[]> argsArraysList = new( );
            foreach ( DataRowAttribute parameters in testMethod.GetAttributes<DataRowAttribute>( false ) )
            {
                argsArraysList.Add( parameters.Data );
            }
            this.argsArrays = argsArraysList.ToArray( );
            this.ConstructArgumentsList( );

            // Invoke the test
            List<TestResult> results = new( );
            foreach ( object[] args in this.argumentsList )
            {
                try
                {
                    TestResult result = testMethod.Invoke( args );
                    results.Add( result );
                }
                catch ( Exception e )
                {
                    results.Add( new TestResult( )
                    {
                        Outcome = UnitTestOutcome.Failed ,
                        TestFailureException = e ,
                    } );
                }
            }

            return results.ToArray( );
        }

        private void ConstructArgumentsList( )
        {
            int num_params = this.argsArrays.Length;

            int[] indices = new int[num_params];

            bool done = num_params == 0;
            while ( !done )
            {
                // Get next arguemnt combination
                object[] args = new object[num_params];
                for ( int i = 0 ; i < num_params ; i += 1 )
                {
                    args[i] = this.argsArrays[i][indices[i]];
                }
                this.argumentsList.Add( args );

                // increment indices
                for ( int i = num_params - 1 ; i >= 0 ; i -= 1 )
                {
                    indices[i] += 1;
                    if ( indices[i] >= this.argsArrays[i].Length )
                    {
                        indices[i] = 0;

                        if ( i == 0 )
                        {
                            done = true;
                            break;
                        }
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }
    }
}

I'm not going to use it because I'm not 100% happy with it yet, but I may fiddle some more and publish something later.

Related