Does my Button Look Big in This - PowerPoint PPT Presentation

1 / 25
About This Presentation
Title:

Does my Button Look Big in This

Description:

Does my Button Look Big in This – PowerPoint PPT presentation

Number of Views:24
Avg rating:3.0/5.0
Slides: 26
Provided by: adamco
Category:
Tags: button | fug | look

less

Transcript and Presenter's Notes

Title: Does my Button Look Big in This


1
Does my Button Look Big in This?
  • Building Testable AJAX Applications

Adam Connors and Joe Walnes
2
Agenda
  • Demonstrate a typical (hard to test) AJAX
    application.
  • Refactor this application into distinct
    components.
  • Test these.

3
Iteration 1 A Simple Web Dictionary
DEMO
4
Iteration 1 A Simple Web Dictionary
ltinput id"input" type"text" onkeyup"inputChange
d()"/gt
function inputChanged() ...
xmlHttpRequest.onreadystatechange function()
... for (var i 0 i lt results.length
i) var listItem
document.createElement("li")
listItem.onClick function()
previewFrame.src ...
resultsList.appendChild(listItem)
xmlHttpRequest.open("GET", "../words?word"
encodeURIComponent(input.value), true)
xmlHttpRequest.send(null)
index.js
5
Iteration 1 Problems
  • function inputChanged() contains all the
    intelligence
  • Very difficult to test each step in isolation.

6
Tackle complexity by breaking things down
  • Refactor code into separate concerns.
  • Use the most suitable approach to test each
    component.
  • Some components are easier to test than others.

7
Iteration 2 Extracting the View
view.js
function View() this.input
document.getElementById(input)
this.resultsList document.getElementById(result
sList) this.previewFrame document.getElement
ById(previewFrame) ...
8
Iteration 2 Extracting the View
view.js
View.prototype.getInput function() return
this.input.value
View.prototype.setInput function(newInput)
this.input.value newInput
View.prototype.showPreview function(url)
this.previewFrame.src url
View.prototype.showResults function(results)
var self this for (var i 0 i lt
results.length i) var listItem
document.createElement("li")
listItem.appendChild(document.createTextNode(resul
tsi)) listItem.onclick function()
self.onPickResult(this.innerHTML)
this.resultsList.appendChild(listItem)
View.prototype.onChangeInput
function(newInput)
View.prototype.onPickResult function(result)

9
Iteration 2 Extracting the View
view.js
getInput() setInput() showResults() showPreview()
onChangeInput() onPickResult()
function init() view new View(...)
view.onChangeInput function(newInput)
... xmlHttpRequest.onreadystatechange
function() view.showResults(results)
// Make HTTP Request ...
view.onPickResult function(result)
view.setInput(result) view.showPreview(...)

index.js
10
The Humble Object
  • Any object that is difficult to test should have
    minimal behaviour. That way, if we are unable to
    include it in our test suite we minimise the
    chances of an undetected failure.
  • Martin Fowler

11
Passive View
  • A Passive View reduces the behaviour of the UI
    components to the absolute minimum by using a
    controller that not just handles responses to
    user events, but also does all the updating of
    the view.

12
Iteration 2 Testing the View
DEMO
13
Iteration 2 Testing the View
ltscript type"text/javascript"gt var operations
name "setInput", args"'word'",
name "showResults", args"'apple','aardvark','
ant','arm'", name "showPreview",
args"'http//www.google.com/'", name
"hidePreview" var events
name "onChangeInput", name
"onPickResult" function init()
var view window.frames'ui'.view
window.frames'panel'.setup(view, operations,
events) lt/scriptgt
14
Iteration 3 Extracting the Datasource
  • Requesting data from the web server is another
    distinct responsibility.
  • By separating these concerns we can test these in
    insolation.

15
Iteration 3 Extracting the Datasource
var view function init() view new
View() view.onChangeInput
function(newInput) var xmlHttpRequest
createXmlHttpRequest() xmlHttpRequest.onready
statechange function() // Check
readyState. // Parse response.
view.showResults(results) //
Make HTTP request. xmlHttpRequest.send(null)

index.js
BEFORE
AFTER
var view var dataSource function init()
view new View() dataSource new
DataSource() view.onChangeInput
function(newInput) dataSource.request(url,
function(response) view.showResults(respon
se.results) )
index.js
DataSource.prototype.request function(url,
callbackFunction) ...
datasource.js
16
Iteration 4 Extracting the Controller
  • The role of the Controller is to plumb the View
    and Datasource together in such a way that it
    encapsulates the flow of the application.

