RailsCasts Pro episodes are now free!

Learn more or hide this

Recent Comments

Avatar

I see TDD as adopting the philosophy that writing tests/specs/etc is an integral part of one's development process, not some special separate step -- and yet, all the literature (books, blogs, screencasts) illustrates development without the testing, unless the testing tools are themselves the subject matter, and then you kind of see it in isolation.

This was the motivation behind my tweet asking if others would like to see testing integrated into every episode, almost showing how I would solve the problem in the real world.

However, the issue is that the episodes would be too long, and it would muddy whatever else I'm trying to teach. This is why I opted to keep the testing episodes separate.

I almost scrapped this episode because it was difficult to get the flow right. This is because the example was not contrived and optimized for presentation. But now I see there is a lot of value in non-contrived examples.

I've been slow to adopt TDD practices, not because I need any more convincing of their benefit, but because I feel like I don't have a real understanding how it should be done (and even moreso in Rails development, where there's a lot of support infrastructure that needs setting up to spin up the app's environment, test database, fixtures, mocking out authentication, etc, before you can even get started running the tests!) and that frustrates me.

I have struggled with this too, but I feel my testing workflow is finally at a point where I'm confident to share it with others. The way I show how to test here is relatively easy once you get past the initial hurdle.

Avatar

I didn't know about the :broken tag, thanks for bringing it to my attention.

Avatar

This is really refreshing to see. I think seeing TDD in real action like this may do a lot to help its adoption. I've long felt that there's been a bit of a hypocrisy particularly in the Ruby community that we have it hammered into our heads that we should be doing TDD or BDD or what have you, but are barely at all shown how. I see TDD as adopting the philosophy that writing tests/specs/etc is an integral part of one's development process, not some special separate step -- and yet, all the literature (books, blogs, screencasts) illustrates development without the testing, unless the testing tools are themselves the subject matter, and then you kind of see it in isolation. Even this episode, you've titled it "How I Test" rather than just, "How I Develop." If TDD should be the default way of working, we should treat it that way rather than always talking about it as some special thing.

I've been slow to adopt TDD practices, not because I need any more convincing of their benefit, but because I feel like I don't have a real understanding how it should be done (and even moreso in Rails development, where there's a lot of support infrastructure that needs setting up to spin up the app's environment, test database, fixtures, mocking out authentication, etc, before you can even get started running the tests!) and that frustrates me. I feel guilty every day that goes by that I don't do the right thing. So I'm all in favor of more episodes like this showing the full process. I'll likely be returning to this episode a few times and taking some notes.

Also, I think multiple assertions per example is fine, provided they are closely related and the later ones are more specific (maybe depending on something being tested for in one of the previous ones).

Avatar

Ryan, in your episode you commented out the whole test at some point. We could achieve almost the same effect by using :broken tag in RSpec.

Avatar

Loved this episode Ryan, real world TDD is something that doesn't recieve enough coverage in my opinion.

Would be great if future episodes would include tests. Perhaps not in the video (although I really enjoy TDD, it might make the episodes too long), but as sourcecode.

One episode per month in which you would do this kind of real world TDD on video, would make me write better tests.

Avatar

If you are having problems with the Albino gem returning nothing, make sure you are running Pygmentize under Python2.

I was encountering this issue from the command line:

pygmentize -l ruby -f html -O encoding=utf-8 test.rb

*** Error while highlighting:
TypeError: must be str, not bytes
   (file "/usr/lib/python3.2/codecs.py", line 356, in write)

In Arch Linux for example the default Python is Python3.2, installing Pygmentize under Python2.7 solved this for me.

Avatar

I like your test style as you don't really refactor your tests. This leaves them really 'readable'. Great episode! Thanks you!

Avatar

I personally think that it works well in models, but in request specs you end up having to refector pretty heavily to make it efficient.

I do aim for 1 assertion per test in the model though...

Avatar

I have looked into the one-assertion-per-test style but could not make it work for me. The test suite can easily become very slow. I have not heard a convincing argument for it. Some say it makes it much easier to determine what failed, but RSpec makes it easy to see which exact line is causing the failure.

Avatar

I agree. SimpleCov is awesome. I considered including it but decided to take it out at the last minute because it made the episode too long.

Avatar

Hmm, that is strange. It is working for me as you saw in the episode. It may be something specific to the RSpec version.

Avatar

Yes, I'd agree with this. For unit-style tests, one assertion per test makes sense, but for integration or acceptance tests, multiple assertions are perfectly okay.

Avatar

full fix =)

ruby
    html.gsub(/
      \<code
        \s+
          (?:
            lang=
              (?:
                "(.+?)"
              )
              |
              (?:
                '(.+?)'
              )
          )?
        \s*
      \>
      (.*?)
      \<\/code\>
    /mx) do
      CodeRay.scan($3, $2.blank? ? $1 : $2).div(:css => :class);
    end
