Unit testing software can be difficult, especially when the software wasn't designed to be testable. Dependencies on infrastructure concerns and software we don't control are one of the biggest contributors to testing difficulty. In this session, you'll learn the difference between unit tests and other kinds of tests, how to recognize and invert dependencies, and how to unit test your code's interactions with these dependencies without testing the infrastructure itself.
2. Tweet Away
• LiveTweeting and Photos are encouraged
• Questions and Feedback are welcome
• Use #DevIntersection and/or #breakDependencies
• Or #DevIntersectionBreakingDependenciesToAllowUnitTesting (55 chars with space)
11. UnitTest Characteristics
• Test a single unit of code
• A method, or at most, a class
• Do not test Infrastructure concerns
• Database, filesystem, etc.
• Do not test “through” the UI
• Just code testing code; no screen readers, etc.
12. UnitTests are (should be) FAST
• No dependencies means
1000s of tests per second
• Run them constantly
13. UnitTests are SMALL
• Testing one thing should be simple
• If not, can it be made simpler?
• Should be quick to write
17. Ask yourself:
•Can I test this scenario with a UnitTest?
•Can I test it with an IntegrationTest?
• Can I test it with an automated UITest?
UI
Integration Tests
UnitTests
19. UnitTest?
• Requires a database or file?
• Sends emails?
• Must be executed through the UI?
Not a unit test
20. Dependencies and Coupling
All dependencies point toward
infrastructure
Presentation
Layer
Business Layer
Infrastructure
Data Access
Tests
Tests (and everything else) now
depend on Infrastructure
21. Dependency Inversion Principle
High-level modules should not depend on low-level modules. Both should
depend on abstractions.
Abstractions should not depend on details. Details should depend on
abstractions.
Agile Principles, Patterns, and Practices in C#
22. Depend on Abstractions
All dependencies point
toward Business Logic /
CorePresentation Layer
Business Layer
Infrastructure
DataAccess
UnitTests
IntegrationTestsUITests
23. Inject Dependencies
• Classes should follow Explicit Dependencies Principle
• http://deviq.com/explicit-dependencies-principle
• Prefer Constructor Injection
• Classes cannot be created in an invalid state
https://flic.kr/p/5QsGnB
24. Common Dependencies to Decouple
•Database
•File System
•Email
•Web APIs
•System Clock
•Configuration
•Thread.Sleep
•Random
25. Tight Couplers: Statics and new
• Avoid static cling
• Calling static methods with side effects
• Remember: new is glue
• Avoid gluing your code to a specific implementation
• Simple types and value objects usually OK
http://ardalis.com/new-is-glue
26. Coupling Code Smells
• Learn more in my Refactoring Fundamentals course on Pluralsight
• http://www.pluralsight.com/courses/refactoring-fundamentals
• Coupling Smells introduce tight coupling between parts of a system
27. Feature Envy
• Characterized by many getter calls
• Violates the “Tell, Don’tAsk” principle
• Instead, try to package data and behavior together
• Keep together things that change together
• Common Closure Principle – Classes that change together are packaged together
28.
29.
30. Law of Demeter
• Or the “StronglyWorded Suggestion of Demeter”
• A Method m on an object O should only call methods on
• O itself
• m’s parameters
• Objects created within m
• O’s direct fields and properties
• Global variables and static methods
36. Explicit Dependencies Principle
• Methods and classes should require their collaborators as parameters
• Objects should never exist in an invalid state
http://deviq.com/explicit-dependencies-principle/
37. Constructor Smells
• new keyword (or static calls) in constructor or field declaration
• Anything more than field assignment!
• Database access in constructor
• Complex object graph construction
• Conditionals or Loops
38. Good Constructors
• Do not create collaborators, but instead accept them as parameters
• Use a Factory for complex object graph creation
• Avoid instantiating fields at declaration
39. “
”
IoC Containers are just factories on
steroids.
Don’t be afraid to use them where they can help
40.
41.
42.
43.
44. Avoid Initialize Methods
• Moving code out of the constructor and into Init()
• If called from constructor, no different
• If called later, leaves object in invalid state until called
• Object has too many responsibilities
• If Initialize depends on infrastructure, object will still be hard to test
45.
46.
47. “Test” Constructors
• “It’s OK, I’ll provide an “extra” constructor my tests can use!”
• Great! As long as we don’t have to test any other classes that use the other
constructor.
48.
49. Avoid Digging into Collaborators
• Pass in the specific object(s) you need
• Avoid using “Context” or “Manager” objects to access additional
dependencies
• Violates Law of Demeter: Context.SomeItem.Foo()
• Suspicious Names: environment, principal, container
• Symptoms:Tests have mocks that return mocks
50.
51.
52. Avoid Global State Access
• Singletons
• Static fields or methods
• Static initializers
• Registries
• Service Locators
53. Singletons
• Avoid classes that implement their own lifetime tracking
• GoF Singleton Pattern
• It’s OK to have a container manage object lifetime and enforce having only a
single instance of a class within your application
58. Summary
• Inject Dependencies
• Remember “New is glue”.
• Keep yourAPIs honest
• Remember the Explicit Dependencies Principle.Tell your friends.
• Maintain seams and keep coupling loose
59. Thanks!
• Questions?
• Follow me at @ardalis
• Check out http://DevIQ.com for more on these topics
• Take Pride inYour Code!
• References
• http://misko.hevery.com/code-reviewers-guide/
• Working Effectively with Legacy Code
by Michael Feathers
Please use Event Board
to fill out a session evaluation.
Editor's Notes
https://twitter.com/hashtag/devintersection
https://twitter.com/hashtag/breakdependencies
Set low expectations
And specifically, we want fast, automated tests.
Developers like to build things, but often the way they connect parts of an application together (if there even are separate parts) is like using super glue. Good luck testing the interface of one building block.
If you haven’t seen it yet, you should. This guy wants to use krazy glue to keep anything from changing. Probably not how we want our software to be designed.
I want to answer these questions in this session.
1.
2.
3.
…unless the system is difficult to break apart
Avoid names that provide no information:
CustomerTests
Test1
Test2
Test3
PC (seams) vs. MAC (no seams)
PC – Power supply goes bad. Solution: Order new power supply, open case, replace.
MAC – Power supply goes bad. Solution: Buy a new Mac.
Because they’re small and fast, you should have a lot of unit tests. They should be the foundation of your testing approach.
Integration tests can include tests of services that sit between different layers of your application.
Defense in Depth
These aren’t the only kinds of tests
Test at the lowest level you can, since that will be the least expensive test to write, run, and maintain.
ReSharper calls any test you run a unit test, regardless of what kind of test it actually is.
Also, they shouldn’t be terribly complex, slow, or involve more than one or two classes.
Dependencies are transitive. If A depends on B and B depends on C, then A depends on C.
When I talk about breaking dependencies, I’m also talking about reducing coupling. Loosely coupled code can be tested more easily, since there are fewer connections to have to break apart, and they’re easier to swap out.
This can be difficult to test, especially if either of the two classes in question are difficult to set up in a particular state.
To fix this, move the behavior where it belongs.
This can of course be refactored further to eliminate the conditional complexity, using inheritance or a compositional pattern.
“Well, the code is more like guidelines than rules.”
Assume as little as possible about collaborators’ collaborators
Law of Demeter is not just about having two many dots in an expression.
Imagine when you order a pizza, the delivery person uses code like this to get paid. Does this seem to model how things actually work? Should the delivery person have access to your wallet and its contents? What if you don’t have a wallet? This code is now tightly coupled not only to a Customer, but to a Wallet and its implementation.
What if instead the Customer could respond to requests for payment, like this? Note that wallet is no longer exposed outside of the Customer class.
At this point, the original code makes a bit more sense. Payment is requested, and how it is achieved is no longer a concern of the calling code. Payment can come from a money clip, purse, or from under a mattress and this code will still work.
Testing this code could use a simplified fake customer and wouldn’t require implementation of both a fake customer and a fake wallet, making test code simpler.
Reduce coupling – keep your classes from having to know too much about the details of their collaborators.
Drink water
Constructors should be extremely simple. They shouldn’t choose the object’s collaborators, or how the object is retrieved from persistence. These things violate the Single Responsibility Principle for the constructor’s class, and tightly couple the class to certain implementation details. Constructors that accept collaborators provide seams – hard-coding collaborators erases such seams.
New is probably OK for initializing simple things like strings, datetimes, or collections to avoid nulls. Prefer the new C# 6 syntax for this with properties.
Note, it’s fine to create simple types and value objects – simply consider whether it’s making the object harder to test or not when deciding.
This code is difficult to test and tightly coupled to a dbContext, which in this case probably makes it impossible to test this code without a database.
Note that in this case the only thing the constructor does is assign parameters to fields, and no fields are instantiated as part of their declaration.
Likewise, avoid making static lookup calls or assignments within a constructor, especially ones that depend on infrastructure or the local environment.
Avoid depending on configuration directly – remember it’s a dependency.
The solution is the same – avoid creating things in the constructor in favor of passing these things in. If you can’t pass in the thing itself, pass in a means of getting to it (like we’re doing with IConfig in this case), but keep it simple as possible.
Dependency injection again saves the day. The initialization logic is a separate responsibility from whatever the service does, and so belongs in another class.
Adds coupling in an invisible, hard to track manner.
Makes testing difficult, especially parallel testing or testing in any order
Causes Spooky Action at a Distance – your method causes some unexpected behavior in some other part of the system