3. Why certain code is really hard to test?
In order to test;
First, we need to instantiate an OBJECT, Duh!
Seems obvious;
But we put lot of code in the constructor.
4. public class Document
{
private readonly string html;
public Document(string url)
{
HtmlClient client = new HtmlClient();
html = client.Get(url);
}
}
How would you test this?
5. public class Document
{
private readonly string html;
public Document(HtmlClient client, string url)
{
html = client.Get(url);
} Document doesn’t care
}
about client
Passing but
not saving
locally
6. public class Document public class Printer
{ {
private readonly string html; public void Print(Document html)
public Document(string html) {
{ // do the printing work here.
this.html = html; }
} }
}
public class DocumentFactory
{
private readonly HtmlClient client;
public DocumentFactory(HtmlClient client)
{
this.client = client;
}
public Document Build(string url)
{
return new Document(client.Get(url));
}
}
7. Why certain code is really hard to test?
Testing is all about;
Instantiating small portion of application, poke in, and assert.
But, the fact is;
Hard to construct objects where we start skipping tests.
8. Service locator lies!
What it tries to solve?
a.k.a. Context, Repository, Locator, Manager, Environment…
Get rid of “new” keyword.
Inversion of Control.
Better than having a Singleton, coz singletons are hard to test.
But, this is where Service Locator lies;
Encourages ambiguous API.
Don’t know what to mock in tests.
Violates Law of Demeter.
9. public class House
{
public House(Locator locator)
{
// What needs to be mocked?
}
}
How do we mock this
Service Locator?
This is
where API
lies
10. public class House
{
public House(Door d, Roof r, Window w)
{
// What needs to be mocked?
}
}
We know what are needed
11. Service locator lies!
Mixing responsibilities;
Object lookup and Object creation (Factory).
Not only that;
Anything that depends on Service Locator now depends on
everything else.
That’s where it violates Law of Demeter.
12. Law of Demeter explained!
How do you pay for your Goods?
Give the money?
Or Give the wallet?
13. public class Goods
{
AccountsReceivable ar;
public void Purchase(Customer c)
{
Money m = c.GetWallet().GetMoney();
ar.RecordSale(this, m);
}
}
Law of Demeter: Violated
14. public class Goods
{
AccountsReceivable ar;
public void Purchase(Money m)
{
ar.RecordSale(this, m);
}
}
[TestClass] Law of Demeter: Obeyed
public class GoodsTest
{
[TestMethod]
public Purchase_The_Right_Way()
{
AccountsReceivable ar = new MockAR();
Goods g = new Goods(ar);
g.Purchase(new Money(24, LKR));
assertEquels(24, ar.GetSales());
}
}
15. Law of Demeter explained!
How do you pay for your Goods?
Give the money?
Give the wallet?
Law of Demeter says;
Ask only objects you directly need.
a.GetX().GetY()… is Writing Shy Code.. Transitive Navigation, a
Chaos.
serviceLocator.GetService() is breaking LOD.
DI is what saves you from all of these.
16. Myths about DI!
DI makes refactoring really hard!
If a child object needs a new parameter, then I have to pass it
through all of its parents.
But;
Parent is not making a Child, it only asks for Child.
So when child ask for a dependency, Parent is not aware.
17. public class HouseFactory
{
public House Build()
{
Color color = Color.Red;
DoorKnob knob = new DoorKnob(color);
Window window = new SmallWindow();
Door door = new Door(knob, window);
return new House(door);
}
}
House doesn’t even know
that Door has a DoorKnob
18. Myths about DI!
DI makes refactoring really hard!
If a child object needs a new parameter, then I have to pass it
through all of its parents.
But;
Parent is not making a Child, it only asks for Child.
So when child ask for a dependency, Parent is not aware.
So;
This makes easier to construct objects.
19. Few rules
When Constructing objects;
Only inject objects what are …
Lifetime is >= injectee
Rest of the objects …
Pass it via Methods
21. public class House
{
public House(Door door)
{
Preconditions.CheckNotNull(door);
this.door = door;
}
public void Paint(Color color)
{
// Paint the house, nothing to do with Doors.
}
}
Doesn’t allow me to pass
[TestMethod]
void House_Painted_In_Red()
Null
{
House house = new House(null);
house.Paint(Color.Red);
assertEquels(Color.Red, house.GetColor());
}
22. Few rules
Paranoid programming;
Null checking as preconditions.
What prefer is;
An application with tests that proves it works.
Rather than having preconditions.
You’re API with External calls, this might not applicable.
What you really saying is;
You don’t trust yourself.
23. Few rules
In production code;
I don’t want to pass in Null.
Pass me a Null Value Object rather.
Avoid null checks.
Make sure you get real objects which you can call methods.
But, in Tests;
The opposite of Production code.
24. In summary
We have Two Piles;
Objects – Business logic, this is where you write code, most bugs
are found.
New Keywords – Providers, Factories, Builders etc. responsibility
to construct the objects, DI.
Separating Two Piles;
Tests become small and easy to write.
When wiring is wrong; It often blows up.
System failures; Bug in a class or missing unit test.
Example 1: Document class explained!How would you test this?We can use a test server
Example 2: Mocked HtmlClient explained!Doesn’t care how instantiated, we just need it.We have to go thru same instantiate process when we create Document every time.Passing but not saving locally.Law of Demeter.
Example 3: SOLUTION – Get rid of HtmlClient explained!Easy to test coz easy to construct.What preferable.Separating construction from work.
Example 4: Testing House with Locator, what to override?Instead of passing a,b, & c, we now pass Service Locator.Suppose, House needs a “d”, somehow we pass it via Locator. So now some tests start failing, and we don’t know the reason.When we are using House, we need to give; House, Then Service Locator, And then rest of the application.
Example 5: Testing House with Door, Roof, and Window!Now everything is clear.We know what are needed for our test.
Example 6: LOD Violated – Purchase goods via Customer, Wallet, Money to record the sale.Not very interesting test, coz we have to Arrange Customer, Wallet, and put money etc.
Example 7: LOD Obeyed – Purchase goods by passing Money.Greatly simplifies my tests.
Example 8: Pass Color for DoorKnob doesn’t affect House.House doesn’t even know that Door has a DoorKnob.
Factory is responsible to construct objects, not parent.So now it’s easier, not harder.
Example 9: Preconditions.CheckNotNullMakes hard to instantiate objects
Abandon new operator (99%).Not responsible constructing. Done by DI.Don’t ask anything that you don’t need. If its in constructor, it is shared by many places.