17
Iteration 4 Extracting the Controller
var view var dataSource var controller functio
n init() view new View() dataSource
new DataSource() controller new
Controller(view, dataSource)
index.js
view.js
getInput() setInput() showResults() showPreview()
onChangeInput() onPickResult()
datasource.js
function Controller(view, dataSource)
view.onChangeInput function(newInput)
view.hidePreview() dataSource.request(url,
function(response) view.showResults(result
s) ) view.onPickResult
function(result) view.setInput(result)
view.showPreview(...)
controller.js
request()
18
Recap Iteration 1
function inputChanged() var input
document.getElementById('input') var
xmlHttpRequest createXmlHttpRequest() var
previewFrame document.getElementById("previewFra
me") previewFrame.src "aboutblank"
xmlHttpRequest.onreadystatechange function()
if (...) var response eval("("
xmlHttpRequest.responseText ")") var
results response.results var resultsList
document.getElementById("resultsList")
if (input.value response.query)
while (resultsList.hasChildNodes())
resultsList.removeChild(resultsList.firstChild)
for (var i 0 i lt
results.length i) var listItem
document.createElement("li")
listItem.appendChild(document.createTextNode(resul
tsi)) listItem.onclick function()
input.value this.innerHTML
previewFrame.src ...)
resultsList.appendChild(listItem)
xmlHttpRequest.open("GET",...,
true) xmlHttpRequest.send(null)
index.js
ITERATION 1
Backend Interaction
DOM Interaction
19
Recap Iteration 4
var view var dataSource var controller functio
n init() view new View() dataSource
new DataSource() controller new
Controller(view, dataSource)
index.js
DOM Interaction
ITERATION 4
view.js
getInput() setInput() showResults() showPreview()
onChangeInput() onPickResult()
function Controller(view, dataSource)
view.onChangeInput function(newInput)
view.hidePreview() dataSource.request(url,
function(response) view.showResults(result
s) ) view.onPickResult
function(result) view.setInput(result)
view.showPreview(...)
controller.js
datasource.js
request()
Backend Interaction
20
Testing the Controller
  • The Controller wires all the events together
    The flow of the application.
  • As an application grows, the flow logic can get
    complicated.
  • With the hard to test bits in other places, its
    now easy to test this part in isolation.

21
Testing the Controller (2)
var view var dataSource function setUp()
view makeRecorder(...) dataSource
makeRecorder(...) new Controller(view,
dataSource)
controller-test.html
function testRequestsDataFromUrlWhenInputChanges
() view.onChangeInput("hello")
assertEquals("../words?wordhello",
dataSource.request.lastCall.url)
function testHidesPreviewWhenInputChanges()
assertUndefined(view.hidePreview.wasCalled)
view.onChangeInput("stuff")
assertNotUndefined(view.hidePreview.wasCalled)

function testShowsPreviewWhenResultPicked()
assertUndefined(view.showPreview.wasCalled)
view.onPickResult("something")
assertNotUndefined(view.showPreview.wasCalled)
assertEquals("http//www.google.com/search?qdef
inesomething", view.showPreview.calls0.url
)
function testShowsResultsWhenDataSourceFiresCallba
ck() view.onChangeInput("hel")
assertUndefined(view.showResults.wasCalled)
dataSource.request.lastCall.callback(...)
assertNotUndefined(view.showResults.wasCalled)
assertNotUndefined(view.showResults.lastCall.resu
lts) assertObjectEquals(...,
view.showResults.calls0.results)
22
Testing the Controller (3)
DEMO
23
Testing the Datasource
var url '../words?wordcheesemak' var
expectedResponse '"query""cheesemak",
"results""cheesemaker","cheesemakers","cheesemak
ing"' var actualResponse function
setUpPage() var dataSource new
DataSource() dataSource.request(url,
function(response) actualResponse
response.toJSONString() setUpPageStatus
'complete' )
datasource-test.html
function testReturnsExpectedSpellingSuggestions()
assertEquals(expectedResponse,
actualResponse)
24
Summary
  • Decompose code into manageable components with
    distinct responsibilities.
  • Minimise the behaviour of components that are
    difficult to test.
  • Use appropriate techniques and tools to test each
    in isolation. (Sometimes automated, sometimes
    manual.)

25
Links
Adam Connors adamconnors_at_google.com Joe Walnes
joejoejoe_at_google.com
  • passive view
  • humble dialog box
  • gui architectures
  • jsunit
  • selenium testing
  • watir
  • javascript language survey
  • Json
  • (Details of code samples will come with follow-up
    email)
Write a Comment
User Comments (0)
About PowerShow.com