Search code examples
c#.net-coremstest

How do test all combinations of parameters in DataRows


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.


Solution

  • 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.