Guest User

Untitled

a guest
Aug 23rd, 2018
101
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 3.91 KB | None | 0 0
  1. # A few thoughts on testing rich-client applications
  2. # Jan Dudek, Arkency
  3.  
  4.  
  5.  
  6.  
  7. # We've created a game that has some animations, uses Facebook API. Almost no rendering on the server. Client-server communication through JSON REST API.
  8.  
  9.  
  10.  
  11.  
  12. # First try: Selenium tests.
  13.  
  14. # Slow, unreliable. Running tests quickly begins to take minutes.
  15. # Hard to test animation, time-dependent actions, race conditions.
  16. # Does not cover API very well. (Need to test server-side anyway).
  17.  
  18. # We decided to have separate tests for client and server.
  19. # A few Selenium scenarios remain, but I hate them.
  20.  
  21.  
  22.  
  23.  
  24. # App architecture:
  25.  
  26. # Do-it-yourself MVC (no Backbone, Spine, etc.)
  27. # Mostly "plain old objects"
  28. # Dependency injection almost everywhere
  29. # One class ("Application") that wires all objects together
  30.  
  31. # Jasmine as unit and acceptance tests
  32.  
  33.  
  34.  
  35.  
  36. # What makes testing hard? External actors.
  37.  
  38. # Server-side API
  39. # Facebook API
  40. # Time
  41.  
  42.  
  43.  
  44.  
  45. # AJAX calls abstracted to ServerSide class
  46.  
  47. # Easy to mock/replace in tests
  48. # Easy to control order of server responses - allows testing race conditions. (Probably impossible in Selenium).
  49.  
  50.  
  51.  
  52.  
  53. # Facebook SDK - ugly, everything is global
  54. #
  55. # replaced with FacebookAdapter, easy to mock in tests
  56.  
  57.  
  58.  
  59.  
  60. # Clock object - easily test waiting for something to happen.
  61. #
  62. # Triggers "tick" event every 10ms (using window.setInterval)
  63. # In tests replaced by fake object that can be told to move time forward
  64. # e.g. clock.advance(miliseconds: 300)
  65.  
  66. # Also provides replacement for window.setTimeout - clock.setTimeout
  67. # clock.setTimeout has inverted order of arguments. window.setTimeout was so annoying!
  68.  
  69.  
  70.  
  71.  
  72. # Example unit test
  73.  
  74. describe "Countdown", ->
  75. beforeEach ->
  76. @clock = new TestClock
  77. @countdown = new Countdown(@clock, 5)
  78. @updateObserver = jasmine.createSpy("updateObserver")
  79. @finishObserver = jasmine.createSpy("finishObserver")
  80. @countdown.bind("updated", @updateObserver)
  81. @countdown.bind("finished", @finishObserver)
  82. @countdown.start()
  83.  
  84. describe "after 1 second", ->
  85. beforeEach ->
  86. @clock.advance(seconds: 1)
  87.  
  88. it "should update to 4 seconds", ->
  89. expect(@updateObserver).toHaveBeenCalledWith(4)
  90.  
  91. describe "after 5 seconds", ->
  92. beforeEach ->
  93. @clock.advance(seconds: 5)
  94.  
  95. it "should update with each second", ->
  96. expect(@updateObserver).toHaveBeenCalledWith(4)
  97. expect(@updateObserver).toHaveBeenCalledWith(3)
  98. expect(@updateObserver).toHaveBeenCalledWith(2)
  99. expect(@updateObserver).toHaveBeenCalledWith(1)
  100. expect(@updateObserver).toHaveBeenCalledWith(0)
  101.  
  102. it "should trigger 'finished' event", ->
  103. expect(@finishObserver).toHaveBeenCalled()
  104.  
  105.  
  106.  
  107.  
  108. # Example acceptance test:
  109.  
  110. beforeEach ->
  111. # ...
  112. @app = new Application(@facebook, @server, @audioPlayer, @clock)
  113. @app.start()
  114.  
  115. scenario "player enters the game", ->
  116. expect($("#logo")).toBeVisible()
  117. expect($("#throbber")).toBeVisible()
  118.  
  119. @server.respondTo("GET /player", { name: "John Doe", accepted_rules: false })
  120. @facebook.finishInitialization()
  121. @audioPlayer.finishInitialization()
  122.  
  123. expect($("#throbber")).not.toBeVisible()
  124. expect(@facebook.setFrameSize).toHaveBeenCalled()
  125. expect($("#status .name")).toHaveText("John Doe")
  126.  
  127. $("#game_screen a.play").click()
  128. @server.respondTo("postGameSession", { ... })
  129.  
  130. # and so on...
  131.  
  132.  
  133.  
  134.  
  135. # Benefits of this approach
  136.  
  137. # Much, much faster than Selenium - 3-5 seconds instead of minutes
  138. # Easy control of time and mocking external services (possible in Selenium via execute_script, but very hacky)
  139. # Thanks to mocking clock/external calls - only synchronous code in tests. Big win.
  140. # Testing race conditions
  141.  
  142.  
  143.  
  144.  
  145. # Issues
  146.  
  147. # We have to manually ensure that API is used correctly :(
  148. # Only single-page apps
  149. # Still undecided how to structure acceptance tests in Jasmine (TestUser object? long or short scenarios?)
  150. # Dependency injection - helpful, but Application class becomes messy; some classes with a dozen parameters in constructor
  151.  
  152.  
  153.  
  154.  
  155.  
  156. # Thanks!
Add Comment
Please, Sign In to add comment