Alexander Tarnowski 1
Swenug 2014-04-23
Testablity for developers
2
Why testability and testing for developers?
Unit testingMaintenanceContinuous integrationAutomated acceptance testing
Alexander Tarnowski
3
But…
Alexander Tarnowski
Ongoing project
http://leanpub.com/developer_testing
alexander_tar
blog.crisp.se/author/alexandertarnowskiBlog
www
www.techbookreader.comBlog agilamyter.com
5Alexander Tarnowski
Testability costs moneyM
isco
ncep
tion
6Alexander Tarnowski
RequirementsSpecificati
onArchitectural design
Detaileddesign
Coding
Unit testing
Integration
testing
Systemtesting
Acceptance
testing
Mis
conc
epti
onTesting is a criticizing activity
that can build quality in
7Alexander Tarnowski
Automated Tools
ManualAutomated& manual
Q1Q2 Q3
Q4
8
Defining testability
Alexander Tarnowski
The Black Box
If this were our system…
9
Defining testabilityDefinition 1: Testability = decoupling
Origin: The TDD community
Alexander Tarnowski
10
Defining testabilityDefinition 2: Testability = Controllability Observability
Origin: The testing community
Alexander Tarnowski
11
Defining testabilityDefinition 3: Testability = ControllabilityObservabilityOrthogonalityDeployability
Origin: the ”Developer Testing” book
Alexander Tarnowski
Alexander Tarnowski 12
Know the driving forces behind testabilityThey are:ControllabilityObservabilityOrthogonalityDeployability
Make concious decisions to achieve them
Programmer wisdom
13
Fundamental testing techniques
Alexander Tarnowski
14
Why developers should know about fundamental testing
techniquesPass tester testsIncorporate them in their own tests
Alexander TarnowskiImage: Stuart Miles/FreeDigitalPhotos.net
Alexander Tarnowski 15
Equivalence partitioning
0-17 18-23 24-60 60-100 101+
0
0.2
0.4
0.6
0.8
11.2
1.4
1.6
1.8
WomenMen
Insurance premiums
Alexander Tarnowski 16
Typical equivalence classes
0-10, 11-23, 24-100
Ranges
0, 3, 6, 9, 12, 15
Defined by functions
e.g. f(x) = x mod 3
Sharing a propertye.g. red and blue cars Induction
proofs
Alexander Tarnowski 17
Partition data into equivalence classes
Cover each partition with at least one test case
Programmer wisdom
18
Boundary values
Alexander Tarnowski
Ages 24-60
22 23 24 25 26 58 59 60 61 62
19
Common edge cases
Alexander Tarnowski
Numbers0, -1
Integer.MAX_VALUE/int.MaxValuen-1, n+1, m-1, m+1
Strings”” – empty string
nullMax input length
DatesPrecision
Days per monthDifferent locales
Leap years
Collections{} – empty collection
nullIndexing → iteration
Alexander Tarnowski 20
Boundary values and equivalence partitions drive the number of tests
Remember to check boundary valuesRemember to check common edge
cases
Programmer wisdom
Alexander Tarnowski 21
Use parameterized tests to capture boundary values
[TestCase(18, Gender.Male, Result = 1.75)][TestCase(23, Gender.Male, Result = 1.75)][TestCase(24, Gender.Male, Result = 1.0)]// ...public double VerifyPremiumFactor(int age, Gender gender){ return new PremiumRuleEngine().GetPremiumFactor(age, gender);}
Programmer wisdom
22
State transition testing
Alexander Tarnowski
Start
Reset
Press min+sec
Setting timePress min
Press min
Press sec
Counting down
Press min+sec
Press start/stop
Press start/stop
Press min+sec
Press min+sec
Counting up
Press start/stop
Press min+sec
Alexander Tarnowski 23
Use for clarifying functionality and finding missing or incorrect transitions
Basis for model-based testing
Programmer wisdom
24
Decision tables
Alexander Tarnowski
Age 18-23 18-23 24-59 24-59 60+ 60+
Gender Male Female
Male Female Male Female
Premium factor 1.0 N N N Y N N
Premium factor 1.05 N N Y N N N
Premium factor 1.25 N N N N N Y
Premium factor 1.35 N N N N Y N
Premium factor 1.65 N Y N N N N
Premium factor 1.75 Y N N N N N
Condition
Condition alternative
Action Action entry
25
Decision tables
Alexander Tarnowski
Age 18-23 18-23 24-59 24-59 60+ 60+
Gender Male Female
Male Female Male Female
Premium factor 1.75 1.65 1.05 1 1.35 1.25
Alexander Tarnowski 26
Decision tables translate directly into parameterized/data-driven tests!
Programmer wisdom
27
Anti-testability constructs
Alexander Tarnowski
28
Temporal coupling & state
Alexander Tarnowski
var car = new Car();10 LOCcar.Initialize();while(itsRaining) { 50 LOC}if (sky.StarsProperlyAligned()) { car.SetGear(1);}30 LOCcar.StartEngine();car.ReleaseClutch();
Alexander Tarnowski 29
Avoid partially initialized objectsKeep temporal coupling in mind
when designing messages and protocols
Limit state by keeping program elements small and short-lived
Programmer wisdom
30
Indirect inputDirect input (e.g. pure functions):int DoubleValue(int i) { return i * 2;}
Alexander Tarnowski
DateTime.Now.Minute;new Calculator.Add(10, 10);using (StreamReader sr = new StreamReader(“Magic.txt")){ int.Parse(sr.ReadToEnd()); }
Indirect inputint DoMagic(int i) { return i +}
Alexander Tarnowski 31
Indirect input is natural and we shouldn’t fear it
To cope with indirect input we need control points and stubs
Programmer wisdom
32
Indirect output
Alexander Tarnowski
Direct output (i.e. observable through the public API):int DoubleValue(int i) { return i * 2;}
Indirect outputbool DoMagic(int i) { <insert code here> return true;}
Console.Out.WriteLine(i * 42);RemoteEndpoint.Invoke(42, ”magic”, i);EventLog.WriteEntry(”MagicApp”, ”value:” + i);
Alexander Tarnowski 33
Indirect output is natural and we shouldn’t fear it
To cope with indirect output we need observation points and mock objects
Programmer wisdom
34
Domain-to-range ratio
Alexander Tarnowski
Input values(domain, D)
Output values
(range, R)
Correct computation
Faulty computation
DRR = |D| / |R|
f(0) 0
f(1-9) 1
f(11-99) 2
f(100-999) 3
f(1000-9999) 4
….
35
Compressionbool IsValidAge(int age)
Alexander Tarnowski
82
false
~4*109
true
Alexander Tarnowski 36
Use high DRR as a warning flagBe aware of information lossAvoid using too large data typesIntroduce abstractions (OO/DDD)
Programmer wisdom
37
Dependencies
Alexander Tarnowski
38
Relations between objects
Alexander Tarnowski
public class ListWrapper{ private IList<int> wrapped; public int WrappedListSize { get { return wrapped.Count; } } public ListWrapper() { wrapped = new List<int> { 1, 2, 3 }; }} Indirect input
Legacy
code 101
39
Pass in the dependency
Alexander Tarnowski
public class ListWrapper{ private IList<int> wrapped; public int WrappedListSize { get { return wrapped.Count; } } public ListWrapper(IList<int> data) { wrapped = data; }}
40
Use factory method
Alexander Tarnowski
public class ListWrapper{ private IList<int> wrapped; public int WrappedListSize { get { return wrapped.Count; } } public ListWrapper() { wrapped = CreateWrappedList(); } protected virtual IList<int> CreateWrappedList() { return new List<int> { 1, 2, 3 }; }}
41
Provide external factory or builder
Alexander Tarnowski
public class ListWrapper{ private IList<int> wrapped; public int WrappedListSize { get { return wrapped.Count; } } public ListWrapper(IntegerListBuilder builder) { wrapped = builder.Build(); } }
42Alexander Tarnowski
public class IntegerListBuilder{ private int startingAt = 1; private int endingWith = 10; public IntegerListBuilder StartingAt(int startingAt) { this.startingAt = startingAt; return this; } public IntegerListBuilder EndingWith(int endingWith) { this.endingWith = endingWith; return this; } public IList<int> Build() { return Enumerable.Range(startingAt, endingWith).ToList(); }}
Alexander Tarnowski 43
Making code testable is about making dependencies explicit
Generic strategies for doing so:Passing them in Using factory methodsUsing factories or builders
Programmer wisdom
44
System resource dependencies
FilesSystem clockSockets, etc
Alexander Tarnowski
45
SolutionsSolution #1: Your own abstractionpublic interface ITimeSource{ DateTime Now { get; }}
Alexander Tarnowski
Solution #2: Fakesusing (ShimsContext.Create()) { System.Fakes.ShimDateTime.NowGet = () => { return new DateTime(fixedYear, 1, 1); };
Alexander Tarnowski 46
There isn’t a single problem in computer science that cannot be solved by adding another layer of indirection. This is certainly true of system resoruce dependencies.
Use Fakes if there’s no other way. But first try without.
Programmer wisdom
47
Dependencies between layers
Alexander Tarnowski
PresentationBusiness
Data access
Theoretical dependenciesActual
dependencies
Alexander Tarnowski 48
Clean up messy cross-layer dependencies
Use Dependency Inversion; depend on abstractions
Use Dependency Injection
Programmer wisdom
49
Duplication
Alexander Tarnowski
?
50
Copy and pastepublic class CustomerRepository{ public void Create(Customer customer) { if (customer.Gender == Gender.Unknown || !customer.YearOfBirth.HasValue) { throw new ArgumentException("Incomplete customer"); } … }
public void Update(Customer customer) { if (customer.Gender == Gender.Unknown || !customer.YearOfBirth.HasValue) { throw new ArgumentException("Incomplete customer"); } … }
Alexander Tarnowski
51
Block copy and paste
Alexander Tarnowski
public class CustomerRepository{ public void Create(Customer customer) { if (customer.Gender == Gender.Unknown || !customer.YearOfBirth.HasValue) { throw new ArgumentException("Incomplete customer"); }
if (DateTime.Now.Year - customer.YearOfBirth.Value < 18) { throw new ArgumentOutOfRangeException("Underage customer"); } … }
public void Update(Customer customer) { if (customer.Gender == Gender.Unknown || !customer.YearOfBirth.HasValue) { throw new ArgumentException("Incomplete customer"); }
if (DateTime.Now.Year - customer.YearOfBirth.Value < 18) { throw new ArgumentOutOfRangeException("Underage customer"); } … }
52
Constructor copy and paste
public NetworkInterface(IPAddress ipAddress, NetMask netMask, IPAddress broadcast, IPAddress defaultRoute){ this.ipAddress = ipAddress; this.netMask = netMask; this.broadcast = broadcast; this.defaultRoute = defaultRoute;}
public NetworkInterface(IPAddress ipV6Address, NetMaskIpV6 ipV6NetMask, IPAddress ipV6DefaultRoute){ this.ipV6Address = ipV6Address; this.ipV6NetMask = ipV6NetMask; this.ipV6DefaultRoute = ipV6DefaultRoute;}
public NetworkInterface(IPAddress ipAddress, NetMask netMask, IPAddress broadcast, IPAddress defaultRoute,IPAddress ipV6Address, NetMaskIpV6 ipV6NetMask, IPAddress ipV6DefaultRoute)
{ this.ipAddress = ipAddress; this.netMask = netMask; this.broadcast = broadcast; this.defaultRoute = defaultRoute; this.ipV6Address = ipV6Address; this.ipV6NetMask = ipV6NetMask; this.ipV6DefaultRoute = ipV6DefaultRoute;}
Alexander Tarnowski
53
The same method in different contexts
Alexander Tarnowski
x 8
public static long DiffTime(DateTime t1, DateTime t2){ if (t1.Date != t2.Date) { throw new ArgumentException("Incomparable dates"); } return (t2.Hour - t1.Hour) * 60;}
54
Divergent duplicated methods in different contexts
public static long DiffTime(DateTime t1, DateTime t2){ if (t1.Date != t2.Date) { throw new ArgumentException("Incomparable dates"); } return (t2.Hour * 60 + t2.Minute) - (t1.Hour * 60 + t1.Minute);}
Alexander Tarnowski
in 7/8 places…
Alexander Tarnowski 55
Code that’s been mechanically copied and pasted increases the size of the code base and is annoying…
… but the true danger lies in convergence
Programmer wisdom
56
Different methods or classes doing similar things
Ignorance FearLaziness ChoiceConflict
Alexander Tarnowski
57
Different ways of doing similar things
Alexander Tarnowski
Module Alpha uses… Module Bravo uses…
Logging framework Apple Logging framework Banana
Hand-written SQL An O/R mapper
A date/time library Time computations implemented ”by hand”
Client-side validation Server-side validation
58
Competing domain models
Alexander TarnowskiImage: vectorolie,Danilo Rizzuti/FreeDigitalPhotos.net
Alexander Tarnowski 59
Understand that mental duplication makes testing, understanding, and sometimes even recruiting harder
Have a long-term strategy for dealing with mental duplication
Duplication makes things ”hard”. If it’s hard then…
Programmer wisdom
60
Test-driven development
Alexander Tarnowski
Refactor
Green
Red
61
Properties of test-driven code
Alexander Tarnowski
Code driven
by tests
Executable in
isolation
Small
Does one thing
Indirect creation
Pure function
s separeted from side
effects
• Drives design, not correctness• Negative tests are not used• There are two kinds of TDD
Classic TDD London school
TDD and testability
Alexander Tarnowski
Alexander Tarnowski 63
Supplement tests that drive design with tests that drive correctness!
Programmer wisdom
64
Summary
Alexander Tarnowski
Testability is a skill
Developers do supporting technical testing
The Black Box
Testability: Controllability Observability Orthogonality Deployability
Testing techniques
Anti-testability constructs Temporal coupling Indirect input Indirect output High DRR
Duplication: Mechanical Mental TDD drives design,
not correctness!
Dependencies: Pass in Factory methods Factories/Builders Fakes