Testability for Developers

Download Testability for Developers

Post on 23-Aug-2014

2.441 views

Embed Size (px)

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.

TRANSCRIPT

<ul><li> Swenug 2014-04-23 Alexander Tarnowski 1 Testablity for developers </li> <li> Why testability and testing for developers? Unit testing Maintenance Continuous integration Automated acceptance testing Alexander Tarnowski 2 </li> <li> But Alexander Tarnowski 3 </li> <li> Ongoing project http://leanpub.com/developer_testing alexander.tarnowski@crisp.se alexander_tar blog.crisp.se/author/alexandertarnowskiBlog www www.techbookreader.com Blog agilamyter.com </li> <li> Alexander Tarnowski 5 Testability costs money Misconception </li> <li> Alexander Tarnowski 6 Requireme nts Specificatio n Architectur al design Detailed design Coding Unit testing Integration testing System testing Acceptance testing MisconceptionTesting is a criticizing activity that can build quality in </li> <li> Alexander Tarnowski 7 Automated Tools Manual Automated &amp; manual Q1 Q2 Q3 Q4 </li> <li> Defining testability Alexander Tarnowski 8 If this were our system </li> <li> Defining testability Definition 1: Testability = decoupling Origin: The TDD community Alexander Tarnowski 9 </li> <li> Defining testability Definition 2: Testability = Controllability Observability Origin: The testing community Alexander Tarnowski 10 </li> <li> Defining testability Definition 3: Testability = Controllability Observability Orthogonality Deployability Origin: the Developer Testing book Alexander Tarnowski 11 </li> <li> Know the driving forces behind testability They are: Controllability Observability Orthogonality Deployability Make concious decisions to achieve them Alexander Tarnowski 12 Programmer wisdom </li> <li> Fundamental testing techniques Alexander Tarnowski 13 </li> <li> 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 </li> <li> Equivalence partitioning 0 0.2 0.4 0.6 0.8 1 1.2 1.4 1.6 1.8 0-17 18-23 24-60 60-100 101+ Women Men Insurance premiums Alexander Tarnowski 15 </li> <li> 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 </li> <li> Partition data into equivalence classes Cover each partition with at least one test case Alexander Tarnowski 17 Programmer wisdom </li> <li> Boundary values Alexander Tarnowski 18 Ages 24-60 22 23 24 25 26 58 59 60 61 62 </li> <li> 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 </li> <li> 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 </li> <li> 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 </li> <li> 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 </li> <li> Use for clarifying functionality and finding missing or incorrect transitions Basis for model-based testing Alexander Tarnowski 23 Programmer wisdom </li> <li> 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 </li> <li> 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 </li> <li> Decision tables translate directly into parameterized/data-driven tests! Alexander Tarnowski 26 Programmer wisdom </li> <li> Anti-testability constructs Alexander Tarnowski 27 </li> <li> Temporal coupling &amp; 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(); </li> <li> 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 </li> <li> 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 + } </li> <li> Indirect input is natural and we shouldnt fear it To cope with indirect input we need control points and stubs Alexander Tarnowski 31 Programmer wisdom </li> <li> 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) { return true; } Console.Out.WriteLine(i * 42); RemoteEndpoint.Invoke(42, magic, i); EventLog.WriteEntry(MagicApp, value: + i); </li> <li> Indirect output is natural and we shouldnt fear it To cope with indirect output we need observation points and mock objects Alexander Tarnowski 33 Programmer wisdom </li> <li> Domain-to-range ratio Alexander Tarnowski 34 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 . </li> <li> Compression bool IsValidAge(int age) Alexander Tarnowski 35 82 false ~4*109 true </li> <li> 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 </li> <li> Dependencies Alexander Tarnowski 37 </li> <li> Relations between objects Alexander Tarnowski 38 public class ListWrapper { private IList wrapped; public int WrappedListSize { get { return wrapped.Count; } } public ListWrapper() { wrapped = new List { 1, 2, 3 }; } } Indirect input </li> <li> Pass in the dependency Alexander Tarnowski 39 public class ListWrapper { private IList wrapped; public int WrappedListSize { get { return wrapped.Count; } } public ListWrapper(IList data) { wrapped = data; } } </li> <li> Use factory method Alexander Tarnowski 40 public class ListWrapper { private IList wrapped; public int WrappedListSize { get { return wrapped.Count; } } public ListWrapper() { wrapped = CreateWrappedList(); } protected virtual IList CreateWrappedList() { return new List { 1, 2, 3 }; } } </li> <li> Provide external factory or builder Alexander Tarnowski 41 public class ListWrapper { private IList wrapped; public int WrappedListSize { get { return wrapped.Count; } } public ListWrapper(IntegerListBuilder builder) { wrapped = builder.Build(); } } </li> <li> 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 Build() { return Enumerable.Range(startingAt, endingWith).ToList(); } } </li> <li> 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 </li> <li> System resource dependencies Files System clock Sockets, etc Alexander Tarnowski 44 </li> <li> 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 = () =&gt; { return new DateTime(fixedYear, 1, 1); }; </li> <li> There isnt 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 theres no other way. But first try without. Alexander Tarnowski 46 Programmer wisdom </li> <li> Dependencies between layers Alexander Tarnowski 47 Presentation Business Data access Theoretic al dependen cies Actual dependen cies </li> <li> Clean up messy cross-layer dependencies Use Dependency Inversion; depend on abstractions Use Dependency Injection Alexander Tarnowski 48 Programmer wisdom </li> <li> Duplication Alexander Tarnowski 49 </li> <li> 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 </li> <li> 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 &lt; 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 &lt; 18) { throw new ArgumentOutOfRangeException("Underage customer"); } } </li> <li> 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 52 </li> <li> 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; } </li> <li> 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 </li> <li> Code thats 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 </li> <li> Different methods or classes doing similar things Ignorance Fear Laziness Choice Conflict Alexander Tarnowski 56 </li> <li> 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...</li></ul>