1. Trang chủ
  2. » Công Nghệ Thông Tin

Phát triển Javascript - part 26 pptx

10 54 0
Tài liệu đã được kiểm tra trùng lặp

Đang tải... (xem toàn văn)

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 10
Dung lượng 2,39 MB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

Listing 11.9 Adding the addObserver method function addObserver { } Observable.prototype.addObserver = addObserver; With the method in place, Listing 11.10 shows that the test now fails

Trang 1

tddjs.util is undefined

()@http://localhost:4224/ /observable_test.js:5

11.2.1.2 Making the Test Pass

Fear not! Failure is actually a good thing: It tells us where to focus our efforts

The first serious problem is that tddjs.util doesn’t exist Listing 11.5 adds

the object using the tddjs.namespace method Save the listing in src/

observable.js

Listing 11.5 Creating the util namespace

tddjs.namespace("util");

Running the tests again yields a new error, as seen in Listing 11.6

Listing 11.6 Tests still failing

chris@laptop:~/projects/observable$ jstestdriver tests all

E

Total 1 tests (Passed: 0; Fails: 0; Errors: 1) (1.00 ms)

Firefox 3.6.3 Linux: Run 1 tests \

(Passed: 0; Fails: 0; Errors 1) (1.00 ms)

Observable.addObserver.test \

should store function error (1.00 ms): \

tddjs.util.Observable is not a constructor

()@http://localhost:4224/ /observable_test.js:5

Listing 11.7 fixes this new issue by adding an empty Observable constructor

Listing 11.7 Adding the constructor

(function () {

function Observable() {

}

tddjs.util.Observable = Observable;

}());

To work around the issues with named function expressions discussed in

Chapter 5, Functions, the constructor is defined using a function declaration

in-side an immediately called closure Running the test once again brings us directly

to the next problem, seen in Listing 11.8

Download from www.eBookTM.com

Trang 2

Listing 11.8 Missing addObserver method

chris@laptop:~/projects/observable$ jstestdriver tests all

E

Total 1 tests (Passed: 0; Fails: 0; Errors: 1) (0.00 ms)

Firefox 3.6.3 Linux: Run 1 tests \

(Passed: 0; Fails: 0; Errors 1) (0.00 ms)

Observable.addObserver.test \

should store function error (0.00 ms): \

observable.addObserver is not a function

()@http://localhost:4224/ /observable_test.js:8

Listing 11.9 adds the missing method

Listing 11.9 Adding the addObserver method

function addObserver() {

}

Observable.prototype.addObserver = addObserver;

With the method in place, Listing 11.10 shows that the test now fails in place

of a missing observers array

Listing 11.10 The observers array does not exist

chris@laptop:~/projects/observable$ jstestdriver tests all

E

Total 1 tests (Passed: 0; Fails: 0; Errors: 1) (1.00 ms)

Firefox 3.6.3 Linux: Run 1 tests \

(Passed: 0; Fails: 0; Errors 1) (1.00 ms)

Observable.addObserver.test \

should store function error (1.00 ms): \

observable.observers is undefined

()@http://localhost:4224/ /observable_test.js:10

As odd as it may seem, Listing 11.11 now defines the observers array inside

the addObserver method Remember, when a test is failing, we’re instructed to

do the simplest thing that could possibly work, no matter how dirty it feels We will

get the chance to review our work once the test is passing

Trang 3

Listing 11.11 Hard-coding the array

function addObserver(observer) {

this.observers = [observer];

}

Success! As Listing 11.12 shows, the test now passes

Listing 11.12 Test passing

chris@laptop:~/projects/observable$ jstestdriver tests all

Total 1 tests \

(Passed: 1; Fails: 0; Errors: 0) (0.00 ms)

Firefox 3.6.3 Linux: Run 1 tests \

(Passed: 1; Fails: 0; Errors 0) (0.00 ms)

11.2.2 Refactoring

While developing the current solution, we have taken the quickest possible route

to a passing test Now that the bar is green, we can review the solution and perform

any refactoring we deem necessary The only rule in this last step is to keep the bar

green This means we will have to refactor in tiny steps as well, making sure we

don’t accidentally break anything

The current implementation has two issues we should deal with The test makes

detailed assumptions about the implementation of Observable and the

addOb-serverimplementation is hard-coded to our test

We will address the hard-coding first To expose the hard-coded solution,

Listing 11.13 augments the test to make it add two observers instead of one

Listing 11.13 Exposing the hard-coded solution

"test should store function": function () {

var observable = new tddjs.util.Observable();

var observers = [function () {}, function () {}];

observable.addObserver(observers[0]);

observable.addObserver(observers[1]);

assertEquals(observers, observable.observers);

}

Download from www.eBookTM.com

Trang 4

As expected, the test now fails The test expects that functions added as

ob-servers should stack up like any element added to an array To achieve this, we

