Skip to content
Merged

Docs #3371

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 29 additions & 2 deletions TUnit.Core/Attributes/TestData/ArgumentsAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,33 @@

namespace TUnit.Core;

/// <summary>
/// Provides a way to supply inline data for parameterized tests.
/// </summary>
/// <remarks>
/// <para>
/// The <c>ArgumentsAttribute</c> allows you to specify test data directly within your test definition,
/// rather than having to create a separate data source class.
/// </para>
/// <para>
/// Each attribute instance represents a single test case that will be executed.
/// </para>
/// <para>
/// Multiple <c>ArgumentsAttribute</c> instances can be applied to a single test method to create
/// multiple test cases with different input values.
/// </para>
/// <example>
/// <code>
/// [Test]
/// [Arguments(1, 2, 3)]
/// [Arguments(10, 20, 30)]
/// public void TestAddition(int a, int b, int expected)
/// {
/// Assert.That(a + b).IsEqualTo(expected);
/// }
/// </code>
/// </example>
/// </remarks>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = true)]
public sealed class ArgumentsAttribute : Attribute, IDataSourceAttribute, ITestRegisteredEventReceiver
{
Expand Down Expand Up @@ -32,7 +59,7 @@ public ArgumentsAttribute(params object?[]? values)
#endif
public ValueTask OnTestRegistered(TestRegisteredContext context)
{
if(!string.IsNullOrEmpty(Skip))
if (!string.IsNullOrEmpty(Skip))
{
context.TestContext.SkipReason = Skip;
context.TestContext.TestDetails.ClassInstance = SkippedTestInstance.Instance;
Expand Down Expand Up @@ -60,7 +87,7 @@ public override async IAsyncEnumerable<Func<Task<T>>> GetTypedDataRowsAsync(Data
#endif
public ValueTask OnTestRegistered(TestRegisteredContext context)
{
if(!string.IsNullOrEmpty(Skip))
if (!string.IsNullOrEmpty(Skip))
{
context.TestContext.SkipReason = Skip;
context.TestContext.TestDetails.ClassInstance = SkippedTestInstance.Instance;
Expand Down
40 changes: 40 additions & 0 deletions TUnit.Core/Attributes/TestData/MatrixSourceAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,45 @@

namespace TUnit.Core;

/// <summary>
/// Provides a way to specify discrete values for a test parameter.
/// When multiple parameters use the <see cref="MatrixAttribute"/>, TUnit will generate tests for all combinations.
/// </summary>
/// <remarks>
/// <para>
/// The Matrix attribute creates a combinatorial test matrix when used on multiple parameters.
/// Each parameter decorated with <see cref="MatrixAttribute"/> contributes its values to the combinations.
/// </para>
/// <para>
/// Example:
/// <code>
/// [Test]
/// public void MatrixTest(
/// [Matrix(1, 2)] int x,
/// [Matrix("a", "b")] string y)
/// {
/// // This will run 4 test cases:
/// // x=1, y="a"
/// // x=1, y="b"
/// // x=2, y="a"
/// // x=2, y="b"
/// }
/// </code>
/// </para>
/// <para>
/// You can exclude specific values from the matrix by using the <c>Excluding</c> property:
/// <code>
/// [Test]
/// public void MatrixWithExclusionsTest(
/// [Matrix(1, 2, 3) { Excluding = [3] }] int x,
/// [Matrix("a", "b")] string y)
/// {
/// // This will exclude combinations with x=3
/// }
/// </code>
/// </para>
/// </remarks>
/// <param name="objects">The values to be used for this parameter in the test matrix.</param>
[AttributeUsage(AttributeTargets.Parameter)]
public class MatrixAttribute(params object?[]? objects) : TUnitAttribute
{
Expand All @@ -14,5 +53,6 @@ protected MatrixAttribute() : this(null)
public object?[]? Excluding { get; init; }
}

/// <inheritdoc/>
[AttributeUsage(AttributeTargets.Parameter)]
public class MatrixAttribute<T>(params T?[]? objects) : MatrixAttribute(objects?.Cast<object>().ToArray()), IInfersType<T>;
43 changes: 43 additions & 0 deletions TUnit.Core/Attributes/TestMetadata/NotInParallelAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,49 @@

namespace TUnit.Core;

/// <summary>
/// Specifies that a test method, test class, or assembly should not be executed in parallel with other tests that share the same constraint keys.
/// </summary>
/// <remarks>
/// The NotInParallelAttribute helps control test execution to prevent tests from interfering with each other when they
/// access shared resources. When multiple tests share the same constraint key, they will be executed sequentially rather than in parallel.
///
/// NotInParallel can be applied at the method, class, or assembly level.
/// When applied at a class level, all test methods in the class will follow the parallel execution constraint.
/// When applied at the assembly level, it affects all tests in the assembly.
///
/// Method-level attributes take precedence over class-level attributes, which take precedence over assembly-level attributes.
///
/// Tests with no overlapping constraint keys can still run in parallel with each other.
/// To prevent a test from running in parallel with any other test, use the attribute without specifying constraint keys.
/// </remarks>
/// <example>
/// <code>
/// // Prevent this test from running in parallel with any other test with the "Database" constraint key
/// [Test]
/// [NotInParallel("Database")]
/// public void TestThatAccessesDatabase()
/// {
/// // This test will not run in parallel with any other test that has the "Database" constraint
/// }
///
/// // Prevent this test from running in parallel with tests that have either "Api" or "Database" constraint keys
/// [Test]
/// [NotInParallel(new[] { "Api", "Database" })]
/// public void TestThatAccessesMultipleResources()
/// {
/// // This test will not run in parallel with tests that have "Api" or "Database" constraints
/// }
///
/// // Prevent this test from running in parallel with any other test
/// [Test]
/// [NotInParallel]
/// public void TestThatMustRunIsolated()
/// {
/// // This test will run exclusively
/// }
/// </code>
/// </example>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly)]
public class NotInParallelAttribute : SingleTUnitAttribute, ITestDiscoveryEventReceiver, IScopedAttribute<NotInParallelAttribute>
{
Expand Down
41 changes: 41 additions & 0 deletions TUnit.Core/Attributes/TestMetadata/RepeatAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,52 @@
namespace TUnit.Core;

/// <summary>
/// Specifies that a test method, test class, or assembly should be repeated a specified number of times.
/// </summary>
/// <remarks>
/// The RepeatAttribute causes the test to be executed the specified number of times.
/// This is useful for testing consistency across multiple executions or for stress testing.
///
/// Repeat can be applied at the method, class, or assembly level.
/// When applied at a class level, all test methods in the class will be repeated.
/// When applied at the assembly level, it affects all tests in the assembly.
///
/// Method-level attributes take precedence over class-level attributes, which take precedence over assembly-level attributes.
/// </remarks>
/// <example>
/// <code>
/// [Test]
/// [Repeat(5)]
/// public void TestThatShouldBeConsistent()
/// {
/// // This test will run 5 times
/// Assert.That(MyFunction()).IsTrue();
/// }
///
/// [Test]
/// [Repeat(100)]
/// public void StressTest()
/// {
/// // /Run this test 100 times to ensure reliability under load
/// var result = ComplexOperation();
/// Assert.That(result).IsValid();
/// }
/// </code>
/// </example>
// Don't think there's a way to enable inheritance on this because the source generator needs to access the constructor argument
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly)]
public sealed class RepeatAttribute : TUnitAttribute, IScopedAttribute<RepeatAttribute>
{
/// <summary>
/// Gets the number of times the test should be repeated.
/// </summary>
public int Times { get; }

/// <summary>
/// Initializes a new instance of the <see cref="RepeatAttribute"/> class with the specified number of repetitions.
/// </summary>
/// <param name="times">The number of times to repeat the test. Must be a non-negative integer.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="times"/> is less than 0.</exception>
public RepeatAttribute(int times)
{
if (times < 0)
Expand Down
48 changes: 48 additions & 0 deletions TUnit.Core/Attributes/TestMetadata/SkipAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,40 @@

namespace TUnit.Core;

/// <summary>
/// Specifies that a test method, test class, or assembly should be skipped during test execution.
/// </summary>
/// <remarks>
/// When applied to a test method, class, or assembly, the SkipAttribute prevents the test(s) from being executed
/// and marks them as skipped with the provided reason.
///
/// Skip can be applied at the method, class, or assembly level.
/// When applied at a class level, all test methods in the class will be skipped.
/// When applied at the assembly level, it affects all tests in the assembly.
/// </remarks>
/// <example>
/// <code>
/// [Test]
/// [Skip("Not implemented yet")]
/// public void TestThatIsNotReady()
/// {
/// // This test will be skipped with the reason "Not implemented yet"
/// }
///
/// // Example of a custom skip attribute with conditional logic
/// public class SkipOnLinuxAttribute : SkipAttribute
/// {
/// public SkipOnLinuxAttribute() : base("Test not supported on Linux")
/// {
/// }
///
/// public override Task&lt;bool&gt; ShouldSkip(TestRegisteredContext context)
/// {
/// return Task.FromResult(System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.Linux));
/// }
/// }
/// </code>
/// </example>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = true)]
public class SkipAttribute : Attribute, ITestRegisteredEventReceiver
{
Expand Down Expand Up @@ -29,5 +63,19 @@ public async ValueTask OnTestRegistered(TestRegisteredContext context)
}
}

/// <summary>
/// Determines whether a test should be skipped.
/// </summary>
/// <param name="context">The test context containing information about the test being registered.</param>
/// <returns>
/// A task that represents the asynchronous operation.
/// The task result is true if the test should be skipped; otherwise, false.
/// </returns>
/// <remarks>
/// Can be overridden in derived classes to implement conditional skip logic
/// based on specific conditions or criteria.
///
/// The default implementation always returns true, meaning the test will always be skipped.
/// </remarks>
public virtual Task<bool> ShouldSkip(TestRegisteredContext context) => Task.FromResult(true);
}
28 changes: 27 additions & 1 deletion docs/docs/execution/repeating.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,30 @@ public class MyTestClass

}
}
```
```

## Global Repeat

In case you want to apply the repeat logic to all tests in a project, you can add the attribute on the assembly level.

```csharp
[assembly: Repeat(3)]
```

Or you can apply the repeat policy on all the tests in a class like this:

```csharp
[Repeat(3)]
public class MyTestClass
{
}
```

The more specific attribute will always override the more general one.
For example, the `[Repeat(3)]` on a method will override the `[Repeat(5)]` on the class,
which in turn will override the `[Repeat(7)]` on the assembly.

So the order of precedence is:
1. Method
1. Class
1. Assembly
28 changes: 27 additions & 1 deletion docs/docs/execution/timeouts.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,30 @@ public class MyTestClass

}
}
```
```

## Global Timeout

In case you want to apply the timeout to all tests in a project, you can add the attribute on the assembly level.

```csharp
[assembly: Timeout(3000)]
```

Or you can apply the Timeout on all the tests in a class like this:

```csharp
[Timeout(3000)]
public class MyTestClass
{
}
```

The more specific attribute will always override the more general one.
For example, the `[Timeout(3000)]` on a method will override the `[Timeout(5000)]` on the class,
which in turn will override the `[Timeout(7000)]` on the assembly.

So the order of precedence is:
1. Method
1. Class
1. Assembly
16 changes: 16 additions & 0 deletions docs/docs/test-authoring/skip.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,19 @@ public class MyTestClass
}
```

## Global Skipping

In case you want to skip all tests in a project, you can add the attribute on the assembly level.

```csharp
[assembly: Skip("Skipping all tests in this assembly")]
```

Or you can skip all the tests in a class like this:

```csharp
[Skip("Skipping all tests in this class")]
public class MyTestClass
{
}
```