Skip to content

Commit 066e12b

Browse files
committed
feat: funit test aggregation for distributed projects
1 parent c1a66ec commit 066e12b

11 files changed

Lines changed: 566 additions & 10 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
SOURCE_TYPE=repo
2+
SOURCE_ADDRESS=https://github.com/codecov/codecov-action.git

examples/advanced/SpaceflightsDistributed/SpaceflightsDistributed.DataProcessing/Flows/DataProcessing/Steps/PreprocessCompaniesStep.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Flowthru.Core.Steps;
2+
using Flowthru.FUnit;
23
using SpaceflightsDistributed.DataProcessing.Data._01_Raw.Schemas;
34
using SpaceflightsDistributed.DataProcessing.Data._02_Intermediate.Schemas;
45

@@ -48,4 +49,46 @@ private static bool TryParsePercentage(string value, out decimal result)
4849
result = parsed / 100m;
4950
return true;
5051
}
52+
53+
#if FUNIT_ENABLED
54+
/// <summary>FUnit tests for <see cref="PreprocessCompaniesStep"/>.</summary>
55+
public class Tests : Flowthru.FUnit.FunitContext
56+
{
57+
private static readonly CompanySchema ValidRaw =
58+
new()
59+
{
60+
Id = "1",
61+
CompanyRating = "90%",
62+
IataApproved = "t",
63+
CompanyLocation = "UK",
64+
};
65+
66+
[StepTest(typeof(PreprocessCompaniesStep))]
67+
public void ValidRecord_ParsesCorrectly()
68+
{
69+
var result = Invoke(Create(), Samples.Of(ValidRaw)).ToList();
70+
71+
Assert.That(result, Has.Count.EqualTo(1));
72+
Assert.That(result[0].CompanyRating, Is.EqualTo(0.90m));
73+
Assert.That(result[0].IataApproved, Is.True);
74+
}
75+
76+
[StepTest(typeof(PreprocessCompaniesStep))]
77+
public void IataApprovedFalse_ParsesCorrectly()
78+
{
79+
var result = Invoke(Create(), Samples.Of(ValidRaw with { IataApproved = "f" })).ToList();
80+
81+
Assert.That(result[0].IataApproved, Is.False);
82+
}
83+
84+
[StepTest(typeof(PreprocessCompaniesStep))]
85+
public void InvalidRating_RecordIsFiltered()
86+
{
87+
var result = Invoke(Create(), Samples.Of(ValidRaw with { CompanyRating = "not-a-percent" }))
88+
.ToList();
89+
90+
Assert.That(result, Is.Empty);
91+
}
92+
}
93+
#endif
5194
}

examples/advanced/SpaceflightsDistributed/SpaceflightsDistributed.DataProcessing/SpaceflightsDistributed.DataProcessing.csproj

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,34 @@
1414
<PackageReference Include="Flowthru" Version="FlowthruVersion" />
1515
</ItemGroup>
1616

17+
<!-- Flowthru.FUnit reference: ProjectReference when building from source, PackageReference for templates -->
18+
<ItemGroup Condition="Exists('../../../../src/core/Flowthru.FUnit/Flowthru.FUnit.csproj')">
19+
<ProjectReference Include="../../../../src/core/Flowthru.FUnit/Flowthru.FUnit.csproj" />
20+
</ItemGroup>
21+
<ItemGroup Condition="!Exists('../../../../src/core/Flowthru.FUnit/Flowthru.FUnit.csproj')">
22+
<PackageReference Include="Flowthru.FUnit" Version="FlowthruVersion" />
23+
</ItemGroup>
24+
25+
<!-- Define FUNIT_ENABLED so step test inner classes can be conditionally compiled -->
26+
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
27+
<DefineConstants>$(DefineConstants);FUNIT_ENABLED</DefineConstants>
28+
</PropertyGroup>
29+
30+
<!-- NUnit: only included in Debug. Microsoft.NET.Test.Sdk is intentionally excluded
31+
because this is a class library — test discovery is handled by the harness. -->
32+
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
33+
<PackageReference Include="NUnit" Version="4.2.2" />
34+
<PackageReference Include="NUnit.Analyzers" Version="4.4.0" />
35+
<Using Include="NUnit.Framework" />
36+
</ItemGroup>
37+
1738
<ItemGroup>
1839
<PackageReference Include="MathNet.Numerics" Version="5.0.0" />
1940
</ItemGroup>
2041

