Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Agile Web Development With Rails, 2nd Edition (2006).pdf
Скачиваний:
30
Добавлен:
17.08.2013
Размер:
6.23 Mб
Скачать

USING MOCK OBJECTS 224

Say (as a contrived example) we notice that the User.encrypted_password method seems to be taking far too long. Let’s first find out if that’s the case.

depot> ruby script/performance/benchmarker 'User.encrypted_password("secret", "salt")'

 

user

system

total

 

real

#1

1.650000

0.030000

1.680000

(

1.761335)

Wow, 1.8 elapsed seconds to run one method seems high! Let’s run the profiler to dig into this.

depot> ruby script/performance/profiler 'User.encrypted_password("secret", "salt")'

Loading Rails...

Using

the standard Ruby profiler.

 

 

 

%

cumulative

self

 

self

total

 

time

seconds

seconds

calls

ms/call

ms/call

name

78.65

58.63

58.63

1

58630.00 74530.00

Integer#times

21.33

74.53

15.90

1000000

0.02

0.02

Math.sin

1.25

75.46

0.93

1

930.00

930.00

Profiler__.start_profile

0.01

75.47

0.01

12

0.83

0.83

Symbol#to_sym

. . .

 

 

 

 

 

 

0.00

75.48

0.00

1

0.00

0.00

Hash#update

That’s strange: the method seems to be spending most of its time in the times and sin methods. Let’s look at the source:

def self.encrypted_password(password, salt) 1000000.times { Math.sin(1)} string_to_hash = password + salt Digest::SHA1.hexdigest(string_to_hash)

end

Oops! That loop at the top was added when I wanted to slow things down during some manual testing, and I must have forgotten to remove it before I deployed the application. Guess I lose the use of the red stapler for a week.

Finally, remember the log files. They’re a gold mine of useful timing information.

13.6Using Mock Objects

At some point we’ll need to add code to the Depot application to actually collect payment from our dear customers. So imagine that we’ve filled out all the paperwork necessary to turn credit card numbers into real money in our bank account. Then we created a PaymentGateway class in the file lib/payment_gateway.rb that communicates with a credit-card processing gateway. And we’ve wired up the Depot application to handle credit cards by adding the following code to the save_order action of the StoreController.

gateway = PaymentGateway.new

response = gateway.collect(:login

=>

'username',

:password

=>

'password',

Report erratum

 

 

USING MOCK OBJECTS

225

:amount

=> @cart.total_price,

 

:card_number

=> @order.card_number,

 

:expiration

=>

@order.card_expiration,

 

:name

=>

@order.name)

 

When the collect method is called, the information is sent out over the network to the back-end credit-card processing system. This is good for our pocketbook, but it’s bad for our functional test because the StoreController now depends on a network connection with a real, live credit card processor on the other end. And even if we had both of those available at all times, we still don’t want to send credit card transactions every time we run the functional tests.

Instead, we simply want to test against a mock, or replacement, PaymentGateway object. Using a mock frees the tests from needing a network connection and ensures more consistent results. Thankfully, Rails makes stubbing out objects a breeze.

To stub out the collect method in the testing environment, all we need to do is create a payment_gateway.rb file in the test/mocks/test directory. Let’s look at the details of naming here.

First, the filename must match the name of the file we’re trying to replace. We can stub out a model, controller, or library file: the only constraint is that the filename must match. Second, look at the path of the stub file. We put it in the test subdirectory of the test/mocks directory. This subdirectory holds all the stub files that are used in the test environment. If we wanted to stub out files while in the development environment, we’d have put our stubs in the directory test/mocks/development.

Now let’s look at the file itself.

require 'lib/payment_gateway'

class PaymentGateway

# I'm a stubbed out method def collect(request)

true end

end

Notice that the stub file actually loads the original PaymentGateway class (using require). It then reopens the PaymentGateway class and overrides just the collect method. That means we don’t have to stub out all the methods of PaymentGateway, just the methods we want to redefine for when the tests run. In this case, the new collect method simply returns a fake response.

With this file in place, the StoreController will use the stub PaymentGateway class. This happens because Rails arranges the search path to include the mock path first—the file test/mocks/test/payment_gateway.rb is loaded instead of lib/payment_gateway.rb.

Report erratum

USING MOCK OBJECTS 226

That’s all there is to it. By using stubs, we can streamline the tests and concentrate on testing what’s most important. And Rails makes it painless.

Stubs vs. Mocks

You may have noticed that the previous section uses the term stub for these fake classes and methods but that Rails places them in a subdirectory of test/mocks. Rails is playing a bit fast and loose with its terminology here. What it calls mocks are really just stubs: faked-out chunks of code that eliminate the need for some resource.

However, if you really want mock objects—objects that test to see how they are used and create errors if used improperly—then Rails has an answer. As of 1.2, Rails includes Flex Mock,6 Jim Weirich’s Ruby library for mock objects. You can use it in any of your tests, but you’ll need to require it explicitly.

require "flexmock"

What We Just Did

We wrote some tests for the Depot application, but we didn’t test everything. However, with what we now know, we could test everything. Indeed, Rails has excellent support to help you write good tests. Test early and often—you’ll catch bugs before they have a chance to run and hide, your designs will improve, and your Rails application will thank you for it.

6. http://onestepback.org/software/flexmock/

Report erratum