Skip to content

Commit 6f435e2

Browse files
authored
Merge pull request #22 from mille5a9/combinatorial-range-attribute
CombinatorialRangeAttribute
2 parents d595039 + d40f306 commit 6f435e2

File tree

5 files changed

+181
-0
lines changed

5 files changed

+181
-0
lines changed

README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,4 +123,36 @@ combinatorial. In many cases the test reduction can be much greater.
123123
Notice that although the test cases are fewer, you can still find a test
124124
case that covers any *two* parameter values (thus *pair*wise).
125125

126+
To run a test with a parameter over a range of values, we have
127+
`CombinatorialRangeAttribute` to generate tests over intervals of integers.
128+
```csharp
129+
[Theory, CombinatorialData]
130+
public void CombinatorialCustomRange([CombinatorialRange(0, 5)] int p1, [CombinatorialRange(0, 3, 2)] int p2)
131+
{
132+
// Combinatorial generates these test cases:
133+
// 0 0
134+
// 1 0
135+
// 2 0
136+
// 3 0
137+
// 4 0
138+
// 0 2
139+
// 1 2
140+
// 2 2
141+
// 3 2
142+
// 4 2
143+
}
144+
```
145+
146+
`CombinatorialRangeAttribute` has two distinct constructors.
147+
When supplied with two integers `from` and `count`, Xunit
148+
will create a test case where the parameter equals `from`, and
149+
it will increment the parameter by 1 for `count` number of cases.
150+
151+
In the second constructor, `CombinatorialRangeAttribute`
152+
accepts three integer parameters. In the generated cases, the
153+
parameter value will step up from the first integer to the
154+
second integer, and the third integer specifies the interval of
155+
which to increment.
156+
157+
126158
[NuPkg]: https://www.nuget.org/packages/Xunit.Combinatorial
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
namespace Xunit.Combinatorial.Tests
2+
{
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Text;
7+
using System.Threading.Tasks;
8+
9+
public class CombinatorialRangeAttributeTests
10+
{
11+
[Theory]
12+
[InlineData(0, 5)]
13+
public void CountOfIntegers_HappyPath_SetsAttributeWithRange(int from, int count)
14+
{
15+
object[] values = { 0, 1, 2, 3, 4 };
16+
var attribute = new CombinatorialRangeAttribute(from, count);
17+
Assert.Equal(values, attribute.Values);
18+
}
19+
20+
[Theory]
21+
[InlineData(0, -2)]
22+
public void CountOfIntegers_NegativeCount_ArgOutOfRange(int from, int count)
23+
{
24+
Assert.Throws<ArgumentOutOfRangeException>(() => new CombinatorialRangeAttribute(from, count));
25+
}
26+
27+
[Theory]
28+
[InlineData(0, 7, 2, true)]
29+
[InlineData(0, 8, 2, false)]
30+
public void IntegerStep_HappyPath_SetsAttributeWithRange(int from, int to, int step, bool unevenInterval)
31+
{
32+
object[] expectedValues = unevenInterval ? new object[]{ 0, 2, 4, 6 } : new object[]{ 0, 2, 4, 6, 8 };
33+
34+
var attribute = new CombinatorialRangeAttribute(from, to, step);
35+
Assert.Equal(expectedValues, attribute.Values);
36+
}
37+
38+
[Theory]
39+
[InlineData(4, 2, 1)]
40+
[InlineData(1, 5, -1)]
41+
public void IntegerStep_InvalidIntervalAndStep_ArgOutOfRange(int from, int to, int step)
42+
{
43+
Assert.Throws<ArgumentOutOfRangeException>(() => new CombinatorialRangeAttribute(from, to, step));
44+
}
45+
}
46+
}

src/Xunit.Combinatorial.Tests/SampleUses.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,24 @@ public void CombinatorialCustomData([CustomValues] int v1, [CustomValues] int v2
5050
// 15 15
5151
}
5252