42+
<!-- Source-tree: explicitly import FUnit targets to register the source generator as an Analyzer.
43+
NuGet package consumers get this automatically via build/Flowthru.FUnit.targets auto-import. -->
44+
<Import Project="../../../../src/core/Flowthru.FUnit/build/Flowthru.FUnit.targets"
45+
Condition="Exists('../../../../src/core/Flowthru.FUnit/build/Flowthru.FUnit.targets')" />
46+
2147
</Project>

examples/advanced/SpaceflightsDistributed/SpaceflightsDistributed.DataScience/Flows/DataScience/Steps/SplitDataStep.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Flowthru.Core.Steps;
2+
using Flowthru.FUnit;
23
using SpaceflightsDistributed.DataProcessing.Data._03_Primary.Schemas;
34
using SpaceflightsDistributed.DataScience.Data._05_ModelInput.Schemas;
45

@@ -64,4 +65,51 @@ public static Func<
6465
return (trainData, testData);
6566
};
6667
}
68+
69+
#if FUNIT_ENABLED
70+
/// <summary>FUnit tests for <see cref="SplitDataStep"/>.</summary>
71+
public class Tests : Flowthru.FUnit.FunitContext
72+
{
73+
private static ModelInputTableSchema MakeRow(string id, decimal price = 1000m) =>
74+
new()
75+
{
76+
ShuttleId = id,
77+
ShuttleType = "Type1",
78+
CompanyId = "c1",
79+
Engines = 2,
80+
PassengerCapacity = 100,
81+
Crew = 5,
82+
DCheckComplete = true,
83+
MoonClearanceComplete = false,
84+
Price = price,
85+
IataApproved = true,
86+
CompanyRating = 0.9m,
87+
ReviewScoresRating = 4.5m,
88+
};
89+
90+
[StepTest(typeof(SplitDataStep))]
91+
public void Split_ProducesCorrectTrainTestRatio()
92+
{
93+
var rows = Enumerable.Range(0, 10).Select(i => MakeRow(i.ToString()));
94+
var options = new ModelOptions { TestSize = 0.2, RandomState = 42 };
95+
96+
var (train, test) = Invoke(Create(options), rows);
97+
98+
Assert.That(train.Count(), Is.EqualTo(8));
99+
Assert.That(test.Count(), Is.EqualTo(2));
100+
}
101+
102+
[StepTest(typeof(SplitDataStep))]
103+
public void Split_IsReproducibleWithSameRandomState()
104+
{
105+
var rows = Enumerable.Range(0, 10).Select(i => MakeRow(i.ToString())).ToList();
106+
var options = new ModelOptions { TestSize = 0.3, RandomState = 7 };
107+
108+
var (train1, _) = Invoke(Create(options), rows);
109+
var (train2, _) = Invoke(Create(options), rows);
110+
111+
Assert.That(train1.Select(r => r.Label), Is.EqualTo(train2.Select(r => r.Label)));
112+
}
113+
}
114+
#endif
67115
}

examples/advanced/SpaceflightsDistributed/SpaceflightsDistributed.DataScience/SpaceflightsDistributed.DataScience.csproj

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,34 @@
1818
<ProjectReference Include="../SpaceflightsDistributed.DataProcessing/SpaceflightsDistributed.DataProcessing.csproj" />
1919
</ItemGroup>
2020

21+
<!-- Flowthru.FUnit reference: ProjectReference when building from source, PackageReference for templates -->
22+
<ItemGroup Condition="Exists('../../../../src/core/Flowthru.FUnit/Flowthru.FUnit.csproj')">
23+
<ProjectReference Include="../../../../src/core/Flowthru.FUnit/Flowthru.FUnit.csproj" />
24+
</ItemGroup>
25+
<ItemGroup Condition="!Exists('../../../../src/core/Flowthru.FUnit/Flowthru.FUnit.csproj')">
26+
<PackageReference Include="Flowthru.FUnit" Version="FlowthruVersion" />
27+
</ItemGroup>
28+
29+
<!-- Define FUNIT_ENABLED so step test inner classes can be conditionally compiled -->
30+
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
31+
<DefineConstants>$(DefineConstants);FUNIT_ENABLED</DefineConstants>
32+
</PropertyGroup>
33+
34+
<!-- NUnit: only included in Debug. Microsoft.NET.Test.Sdk is intentionally excluded
35+
because this is a class library — test discovery is handled by the harness. -->
36+
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
37+
<PackageReference Include="NUnit" Version="4.2.2" />
38+
<PackageReference Include="NUnit.Analyzers" Version="4.4.0" />
39+
<Using Include="NUnit.Framework" />
40+
</ItemGroup>
41+
2142
<ItemGroup>
2243
<PackageReference Include="MathNet.Numerics" Version="5.0.0" />
2344
</ItemGroup>
2445

