Skip to content

Commit cd4a97a

Browse files
committed
BenMorris/NetArchTest#105 - Highlighting "why" a type fails test
1 parent 0e0caab commit cd4a97a

File tree

20 files changed

+210
-140
lines changed

20 files changed

+210
-140
lines changed

README.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
1-
# NetArchTest.eNhancedEdition
1+
[![Nuget](https://img.shields.io/nuget/v/NetArchTest.eNhancedEdition?color=%23004880&label=NetArchTest.eNhancedEdition%20nuget)](https://www.nuget.org/packages/NetArchTest.eNhancedEdition)
2+
3+
# NetArchTest.eNhancedEdition
24

35
A fluent API for .Net Standard that can enforce architectural rules in unit tests and create a *self-testing architecture*. Inspired by the [ArchUnit](https://www.archunit.org/) library for Java.
46

57
NetArchTest.eNhancedEdition is based on [NetArchTest v1.3.2](https://github.com/BenMorris/NetArchTest). If you are not familiar with NetArchTest, you should start by reading [introduction on Ben's blog](https://www.ben-morris.com/writing-archunit-style-tests-for-net-and-c-for-self-testing-architectures).
68

79
### Rationale
810

9-
NetArchTest is well established mature library, but in order to push things forward, a few breaking changes had to be made, and that is how **eNhancedEdition** was born. eNhancedEdition uses almost identical Fluent API as a base library, but it is not 100% backward compatible, and it will never be. The main goal is to offer more, and not to be only a replacement.
11+
NetArchTest is well established mature library, but in order to push things forward, a few breaking changes had to be made, and that is how **eNhancedEdition** was born. eNhancedEdition uses almost identical Fluent API as a base library, but it is not 100% backward compatible, and it will never be.
12+
13+
What **eNhancedEdition** has to offer:
14+
- [Slices](#slices)
15+
- solved BenMorris/NetArchTest#105 - dependency search functions: HaveDependencyOnAny/OnlyHaveDependencyOn explain why a type fails test
16+
17+
1018

11-
### Index
19+
## Index
1220

1321
* [Getting started](#getting-started)
1422
* [Examples](#examples)

src/NetArchTest.Rules/Assemblies/TypeSpec.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ internal sealed class TypeSpec
66
{
77
public TypeDefinition Definition { get; }
88
public string FullName => Definition.FullName;
9+
// Only for use by FunctionSequence
910
internal bool IsSelected { get; set; }
11+
// Can be use by any function
12+
internal bool IsPassing { get; set; }
13+
public string Explanation { get; set; }
1014

1115

1216
public TypeSpec(TypeDefinition definition)
@@ -18,7 +22,7 @@ public TypeSpec(TypeDefinition definition)
1822

1923
public TypeWrapper CreateWrapper()
2024
{
21-
return new TypeWrapper(Definition);
25+
return new TypeWrapper(Definition, Explanation);
2226
}
2327
}
2428
}

src/NetArchTest.Rules/Assemblies/TypeWrapper.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ internal sealed class TypeWrapper : IType
1313
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
1414
private readonly Lazy<Type> _type;
1515

16-
internal TypeWrapper(TypeDefinition monoTypeDefinition)
16+
internal TypeWrapper(TypeDefinition monoTypeDefinition, string explanation)
1717
{
1818
_monoTypeDefinition = monoTypeDefinition;
1919
_type = new Lazy<Type>(() =>
@@ -26,12 +26,14 @@ internal TypeWrapper(TypeDefinition monoTypeDefinition)
2626
catch { }
2727
return type;
2828
});
29+
Explanation = explanation;
2930
}
3031

3132

3233
public Type ReflectionType => _type.Value;
3334
public string FullName => _monoTypeDefinition.FullName;
3435
public string Name => _monoTypeDefinition.Name;
36+
public string Explanation { get; }
3537

3638

3739
public static implicit operator System.Type(TypeWrapper type)

src/NetArchTest.Rules/Condition.Dependencies.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public sealed partial class Condition
1111
/// <returns>An updated set of conditions that can be applied to a list of types.</returns>
1212
public ConditionList HaveDependencyOnAny(params string[] dependencies)
1313
{
14-
_sequence.AddFunctionCall(x => FunctionDelegates.HaveDependencyOnAny(x, dependencies, true));
14+
_sequence.AddFunctionCall((context, inputTypes) => FunctionDelegates.HaveDependencyOnAny(context, inputTypes, dependencies, true));
1515
return new ConditionList(_types, _should, _sequence);
1616
}
1717

@@ -22,7 +22,7 @@ public ConditionList HaveDependencyOnAny(params string[] dependencies)
2222
/// <returns>An updated set of conditions that can be applied to a list of types.</returns>
2323
public ConditionList HaveDependencyOnAll(params string[] dependencies)
2424
{
25-
_sequence.AddFunctionCall(x => FunctionDelegates.HaveDependencyOnAll(x, dependencies, true));
25+
_sequence.AddFunctionCall((context, inputTypes) => FunctionDelegates.HaveDependencyOnAll(context, inputTypes, dependencies, true));
2626
return new ConditionList(_types, _should, _sequence);
2727
}
2828

@@ -33,7 +33,7 @@ public ConditionList HaveDependencyOnAll(params string[] dependencies)
3333
/// <returns>An updated set of conditions that can be applied to a list of types.</returns>
3434
public ConditionList OnlyHaveDependencyOn(params string[] dependencies)
3535
{
36-
_sequence.AddFunctionCall(x => FunctionDelegates.OnlyHaveDependenciesOnAnyOrNone(x, dependencies, true));
36+
_sequence.AddFunctionCall((context, inputTypes) => FunctionDelegates.OnlyHaveDependenciesOnAnyOrNone(context, inputTypes, dependencies, true));
3737
return new ConditionList(_types, _should, _sequence);
3838
}
3939

@@ -44,7 +44,7 @@ public ConditionList OnlyHaveDependencyOn(params string[] dependencies)
4444
/// <returns>An updated set of conditions that can be applied to a list of types.</returns>
4545
public ConditionList NotHaveDependencyOnAny(params string[] dependencies)
4646
{
47-
_sequence.AddFunctionCall(x => FunctionDelegates.HaveDependencyOnAny(x, dependencies, false));
47+
_sequence.AddFunctionCall((context, inputTypes) => FunctionDelegates.HaveDependencyOnAny(context, inputTypes, dependencies, false));
4848
return new ConditionList(_types, _should, _sequence);
4949
}
5050

@@ -55,7 +55,7 @@ public ConditionList NotHaveDependencyOnAny(params string[] dependencies)
5555
/// <returns>An updated set of conditions that can be applied to a list of types.</returns>
5656
public ConditionList NotHaveDependencyOnAll(params string[] dependencies)
5757
{
58-
_sequence.AddFunctionCall(x => FunctionDelegates.HaveDependencyOnAll(x, dependencies, false));
58+
_sequence.AddFunctionCall((context, inputTypes) => FunctionDelegates.HaveDependencyOnAll(context, inputTypes, dependencies, false));
5959
return new ConditionList(_types, _should, _sequence);
6060
}
6161

@@ -66,7 +66,7 @@ public ConditionList NotHaveDependencyOnAll(params string[] dependencies)
6666
/// <returns>An updated set of conditions that can be applied to a list of types.</returns>
6767
public ConditionList HaveDependencyOtherThan(params string[] dependencies)
6868
{
69-
_sequence.AddFunctionCall(x => FunctionDelegates.OnlyHaveDependenciesOnAnyOrNone(x, dependencies, false));
69+
_sequence.AddFunctionCall((context, inputTypes) => FunctionDelegates.OnlyHaveDependenciesOnAnyOrNone(context, inputTypes, dependencies, false));
7070
return new ConditionList(_types, _should, _sequence);
7171
}
7272
}

src/NetArchTest.Rules/ConditionList.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public TestResult GetResult()
5353
}
5454

5555
// If we've failed, get a collection of failing types so these can be reported in a failing test.
56-
var failedTypes = _sequence.ExecuteExtended(_types, selected: !_should);
56+
var failedTypes = _sequence.ExecuteToGetFailingTypes(_types, selected: !_should);
5757
return TestResult.Failure(failedTypes);
5858
}
5959

src/NetArchTest.Rules/Dependencies/DependencySearch.cs

Lines changed: 18 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,76 +9,64 @@ namespace NetArchTest.Dependencies
99
/// </summary>
1010
internal class DependencySearch
1111
{
12+
private readonly bool explainYourself;
13+
14+
public DependencySearch(bool explainYourself)
15+
{
16+
this.explainYourself = explainYourself;
17+
}
18+
1219
/// <summary>
1320
/// Finds types that have a dependency on any item in the given list of dependencies.
1421
/// </summary>
15-
/// <param name="input">The set of type definitions to search.</param>
16-
/// <param name="dependencies">The set of dependencies to look for.</param>
17-
/// <returns>A list of found types.</returns>
1822
public IEnumerable<TypeSpec> FindTypesThatHaveDependencyOnAny(IEnumerable<TypeSpec> input, IEnumerable<string> dependencies)
1923
{
2024
return FindTypes(input, TypeDefinitionCheckingResult.SearchType.HaveDependencyOnAny, dependencies, true);
2125
}
2226

2327
/// <summary>
2428
/// Finds types that have a dependency on every item in the given list of dependencies.
25-
/// </summary>
26-
/// <param name="input">The set of type definitions to search.</param>
27-
/// <param name="dependencies">The set of dependencies to look for.</param>
28-
/// <returns>A list of found types.</returns>
29+
/// </summary>
2930
public IEnumerable<TypeSpec> FindTypesThatHaveDependencyOnAll(IEnumerable<TypeSpec> input, IEnumerable<string> dependencies)
3031
{
3132
return FindTypes(input, TypeDefinitionCheckingResult.SearchType.HaveDependencyOnAll, dependencies, true);
3233
}
3334

3435
/// <summary>
3536
/// Finds types that may have a dependency on any item in the given list of dependencies, but cannot have a dependency that is not in the list.
36-
/// </summary>
37-
/// <param name="input">The set of type definitions to search.</param>
38-
/// <param name="dependencies">The set of dependencies to look for.</param>
39-
/// <returns>A list of found types.</returns>
40-
public IEnumerable<TypeSpec> FindTypesThatOnlyHaveDependenciesOnAnyOrNone(IEnumerable<TypeSpec> input, IEnumerable<string> dependencies)
37+
/// </summary>
38+
public IEnumerable<TypeSpec> FindTypesThatOnlyHaveDependencyOnAnyOrNone(IEnumerable<TypeSpec> input, IEnumerable<string> dependencies)
4139
{
4240
return FindTypes(input, TypeDefinitionCheckingResult.SearchType.OnlyHaveDependenciesOnAnyOrNone, dependencies, false);
4341
}
4442

4543
/// <summary>
4644
/// Finds types that have a dependency on any item in the given list of dependencies, but cannot have a dependency that is not in the list.
47-
/// </summary>
48-
/// <param name="input">The set of type definitions to search.</param>
49-
/// <param name="dependencies">The set of dependencies to look for.</param>
50-
/// <returns>A list of found types.</returns>
51-
public IEnumerable<TypeSpec> FindTypesThatOnlyHaveDependenciesOnAny(IEnumerable<TypeSpec> input, IEnumerable<string> dependencies)
45+
/// </summary>
46+
public IEnumerable<TypeSpec> FindTypesThatOnlyHaveDependencyOnAny(IEnumerable<TypeSpec> input, IEnumerable<string> dependencies)
5247
{
5348
return FindTypes(input, TypeDefinitionCheckingResult.SearchType.OnlyHaveDependenciesOnAny, dependencies, false);
5449
}
5550

5651
/// <summary>
5752
/// Finds types that have a dependency on every item in the given list of dependencies, but cannot have a dependency that is not in the list.
5853
/// </summary>
59-
/// <param name="input">The set of type definitions to search.</param>
60-
/// <param name="dependencies">The set of dependencies to look for.</param>
61-
/// <returns>A list of found types.</returns>
62-
public IEnumerable<TypeSpec> FindTypesThatOnlyOnlyHaveDependenciesOnAll(IEnumerable<TypeSpec> input, IEnumerable<string> dependencies)
54+
public IEnumerable<TypeSpec> FindTypesThatOnlyOnlyHaveDependencyOnAll(IEnumerable<TypeSpec> input, IEnumerable<string> dependencies)
6355
{
6456
return FindTypes(input, TypeDefinitionCheckingResult.SearchType.OnlyHaveDependenciesOnAll, dependencies, false);
6557
}
6658

6759
private IEnumerable<TypeSpec> FindTypes(IEnumerable<TypeSpec> input, TypeDefinitionCheckingResult.SearchType searchType, IEnumerable<string> dependencies, bool serachForDependencyInFieldConstant)
68-
{
69-
var output = new List<TypeSpec>();
70-
var searchTree = new CachedNamespaceTree(dependencies);
60+
{
61+
var searchTree = new CachedNamespaceTree(dependencies);
7162

7263
foreach (var type in input)
7364
{
74-
var context = new TypeDefinitionCheckingContext(type, searchType, searchTree, serachForDependencyInFieldConstant);
75-
if (context.IsTypeFound())
76-
{
77-
output.Add(type);
78-
}
65+
var context = new TypeDefinitionCheckingContext(type, searchType, searchTree, serachForDependencyInFieldConstant, explainYourself);
66+
type.IsPassing = context.IsTypeFound();
7967
}
8068

81-
return output;
69+
return input;
8270
}
8371
}
8472
}

src/NetArchTest.Rules/Dependencies/TypeDefinitionCheckingContext.cs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,26 @@ namespace NetArchTest.Dependencies
66
{
77
internal class TypeDefinitionCheckingContext
88
{
9-
private readonly TypeDefinition _typeToCheck;
9+
private readonly TypeSpec _typeToCheck;
1010
private readonly TypeDefinitionCheckingResult _result;
1111
private readonly bool _serachForDependencyInFieldConstant;
12+
private readonly bool explainYourself;
1213

13-
public TypeDefinitionCheckingContext(TypeSpec typeToCheck, TypeDefinitionCheckingResult.SearchType searchType, ISearchTree searchTree, bool serachForDependencyInFieldConstant = false)
14+
public TypeDefinitionCheckingContext(TypeSpec typeToCheck, TypeDefinitionCheckingResult.SearchType searchType, ISearchTree searchTree, bool serachForDependencyInFieldConstant = false, bool explainYourself = false)
1415
{
15-
_typeToCheck = typeToCheck.Definition;
16+
_typeToCheck = typeToCheck;
1617
_result = new TypeDefinitionCheckingResult(searchType, searchTree);
1718
_serachForDependencyInFieldConstant = serachForDependencyInFieldConstant;
19+
this.explainYourself = explainYourself;
1820
}
1921

2022
public bool IsTypeFound()
2123
{
22-
CheckType(_typeToCheck);
24+
CheckType(_typeToCheck.Definition);
25+
if (explainYourself)
26+
{
27+
_typeToCheck.Explanation = _result.ExplainWhy();
28+
}
2329
return _result.IsTypeFound();
2430
}
2531

@@ -224,13 +230,13 @@ private void CheckMethodBodyInstructions(MethodDefinition methodToCheck)
224230
}
225231
break;
226232
case FieldReference fieldReference:
227-
if (fieldReference.DeclaringType != _typeToCheck)
233+
if (fieldReference.DeclaringType != _typeToCheck.Definition)
228234
{
229235
CheckTypeReference(fieldReference.DeclaringType);
230236
}
231237
break;
232238
case MethodReference methodReference:
233-
if (methodReference.DeclaringType != _typeToCheck)
239+
if (methodReference.DeclaringType != _typeToCheck.Definition)
234240
{
235241
CheckTypeReference(methodReference.DeclaringType);
236242
}
@@ -276,7 +282,7 @@ private void CheckTypeReference(TypeReference reference)
276282
}
277283
private void CheckDependency(TypeReference dependency)
278284
{
279-
_result.CheckDependency(dependency);
285+
_result.CheckDependency(dependency);
280286
}
281287
}
282288
}

