Testability for Developers

Preview:

DESCRIPTION

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.

Citation preview

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.tarnowski@crisp.se

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

Recommended