46+
<!-- Source-tree: explicitly import FUnit targets to register the source generator as an Analyzer.
47+
NuGet package consumers get this automatically via build/Flowthru.FUnit.targets auto-import. -->
48+
<Import Project="../../../../src/core/Flowthru.FUnit/build/Flowthru.FUnit.targets"
49+
Condition="Exists('../../../../src/core/Flowthru.FUnit/build/Flowthru.FUnit.targets')" />
50+
2551
</Project>

examples/advanced/SpaceflightsDistributed/SpaceflightsDistributed.Reporting/Flows/Reporting/Steps/ComparePassengerCapacityStep.cs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Flowthru.Core.Steps;
2+
using Flowthru.FUnit;
23
using SpaceflightsDistributed.DataProcessing.Data._02_Intermediate.Schemas;
34
using SpaceflightsDistributed.Reporting.Data._08_Reporting.Schemas;
45

@@ -26,4 +27,55 @@ public static Func<
2627
});
2728
};
2829
}
30+
31+
#if FUNIT_ENABLED
32+
/// <summary>FUnit tests for <see cref="ComparePassengerCapacityStep"/>.</summary>
33+
public class Tests : Flowthru.FUnit.FunitContext
34+
{
35+
private static PreprocessedShuttleSchema MakeShuttle(string type, int capacity) =>
36+
new()
37+
{
38+
Id = System.Guid.NewGuid().ToString(),
39+
ShuttleType = type,
40+
CompanyId = "c1",
41+
Engines = 2,
42+
PassengerCapacity = capacity,
43+
Crew = 4,
44+
Price = 500m,
45+
DCheckComplete = true,
46+
MoonClearanceComplete = false,
47+
};
48+
49+
[StepTest(typeof(ComparePassengerCapacityStep))]
50+
public void GroupsByShuttleType()
51+
{
52+
var input = new[]
53+
{
54+
MakeShuttle("TypeA", 100),
55+
MakeShuttle("TypeA", 200),
56+
MakeShuttle("TypeB", 50),
57+
};
58+
59+
var result = Invoke(Create(), input).ToList();
60+
61+
Assert.That(result, Has.Count.EqualTo(2));
62+
Assert.That(
63+
result.Single(r => r.ShuttleType == "TypeA").AvgPassengerCapacity,
64+
Is.EqualTo(150m)
65+
);
66+
Assert.That(
67+
result.Single(r => r.ShuttleType == "TypeB").AvgPassengerCapacity,
68+
Is.EqualTo(50m)
69+
);
70+
}
71+
72+
[StepTest(typeof(ComparePassengerCapacityStep))]
73+
public void EmptyInput_ReturnsEmpty()
74+
{
75+
var result = Invoke(Create(), Enumerable.Empty<PreprocessedShuttleSchema>()).ToList();
76+
77+
Assert.That(result, Is.Empty);
78+
}
79+
}
80+
#endif
2981
}

examples/advanced/SpaceflightsDistributed/SpaceflightsDistributed.Reporting/SpaceflightsDistributed.Reporting.csproj

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,36 @@
1919
<ProjectReference Include="../SpaceflightsDistributed.DataScience/SpaceflightsDistributed.DataScience.csproj" />
2020
</ItemGroup>
2121

