diff --git a/README.md b/README.md
index 5797025..b5e0642 100644
--- a/README.md
+++ b/README.md
@@ -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
@@ -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.
@@ -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#
@@ -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#
@@ -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`.
diff --git a/Samples/Calculator/Calculator.CleanTests/Calculator.CleanTests.csproj b/Samples/Calculator/Calculator.CleanTests/Calculator.CleanTests.csproj
index 72487f5..a34d933 100644
--- a/Samples/Calculator/Calculator.CleanTests/Calculator.CleanTests.csproj
+++ b/Samples/Calculator/Calculator.CleanTests/Calculator.CleanTests.csproj
@@ -14,8 +14,8 @@
allruntime; build; native; contentfiles; analyzers; buildtransitive
-
-
+
+
diff --git a/Samples/Job Agency/JobAgency.CleanTests/JobAgency.CleanTests.csproj b/Samples/Job Agency/JobAgency.CleanTests/JobAgency.CleanTests.csproj
index 6887d1d..3ab482d 100644
--- a/Samples/Job Agency/JobAgency.CleanTests/JobAgency.CleanTests.csproj
+++ b/Samples/Job Agency/JobAgency.CleanTests/JobAgency.CleanTests.csproj
@@ -12,7 +12,7 @@
allruntime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/Samples/Job Agency/JobAgency.Data/JobAgency.Data.csproj b/Samples/Job Agency/JobAgency.Data/JobAgency.Data.csproj
index 8f6f023..7760e34 100644
--- a/Samples/Job Agency/JobAgency.Data/JobAgency.Data.csproj
+++ b/Samples/Job Agency/JobAgency.Data/JobAgency.Data.csproj
@@ -11,7 +11,7 @@
-
+
diff --git a/Tests/TryAtSoftware.CleanTests.Benchmark/TryAtSoftware.CleanTests.Benchmark.csproj b/Tests/TryAtSoftware.CleanTests.Benchmark/TryAtSoftware.CleanTests.Benchmark.csproj
index 39060f1..7d0b9db 100644
--- a/Tests/TryAtSoftware.CleanTests.Benchmark/TryAtSoftware.CleanTests.Benchmark.csproj
+++ b/Tests/TryAtSoftware.CleanTests.Benchmark/TryAtSoftware.CleanTests.Benchmark.csproj
@@ -6,7 +6,7 @@
-
+
diff --git a/Tests/TryAtSoftware.CleanTests.UnitTests/ConstructionManagerTests.cs b/Tests/TryAtSoftware.CleanTests.UnitTests/ConstructionManagerTests.cs
new file mode 100644
index 0000000..8073fc9
--- /dev/null
+++ b/Tests/TryAtSoftware.CleanTests.UnitTests/ConstructionManagerTests.cs
@@ -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