Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
309e4f5
Added the `Outer demands` attribute.
TonyTroeff Aug 1, 2023
9824025
Added the `Outer demands` property to the clean utility descriptor.
TonyTroeff Aug 1, 2023
d5cd5f5
Outer demands are successfully set for each clean utility descriptor …
TonyTroeff Aug 1, 2023
8000e24
Outer demands should be successfully serialized and deserialized alon…
TonyTroeff Aug 1, 2023
0beea0a
Merge branch 'troeff/performance-optimizations' into troeff/outer-dem…
TonyTroeff Aug 4, 2023
7a060fc
Added a function that should copy full construction graph instances.
TonyTroeff Aug 5, 2023
6e16729
Added a simple function that should help normalizing the full constru…
TonyTroeff Aug 5, 2023
87b7884
Merge branch 'troeff/performance-optimizations' into troeff/outer-dem…
TonyTroeff Aug 5, 2023
5b31c84
Simplified the logic for normalizing the construction graphs of the d…
TonyTroeff Aug 5, 2023
5280855
Merge remote-tracking branch 'origin/main' into troeff/outer-demands
TonyTroeff Aug 6, 2023
6b9298f
Updating some packages. Trying to fix code smells.
TonyTroeff Aug 7, 2023
1e20a68
Finalizing the outer demands implementation
TonyTroeff Aug 7, 2023
75fbed5
Started writing some simple tests covering the new functionality
TonyTroeff Aug 7, 2023
1a127bd
Wrote one more test covering that outer utilities are successfully ap…
TonyTroeff Aug 7, 2023
8a4db0c
Experimenting with the samples.
TonyTroeff Aug 7, 2023
014b091
Added tests covering what happens when none of the outer demands can …
TonyTroeff Aug 7, 2023
94046fc
Trying to reduce cognitive complexity within the construction manager.
TonyTroeff Aug 7, 2023
5964761
Made imporvements to the documentation. Added some basic information …
TonyTroeff Aug 8, 2023
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
34 changes: 24 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,11 @@ And if we had to do the same for every new functionality that is coming, that wo

The main goals that have been accomplished are:

- Automatic generation of test cases using all proper variations of registered `clean utilities`
- Every `clean utility` can define external demands that represent conditions about what other `clean utilities` should be present within a variation in order to generate a test case with it
- Automatic generation of test cases using all proper combinations of registered `clean utilities`
- Every `clean utility` can define external demands that represent conditions about what other `clean utilities` should be present within a combination in order to generate a test case with it
- Every `clean utility` can depend internally on other `clean utilities`
- Every `clean utility` can define internal demands that represent conditions about what `clean utilities` should be injected upon initialization
- Every `clean utility` can define outer demands that represent conditions oriented towards the superior utilities
- Global and local `clean utilities` - local `clean utilities` are instantiated for every test case; global `clean utilities` are instantiated only once and can be used to share common context between similar test cases
- Parallel execution of tests cases

Expand Down Expand Up @@ -125,7 +126,9 @@ _Enabling test metadata presentation methods often has performance impact over t
## What are the `clean utilities`?

The `clean utility` is a key component for our library. Every `clean utility` has a `category` and a `name` that are required.
One test may require many `clean utilities` and whenever there are two or more utilities from the same category that can be used for its execution, then a test case will be generated for each possible variation of utilities.

> One test may require utilities from many categories.
> The corresponding test cases will be generated using **unique** combination of utilities from the required categories.

Every `clean utility` can me marked as `local` or `global`.
`Local clean utilities` will be instantiated at least once for every test case requiring their participation.
Expand Down Expand Up @@ -165,11 +168,18 @@ If this is not possible, the test assembly should be explicitly decorated with a
```C#
[assembly: TryAtSoftware.CleanTests.Core.Attributes.SharesUtilitiesWith("Assembly.With.Shared.CleanUtilities")]
```
### Dependencies

Every `clean utility` can depend on other `clean utiliites`.
This relationship can be modelled throughout the `WithRequirements` attribute.

When generating test cases, each unique instantiation procedure (i.e. the resolution of dependencies) for a given `clean utility` will be presumed as a separate member of the combinatorial set.
For example, if the dependencies of a given utility can be resolved in **N** different ways, the generation process will use all **N** different instantiation procedures as if they were different utilities.

