Skip to content

Commit

Permalink
Dropped .NET Standard target.
Browse files Browse the repository at this point in the history
  • Loading branch information
sdcondon committed May 5, 2024
1 parent 150ec64 commit b5b0f61
Show file tree
Hide file tree
Showing 34 changed files with 34 additions and 5,874 deletions.
4 changes: 2 additions & 2 deletions src/FlUnit.Documentation/wwwroot/md/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ On the to-do list for soon-ish:
- Basic test tidy-up support. Open questions here about if/when we should consider objects (prerequisites,
test function return values) to be "owned" by the test, and thus its responsibility to dispose of. What
is the ideal default behaviour, and by what mechanisms should we support deviation from that.
- Test attachment support
- Test run parameter support, attachment support
- Allow for instantiable test fixtures rather than just static test properties.
Of course, test prerequisites and builder reusability perhaps offers an alternative way to approach this kind of thing, but there's
almost certainly still value in this.
Expand Down Expand Up @@ -55,7 +55,7 @@ Then return testmetadata that now optionally include case/assertion index.
This index info would need to be included in VSTest case serialization (see VSTest.TestDiscoverer and TestContainer).
TestRun would need to then act accordingly (TBD whether it could/should execute once but split the results, or rerun) based on the metadata.
Of course a gotcha here is that GivenEach.. doesn't have to return the same number of cases each time (which I maintain is good behaviour - allows for storage of cases in external media). Would need to handle that gracefully.
Problems here: simply can't if target bitness differs. Test code execution on test discovery probably not something to pursue, all things considered*
Problems here: simply can't if e.g. target bitness differs. Test code execution on test discovery probably not something to pursue, all things considered*