22+
<!-- Flowthru.FUnit reference: ProjectReference when building from source, PackageReference for templates -->
23+
<ItemGroup Condition="Exists('../../../../src/core/Flowthru.FUnit/Flowthru.FUnit.csproj')">
24+
<ProjectReference Include="../../../../src/core/Flowthru.FUnit/Flowthru.FUnit.csproj" />
25+
</ItemGroup>
26+
<ItemGroup Condition="!Exists('../../../../src/core/Flowthru.FUnit/Flowthru.FUnit.csproj')">
27+
<PackageReference Include="Flowthru.FUnit" Version="FlowthruVersion" />
28+
</ItemGroup>
29+
30+
<!-- Define FUNIT_ENABLED so step test inner classes can be conditionally compiled -->
31+
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
32+
<DefineConstants>$(DefineConstants);FUNIT_ENABLED</DefineConstants>
33+
</PropertyGroup>
34+
35+
<!-- NUnit: only included in Debug. Microsoft.NET.Test.Sdk is intentionally excluded
36+
because this is a class library — test discovery is handled by the harness. -->
37+
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
38+
<PackageReference Include="NUnit" Version="4.2.2" />
39+
<PackageReference Include="NUnit.Analyzers" Version="4.4.0" />
40+
<Using Include="NUnit.Framework" />
41+
</ItemGroup>
42+
2243
<ItemGroup>
2344
<PackageReference Include="Plotly.NET" Version="5.1.0" />
2445
<PackageReference Include="Plotly.NET.CSharp" Version="0.13.0" />
2546
<PackageReference Include="Plotly.NET.ImageExport" Version="6.1.0" />
2647
</ItemGroup>
2748

49+
<!-- Source-tree: explicitly import FUnit targets to register the source generator as an Analyzer.
50+
NuGet package consumers get this automatically via build/Flowthru.FUnit.targets auto-import. -->
51+
<Import Project="../../../../src/core/Flowthru.FUnit/build/Flowthru.FUnit.targets"
52+
Condition="Exists('../../../../src/core/Flowthru.FUnit/build/Flowthru.FUnit.targets')" />
53+
2854
</Project>

examples/advanced/SpaceflightsDistributed/SpaceflightsDistributed/SpaceflightsDistributed.csproj

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,15 @@
77
<Nullable>enable</Nullable>
88
</PropertyGroup>
99

10+
<!-- FUnitAggregate: collect [StepTest] methods from all referenced sub-libraries
11+
and emit NUnit runner classes in this project. Microsoft.NET.Test.Sdk's
12+
OutputType=Exe override is harmless here because this is already an Exe.
13+
GenerateProgramFile=false prevents the SDK from injecting a competing Main. -->
14+
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
15+
<FUnitAggregate>true</FUnitAggregate>
16+
<GenerateProgramFile>false</GenerateProgramFile>
17+
</PropertyGroup>
18+
1019
<!-- Flowthru reference: ProjectReference when building from source, PackageReference for templates -->
1120
<ItemGroup Condition="Exists('../../../../src/core/Flowthru/Flowthru.csproj')">
1221
<ProjectReference Include="../../../../src/core/Flowthru/Flowthru.csproj" />
@@ -21,10 +30,33 @@
2130
<ProjectReference Include="../SpaceflightsDistributed.Reporting/SpaceflightsDistributed.Reporting.csproj" />
2231
</ItemGroup>
2332

33+
<!-- Test packages: only in Debug so Release builds carry zero test infrastructure -->
34+
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
35+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
36+
<PackageReference Include="NUnit" Version="4.2.2" />
37+
<PackageReference Include="NUnit.Analyzers" Version="4.4.0" />
38+
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
39+
<Using Include="NUnit.Framework" />
40+
</ItemGroup>
41+
42+
<!-- Flowthru.FUnit: only in Debug. Provides the source generator (StepTestRegistryGenerator)
43+
that aggregates [StepTest] methods from referenced sub-libraries via FUnitAggregate=true. -->
44+
<ItemGroup Condition="'$(Configuration)' == 'Debug' AND Exists('../../../../src/core/Flowthru.FUnit/Flowthru.FUnit.csproj')">
45+
<ProjectReference Include="../../../../src/core/Flowthru.FUnit/Flowthru.FUnit.csproj" />
46+
</ItemGroup>
47+
<ItemGroup Condition="'$(Configuration)' == 'Debug' AND !Exists('../../../../src/core/Flowthru.FUnit/Flowthru.FUnit.csproj')">
48+
<PackageReference Include="Flowthru.FUnit" Version="FlowthruVersion" />
49+
</ItemGroup>
50+
2451
<ItemGroup>
2552
<None Update="appsettings.json">
2653
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
2754
</None>
2855
</ItemGroup>
2956

57+
<!-- Source-tree: explicitly import FUnit targets to register the source generator as an Analyzer.
58+
NuGet package consumers get this automatically via build/Flowthru.FUnit.targets auto-import. -->
59+
<Import Project="../../../../src/core/Flowthru.FUnit/build/Flowthru.FUnit.targets"
60+
Condition="Exists('../../../../src/core/Flowthru.FUnit/build/Flowthru.FUnit.targets')" />
61+
3062
</Project>

0 commit comments

Comments
 (0)