Loopt unit test experiences

1,665 views

Published on

We all hear how unit tests can ensure higher quality code and help us in day to day refactoring, but is it feasible to write and maintain unit tests in a fast paced startup company?

This is a presentation by server lead, Heine Frifeldt, on how unit tests was gradually introduced into the server code base at Loopt, which tools are used in the continuous build environment, coding techniques and lessons learned.

Published in: Technology, Education
  • Be the first to comment

  • Be the first to like this

Loopt unit test experiences

  1. 1. Unit Testing for startups<br />Experiences from Loopt<br />
  2. 2. Heine Frifeldt <heine@loopt.com><br /><ul><li>Server Team Manager
  3. 3. Got Introduced to extreme programming in 2000 @ Adomo DK
  4. 4. Have previously tried to have lots of manual tests
  5. 5. Realized the value of unit tests
  6. 6. Continued Agile Development in Adomo US
  7. 7. Joined Loopt in 2008
  8. 8. Code examples are in C#, but topics should apply regardless of language
  9. 9. Feel free to ask questions</li></ul>2<br />
  10. 10. Loopt – What do we do?<br />3<br />Connecting You with Friends and Family<br />
  11. 11. Loopt – What do we do?<br />4<br />Connecting You with the Places You Go<br />
  12. 12. Loopt – What do we do?<br />5<br />Connecting You with Your Local Businesses<br />
  13. 13. Time for Unit Test in Startup?<br /><ul><li>Turn one-off tests into automated tests
  14. 14. Takes extra time upfront but it’s worth it
  15. 15. Legacy clients likely for mobile companies
  16. 16. Confident deployments
  17. 17. Unit tests work as documentation
  18. 18. Easy way for new employees to get familiarized with code</li></ul>6<br />
  19. 19. Overview<br /><ul><li>Initial state of Loopt tests
  20. 20. Untestable code
  21. 21. Code structure
  22. 22. Improved code
  23. 23. Lessons learned
  24. 24. Tools</li></ul>7<br />
  25. 25. Initial Experience<br /><ul><li>My first UT experiences we wrote the tests along with code
  26. 26. You structure your code for testability
  27. 27. Adding tests for existing code can be much harder
  28. 28. Believe common startup problem
  29. 29. You get to a point where you realize it would be nice with unit tests</li></ul>8<br />
  30. 30. Unit Tests at Loopt<br /><ul><li>General support behind Unit Test
  31. 31. Most eng. wanted to add unit tests, but it was hard, so in practice new tests were rarely added
  32. 32. VPE had previous good experiences with unit tests and our deployment did not have good track record
  33. 33. The existing tests were neglected
  34. 34. One test project for all projects
  35. 35. Few tests
  36. 36. End to end functional tests
  37. 37. Complex architecture and code that requires mobile to invoke</li></ul>9<br />
  38. 38. Code Example (Database Access Layer)<br /><ul><li>One of my first tasks was to add a LockedOut property to our User class
  39. 39. In theory to test it, Initialize LooptUser, Emulate failed login N times, Verify field got set
  40. 40. In practice
  41. 41. Constructor takes phone number (or session, or …) which reads and sets all relevant properties directly from DB
  42. 42. Login is a static method in a different class which makes direct DB calls
  43. 43. Checks your phone make/model
  44. 44. Makes external billing checks to certain carriers</li></ul>10<br />
  45. 45. Code Example (Business Logic)<br /><ul><li>Loopt Cell Server takes binary stream from clients and processes request
  46. 46. Stream passed down through the flow – and maybe modified
  47. 47. Some tests used the raw binary stream to verify functionality</li></ul>11<br />
  48. 48. Code Example (Business Logic)<br />public class DeleteJournal{<br /> private DeleteJournal() {}<br />public static void ProcessRequest(byte[] content) <br /> { <br /> using (SqlData sdp = new SqlData("sp_Delete_Journal")) <br /> {<br /> byte deltype = content[pos++];<br /> byte num = content[pos++]; <br /> entry = BitUtil.ReadInt(content, ref pos);<br />sdp.AddParameter("@EntryID", SqlDbType.Int, entry);<br /> […]<br />12<br />
  49. 49. Code Example (Static Initializers)<br /><ul><li>Carrier class has internal constructor
  50. 50. Get it from carrier factory
  51. 51. Has static initializer which reads carrier settings from a config file</li></ul>static CarrierFactory() <br />{<br />SmppMap = new Dictionary<string, SmppConnectionElement>();<br />CarrierSettings configSection = <br /> (CarrierSettings)ConfigurationManager.GetSection("carriers"); <br />foreach (SmppElemtn sce in configSection.SmppConnectionElements) <br /> {<br />[…]<br />13<br />
  52. 52. Argh!!<br /><ul><li>Ways of testing suggested to me
  53. 53. LooptUser - Create a test account and use number and password in tests
  54. 54. CellServer - Manual test with real phone
  55. 55. Carrier - Use phone number of wanted carrier
  56. 56. Manually create test data
  57. 57. Didn’t know what to change and where to begin and end</li></ul>14<br />
  58. 58. Testable Code Talk<br /><ul><li>Hosted unit test talks about testable code
  59. 59. MiskoHevery@ Google
  60. 60. http://misko.hevery.com/2008/11/11/clean-code-talks-dependency-injection
  61. 61. Key points
  62. 62. Avoid use of statics (your own and base class libraries)
  63. 63. Keep constructors simple - they cannot be overridden.
  64. 64. Separate object graph from logic – remove new operators
  65. 65. Ask for what you need (dependency injection) and have DI framework to initialize root objects
  66. 66. Hollywood Principle</li></ul>15<br />
  67. 67. Improvements<br /><ul><li>Immediate steps (no tools)
  68. 68. Start using dependency injection in new code
  69. 69. Refactor existing code when changes are required
  70. 70. Use our own BuildObject initializers
  71. 71. Use our own mock object implementations for testing
  72. 72. After ~6 months
  73. 73. Core code was more nicely structured
  74. 74. Unit tests and root objects were cluttering up with object graph initializations
  75. 75. Starting using Ninject
  76. 76. After ~2 years
  77. 77. Got Moq demoed. Just started using that.</li></ul>16<br />
  78. 78. Example Class using Dependency Injection<br />public class GrouponAdapter : IGrouponAdapter<br />{<br /> private readonly IPoiController _poiController;<br />private readonly DataContextProvider _contextProvider;<br />private readonly ILooptWebClient _looptWebClient;<br /> [Inject]<br />public GrouponAdapter(IPoiController poiController, DataContextProvider contextProvider,  ILooptWebClient looptWebClient) <br />{<br /> _poiController = poiController; _contextProvider = contextProvider; _looptWebClient = looptWebClient; <br />}<br />…<br />17<br />
  79. 79. Example Ninject Bindings<br />public class LooptLogicModule : NinjectModule<br />{<br />public override void Load() <br /> {<br /> Bind<IPoiController>().To<PoiController>().InSingletonScope(); Bind<ILooptWebClient>().To<LooptWebClient>().InSingletonScope();<br /> … <br />private IKernel _kernel;<br />_kernel.Get<IGrouponAdapter>();<br />18<br />
  80. 80. Example Unit Test using Moq<br />[TestMethod]<br />public void ParseGrouponDeals()<br />{<br />// Results in 6 deals being returned.<br />var webClient = new Mock<ILooptWebClient>();<br />webClient.Setup(c => c.DownloadString(It.IsAny<Uri>())).<br /> Returns(Resources.Groupon_Deals_In_Austin);<br />IGrouponAdapter ga = new GrouponAdapter(null, webClient.Object);<br />Deal[] deals = ga.GrouponDeals(new Coordinate(30.44595, -97.79016)); <br />Assert.AreEqual(6, deals.Length, "Expected 6 deals");<br />}<br /> <br />19<br />
  81. 81. Lessons Learned - Test Barriers<br /><ul><li>Hook up Ninject immediately in new projects
  82. 82. New operators can quickly creep back in
  83. 83. Add helpers when “impossible” or impractical to change
  84. 84. Use IDispose interface for TransientXXXXX test classes
  85. 85. Users
  86. 86. Phone numbers
  87. 87. Carriers
  88. 88. Config files
  89. 89. Non-ideal tests are better than no tests
  90. 90. Well, usually ;-)</li></ul>20<br />
  91. 91. Lessons Learned – Adding Tests<br /><ul><li>No strict test requirements; test can be written up front or after
  92. 92. Tests added later are better than no tests
  93. 93. Add tests when you encounter a bug
  94. 94. Bookmark/revisit hard to test code and set goal to write one test
  95. 95. Restructure / helpers will result in many more tests
  96. 96. Tools can be introduced gradually
  97. 97. Introduce DI from top to bottom to avoid cascading changes</li></ul>21<br />
  98. 98. Current Tools<br /><ul><li>Source Control - Mercurial
  99. 99. Unit Test Framework – Visual Studio
  100. 100. Automated Build Environment - Jenkins
  101. 101. Code style – Dependency Injection
  102. 102. Dependency Injection Framework - Ninject
  103. 103. Mock Framework - Moq</li></ul>22<br />
  104. 104. State of our tests<br /><ul><li>34 Test Projects
  105. 105. 1008 Unit Tests. Executed on commit. Takes ~10 min
  106. 106. 23 Functional tests. Executed nightly. Takes ~1 min
  107. 107. Too long execution time
  108. 108. Flaky tests tend to put us into bad streaks of several days with failing tests
  109. 109. Due to timing
  110. 110. Due to functional test nature</li></ul>23<br />
  111. 111. Jenkins (aka Hudson)<br /><ul><li>Nice overview of projects of their state
  112. 112. Age of failing unit tests
  113. 113. Execution time of unit tests</li></ul>24<br />
  114. 114. Next steps<br /><ul><li>Continue refactoring existing code to use DI/Ninject
  115. 115. Refactor existing tests to be more unit and less functional test
  116. 116. Faster
  117. 117. Not flaky
  118. 118. Better process for dealing with broken tests in crunch mode
  119. 119. Don’t want to ignore false negatives
  120. 120. Don’t notice new failures
  121. 121. Wait for better Ninject integration with ASP.NET, MVC, WCF, NT Services.</li></ul>25<br />
  122. 122. We are hiring ($5000 referral bonus)<br /><ul><li>System Administrator
  123. 123. Build Engineer
  124. 124. Metrics Engineer
  125. 125. Multiple QA
  126. 126. Server
  127. 127. iPhone
  128. 128. Android
  129. 129. Web</li></ul>26<br />

×