### External demands

Every `clean utility` can define `external demands` throughout the `ExternalDemands` attribute. With its help for each `category` a set of demanded `characteristics` can be defined.
These demanded characteristics will alter the way variations of `clean utilities` are generated - all external demands should be satisfied for all utilities participating in the variation.
Every `clean utility` can define `external demands` throughout the `ExternalDemands` attribute.
These demanded characteristics will alter the way combinations of `clean utilities` are generated - all external demands should be satisfied for all utilities participating in the combination.

Example:
```C#
Expand All @@ -181,12 +191,10 @@ public class ConsoleReader : IReader
}
```

### Internal requirements and demands

Every `clean utility` can depend on other `clean utiliites`. This relationship can be modelled throughout the `WithRequirements` attribute.
When such `clean utility` participates in a variation, that same variation will be reused as many times as the number of possible instantiation procedures there are (according to the registered `clean utilities` of the required categories).
### Internal demands

Moreover, internal `demands` can be applied (throughout the `InternalDemands` attribute) to filter out the dependent `clean utilities` according to a predefined set of characteristics.
Every `clean utility` can define `internal demands` throughout the `InternalDemands` attribute.
This type of demanded characteristics can be used to filter out the dependent `clean utilities`.

Example:
```C#
Expand All @@ -207,6 +215,12 @@ public class Engine : IEngine
/* further implementation of the `IEngine` interface... */
}
```

### Outer demands

Every `clean utility` can define `outer demands` throughout the `OuterDemands` attribute.
This type of demanded characteristics can be used to model conditions oriented towards utilities in the outer scope (also known as _superior_ utilities).

## How to use clean tests?

