1 // ***********************************************************************
2 // Copyright (c) 2008 Charlie Poole
4 // Permission is hereby granted, free of charge, to any person obtaining
5 // a copy of this software and associated documentation files (the
6 // "Software"), to deal in the Software without restriction, including
7 // without limitation the rights to use, copy, modify, merge, publish,
8 // distribute, sublicense, and/or sell copies of the Software, and to
9 // permit persons to whom the Software is furnished to do so, subject to
10 // the following conditions:
12 // The above copyright notice and this permission notice shall be
13 // included in all copies or substantial portions of the Software.
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 // ***********************************************************************
25 #define NUNIT_FRAMEWORK
30 using System.Collections;
31 using System.Collections.Generic;
33 using NUnit.Framework.Interfaces;
35 namespace NUnit.Framework.Internal.Builders
38 /// PairwiseStrategy creates test cases by combining the parameter
39 /// data so that all possible pairs of data items are used.
43 /// The number of test cases that cover all possible pairs of test function
44 /// parameters values is significantly less than the number of test cases
45 /// that cover all possible combination of test function parameters values.
46 /// And because different studies show that most of software failures are
47 /// caused by combination of no more than two parameters, pairwise testing
48 /// can be an effective ways to test the system when it's impossible to test
49 /// all combinations of parameters.
52 /// The PairwiseStrategy code is based on "jenny" tool by Bob Jenkins:
53 /// http://burtleburtle.net/bob/math/jenny.html
56 public class PairwiseStrategy : ICombiningStrategy
58 // NOTE: Terminology in this class is based on the literature
59 // relating to strategies for combining variable features when
60 // creating tests. This terminology is more closely related to
61 // higher level testing than it is to unit tests. See
62 // comments in the code for further explanations.
67 /// FleaRand is a pseudo-random number generator developed by Bob Jenkins:
68 /// http://burtleburtle.net/bob/rand/talksmall.html#flea
70 internal class FleaRand
81 /// Initializes a new instance of the FleaRand class.
83 /// <param name="seed">The seed.</param>
84 public FleaRand( uint seed )
93 for ( int i = 0; i < _m.Length; i++ )
98 for ( int i = 0; i < 10; i++ )
111 _q = (uint)_r.Length - 1;
125 uint c = _c + ( ++_z );
128 for ( int i = 0; i < _r.Length; i++ )
130 a = _m[b % _m.Length];
131 _m[b % _m.Length] = d;
132 d = ( c << 19 ) + ( c >> 13 ) + b;
145 /// FeatureInfo represents coverage of a single value of test function
146 /// parameter, represented as a pair of indices, Dimension and Feature. In
147 /// terms of unit testing, Dimension is the index of the test parameter and
148 /// Feature is the index of the supplied value in that parameter's list of
151 internal class FeatureInfo
153 public readonly int Dimension;
155 public readonly int Feature;
158 /// Initializes a new instance of FeatureInfo class.
160 /// <param name="dimension">Index of a dimension.</param>
161 /// <param name="feature">Index of a feature.</param>
162 public FeatureInfo( int dimension, int feature )
164 Dimension = dimension;
170 /// A FeatureTuple represents a combination of features, one per test
171 /// parameter, which should be covered by a test case. In the
172 /// PairwiseStrategy, we are only trying to cover pairs of features, so the
173 /// tuples actually may contain only single feature or pair of features, but
174 /// the algorithm itself works with triplets, quadruples and so on.
176 internal class FeatureTuple
178 private readonly FeatureInfo[] _features;
181 /// Initializes a new instance of FeatureTuple class for a single feature.
183 /// <param name="feature1">Single feature.</param>
184 public FeatureTuple( FeatureInfo feature1 )
186 _features = new FeatureInfo[] { feature1 };
190 /// Initializes a new instance of FeatureTuple class for a pair of features.
192 /// <param name="feature1">First feature.</param>
193 /// <param name="feature2">Second feature.</param>
194 public FeatureTuple( FeatureInfo feature1, FeatureInfo feature2 )
196 _features = new FeatureInfo[] { feature1, feature2 };
203 return _features.Length;
207 public FeatureInfo this[int index]
211 return _features[index];
217 /// TestCase represents a single test case covering a list of features.
219 internal class TestCaseInfo
221 public readonly int[] Features;
224 /// Initializes a new instance of TestCaseInfo class.
226 /// <param name="length">A number of features in the test case.</param>
227 public TestCaseInfo( int length )
229 Features = new int[length];
232 public bool IsTupleCovered( FeatureTuple tuple )
234 for ( int i = 0; i < tuple.Length; i++ )
236 if ( Features[tuple[i].Dimension] != tuple[i].Feature )
247 /// PairwiseTestCaseGenerator class implements an algorithm which generates
248 /// a set of test cases which covers all pairs of possible values of test
253 /// The algorithm starts with creating a set of all feature tuples which we
254 /// will try to cover (see <see
255 /// cref="PairwiseTestCaseGenerator.CreateAllTuples" /> method). This set
256 /// includes every single feature and all possible pairs of features. We
257 /// store feature tuples in the 3-D collection (where axes are "dimension",
258 /// "feature", and "all combinations which includes this feature"), and for
259 /// every two feature (e.g. "A" and "B") we generate both ("A", "B") and
260 /// ("B", "A") pairs. This data structure extremely reduces the amount of
261 /// time needed to calculate coverage for a single test case (this
262 /// calculation is the most time-consuming part of the algorithm).
265 /// Then the algorithm picks one tuple from the uncovered tuple, creates a
266 /// test case that covers this tuple, and then removes this tuple and all
267 /// other tuples covered by this test case from the collection of uncovered
271 /// Picking a tuple to cover
274 /// There are no any special rules defined for picking tuples to cover. We
275 /// just pick them one by one, in the order they were generated.
281 /// Test generation starts from creating a completely random test case which
282 /// covers, nevertheless, previously selected tuple. Then the algorithm
283 /// tries to maximize number of tuples which this test covers.
286 /// Test generation and maximization process repeats seven times for every
287 /// selected tuple and then the algorithm picks the best test case ("seven"
288 /// is a magic number which provides good results in acceptable time).
290 /// <para>Maximizing test coverage</para>
292 /// To maximize tests coverage, the algorithm walks thru the list of mutable
293 /// dimensions (mutable dimension is a dimension that are not included in
294 /// the previously selected tuple). Then for every dimension, the algorithm
295 /// walks thru the list of features and checks if this feature provides
296 /// better coverage than randomly selected feature, and if yes keeps this
300 /// This process repeats while it shows progress. If the last iteration
301 /// doesn't improve coverage, the process ends.
304 /// In addition, for better results, before start every iteration, the
305 /// algorithm "scrambles" dimensions - so for every iteration dimension
306 /// probes in a different order.
309 internal class PairwiseTestCaseGenerator
311 private FleaRand _prng;
313 private int[] _dimensions;
315 private List<FeatureTuple>[][] _uncoveredTuples;
318 /// Creates a set of test cases for specified dimensions.
320 /// <param name="dimensions">
321 /// An array which contains information about dimensions. Each element of
322 /// this array represents a number of features in the specific dimension.
325 /// A set of test cases.
327 public IEnumerable GetTestCases( int[] dimensions )
329 _prng = new FleaRand( 15485863 );
330 _dimensions = dimensions;
334 List<TestCaseInfo> testCases = new List<TestCaseInfo>();
338 FeatureTuple tuple = GetNextTuple();
345 TestCaseInfo testCase = CreateTestCase( tuple );
347 RemoveTuplesCoveredByTest( testCase );
349 testCases.Add( testCase );
353 SelfTest( testCases );
359 private int GetNextRandomNumber()
361 return (int)( _prng.Next() >> 1 );
364 private void CreateAllTuples()
366 _uncoveredTuples = new List<FeatureTuple>[_dimensions.Length][];
368 for ( int d = 0; d < _dimensions.Length; d++ )
370 _uncoveredTuples[d] = new List<FeatureTuple>[_dimensions[d]];
372 for ( int f = 0; f < _dimensions[d]; f++ )
374 _uncoveredTuples[d][f] = CreateTuples( d, f );
379 private List<FeatureTuple> CreateTuples( int dimension, int feature )
381 List<FeatureTuple> result = new List<FeatureTuple>();
383 result.Add( new FeatureTuple( new FeatureInfo( dimension, feature ) ) );
385 for ( int d = 0; d < _dimensions.Length; d++ )
387 if ( d != dimension )
389 for ( int f = 0; f < _dimensions[d]; f++ )
391 result.Add( new FeatureTuple( new FeatureInfo( dimension, feature ), new FeatureInfo( d, f ) ) );
399 private FeatureTuple GetNextTuple()
401 for ( int d = 0; d < _uncoveredTuples.Length; d++ )
403 for ( int f = 0; f < _uncoveredTuples[d].Length; f++ )
405 List<FeatureTuple> tuples = _uncoveredTuples[d][f];
407 if ( tuples.Count > 0 )
409 FeatureTuple tuple = tuples[0];
410 tuples.RemoveAt( 0 );
419 private TestCaseInfo CreateTestCase( FeatureTuple tuple )
421 TestCaseInfo bestTestCase = null;
422 int bestCoverage = -1;
424 for ( int i = 0; i < 7; i++ )
426 TestCaseInfo testCase = CreateRandomTestCase( tuple );
428 int coverage = MaximizeCoverage( testCase, tuple );
430 if ( coverage > bestCoverage )
432 bestTestCase = testCase;
433 bestCoverage = coverage;
440 private TestCaseInfo CreateRandomTestCase( FeatureTuple tuple )
442 TestCaseInfo result = new TestCaseInfo( _dimensions.Length );
444 for ( int d = 0; d < _dimensions.Length; d++ )
446 result.Features[d] = GetNextRandomNumber() % _dimensions[d];
449 for ( int i = 0; i < tuple.Length; i++ )
451 result.Features[tuple[i].Dimension] = tuple[i].Feature;
457 private int MaximizeCoverage( TestCaseInfo testCase, FeatureTuple tuple )
459 // It starts with one because we always have one tuple which is covered by the test.
460 int totalCoverage = 1;
461 int[] mutableDimensions = GetMutableDimensions( tuple );
465 bool progress = false;
467 ScrambleDimensions( mutableDimensions );
469 for ( int i = 0; i < mutableDimensions.Length; i++ )
471 int d = mutableDimensions[i];
473 int bestCoverage = CountTuplesCoveredByTest( testCase, d, testCase.Features[d] );
475 int newCoverage = MaximizeCoverageForDimension( testCase, d, bestCoverage );
477 totalCoverage += newCoverage;
479 if ( newCoverage > bestCoverage )
487 return totalCoverage;
492 private int[] GetMutableDimensions( FeatureTuple tuple )
494 List<int> result = new List<int>();
496 bool[] immutableDimensions = new bool[_dimensions.Length];
498 for ( int i = 0; i < tuple.Length; i++ )
500 immutableDimensions[tuple[i].Dimension] = true;
503 for ( int d = 0; d < _dimensions.Length; d++ )
505 if ( !immutableDimensions[d] )
511 return result.ToArray();
514 private void ScrambleDimensions( int[] dimensions )
516 for ( int i = 0; i < dimensions.Length; i++ )
518 int j = GetNextRandomNumber() % dimensions.Length;
519 int t = dimensions[i];
520 dimensions[i] = dimensions[j];
525 private int MaximizeCoverageForDimension( TestCaseInfo testCase, int dimension, int bestCoverage )
527 List<int> bestFeatures = new List<int>( _dimensions[dimension] );
529 for ( int f = 0; f < _dimensions[dimension]; f++ )
531 testCase.Features[dimension] = f;
533 int coverage = CountTuplesCoveredByTest( testCase, dimension, f );
535 if ( coverage >= bestCoverage )
537 if ( coverage > bestCoverage )
539 bestCoverage = coverage;
540 bestFeatures.Clear();
543 bestFeatures.Add( f );
547 testCase.Features[dimension] = bestFeatures[GetNextRandomNumber() % bestFeatures.Count];
552 private int CountTuplesCoveredByTest( TestCaseInfo testCase, int dimension, int feature )
556 List<FeatureTuple> tuples = _uncoveredTuples[dimension][feature];
558 for ( int i = 0; i < tuples.Count; i++ )
560 if ( testCase.IsTupleCovered( tuples[i] ) )
569 private void RemoveTuplesCoveredByTest( TestCaseInfo testCase )
571 for ( int d = 0; d < _uncoveredTuples.Length; d++ )
573 for ( int f = 0; f < _uncoveredTuples[d].Length; f++ )
575 List<FeatureTuple> tuples = _uncoveredTuples[d][f];
577 for ( int i = tuples.Count - 1; i >= 0; i-- )
579 if ( testCase.IsTupleCovered( tuples[i] ) )
581 tuples.RemoveAt( i );
589 private void SelfTest( List<TestCaseInfo> testCases )
591 for ( int d1 = 0; d1 < _dimensions.Length - 1; d1++ )
593 for ( int d2 = d1 + 1; d2 < _dimensions.Length; d2++ )
595 for ( int f1 = 0; f1 < _dimensions[d1]; f1++ )
597 for ( int f2 = 0; f2 < _dimensions[d2]; f2++ )
599 FeatureTuple tuple = new FeatureTuple( new FeatureInfo( d1, f1 ), new FeatureInfo( d2, f2 ) );
601 if ( !IsTupleCovered( testCases, tuple ) )
603 throw new InvalidOperationException( string.Format( "PairwiseStrategy : Not all pairs are covered : {0}", tuple.ToString() ) );
611 private bool IsTupleCovered( List<TestCaseInfo> testCases, FeatureTuple tuple )
613 foreach ( TestCaseInfo testCase in testCases )
615 if ( testCase.IsTupleCovered( tuple ) )
629 /// Gets the test cases generated by this strategy instance.
631 /// <returns>A set of test cases.</returns>
632 public IEnumerable<ITestCaseData> GetTestCases(IEnumerable[] sources)
634 List<ITestCaseData> testCases = new List<ITestCaseData>();
635 List<object>[] valueSet = CreateValueSet(sources);
636 int[] dimensions = CreateDimensions(valueSet);
638 IEnumerable pairwiseTestCases = new PairwiseTestCaseGenerator().GetTestCases( dimensions );
640 foreach (TestCaseInfo pairwiseTestCase in pairwiseTestCases)
642 object[] testData = new object[pairwiseTestCase.Features.Length];
644 for (int i = 0; i < pairwiseTestCase.Features.Length; i++)
646 testData[i] = valueSet[i][pairwiseTestCase.Features[i]];
649 TestCaseParameters parms = new TestCaseParameters(testData);
650 testCases.Add(parms);
656 private List<object>[] CreateValueSet(IEnumerable[] sources)
658 var valueSet = new List<object>[sources.Length];
660 for (int i = 0; i < valueSet.Length; i++)
662 var values = new List<object>();
664 foreach (object value in sources[i])
669 valueSet[i] = values;
675 private int[] CreateDimensions(List<object>[] valueSet)
677 int[] dimensions = new int[valueSet.Length];
679 for (int i = 0; i < valueSet.Length; i++)
681 dimensions[i] = valueSet[i].Count;