53+
[Theory, CombinatorialData]
54+
public void CombinatorialCustomRange([CombinatorialRange(0, 5)] int p1, [CombinatorialRange(0, 3, 2)] int p2)
55+
{
56+
// Combinatorial generates these test cases:
57+
// 0 0
58+
// 1 0
59+
// 2 0
60+
// 3 0
61+
// 4 0
62+
// 0 2
63+
// 1 2
64+
// 2 2
65+
// 3 2
66+
// 4 2
67+
Assert.True(p1 == 0 || p1 == 1 || p1 == 2 || p1 == 3 || p1 == 4);
68+
Assert.True(p2 == 0 || p2 == 2);
69+
}
70+
5371
[AttributeUsage(AttributeTargets.Parameter)]
5472
private class CustomValuesAttribute : CombinatorialValuesAttribute
5573
{
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright (c) Andrew Arnott. All rights reserved. Licensed under the Ms-PL.
2+
3+
namespace Xunit
4+
{
5+
using System;
6+
7+
/// <summary>
8+
/// Specifies which range of values for this parameter should be used for running the test method.
9+
/// </summary>
10+
[AttributeUsage(AttributeTargets.Parameter)]
11+
public class CombinatorialRangeAttribute : Attribute
12+
{
13+
/// <summary>
14+
/// Initializes a new instance of the <see cref="CombinatorialRangeAttribute"/> class.
15+
/// </summary>
16+
/// <param name="from">The value at the beginning of the range.</param>
17+
/// <param name="count">
18+
/// The quantity of consecutive integer values to include.
19+
/// Cannot be less than 1, which would conceptually result in zero test cases.
20+
/// </param>
21+
public CombinatorialRangeAttribute(int from, int count)
22+
{
23+
if (count < 1)
24+
{
25+
throw new ArgumentOutOfRangeException(nameof(count));
26+
}
27+
28+
object[] values = new object[count];
29+
for (int i = 0; i < count; i++)
30+
{
31+
values[i] = from + i;
32+
}
33+
34+
this.Values = values;
35+
}
36+
37+
/// <summary>
38+
/// Initializes a new instance of the <see cref="CombinatorialRangeAttribute"/> class.
39+
/// </summary>
40+
/// <param name="from">The value at the beginning of the range.</param>
41+
/// <param name="to">
42+
/// The value at the end of the range.
43+
/// Cannot be less than "from" parameter.
44+
/// When "to" and "from" are equal, CombinatorialValues is more appropriate.
45+
/// </param>
46+
/// <param name="step">
47+
/// The number of integers to step for each value in result.
48+
/// Cannot be less than one. Stepping zero or backwards is not useful.
49+
/// Stepping over "to" does not add another value to the range.
50+
/// </param>
51+
public CombinatorialRangeAttribute(int from, int to, int step)
52+
{
53+
if (to <= from)
54+
{
55+
throw new ArgumentOutOfRangeException(nameof(to));
56+
}
57+
58+
if (step < 1)
59+
{
60+
throw new ArgumentOutOfRangeException(nameof(step));
61+
}
62+
63+
int count = ((to - from) / step) + 1;
64+
object[] values = new object[count];
65+
for (int i = 0; i < count; i++)
66+
{
67+
values[i] = from + (i * step);
68+
}
69+
70+
this.Values = values;
71+
}
72+
73+
/// <summary>
74+
/// Gets the values that should be passed to this parameter on the test method.
75+
/// </summary>
76+
/// <value>An array of values.</value>
77+
public object[] Values { get; }
78+
}
79+
}

src/Xunit.Combinatorial/ValuesUtilities.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ internal static IEnumerable<object> GetValuesFor(ParameterInfo parameter)
2626
return valuesAttribute.Values;
2727
}
2828

29+
var rangeAttribute = parameter.GetCustomAttribute<CombinatorialRangeAttribute>();
30+
if (rangeAttribute != null)
31+
{
32+
return rangeAttribute.Values;
33+
}
34+
2935
return GetValuesFor(parameter.ParameterType);
3036
}
3137

0 commit comments

Comments
 (0)