This library is built atop [XUnit](https://xunit.net/) so if you are familiar with the way this framework operates, you are most likely ready to use `clean tests`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="7.0.7" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="7.0.9" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.0" />
<PackageReference Include="TryAtSoftware.Equalizer" Version="1.0.1" />
<PackageReference Include="TryAtSoftware.Randomizer" Version="2.0.1" />
<PackageReference Include="xunit" Version="2.4.2" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.0" />
<PackageReference Include="TryAtSoftware.Equalizer" Version="1.0.1" />
<PackageReference Include="TryAtSoftware.Randomizer" Version="2.0.1" />
<PackageReference Include="xunit" Version="2.4.2" />
Expand Down
2 changes: 1 addition & 1 deletion Samples/Job Agency/JobAgency.Data/JobAgency.Data.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Options" Version="7.0.1" />
<PackageReference Include="MongoDB.Driver" Version="2.19.2" />
<PackageReference Include="MongoDB.Driver" Version="2.20.0" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.5" />
<PackageReference Include="BenchmarkDotNet" Version="0.13.7" />
</ItemGroup>

<ItemGroup>
Expand Down
198 changes: 198 additions & 0 deletions Tests/TryAtSoftware.CleanTests.UnitTests/ConstructionManagerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
namespace TryAtSoftware.CleanTests.UnitTests;

using System.Text.Json;
using TryAtSoftware.CleanTests.Core.Construction;
using TryAtSoftware.CleanTests.UnitTests.Constants;
using TryAtSoftware.CleanTests.UnitTests.Extensions;
using TryAtSoftware.CleanTests.UnitTests.Parametrization;
using TryAtSoftware.Randomizer.Core.Helpers;

public class ConstructionManagerTests
{
[Theory(Timeout = UnitTestConstants.Timeout)]
[MemberData(nameof(GetDependenciesManagerSetups))]
public void DependencyGraphsShouldBeConstructedSuccessfully(EnvironmentSetup setup, string pathToExpectedResult)
{
var assemblyTestData = setup.MaterializeAsAssemblyData();
var manager = new ConstructionManager(assemblyTestData);

var allUtilities = assemblyTestData.CleanUtilities.GetAllValues().ToArray();
var expectedOutput = File.ReadLines(pathToExpectedResult).ToArray();
Assert.Equal(allUtilities.Length, expectedOutput.Length);

for (var i = 0; i < allUtilities.Length; i++)
{
var constructionPaths = manager.BuildIndividualConstructionGraphs(new[] { allUtilities[i].Id });
var output = JsonSerializer.Serialize(constructionPaths);
Assert.Equal(expectedOutput[i], output);
}
}

[Fact]
public void OuterDemandsShouldBeAppliedCorrectlyAtRootLevel()
{
var utilitiesCount = RandomizationHelper.RandomInteger(3, 10);
var setup = new EnvironmentSetup("outer_demands_env");
setup.WithCategory("FL1", 1).WithCategory("FL2", utilitiesCount).WithCategory("SL1", utilitiesCount);
setup.WithRequirements("FL1", 1, "SL1");

for (var i = 1; i <= utilitiesCount; i++)
{
var characteristic = $"C-{i}";
setup.WithCharacteristics("FL2", i, characteristic);
setup.WithOuterDemands("SL1", i, "FL2", characteristic);
}

var assemblyTestData = setup.MaterializeAsAssemblyData();
var manager = new ConstructionManager(assemblyTestData);

var fl1Utilities = assemblyTestData.CleanUtilities.Get("FL1").ToArray();
Assert.Single(fl1Utilities);

var fl2Utilities = assemblyTestData.CleanUtilities.Get("FL2").ToArray();
Assert.Equal(utilitiesCount, fl2Utilities.Length);

var sl1Utilities = assemblyTestData.CleanUtilities.Get("SL1").ToArray();
Assert.Equal(utilitiesCount, sl1Utilities.Length);

for (var i = 0; i < utilitiesCount; i++)
{
var utilityIds = new[] { fl1Utilities[0].Id, fl2Utilities[i].Id };
var constructionGraphs = manager.BuildIndividualConstructionGraphs(utilityIds);

Assert.Single(constructionGraphs);
var fl1ConstructionGraph = constructionGraphs[0][0];

Assert.NotNull(fl1Utilities);
Assert.Equal(fl1Utilities[0].Id, fl1ConstructionGraph.Id);

var sl1ConstructionGraph = Assert.Single(fl1ConstructionGraph.Dependencies);
Assert.NotNull(sl1ConstructionGraph);
Assert.Equal(sl1Utilities[i].Id, sl1ConstructionGraph.Id);

var fl2ConstructionGraph = constructionGraphs[0][1];
Assert.NotNull(fl2ConstructionGraph);
Assert.Equal(fl2Utilities[i].Id, fl2ConstructionGraph.Id);

Assert.Empty(fl2ConstructionGraph.Dependencies);
}
}

[Fact]
public void UnsatisfiableOuterDemandsShouldBeHandledCorrectlyAtRootLevel()
{
var utilitiesCount = RandomizationHelper.RandomInteger(3, 10);
var setup = new EnvironmentSetup("outer_demands_env");
setup.WithCategory("FL1", 1).WithCategory("FL2", utilitiesCount).WithCategory("SL1", utilitiesCount);
setup.WithRequirements("FL1", 1, "SL1");

for (var i = 1; i <= utilitiesCount; i++)
{
setup.WithCharacteristics("FL2", i, $"C{i}");
setup.WithOuterDemands("SL1", i, "FL2", $"D{i}");
}

var assemblyTestData = setup.MaterializeAsAssemblyData();
var manager = new ConstructionManager(assemblyTestData);

var fl1Utilities = assemblyTestData.CleanUtilities.Get("FL1").ToArray();
Assert.Single(fl1Utilities);

var fl2Utilities = assemblyTestData.CleanUtilities.Get("FL2").ToArray();
Assert.Equal(utilitiesCount, fl2Utilities.Length);

for (var i = 0; i < utilitiesCount; i++)
{
var utilityIds = new[] { fl1Utilities[0].Id, fl2Utilities[i].Id };
var constructionGraphs = manager.BuildIndividualConstructionGraphs(utilityIds);

Assert.Empty(constructionGraphs);
}
}

[Fact]
public void OuterDemandsShouldBeAppliedCorrectlyAtDeeperLevels()
{
var utilitiesCount = RandomizationHelper.RandomInteger(3, 10);
var setup = new EnvironmentSetup("outer_demands_env");
setup.WithCategory("FL1", 1).WithCategory("SL1", 1).WithCategory("SL2", utilitiesCount).WithCategory("TL1", utilitiesCount);
setup.WithRequirements("FL1", 1, "SL1", "SL2").WithRequirements("SL1", 1, "TL1");

for (var i = 1; i <= utilitiesCount; i++)
{
var characteristic = $"C-{i}";
setup.WithCharacteristics("SL2", i, characteristic);
setup.WithOuterDemands("TL1", i, "SL2", characteristic);
}

var assemblyTestData = setup.MaterializeAsAssemblyData();
var manager = new ConstructionManager(assemblyTestData);

var fl1Utilities = assemblyTestData.CleanUtilities.Get("FL1").ToArray();
Assert.Single(fl1Utilities);

var sl1Utilities = assemblyTestData.CleanUtilities.Get("SL1").ToArray();
Assert.Single(sl1Utilities);

var sl2Utilities = assemblyTestData.CleanUtilities.Get("SL2").ToArray();
Assert.Equal(utilitiesCount, sl2Utilities.Length);

var tl1Utilities = assemblyTestData.CleanUtilities.Get("TL1").ToArray();
Assert.Equal(utilitiesCount, tl1Utilities.Length);

var utilityIds = new[] { fl1Utilities[0].Id };
var constructionGraphs = manager.BuildIndividualConstructionGraphs(utilityIds);

Assert.Equal(utilitiesCount, constructionGraphs.Length);
for (var i = 0; i < utilitiesCount; i++)
{
var fl1ConstructionGraph = constructionGraphs[i][0];

Assert.NotNull(fl1Utilities);
Assert.Equal(fl1Utilities[0].Id, fl1ConstructionGraph.Id);
Assert.Equal(2, fl1ConstructionGraph.Dependencies.Count);

var sl1ConstructionGraph = fl1ConstructionGraph.Dependencies[0];
Assert.NotNull(sl1ConstructionGraph);
Assert.Equal(sl1Utilities[0].Id, sl1ConstructionGraph.Id);

var sl2ConstructionGraph = fl1ConstructionGraph.Dependencies[1];
Assert.NotNull(sl2ConstructionGraph);
Assert.Equal(sl2Utilities[i].Id, sl2ConstructionGraph.Id);
Assert.Empty(sl2ConstructionGraph.Dependencies);

var tl1ConstructionGraph = Assert.Single(sl1ConstructionGraph.Dependencies);
Assert.NotNull(tl1ConstructionGraph);
Assert.Equal(tl1Utilities[i].Id, tl1ConstructionGraph.Id);
Assert.Empty(tl1ConstructionGraph.Dependencies);
}
}

[Fact]
public void UnsatisfiableOuterDemandsShouldBeHandledCorrectlyAtDeeperLevels()
{
var utilitiesCount = RandomizationHelper.RandomInteger(3, 10);
var setup = new EnvironmentSetup("outer_demands_env");
setup.WithCategory("FL1", 1).WithCategory("SL1", 1).WithCategory("SL2", utilitiesCount).WithCategory("TL1", utilitiesCount);
setup.WithRequirements("FL1", 1, "SL1", "SL2").WithRequirements("SL1", 1, "TL1");

for (var i = 1; i <= utilitiesCount; i++)
{
setup.WithCharacteristics("SL2", i, $"C{i}");
setup.WithOuterDemands("TL1", i, "SL2", $"D{i}");
}

var assemblyTestData = setup.MaterializeAsAssemblyData();
var manager = new ConstructionManager(assemblyTestData);

var fl1Utilities = assemblyTestData.CleanUtilities.Get("FL1").ToArray();
Assert.Single(fl1Utilities);

var utilityIds = new[] { fl1Utilities[0].Id };
var constructionGraphs = manager.BuildIndividualConstructionGraphs(utilityIds);

Assert.Empty(constructionGraphs);
}

public static IEnumerable<object[]> GetDependenciesManagerSetups() => TestParameters.ConstructObservableConstructionManagerSetups().Select(dependenciesManagerSetup => new object[] { dependenciesManagerSetup.EnvironmentSetup, dependenciesManagerSetup.PathToExpectedResult });
}

This file was deleted.

Loading