Avatar

Should have re-read Fablo's post properly. Removing h() fixed it.

Avatar

I just get a load of un-rendered HTML. Help!

Avatar

Great screencast! Having been passionate about testing, yet being totally self taught, it is great to see the similarities and differences in our testing process.

One thing I am curious about is: What do you think about the approach of having only one assertion per test? I have read quite often that people take this approach in TDD, but it seems like, at least when writing request specs, the responses from a failing test make it very clear which assertion failed. Perhaps this is better practice in model specs?

Avatar

BTW, I just created the CoffeeScript syntax highliter for highlight.js https://github.com/dnagir/highlightjs-coffeescript

A bit different tool, but might help others I guess.

Avatar

Thanks for the great screencast.

I think You missed one thing for factory_girl.
Its problem om models with, for example,

rails
validates :name, :uniqueness => true

Its very annoying while working with associated model, like
:hotel belongs_to => :city, and city have validation on uniqueness of name.

How to deal with this problems?

Avatar

Nice job, This should convert some @NTDD's to @TDD's

Avatar

You could use UrlStore https://github.com/grosser/url_store and just encode user id and expiration time inside the url, no new columns needed. Only drawback is that the token gets longer.

code = UrlStore.encode([user.id, 2.hours.from_now])
...
if id, expires = UrlStore.decode(params[:id])
  if expires > Time.zone.now
    ...
Avatar

Great screencast!

I would also recommend using a metrics tool (I use simplecov, but there are others) to automatically generate reports showing you how much of your code base is tested. It only shows what code has been executed (as opposed to actual functionality being tested), but it goes a long way toward improving your coverage.

Avatar

You suggest to remove this line:

rails
config.fixture_path = "#{::Rails.root}/spec/fixtures"

But when I remove it, my tests hang at startup, I have to "kill -KILL", even I don't use fixtures at all. So I would not recommend to remove that line.

Avatar

Great ! You seem to test your code pretty much like I do.

I tried to use Cucumber, I really did. But I soon realized that I spent too much time trying to write tests that read great, maintaining an horrible stack of "steps", for pretty much nothing in return.

RSpec acceptance testing is just the level of abstraction I need.

Avatar

Thank you very much for another great screencast! I agree that doing complex testing in each episode would be an overkill, but an episode dedicated to testing only from time to time is a really great idea and will encourage not-so-experienced developers like myself that testing really is the way to go.

Also you could at some point return to the idea of walkthrough screencasts like the ones you did around the release of 3.0. I personally also considered them a great help and they made me a better coder.

Thank you very much again and keep up the amazing work!

Avatar

Nice Ryan!
I like how you said "close the browser and write some tests" I think thats a good start for me.
I've always wanted to know how a pro tested. I know on my next project i'll be using guard for testing.

Avatar

Hey Ryan!

I'm new to Rails and I really like Your screencasts.
However, I think I've found kind of security flaw in this one.

In password_resets_controller.rb you do:

ruby
@user.update_attributes(params[:user])

It allows the user to change any attribute he wants using prepared form/request.

I realize this is just an example, and in production you probably wouldn't do it, but still, it's not good example IMHO.

Correct me if I'm wrong.

Avatar

Once again you've provided an excellent and educational screencast. It looks like our testing environments are quite similar. However, I've only recently gotten into testing and I was struggling with finding an approach that made the most sense to me, and controller and view specs happened to be the ones giving me the most trouble. Thank you for providing another perspective of how to tackle testing. It was a tremendous help.

Keep up the great work.

Avatar

Great episode! As a developer that mostly works in isolation, I pick up a lot of my coding style from seeing how others do this and that on the web. One of the drawbacks is that you don't have that much of an opportunity to learn about other coders' testing styles, so this is a huge help. Hopefully push me further in a direction I know I need to go. As many times as I'm allowed, I vote (and greatly appreciate) testing included in these railscasts. Thanks again man.

Avatar

you've outdone yourself with this one ryan. Seeing real world TDD like this is amazing.

Avatar

Finished checking it out. Definitely a faster paced screencast, but throughout I've identified things that I can read up on and do further study with later. I love how you covered both ui testing and actual functionality (making sure the email is actually sent, not just that the email link does something visually)

Luckily I've used RSpec and Capybara before, so I was able to follow along decently enough, but at least some experience with both is probably a pre-requisite to keeping up.

Overall, loved the screencast and would love to see more. Thanks as always, Ryan!

Avatar

Hey, Great screencast.

I am however getting an error when trying to use a block:

When I use this:

rails
<% ab_test("signup_test", ["START YOUR MEMBERSHIP","SIGN UP", "CLICK HERE"]) do |button_text| %>
  <%= button_to button_text, :new_user_registration, {:method=>:get, 'data-theme'=>'mp', :onclick=>"_gaq.push(['_trackEvent', 'Users', 'Sign Up']);"} %>  
<% end %>

I get this:

ruby
undefined method `block_called_from_erb?' for #<#<Class:0x105245f30>:0x105243ca8>

Any ideas what that means? Have you seen that before?

Thanks for all the great work and all the help.

Avatar
SET "password_digest = '$2a$10erAAjsAfskv...' 

appears unfiltered in the log files (Rails 3.1.rc4) Is this a security issue ?

Avatar

seeds.rb has a major flaw. If my app "needs" the data to function, then my tests fail without it. The test environment automatically flushes this seed data. Sure, I could do everything twice and put the same data back in once the test environment loads, but I shouldn't have to. There has to be a way to tell rails to load my seeds after purging in preparation for testing rather than before. (It would be better if somehow rails would only purge the non-seed data, but now I'm just dreaming.)

Avatar

Cool video but it's very hard to understand for a beginner like myself.

Anyone have a tutorial somewhere of a dumbed down version of this?
:)

Avatar

Ryan make a wonderful new Screencast! I instantly implemented this within my project.
For a rake-task (to reset auth_token)Ryan recommandedin the video I personally wrote:

ruby
namespace :user do
  desc "Rebuild Auth-Tokens"
  task :rebuild_auth_token => :environment do
    User.transaction do
      User.all.each { |u|
        u.generate_token(:auth_token)
        u.save!
      }
    end
  end
end
Avatar

Perhaps your problem is related to Matt-Bishop's comment?

For anyone who is still using ruby 1.8.7 for rails 3.0.5
SecureRandom.urlsafe_base64 will generate a method not found error. I used SecureRandom.hex as a substitute and it worked. Just thought I'd share in case this helps anyone...

Or use Gabe Kopley's solution:

Nothing wrong with hex, but if you want base64, you could do this:

SecureRandom.base64.tr("+/", "-_")

Avatar

I read that this is a valid REST response. It's saying that "test" doesn't exist (the username is unique).

Avatar

has anyone encountered this error going through this tut!!

undefined method `urlsafe_base64' for SecureRandom:Module

app/models/user.rb:39:in generate_token'
app/models/user.rb:31:in
send_password_reset'
app/controllers/password_resets_controller.rb:7:in `create'

because I have and i've no idea how to solve this..

Avatar

To use highcharts with Rails 3.1.rc4, I've just done the following:

1 - Upgrade to jquery-1.6.1, highcharts 2.1.6

2 - Move highcharts and jquery into vendor/assets/javascripts

3 - Change the call to javascript inside the corresponding views to:

javascript
<%= javascript_include_tag "jquery-1.6.1.min.js", "highcharts", "js/modules/exporting.js" %>
Avatar

Hi Ryan,

Not sure how to make the client side validation work in a form that uses mixed model fields i.e. user & account model

rails
\- form_for @user, :url => signup_path, :validate => true do |f|
   = f.label :email
   = f.text_field :email, :size => 30, :maxlength => 320, :class => 'textInput'
   = f.label "Account Name"
   = text_field_tag 'account[domain]', nil, :size => 20, :maxlength => 40, :class => 'textInput', "data-validate"=>true

Any ideas ?

Avatar

I've implemented the above and it works wonderfully, thank you!

My only issue is that while url_for is producing links correctly in development, in my test environment my links are coming out as www.example.com?subdomain=example. Any idea what I'm missing?

Avatar

FYI, I just found http://livereload.com -- seems pretty interesting for anyone on a mac.

Avatar

Is there then a reason why you didn't use cookies.permanent.signed for the auth_token instead of cookies.permanent? It seems you can chain them and I can't find any drawbacks from using this.

Avatar

... So for using has_secure_password with mongoid, make sure you include ActiveModel::SecurePassword

Avatar

Hi,

Implemented this tutorial on a separate view called "search" so when I click on the search button app will redirect me back to the index and display the results there. What should I do in order to display the results in the "search" view?

Avatar

If you're injecting a form into the HTML after the DOM is loaded, this explains why it will not work and what you should do to fix it:

http://www.nickdizazzo.com/2011/07/jquery-replace-html-rails-3-tokeninput/

Avatar

Great Idea, I've just added openlayers, mapquest and bing as optionnal map providers in the gem. It's now direct to change from one to another (some functionalities may still lack but markers and infowindows are fine)

Avatar

Actually the UsersController should be like this, the above is the update action, which should stay the same:

ruby
  def reset_password
    @user = current_website.users.find_by_password_reset_token!(params[:id])
  end