BenMorris / NetArchTest

A fluent API for .Net that can enforce architectural rules in unit tests.
MIT License
1.39k stars 82 forks source link

new conditions and predicates: OnlyHaveDependenciesOn and opposite HaveDependencyOtherThan #72

Closed NeVeSpl closed 4 years ago

NeVeSpl commented 4 years ago

This is a game-changer and the true reason why #62 was done. Instead of writing what is not allowed, we can write what is allowed. The latter one is usually much shorter than the former one. Also this approach is more intuitive and makes our dependencies explicit.

So instead of

 var result = Types.InAssembly(TestCreationAssembly)
                              .That()
                              .ResideInNamespace("TestMe.TestCreation.Domain")
                              .ShouldNot() 
                              .HaveDependencyOnAny( 
                                "TestMe.TestCreation.Persistence",
                                "TestMe.TestCreation.App",
                                "TestMe.TestCreation.Infrastructure",
                                "TestMe.BuildingBlocks.App",
                                "TestMe.BuildingBlocks.EventBus",
                                "TestMe.BuildingBlocks.Persistence",
                                "TestMe.UserManagement.IntegrationEvents",
                                "Microsoft.EntityFrameworkCore"
                              ) 
                              .GetResult();

we can now write

var result = Types.InAssembly(TestCreationAssembly)
                              .That()
                              .ResideInNamespace("TestMe.TestCreation.Domain")
                              .ShouldNot()                              
                              .HaveDependencyOtherThan(
                                "System",
                                "TestMe.TestCreation.Domain", 
                                "TestMe.SharedKernel.Domain", 
                                "TestMe.BuildingBlocks.Domain")                              
                              .GetResult();

I have a hard time to come by with proper name for fluent api, I considered also:

thus I am open to any sugestion about how to name this feature, for reminder we already have :

BenMorris commented 4 years ago

I would use OnlyHaveDependenciesOn(). It reverses the logic but scans better as a fluent sentence, i.e. "The types should only have dependencies on" reads better than "the types should not have dependency other than"

I would also make it both a condition and a predicate, i.e.

// Predicate
Types.InCurrentDomain().That().OnlyHaveDependenciesOn("Class1", "Class2").Should().BePublic();

// Conditions
Types.InCurrentDomain().Should().OnlyHaveDependenciesOn("Class1", "Class2");
Types.InCurrentDomain().ShouldNot().OnlyHaveDependenciesOn("Class1", "Class2");

Do we also want to consider any\all logic here, i.e. would the following methods be useful:

Types.InCurrentDomain().Should().OnlyHaveDependenciesOnAny("Class1", "Class2");
Types.InCurrentDomain().Should().OnlyHaveDependenciesOnAll("Class1", "Class2");
NeVeSpl commented 4 years ago

I would say that OnlyHaveDependenciesOn should yield a little bit different result set than HaveDependencyOtherThan

After some thought, OnlyHaveDependenciesOn can be used as a shorter name for 3, with some documentation should be clear what is the difference between 3-5.

Dependency matrix, where DR stands for dozen of other dependencies. class\dependency D1 D2 DR
a
b x
c x
d x x
e x
f x x
g x x
h x x x
number
of present
dependencies
from the list
class can have
a dependency
that is not
on the list
result set negation
1 HaveDependencyOnAny(D1, D2) at least 1 yes c, d, e, f, g, h, a, b
2 HaveDependencyOnAll(D1, D2) all yes g, h a, b, c, d, e, f
3 OnlyHaveDependenciesOnAnyOrNone(D1, D2) >=0 no a, c, e, g b, d, f, h
4 OnlyHaveDependenciesOnAny(D1, D2) at least 1 no c, e, g a, b, d, f, h
5 OnlyHaveDependenciesOnAll(D1, D2) all no g a, b, c, d, e, f, h
6 HaveDependencyOtherThan(D1, D2) at least 1 yes b, d, f, h, a, c, e, g
7 HaveDependencyOnAny(DR) at least 1 yes b, d, f, h a, c, e , g
NeVeSpl commented 4 years ago

Since OnlyHaveDependenciesOn is the opposite of HaveDependencyOtherThan, they can both exist in fluent API. Finally, it looks like that:

Types.InCurrentDomain().Should().OnlyHaveDependenciesOn("Class1", "Class2");
Types.InCurrentDomain().ShouldNot().HaveDependencyOtherThan("Class1", "Class2");
Types.InCurrentDomain().ShouldNot().OnlyHaveDependenciesOn("Class1", "Class2");
Types.InCurrentDomain().Should().HaveDependencyOtherThan("Class1", "Class2");

OnlyHaveDependenciesOnAny and OnlyHaveDependenciesOnAll are implemented in dependency search, but they are not exposed in API yet. It can be done later when somebody has any needs of them.

BenMorris commented 4 years ago

This makes sense - the one tweak I would consider is renaming "HaveDependencyOtherThan" to "HaveDependenciesOtherThan".

IMHO it scans better in terms of fluent grammar. It is also correct to imply that the methods checks for "one or more" dependencies that are not in the supplied list.

I can make this change in the prep work for the next release - I am planning to put one out this week.