Not going to do:
- QoL: Perhaps `Then/AndOfReturnValue(rv => rv.ShouldBe..)` and `Then/AndOfGiven1(g => g.Prop.ShouldBe..)` for succinctness? No - Lambda discards work pretty well (to my eyes at least), and `OfGiven1`, `OfGiven2` is better dealt with via complex prereq objects
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ Create a .NET 6 (or above) class library and add some package references:
- You'll also need to include an assertion library of your choice - the example code below uses [`FluentAssertions`](https://www.nuget.org/packages/FluentAssertions/), for example.
- [`coverlet.collector`](https://www.nuget.org/packages/coverlet.collector/) does work with FlUnit tests - so feel free to add that, too.

NB: a .NET Standard 2.0 version of the framework does exist, and targeting earlier versions of the framework does work, but there are some caveats.
Details can be found [here](user-guide/other-notes.md#caveats-when-targeting-net-5-or-earlier). All the examples and documentation below assumes .NET 6+.

As shown below, tests are defined as public static gettable properties of public static classes, with the help of a fluent builder to construct them.
More examples can be found in the [example test project](https://github.com/sdcondon/FlUnit/blob/main/src/Example.TestProject/ExampleTests.cs).

Expand Down
10 changes: 0 additions & 10 deletions src/FlUnit.Documentation/wwwroot/md/user-guide/other-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,6 @@ Note how, by allowing for individual named assertions as part of the test model

I view this as a positive - because being able to see at a glance what I'm asserting without that assertion failing is a powerful thing. Others may disagree..

### Caveats When Targeting .NET 5 or Earlier

While a version of the framework that targets .NET Standard 2.0 exists, and should work properly, there is one thing in particular to note:

**The .NET Standard version uses LINQ for automatic assertion naming, which has some limitations:** In .NET 6, the automatic naming of assertion results is achieved via the [CallerArgumentExpression](https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-10#callerargumentexpression-attribute-diagnostics) attribute and the C# 10 compiler's knowledge thereof.
This of course isn't available in earlier versions, so in the .NET standard version of the framework, automatic naming is facilitated by allowing you to specify assertions as LINQ expressions. These have some limitations and caveats.
For example, LINQ expressions can't represent method calls with optional arguments (which are pretty common in assertion frameworks).
Also, the round trip from expression tree back to string representation may result in some unexpected output.
Finally, building expression trees comes at a performance cost.

### Test Lifetime - Test Property Getter vs Initializer

Test object lifetime is something you don't really need to worry about when writing tests (since its the test adapter that manages this).
Expand Down
31 changes: 0 additions & 31 deletions src/FlUnit.Tests/ActionTestTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,7 @@ public async Task SimpleTest()
Test test = TestThat
.Given(() => new StringBuilder())
.When(sb => { sb.Append('A'); })
#if NET6_0
.ThenReturns(sb => sb.Length.Should().Be(1));
#else
.ThenReturns(sb => sb.Length.Should().Be(1), "sb.Length.Should().Be(1)"); // An example of LINQ limitations..
#endif

// Act & Assert
await new Func<Task>(async () => await test.ArrangeAsync(new TestContext())).Should().NotThrowAsync();
Expand Down Expand Up @@ -293,33 +289,6 @@ public async Task TestOutput()
testContext.ErrorMessages.Should().BeEquivalentTo(new[] { "Argh!" });
}

#if !NET6_0
[TestMethod]
public async Task LinqAssertions()
{
// Arrange
Test test = TestThat
.When(() => { })
.Then(o => Assert.Fail());

// Act & Assert
await new Func<Task>(async () => await test.ArrangeAsync(new TestContext())).Should().NotThrowAsync();
test.Cases.Count.Should().Be(1);

await new Func<Task>(async () => await test.Cases.Single().ActAsync()).Should().NotThrowAsync();
test.Cases.Single().Assertions.Count.Should().Be(1);

var assertion = test.Cases.Single().Assertions.Single();
assertion.ToString().Should().Be("Fail()");
await new Func<Task>(async () => await assertion.AssertAsync()).Should().ThrowAsync<TestFailureException>();
}
#endif

private async Task ArrangeAsyncShouldNotThrow(Test test)
{
await new Func<Task>(async () => await test.ArrangeAsync(new TestContext())).Should().NotThrowAsync();
}

private class Configuration : ITestConfiguration
{
public bool ArrangementFailureCountsAsFailed { get; set; }
Expand Down
2 changes: 1 addition & 1 deletion src/FlUnit.Tests/FlUnit.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net5.0;net6.0</TargetFrameworks>
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
<IsPackable>false</IsPackable>
</PropertyGroup>

Expand Down
34 changes: 0 additions & 34 deletions src/FlUnit.Tests/FunctionTestTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,7 @@ public async Task SimpleTest()
Test test = TestThat
.Given(() => new { x = 1, y = 1 })
.When(given => given.x + given.y)
#if NET6_0
.ThenReturns((_, sum) => sum.Should().Be(2));
#else
.ThenReturns((_, sum) => sum.Should().Be(2), "sum.Should().Be(2)"); // An example of LINQ limitations..
#endif

// Act & Assert
await new Func<Task>(async () => await test.ArrangeAsync(new TestContext())).Should().NotThrowAsync();
Expand Down Expand Up @@ -168,11 +164,7 @@ public async Task ExpectedExceptionThrown()
Test test = TestThat
.Given(() => new { x = 1, y = 0 })
.When(given => given.x / given.y)
#if NET6_0
.ThenThrows((_, exception) => exception.Should().BeOfType<DivideByZeroException>());
#else
.ThenThrows((_, exception) => exception.Should().BeOfType<DivideByZeroException>(), "exception.Should().BeOfType<DivideByZeroException>()"); // An example of LINQ limitations..
#endif

// Act & Assert
await new Func<Task>(async () => await test.ArrangeAsync(new TestContext())).Should().NotThrowAsync();
Expand Down Expand Up @@ -215,11 +207,7 @@ public async Task FailingAssertion()
Test test = TestThat
.Given(() => new { x = 1, y = 1 })
.When(given => given.x + given.y)
#if NET6_0
.ThenReturns((_, sum) => sum.Should().Be(3));
#else
.ThenReturns((_, sum) => sum.Should().Be(3), "sum.Should().Be(3)"); // An example of LINQ limitations..
#endif

// Act & Assert
await new Func<Task>(async () => await test.ArrangeAsync(new TestContext())).Should().NotThrowAsync();
Expand Down Expand Up @@ -294,28 +282,6 @@ public async Task TestOutput()
testContext.ErrorMessages.Should().BeEquivalentTo(new[] { "Argh!" });
}

#if !NET6_0
[TestMethod]
public async Task LinqAssertions()
{
// Arrange
Test test = TestThat
.When(() => 0)
.Then(o => Assert.Fail());

// Act & Assert
await new Func<Task>(async () => await test.ArrangeAsync(new TestContext())).Should().NotThrowAsync();
test.Cases.Count.Should().Be(1);

await new Func<Task>(async () => await test.Cases.Single().ActAsync()).Should().NotThrowAsync();
test.Cases.Single().Assertions.Count.Should().Be(1);

var assertion = test.Cases.Single().Assertions.Single();
assertion.ToString().Should().Be("Fail()");
await new Func<Task>(async () => await assertion.AssertAsync()).Should().ThrowAsync<TestFailureException>();
}
#endif

private class Configuration : ITestConfiguration
{
public bool ArrangementFailureCountsAsFailed { get; set; }
Expand Down
77 changes: 0 additions & 77 deletions src/FlUnit/ActionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ namespace FlUnit
{
internal static class ActionExtensions
{
#if NET6_0
public static Func<ValueTask> ToAsyncWrapper(this Action action)
{
return () =>
Expand All @@ -14,18 +13,7 @@ public static Func<ValueTask> ToAsyncWrapper(this Action action)
return ValueTask.CompletedTask;
};
}
#else
public static Func<Task> ToAsyncWrapper(this Action action)
{
return () =>
{
action.Invoke();
return Task.CompletedTask;
};
}
#endif

#if NET6_0
public static Func<T1, ValueTask> ToAsyncWrapper<T1>(this Action<T1> action)
{
return (a1) =>
Expand All @@ -34,18 +22,7 @@ public static Func<T1, ValueTask> ToAsyncWrapper<T1>(this Action<T1> action)
return ValueTask.CompletedTask;
};
}
#else
public static Func<T1, Task> ToAsyncWrapper<T1>(this Action<T1> action)
{
return (a1) =>
{
action.Invoke(a1);
return Task.CompletedTask;
};
}
#endif

#if NET6_0
public static Func<T1, T2, ValueTask> ToAsyncWrapper<T1, T2>(this Action<T1, T2> action)
{
return (a1, a2) =>
Expand All @@ -54,18 +31,7 @@ public static Func<T1, T2, ValueTask> ToAsyncWrapper<T1, T2>(this Action<T1, T2>
return ValueTask.CompletedTask;
};
}
#else
public static Func<T1, T2, Task> ToAsyncWrapper<T1, T2>(this Action<T1, T2> action)
{
return (a1, a2) =>
{
action.Invoke(a1, a2);
return Task.CompletedTask;
};
}
#endif

#if NET6_0
public static Func<T1, T2, T3, ValueTask> ToAsyncWrapper<T1, T2, T3>(this Action<T1, T2, T3> action)
{
return (a1, a2, a3) =>
Expand All @@ -74,18 +40,7 @@ public static Func<T1, T2, T3, ValueTask> ToAsyncWrapper<T1, T2, T3>(this Action
return ValueTask.CompletedTask;
};
}
#else
public static Func<T1, T2, T3, Task> ToAsyncWrapper<T1, T2, T3>(this Action<T1, T2, T3> action)
{
return (a1, a2, a3) =>
{
action.Invoke(a1, a2, a3);
return Task.CompletedTask;
};
}
#endif

#if NET6_0
public static Func<T1, T2, T3, T4, ValueTask> ToAsyncWrapper<T1, T2, T3, T4>(this Action<T1, T2, T3, T4> action)
{
return (a1, a2, a3, a4) =>
Expand All @@ -94,18 +49,7 @@ public static Func<T1, T2, T3, T4, ValueTask> ToAsyncWrapper<T1, T2, T3, T4>(thi
return ValueTask.CompletedTask;
};
}
#else
public static Func<T1, T2, T3, T4, Task> ToAsyncWrapper<T1, T2, T3, T4>(this Action<T1, T2, T3, T4> action)
{
return (a1, a2, a3, a4) =>
{
action.Invoke(a1, a2, a3, a4);
return Task.CompletedTask;
};
}
#endif

#if NET6_0
public static Func<T1, T2, T3, T4, T5, ValueTask> ToAsyncWrapper<T1, T2, T3, T4, T5>(this Action<T1, T2, T3, T4, T5> action)
{
return (a1, a2, a3, a4, a5) =>
Expand All @@ -114,18 +58,7 @@ public static Func<T1, T2, T3, T4, T5, ValueTask> ToAsyncWrapper<T1, T2, T3, T4,
return ValueTask.CompletedTask;
};
}
#else
public static Func<T1, T2, T3, T4, T5, Task> ToAsyncWrapper<T1, T2, T3, T4, T5>(this Action<T1, T2, T3, T4, T5> action)
{
return (a1, a2, a3, a4, a5) =>
{
action.Invoke(a1, a2, a3, a4, a5);
return Task.CompletedTask;
};
}
#endif

#if NET6_0
public static Func<T1, T2, T3, T4, T5, T6, ValueTask> ToAsyncWrapper<T1, T2, T3, T4, T5, T6>(this Action<T1, T2, T3, T4, T5, T6> action)
{
return (a1, a2, a3, a4, a5, a6) =>
Expand All @@ -134,15 +67,5 @@ public static Func<T1, T2, T3, T4, T5, T6, ValueTask> ToAsyncWrapper<T1, T2, T3,
return ValueTask.CompletedTask;
};
}
#else
public static Func<T1, T2, T3, T4, T5, T6, Task> ToAsyncWrapper<T1, T2, T3, T4, T5, T6>(this Action<T1, T2, T3, T4, T5, T6> action)
{
return (a1, a2, a3, a4, a5, a6) =>
{
action.Invoke(a1, a2, a3, a4, a5, a6);
return Task.CompletedTask;
};
}
#endif
}
}
11 changes: 0 additions & 11 deletions src/FlUnit/ActionExtensions.tt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ namespace FlUnit
{
<# for(int i = 0; i <= MaxPrerequisiteCount + 1; i++) { #>
<# WriteLineIf(i > 0); #>
#if NET6_0
public static Func<<#= TList(i, "", ", ") #>ValueTask> ToAsyncWrapper<#= TList(i, "<", ">") #>(this Action<#= TList(i, "<", ">") #> action)
{
return (<#= List(i, i => $"a{i}") #>) =>
Expand All @@ -22,16 +21,6 @@ namespace FlUnit
return ValueTask.CompletedTask;
};
}
#else
public static Func<<#= TList(i, "", ", ") #>Task> ToAsyncWrapper<#= TList(i, "<", ">") #>(this Action<#= TList(i, "<", ">") #> action)
{
return (<#= List(i, i => $"a{i}") #>) =>
{
action.Invoke(<#= List(i, i => $"a{i}") #>);
return Task.CompletedTask;
};
}
#endif
<# } #>
}
}
Loading

0 comments on commit b5b0f61

Please sign in to comment.