Presentation given for the SweNug user group. Based on the contents of my book "Developer Testing". Covers a variety of test related stuff: defining testability, test techniques, anti-testability constructs, duplication, test-driven development.
11. Defining testability
Definition 3: Testability =
Controllability
Observability
Orthogonality
Deployability
Origin: the ”Developer Testing” book
Alexander Tarnowski 11
12. Know the driving forces behind
testability
They are:
Controllability
Observability
Orthogonality
Deployability
Make concious decisions to achieve
them
Alexander Tarnowski 12
Programmer wisdom
14. Why developers should know
about fundamental testing
techniques
Pass tester tests
Incorporate them in their own tests
Alexander Tarnowski 14
Image: Stuart Miles/FreeDigitalPhotos.net
16. Typical equivalence
classes
Alexander Tarnowski 16
0-10, 11-23, 24-100
Range
s
0, 3, 6, 9, 12, 15
Defined by
functions
e.g. f(x) = x
mod 3
Sharing a property
e.g. red and blue cars Induction
proofs
17. Partition data into equivalence
classes
Cover each partition with at least
one test case
Alexander Tarnowski 17
Programmer wisdom
19. Common edge cases
Alexander Tarnowski 19
Numbers
0, -1
Integer.MAX_VALUE/int.MaxValue
n-1, n+1, m-1, m+1
Strings
”” – empty string
null
Max input length
Dates
Precision
Days per month
Different locales
Leap years
Collections
{} – empty collection
null
Indexing → iteration
20. Boundary values and equivalence
partitions drive the number of
tests
Remember to check boundary
values
Remember to check common edge
cases
Alexander Tarnowski 20
Programmer wisdom
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);
}
Alexander Tarnowski 21
Programmer wisdom
22. State transition testing
Alexander Tarnowski 22
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
23. Use for clarifying functionality and
finding missing or incorrect
transitions
Basis for model-based testing
Alexander Tarnowski 23
Programmer wisdom
24. Decision tables
Alexander Tarnowski 24
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
Conditio
n
Condition
alternative
Action Action entry
25. Decision tables
Alexander Tarnowski 25
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
26. Decision tables translate directly
into parameterized/data-driven
tests!
Alexander Tarnowski 26
Programmer wisdom
28. Temporal coupling & state
Alexander Tarnowski 28
var car = new Car();
10 LOC
car.Initialize();
while(itsRaining)
{
50 LOC
}
if (sky.StarsProperlyAligned())
{
car.SetGear(1);
}
30 LOC
car.StartEngine();
car.ReleaseClutch();
29. Avoid partially initialized objects
Keep temporal coupling in mind
when designing messages and
protocols
Limit state by keeping program
elements small and short-lived
Alexander Tarnowski 29
Programmer wisdom
30. Indirect input
Direct input (e.g. pure functions):
int DoubleValue(int i)
{
return i * 2;
}
Alexander Tarnowski 30
DateTime.Now.Minute;
new Calculator.Add(10, 10);
using (StreamReader sr = new StreamReader(“Magic.txt"))
{
int.Parse(sr.ReadToEnd());
}
Indirect input
int DoMagic(int i)
{
return i +
}
31. Indirect input is natural and we
shouldn’t fear it
To cope with indirect input we need
control points and stubs
Alexander Tarnowski 31
Programmer wisdom
32. Indirect output
Alexander Tarnowski 32
Direct output (i.e. observable through the
public API):
int DoubleValue(int i)
{
return i * 2;
}
Indirect output
bool DoMagic(int i)
{
<insert code here>
return true;
}
Console.Out.WriteLine(i * 42);
RemoteEndpoint.Invoke(42, ”magic”, i);
EventLog.WriteEntry(”MagicApp”, ”value:” + i);
33. Indirect output is natural and we
shouldn’t fear it
To cope with indirect output we
need observation points and mock
objects
Alexander Tarnowski 33
Programmer wisdom
36. Use high DRR as a warning flag
Be aware of information loss
Avoid using too large data types
Introduce abstractions (OO/DDD)
Alexander Tarnowski 36
Programmer wisdom
38. Relations between objects
Alexander Tarnowski 38
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
39. Pass in the dependency
Alexander Tarnowski 39
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 40
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 41
public class ListWrapper
{
private IList<int> wrapped;
public int WrappedListSize
{
get { return wrapped.Count; }
}
public ListWrapper(IntegerListBuilder builder)
{
wrapped = builder.Build();
}
}
42. Alexander Tarnowski 42
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();
}
}
43. Making code testable is about
making dependencies explicit
Generic strategies for doing so:
Passing them in
Using factory methods
Using factories or builders
Alexander Tarnowski 43
Programmer wisdom
45. Solutions
Solution #1:
Your own abstraction
public interface ITimeSource
{
DateTime Now { get; }
}
Alexander Tarnowski 45
Solution #2:
Fakes
using (ShimsContext.Create()) {
System.Fakes.ShimDateTime.NowGet =
() => { return new DateTime(fixedYear, 1, 1); };
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.
Alexander Tarnowski 46
Programmer wisdom
50. Copy and paste
public 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 50
51. Block copy and paste
Alexander Tarnowski 51
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");
}
…
}
53. The same method in different
contexts
Alexander Tarnowski 53
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 54
in 7/8 places…
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
Alexander Tarnowski 55
Programmer wisdom
56. Different methods or classes
doing similar things
Ignorance
Fear
Laziness
Choice
Conflict
Alexander Tarnowski 56
57. Different ways of doing
similar things
Alexander Tarnowski 57
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
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…
Alexander Tarnowski 59
Programmer wisdom
61. Properties of test-driven
code
Alexander Tarnowski 61
Code
driven by
tests
Executabl
e in
isolation
Small
Does one
thing
Indirect
creation
Pure
functions
separete
d from
side
effects
62. • Drives design, not correctness
• Negative tests are not used
• There are two kinds of TDD
Classic TDD London school
TDD and testability
Alexander Tarnowski
63. Supplement tests that drive design
with tests that drive correctness!
Alexander Tarnowski 63
Programmer wisdom
64. Summary
Alexander Tarnowski 64
Testability is a skill
Developers do supporting
technical testing
Testability:
Controllability
Observability
Orthogonality
Deployability
Testing techniques
Anti-testability const
Temporal coupling
Indirect input
Indirect output
High DRR
Duplication:
Mechanical
Mental
TDD drives design
not correctness!
Dependencies:
Pass in
Factory methods
Factories/Builders
Fakes
Notas do Editor
Testbarhet är inte om pengar. Det är om skills!
Controllability = settingstate, configuringFormal definitions: A software component is observable “if distinct outputs are generated from distinct inputs”A software component is controllable “if, given any desired output value, an extra input exists which ‘forces’ the component output to that value”
Max input length, especially true for buffers that can get overrunPrecision = minutes, seconds, milliseconds, etc
Not the complete diagram!Got many aha-moments whendrawing the diagram.Didn’tknowthat the timer couldcountupwards.
f(0) borde rimligtvis ge 1…f(10) ej definierad
Dependencyinjection!
There’s just moreofeverything.Itmessesup the metricsMechnical and mental
Block copy and paste is the inverseofExtractMethod