will move the array instantiation into the constructor and simply delegate

addOb-serverto the array method push as Listing 11.14 shows

Listing 11.14 Adding arrays the proper way

function Observable() {

this.observers = [];

}

function addObserver(observer) {

this.observers.push(observer);

}

With this implementation in place, the test passes again, proving that we have

taken care of the hard-coded solution However, accessing a public property and

making wild assumptions about the implementation of Observable is still an

issue An observable object should be observable by any number of objects, but it

is of no interest to outsiders how or where the observable stores them Ideally, we

would like to be able to check with the observable if a certain observer is registered

without groping around its insides We make a note of the smell and move on Later,

we will come back to improve this test

11.3 Checking for Observers

We will add another method to Observable, hasObserver, and use it to remove

some of the clutter we added when implementing addObserver

11.3.1 The Test

A new method starts with a new test Listing 11.15 describes the desired behavior

for the hasObserver method

Listing 11.15 Expecting hasObserver to return true for existing observers

TestCase("ObservableHasObserverTest", {

"test should return true when has observer": function () {

var observable = new tddjs.util.Observable();

var observer = function () {};

observable.addObserver(observer);

Trang 5

assertTrue(observable.hasObserver(observer));

}

});

We expect this test to fail in the face of a missing hasObserver, which it

does

11.3.1.1 Making the Test Pass

Listing 11.16 shows the simplest solution that could possibly pass the current test

Listing 11.16 Hard-coding hasObserver’s response

function hasObserver(observer) {

return true;

}

Observable.prototype.hasObserver = hasObserver;

Even though we know this won’t solve our problems in the long run, it keeps

the tests green Trying to review and refactor leaves us empty-handed as there are no

obvious points where we can improve The tests are our requirements, and currently

they only require hasObserver to return true Listing 11.17 introduces another

test that expects hasObserver to return false for a non-existent observer, which

can help force the real solution

Listing 11.17 Expecting hasObserver to return false for non-existent observers

"test should return false when no observers": function () {

var observable = new tddjs.util.Observable();

assertFalse(observable.hasObserver(function () {}));

}

This test fails miserably, given that hasObserver always returns true, forcing

us to produce the real implementation Checking if an observer is registered is a

simple matter of checking that the this.observers array contains the object

originally passed to addObserver as Listing 11.18 does

Listing 11.18 Actually checking for observer

function hasObserver(observer) {

return this.observers.indexOf(observer) >= 0;

}

Download from www.eBookTM.com

Trang 6

The Array.prototype.indexOf method returns a number less than 0 if

the element is not present in the array, so checking that it returns a number equal

to or greater than 0 will tell us if the observer exists

11.3.1.2 Solving Browser Incompatibilities

Running the test produces somewhat surprising results as seen in the relevant excerpt

in Listing 11.19

Listing 11.19 Funky results in Internet Explorer 6

chris@laptop:~/projects/observable$ jstestdriver tests all

.EE

Total 3 tests (Passed: 1; Fails: 0; Errors: 2) (11.00 ms)

Microsoft Internet Explorer 6.0 Windows: Run 3 tests \

(Passed: 1; Fails: 0; Errors 2) (11.00 ms)

Observable.hasObserver.test \

should return true when has observer error (11.00 ms): \

Object doesn't support this property or method

Observable.hasObserver.test \

should return false when no observers error (0.00 ms): \

Object doesn't support this property or method

Internet Explorer versions 6 and 7 failed the test with their most generic of error

messages: “Object doesn’t support this property or method.” This can indicate any

number of issues

• We are calling a method on an object that is null

• We are calling a method that does not exist

• We are accessing a property that doesn’t exist

Luckily, TDD-ing in tiny steps, we know that the error has to relate to the

re-cently added call to indexOf on our observers array As it turns out, IE 6 and 7 does

not support the JavaScript 1.6 method Array.prototype.indexOf (which we

cannot really blame it for, it was only recently standardized with ECMAScript 5,

December 2009) In other words, we are dealing with our first browser compatibility

issue At this point, we have three options:

• Circumvent the use of Array.prototype.indexOf in hasObserver,

effectively duplicating native functionality in supporting browsers

• Implement Array.prototype.indexOf for non-supporting browsers

Alternatively implement a helper function that provides the same functionality

Trang 7

• Use a third-party library that provides either the missing method, or a similar

method

Which one of these approaches is best suited to solve a given problem will

depend on the situation; they all have their pros and cons In the interest of keeping

Observableself-contained, we will simply implement hasObserver in terms

of a loop in place of the indexOf call, effectively working around the problem

Incidentally, that also seems to be the “simplest thing that could possibly work” at

this point Should we run into a similar situation later on, we would be advised to

reconsider our decision Listing 11.20 shows the updated hasObserver method

Listing 11.20 Manually looping the array