src/NetArchTest.Rules/Dependencies/TypeDefinitionCheckingResult.cs

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@
77

88
namespace NetArchTest.Dependencies
99
{
10-
/// <summary>
11-
/// Manages the results of dependency search.
12-
/// </summary>
1310
internal class TypeDefinitionCheckingResult
1411
{
1512
public enum SearchType
@@ -26,7 +23,9 @@ public enum SearchType
2623
/// <summary> The list of dependencies that have been found in the search.</summary>
2724
private HashSet<string> _foundDependencies = new HashSet<string>();
2825
private bool _hasDependencyFromOutsideOfSearchTree;
29-
26+
27+
TypeReference lastFoundDependency = null;
28+
TypeReference lastFoundDependencyOutsideOfSearchTree = null;
3029

3130
public TypeDefinitionCheckingResult(SearchType searchType, ISearchTree searchTree)
3231
{
@@ -54,6 +53,42 @@ public bool IsTypeFound()
5453
throw new NotImplementedException();
5554
}
5655
}
56+
public string ExplainWhy()
57+
{
58+
var isTypeFound = IsTypeFound();
59+
switch (_searchType)
60+
{
61+
case SearchType.HaveDependencyOnAll:
62+
if (isTypeFound)
63+
{
64+
return "Has dependency on all provided inputs";
65+
}
66+
else
67+
{
68+
return string.Empty;
69+
}
70+
case SearchType.HaveDependencyOnAny:
71+
if (isTypeFound)
72+
{
73+
return $"Has dependency on: {lastFoundDependency}";
74+
}
75+
else
76+
{
77+
return "Does not have a dependency on any provided inputs";
78+
}
79+
case SearchType.OnlyHaveDependenciesOnAnyOrNone:
80+
if (isTypeFound)
81+
{
82+
return "Does not have a dependency outside of provided inputs";
83+
}
84+
else
85+
{
86+
return $"Has dependency on: {lastFoundDependencyOutsideOfSearchTree}";
87+
}
88+
break;
89+
}
90+
throw new NotImplementedException();
91+
}
5792

5893
/// <summary>
5994
/// If we already know the final answer to the question if type was found,
@@ -96,6 +131,7 @@ public void CheckDependency(TypeReference dependency)
96131
{
97132
_foundDependencies.Add(match);
98133
}
134+
lastFoundDependency = dependency;
99135
}
100136
else
101137
{
@@ -109,6 +145,7 @@ public void CheckDependency(TypeReference dependency)
109145
if (!isGlobalAnonymousCompilerGeneratedType)
110146
{
111147
_hasDependencyFromOutsideOfSearchTree = true;
148+
lastFoundDependencyOutsideOfSearchTree = dependency;
112149
}
113150
}
114151
}

0 commit comments

Comments
 (0)