function hasObserver(observer) {

for (var i = 0, l = this.observers.length; i < l; i++) {

if (this.observers[i] == observer) {

return true;

}

}

return false;

}

11.3.2 Refactoring

With the bar back to green, it’s time to review our progress We now have three

tests, but two of them seem strangely similar The first test we wrote to verify the

correctness of addObserver basically tests for the same things as the test we

wrote to verify hasObserver There are two key differences between the two

tests: The first test has previously been declared smelly, as it directly accesses the

observersarray inside the observable object The first test adds two observers,

ensuring they’re both added Listing 11.21 joins the tests into one that verifies that

all observers added to the observable are actually added

Listing 11.21 Removing duplicated tests

"test should store functions": function () {

var observable = new tddjs.util.Observable();

var observers = [function () {}, function () {}];

observable.addObserver(observers[0]);

observable.addObserver(observers[1]);

Download from www.eBookTM.com

Trang 8

assertTrue(observable.hasObserver(observers[0]));

assertTrue(observable.hasObserver(observers[1]));

}

11.4 Notifying Observers

Adding observers and checking for their existence is nice, but without the ability

to notify them of interesting changes, Observable isn’t very useful

In this section we will add yet another method to our library Sticking to the Java

parallel, we will call the new method notifyObservers Because this method

is slightly more complex than the previous methods, we will implement it step by

step, testing a single aspect of the method at a time

11.4.1 Ensuring That Observers Are Called

The most important task notifyObservers performs is calling all the observers

To do this, we need some way to verify that an observer has been called after the

fact To verify that a function has been called, we can set a property on the function

when it is called To verify the test we can check if the property is set The test in

Listing 11.22 uses this concept in the first test for notifyObservers

Listing 11.22 Expecting notifyObservers to call all observers

TestCase("ObservableNotifyObserversTest", {

"test should call all observers": function () {

var observable = new tddjs.util.Observable();

var observer1 = function () { observer1.called = true; };

var observer2 = function () { observer2.called = true; };

observable.addObserver(observer1);

observable.addObserver(observer2);

observable.notifyObservers();

assertTrue(observer1.called);

assertTrue(observer2.called);

}

});

To pass the test we need to loop the observers array and call each function

Listing 11.23 fills in the blanks

Trang 9

Listing 11.23 Calling observers

function notifyObservers() {

for (var i = 0, l = this.observers.length; i < l; i++) {

this.observers[i]();

}

}

Observable.prototype.notifyObservers = notifyObservers;

11.4.2 Passing Arguments

Currently the observers are being called, but they are not being fed any data They

know something happened, but not necessarily what Although Java’s

implemen-tation defines the update method of observers to receive one or no arguments,

JavaScript allows a more flexible solution We will make notifyObservers take

any number of arguments, simply passing them along to each observer Listing 11.24

shows the requirement as a test

Listing 11.24 Expecting arguments to notifyObservers to be passed

to observers

"test should pass through arguments": function () {

var observable = new tddjs.util.Observable();

var actual;

observable.addObserver(function () {

actual = arguments;

});

observable.notifyObservers("String", 1, 32);

assertEquals(["String", 1, 32], actual);

}

The test compares passed and received arguments by assigning the received

arguments to a variable that is local to the test Running the test confirms that it

fails, which is not surprising as we are currently not touching the arguments inside

notifyObservers

To pass the test we can use apply when calling the observer, as seen in

Listing 11.25

Download from www.eBookTM.com

Trang 10

Listing 11.25 Using apply to pass arguments through notifyObservers

function notifyObservers() {

for (var i = 0, l = this.observers.length; i < l; i++) {

this.observers[i].apply(this, arguments);

}

}

With this simple fix tests go back to green Note that we sent in this as the

first argument to apply, meaning that observers will be called with the observable

as this

11.5 Error Handling

At this point Observable is functional and we have tests that verify its behavior

However, the tests only verify that the observables behave correctly in response to

expected input What happens if someone tries to register an object as an observer

in place of a function? What happens if one of the observers blows up? Those are

questions we need our tests to answer Ensuring correct behavior in expected

situa-tions is important—that is what our objects will be doing most of the time At least

so we could hope However, correct behavior even when the client is misbehaving

is just as important to guarantee a stable and predictable system

11.5.1 Adding Bogus Observers

The current implementation blindly accepts any kind of argument to

addOb-server This contrasts to the Java API we started out comparing to, which allows

objects implementing the Observer interface to register as observers Although

our implementation can use any function as an observer, it cannot handle any value.

The test in Listing 11.26 expects the observable to throw an exception when

at-tempting to add an observer that is not callable

Listing 11.26 Expecting non-callable arguments to cause an exception

"test should throw for uncallable observer": function () {

var observable = new tddjs.util.Observable();

assertException(function () {

observable.addObserver({});

}, "TypeError");

}

Ngày đăng: 04/07/2014, 22:20

w