Showing posts with label tdd. Show all posts
Showing posts with label tdd. Show all posts

Wednesday, May 21, 2008

Story Driven Development

If you are interested in testing and the TDD/BDD movements, then you may find Bryan Helmkamp presentation on "Story Driven Development" worth watching. Bryan explains the differences between unit testing and scenarios. He also shows Webrat, a tool he works on, which makes defining stories in Rails apps very easy.

I like the title that Bryan has chosen for the talk. The name "Story Driven Development" makes it clear that it's a lot about defining requirements, and not only about testing. Additionally, I think it's clear that it focuses on the acceptance level of tests without mentioning unit tests.

He also provides a quote from Robert Martin:

Unit tests: “Make sure you write the code right.”
Scenarios: “Make sure you write the right code.”

You can watch the slides here, or grab the PDF version (which has correct code examples).

Tuesday, February 5, 2008

Remote pair programming

This article describes my experience with remote pair programming, why I chose remote pair programming for one of the projects, how it works and what tools we use.

One of the projects I'm working on is a Rails application. We work on that together with my colleague, Marcin. There's nothing really innovative in this application. I've got enough experience with Rails and IT in general to know that it can be finished in a given time. The interesting bit is, that both me an Marcin live in different cities. Even better, both of us travel a lot and very often we are in different countries.

A distributed team is nothing new in today's IT world. One of the most common approaches to this problem is to divide the system and responsibilities across different people/teams/locations. It can work, I was involved in many such projects. The problem is that it requires a lot of trust, patience and really good skills to know how to split the project. In my opinion, it's a very risky approach, and it's easy to do it wrong. Chad Fowler describes it perfectly in his book: "My job went to India".

Traditional pair programming is a great practice. I know it's still controversial, and I agree that it doesn't have to work for all software developers and all projects, but I've seen it successful often enough to know that there are many benefits of pair programming. The benefits that I find important are:
  • Focus on the task
  • Sharing knowledge
  • Increasing trust
  • Fun
  • Speed of development
In our case, I think all of the factors are important. As always, time is a constraint, so it's important to focus on the most important tasks first. We have different skills - while I'm a little bit more experienced with Rails and TDD, Marcin is my guru when it comes to html/css. He worked on many different IT projects, so his general experience and intuition is very important for me. It means, we both gain a lot from this kind of cooperation. Fun of development is also an important factor here :-) From the remote pair programming sessions we've had so far I can say that I'm also very glad of our efficiency.

The tools we use are nothing new. We use vim + screen + skype. We both login to a development server as the same user. There is a screen session already open, so we just 'screen -x' to it, and now we see the same things at the same time. It's fun to watch, when Marcin is typing something, explaining it using skype, and I see it immediately in my vim. At any point, I can grab the keyboard and just start typing. Cool! I can't imagine working like that without a skype session, though. Using IM is just too slow.

I'm strongly recommending this kind of cooperation. I'm aware of the limitations here. The application must be possible to build on a Linux/Unix machine, but even if it can't, I can imagine some work-arounds to this problem. Different timezones might be an issue here. I don't think the whole project must be built like that. However, planning some remote pair programming sessions may increase the quality of your software.

I like this way of working so much, that I even consider doing it more often. Would anyone be interested in a Rails coaching session like that? All you need is skype and an SSH client.

The articles I found useful when preparing to remote pair programming:

http://blog.lathi.net/articles/2007/10/09/remote-pair-programming

http://mikeburnscoder.wordpress.com/2007/06/21/my-rails-development-environment-version-1-vim-and-screen/

Monday, January 21, 2008

What do I gain from TDD or BDD?

The question what do I gain from doing TDD/BDD is asked quite often. I understand the reasons people ask for that. I have practised TDD since 2001. This post is an explanation of why I TDD/BDD and what I gain from it.

Why do I BDD?

BDD helps me with 3 aspects of software development: Design, Defense, Documentation.
  • Design
    • When I write the specifications(tests) first, I see that my design is simpler (as compared to write the code first) for given requirements, thus better.
      • I know it's not very intuitive, you just have to try it.
    • When you start with tests, your design is more testable.
      • It makes debugging easier.
    • TDD doesn't replace drawing diagrams.
      • It's just an additional design technique.
      • I still enjoy drawing UML-like diagrams!
  • Defense
    • I feel much safer having my application covered with tests, .
      • Thanks to that, I can do some refactoring when I feel it's needed, run the tests, and if it's 'green' I just check it in.
    • Whenever some of my users find any bug, I prepare a test that covers it.
      • Only then I fix the bug and release the change.
      • There's nothing worse than having a bug that reappears again (after fixing it) in the application.
  • Documentation
    • I'm not a fan of big Word documents that describe all details of the requirements.
      • It's just too difficult to synchronize this document with reality after every requirements change
        • Changes are inevitable.
      • There was one large project I was working on, where nobody from the team (including the project manager) knew what all the requirements were!
        • Yes, it is pathological.
    • I store all of the requirements as executable stories/tests.
      • Before releasing any change I run all the stories.
      • When there is a requirement change I just find the story responsible for that, change it appropriately and change the code.
        • Changes are inevitable.
Do I have to TDD/BDD in order to be successful?

Well... No.

I know some very good developers that don't write any tests and are successful. There are other ways of ensuring good design or documentation. As for defense, you may have a very fast testing department that makes sure you didn't break anything. Or maybe you just prefer to manually go through your application. For me it's just too slow,
I prefer an immediate response that is free of any human mistakes. Chances are, you are just so perfect that you don't introduce any breaking changes. Lucky you!

It's also possible that you separate the analysis, design and implementation phases so strongly, that there is no way of changing anything during the implementation phase. I'm not a big fan of this approach but I know that many people are successful with that. I believe that even in this case you can gain a lot from writing acceptance tests (executable stories). Writing unit tests for your classes is also a good thing. I'll try to cover the topic of testing on different levels (acceptance, units) in a separate post.


But it's difficult and expensive, isn't it?

Yes, it is. All I gain doesn't come for free.

First of all, it requires discipline. It takes some time to get used to write tests first.
Also, it takes some time to learn how to do correct TDD/BDD. The good news is that you can start introducing it gradually.

Another thing is that after creating a lot of tests for your applications the whole suite may take some time to run. It's a problem that is not easy to solve, but believe me you'd prefer this kind of problems than a 5-days debugging session.

Is it fun?

Yes, it's a lot of fun!
Again, you have to try it before you know, what I'm talking about. If you're still new to TDD, look around, maybe there is someone experienced nearby who can show you a quick demo.

What next?

Find some TDD tools for your technology. I believe they exist for every programming language. Find a place in your application where you'd like to start testing. Start with something simple. Maybe assuring that a request to a given page is successful? Or maybe asserting that all the values in the Order class are calculated correctly? Make sure you run those tests after every change of your application.

Feel free to ask me questions about TDD or BDD. I have practised it with Java, C#, IronPython and Ruby. The technology doesn't matter too much in this case. You can use comments for that or simply email me at andrzejkrzywda at gmail.com.

Thursday, January 17, 2008

RSpec User Story example

Today, just a quick example showing what kind of wonderful things you can do with the new RSpec and its support for executable User Stories, a feature I was dreaming about for years...

Specification as a user story:



Story: Creating an order

Scenario: admin user creates an order
Given an admin user
And some orders
And some customers

When he creates an order

Then a new order is created
And a new customer is created

Implementation of the user story



Given "an admin user" do
login_as_admin
end

Given "some orders" do
@orders_count = Order.count
end

Given "some customers" do
@customers_count = Customer.count
end

When "he creates an order" do
post '/orders/create',
"order"=>{"address_attributes"=> {"name"=>"Customer 1"}}
end

Then "a new order is created" do
Order.count.should == @orders_count + 1
end

Then "a new customer is created" do
Customer.count.should == @customers_count + 1
end

The output when the user story is run:



Running 1 scenarios

Story: Creating an order

Scenario: admin user creates an order

Given an admin user
And some orders
And some customers

When he creates an order

Then a new order is created
And a new customer is created

1 scenarios: 1 succeeded, 0 failed, 0 pending

Thursday, October 18, 2007

Rails: How to unit test the formatting of model fields in the views.

In our Rails applications we often use Textile/RedCloth or some other libraries to have things like Post's content nicely formatted in the view. I use acts_as_textiled. This plugin simplifies the code a lot. All that is needed in order to make a certain field 'textilized' is this line:

class Post < ActiveRecord::Base
acts_as_textiled :content
end

The question that I want to focus on here is:

How to unit test that a certain field will be nicely formatted in the views?

lolcat - this not so hard
more funny pictures

As Jay Fields has already explained (he focused on testing validations in his article) there are two approaches:
The first one is based on listing all the cases that are important, like whether paragraph tags are correctly inserted or whether bold tags are correct. It may be sometimes useful but for me it's a code smell that for testing a single line in your codebase you need so many test lines of code. Additionally, the acts_as_textiled plugin is nicely tested/specified so why duplicate the code? I'd rather test some of those cases in my Selenium tests or functional tests and choose the second approach:

def test_content_is_nicely_formatted
Post.expects(:acts_as_textiled).with(:content)
load "#{RAILS_ROOT}/app/models/post.rb"
end

I'm using mocha here for setting an expectation that a class method called acts_as_textiled will be called (passing the field name argument) during the load time of the class file. This solution is not ideal. I don't like hardcoding the path here but I still find it simpler that the first approach.
This way testing of things like validations and acts_as_* calls becomes much simpler.

Thursday, June 28, 2007

RSpec for acts_as_taggable

Requirements:

An empty post
- should be valid
- should have no tags
- should allow setting tags

A post with tags
- should show tags
- should be listed as tagged


Implementation:

class Post < ActiveRecord::Base
acts_as_taggable
end


RSpec:

describe "An empty post" do
before(:each) do
@post = Post.new
end

it "should be valid" do
@post.should be_valid
end

it "should have no tags" do
@post.tags.should be_empty
end

it "should allow setting tags" do
@post.tag_list = "tdd, rails"
@post.save
@post.tags.should_not be_empty
end
end

describe "A post with tags" do
before(:each) do
@post = Post.new(:tag_list=>"tdd, python, rails")
@post.save
end

it "should show tags" do
@post.tag_list.names.should == ["tdd", "python", "rails"]
end

it "should be listed as tagged" do
Post.find_tagged_with('tdd').should == [@post]
end
end


More details:
http://agilewebdevelopment.com/plugins/acts_as_taggable_on_steroids

Thursday, June 14, 2007

Testing Rails controllers with mock objects

One of the steps in my 15 TDD steps to create a Rails application article was using the mocking technique.
Mock objects were used in a test for a controller method. From the feedback I received I noticed that this technique is not very popular and requires a more detailed explanation. This article describes why you might want to use mock objects for testing your controllers and how it simplifies the testing setup and code. The mocking library I use in this article is Mocha.

Rails controllers

Before I go into details about testing Rails controllers, let's clarify what is the responsibility of a Rails controller.

Ideally, a controller method should only call one method on a model class. Sometimes the method on the model that we call is some kind of finder like Person.find(:all). The result of the method should then be passed to the view.
A Rails controller should be a thin layer between your model classes and the view part.

A sample implementation looks like that:

def learn
@word = Word.random
end

As we see, we call the 'random method on the Word class which represents some model part of our application. Then we pass the result to the view via @word.

Testing Rails controllers

Being aware of that, we know what we want to test:
A learn method should:

  1. call the Word.random method

  2. pass the result to the view


Problems with traditional approach to testing Rails controllers
Now, what if we simply construct the test like this:

def test_learn_passes_a_random_word
get 'learn'
assert_kind_of Word, assigns('word')
end

The first problem here is that it calls the Word.random method, thus accesses the (test) database. It means we need to have some fixtures loaded so that the random method can retrieve some word from it.
The second problem is that we only test the type (kind_of) of the object assigned to @word. It means we don't fully test the method. We could imagine an implementation of the learn method that passes the tests but does the wrong thing (by assigning any other object to @word). Not ideal.

Testing with mock objects

Fortunately, we can use mock objects here and solve both problems.
We will replace the Word.random method with a method that always returns an object that we prepared before. Another advantage is that:

We don't connect to the database from our unit tests which is A Good Thing.


The object that we prepare before can then be used to assert whether the one passed to the view was the same as the result of calling Word.random method.

random_word = Word.new
Word.expects(:random).returns(random_word)

As we see, we prepare a random_word object, which is simply a new instance of a Word class. The next line does two things:

  • expects(:random) - sets our expectation that the random method will be called

  • returns(random_word) - replaces the real implementation of the 'random' method with an implementation that always returns the random_word object.


Btw, we didn't have to create a new instance of Word here. We could have used Object() there and that would work fine. The crucial part is that we compare the objects we return from Word.random with the one assigned to @word.

What is an expectation?

An expectation is almost like an assertion. The difference is that we set it before the actual call to the method that we test. The test will fail if the expectation isn't met.

We now extend the test with a call to the 'learn' method that we test:

random_word = Word.new
Word.expects(:random).returns(random_word)
get 'learn'

This test should now pass with the following implementation:

def learn
@word = Word.random
end


We also want to test that the object was assigned to @word, so we need an additional line with assertion:

def test_learn_passes_a_random_word
random_word = Word.new
Word.expects(:random).returns(random_word)
get 'learn'
assert_equal random_word, assigns('word')
end


The assertion tests the equality of the random_word object and the @word (we access it using assigns('word')). It's possible only thanks to the fact that we replaced the real Word.random method and have a reference to the result of this method.
Benefits

  • not connecting to a database
  • no need for preparing fixtures
  • faster tests
  • controller tests are less fragile for model changes

Drawbacks

  • some developers may find the tests less readable
  • if you rename the model method, the controller tests will still pass
  • increased need for some integration tests - Selenium or standard Rails integration tests


Conclusion

I hope that this article sheds more light on what are mock objects. I strongly recommend trying to add some tests like that to your test suite. If you want to read more about mocking you should first go to the classic Martin Fowler's article on Mock Objects. After that go to the Jay Field's blog. You can also find some interesting examples on the Mocha website.

Monday, May 28, 2007

Selenium on Rails in 5 minutes

Introduction
Selenium is a very good framework for testing web applications. It's an ideal tool to use when you want to test your Rails apps from a user perspective. There is a Selenium on Rails plugin that simplifies creating and running Selenium tests.

Step 1. Create a Rails app and install the Selenium on Rails plugin

rails selenium_rocks
cd selenium_rocks
script/plugin install http://svn.openqa.org/svn/selenium-on-rails/selenium-on-rails

(it may take a while)

Step 2. Configuration
Create vendor/plugins/selenium-on-rails/config.yml file and paste the following:

environments:
- test
browsers:
safari: '/Applications/Safari.app/Contents/MacOS/Safari'
#firefox: 'c:\Program Files\Mozilla Firefox\firefox.exe'
#ie: 'c:\Program Files\Internet Explorer\iexplore.exe'

Change the browser path to point to your browser.

Step 3. Create a Selenium test

script/generate selenium welcome_page_test

Edit test/selenium/welcome_page_test.sel. Replace the existing content with the following:

setup
open '/'
assert_title 'Ruby on Rails: Welcome aboard'

Step 4. Start the server in a test environment
run in a new terminal:

script/server -e test

Step 5. Run the Selenium test

rake test:acceptance

You should see the following output:

1 tests passed, 0 tests failed

Congratulations!

More information:
Selenium On Rails website
Full-stack Web App Testing with Selenium and Rails (RailsConf 2007)

Friday, May 11, 2007

rcov for rails and how to analyze its reports

rcov is a code coverage tool for Ruby.

There are at least two reasons why it's worth using code coverage tools in your application.
1. It shows you which area of your code is untested.

This information can give you some clue, where it might be worth to cover the code with tests.

2. Gives you information about the code that is never executed.

You run your tests and the code coverage tool reveals a code that was not executed. It either means that this part was not tested or that the code is actually never used by your application. If it's the second case then you may simplify your codebase by removing the code. Don't worry, if you think you will later need it you can always find it your Subversion history. You use version control system, don't you?

rcov for Rails - installation

First, you install rcov gem.

gem install rcov

Then, you go to your application directory and install rcov for rails plugin:

./script/plugin install http://svn.codahale.com/rails_rcov

The plugin extends your Rake by adding new tasks like:

rake test:units:rcov
rake test:functionals:rcov

When you run 'rake test:units:rcov', it first runs all of your unit tests and then creates a coverage report. You can find it in the coverage/units directory.
It also outputs a simple text report:

5 tests, 13 assertions, 0 failures, 0 errors
+----------------------------------+-------+-------+--------+
| File | Lines | LOC | COV |
+----------------------------------+-------+-------+--------+
|app/controllers/application.rb | 7 | 3 | 100.0% |
|app/helpers/application_helper.rb | 3 | 2 | 100.0% |
|app/models/word.rb | 21 | 18 | 100.0% |
+----------------------------------+-------+-------+--------+
|Total | 31 | 23 | 100.0% |
+----------------------------------+-------+-------+--------+
100.0% 3 file(s) 31 Lines 23 LOC

How to analyze rcov results?

That's a result for the current code of our Words application. The only line that is interesting here is the one for word.rb. It says that the tests fully cover the Word class, which is a good thing.
When I run 'rake test:functionals:rcov' I get the following:

1 tests, 2 assertions, 0 failures, 0 errors
+-----------------------------------+-------+-------+--------+
| File | Lines | LOC | COV |
+-----------------------------------+-------+-------+--------+
|app/controllers/application.rb | 7 | 3 | 100.0% |
|app/controllers/words_controller.rb| 6 | 5 | 100.0% |
|app/helpers/application_helper.rb | 3 | 2 | 100.0% |
|app/helpers/words_helper.rb | 2 | 2 | 100.0% |
|app/models/word.rb | 21 | 18 | 22.2% |
+-----------------------------------+-------+-------+--------+
|Total | 39 | 30 | 53.3% |
+-----------------------------------+-------+-------+--------+

First, let's have a look at the second line, which says that we have 100% test coverage for the word_controller. That's nice. Have a look at the last line, related to the word.rb file. Why do we have only 22.2% here? Is that a problem? Does it mean that we have untested code? The answer is 'no'. We shouldn't be bothered about this line. Remember that test:functionals are tests for the controllers and this what we focus our tests on. We don't want to care about model classes here, they are already tested in test:units suite. In our case the reason for a low coverage is that we mock the Word.random method in our controller's tests. The real Word class is never called.

Summarizing, all we should care about in my opinion are results of coverage analysis for model classes when we run test:units and the results for controllers when we run test:functionals.
I suggest you give rcov a try. It should take you only 5 minutes to discover how your tests cover the code of the application. Who knows, maybe you can find some code that was never executed?

Thursday, May 10, 2007

... and some more TDD steps with Rails

We were working together with Marcin on the Words application. It was a lot of fun. We were pair programming for about two hours. First, we started by going through the last TDD steps again. That was good as an exercise and also it was a good introduction for Marcin (he was driving the keyboard during this part) to Mac, TextMate, Rake and Autotest. After we repeated all the steps, we decided that we want to have a way of feeding our database with more words. The idea was to have a form with a text box where a user could paste any kind of text. The text would then be parsed into words which are put into the database.
It was obvious that we need some kind of Word.add_content method. So we started with tests for that method. The final result of those tests is as following:

def assert_count_after_add(count, content)
Word.add_content(content)
assert_equal count, Word.count
end

def test_add_content
assert_count_after_add 2, ""
assert_count_after_add 3, "single"
assert_count_after_add 5, "two words"
assert_count_after_add 9, "four wordzz are funny"
assert_count_after_add 10, "duplicate duplicate"
end

As you can see there is a custom assertion. We also test that if there is already a word in the database we don't want it to be added again.
The implementation for the add_content:

def self.add_content content
if not content.empty?
content.split(" ").each {|eng_word|
if Word.find(:first,
:conditions => "eng = '#{eng_word}'") == nil
Word.new(:eng=>eng_word).save
end
}
end
end

It's not the prettiest piece of code but it should be fine for now.
Oops, it looks like we don't have a test for the fact that words are saved with their english translation only...
After implementing the add_content method we realized that now our database could be filled with words that are not translated (pl field is empty). It means we want to change the implementation of the 'Word.random' method so that it only displays words that are already translated. There is no point in displaying the english version only...
It sounds like we need word.translated? method. Let's write some tests:

def test_translated
assert !Word.new.translated?
assert (Word.new :pl=>"tak", :eng=>"yes").translated?
assert !(Word.new :pl=>"tak").translated?
assert !(Word.new :eng=>"yes").translated?
end

The implementation is simple:

def translated?
pl and eng
end

Now, it's time for adding a test for the fact that Word.random only returns translated words. Again, we call the test several times so that we can assume it works. We can probably refactor it later to some nicer way. We add a word to the database and then assert that it wasn't chosen even after 100 calls. Any ideas how to test it better?

def test_use_words_with_translation
Word.new( :pl=>'tak', :eng=>'yes').save
Word.add_content("hello")
randoms = []
100.times {randoms << Word.random.eng}
assert (randoms.include? "hello") == false
end

The new implementation looks like that:

def self.random
(Word.find(:all).select {|word|word.translated?}.sort_by {rand})[0]
end

Unfortunately, there was no time to create a user interface for adding a content. Sounds, like a nice topic for the next session.

Wednesday, May 9, 2007

15 TDD steps to create a Rails application




Hi,

Testing Rails applications is my passion for over 7 years now. If you sign up to this newsletter you will receive exclusive information about everything related to Ruby unit testing, Rails acceptance testing, JavaScript testing, testable architectures, TDD, BDD and good OO design. 


  Subscribe to the Testing Rails mailing list


Introduction

Several times recently, I have been asked how to develop a Rails application using the Test Driven Development approach. I'm not an expert here, but I've put together some notes on how to start working on a Rails application whilst being test-driven all the time.
As an example I will use a word-learning web application. The simplest use case is to display a random word object (with its Polish translation) from the database.
Every time we refresh we want to see a different word.

1. Create a new Rails application
rails my_app
cd my_app

Run tests with 'rake test'. It fails due to missing database configuration.

2. Set up the databases - config/database.yml
The code below assumes sqlite3 databases.
development:
adapter: sqlite3
database: db/my_app_development.sqlite

test:
adapter: sqlite3
database: db/my_app_test.sqlite

'rake test' now runs fine.

3. Create a Word class with a corresponding unit test
script/generate model Word

4. Write a unit test for the Word class. Edit the test/unit/word_test.rb.
def test_word_is_english_and_polish
word = Word.new :eng=>'never', :pl=>'nigdy'
assert_equal 'never', word.eng
assert_equal 'nigdy', word.pl
end

'rake test' now fails due to missing words table.

5. Edit db/migrate/001_create_words.rb
We are using a migration here in order to create a table. It's a recommended way of dealing with database changes.

def self.up
create_table :words do |t|
t.column :eng, :string
t.column :pl, :string
end

Word.new(:eng=>'yes', :pl=>'tak').save
Word.new(:eng=>'no', :pl=>'nie').save
Word.new(:eng=>'everything', :pl=>'wszystko').save
end

def self.down
drop_table :words
end

The sample words that we are adding use Word.new .. lines, will be added to the development database. It's important to distinguish the 'test' and 'development' database. The first one is only used during tests. The latter is used by default when you start the application.

Apply the migration with 'rake db:migrate'.

'rake test' now succeeds with the following:
'1 tests, 2 assertions, 0 failures, 0 errors'


6. Fixtures and test for word.random. Edit word_test again.
It's not easy to test a method which behaves randomly. Let's assume that it's enough to test that if we have only two words in our database then one of them should be called at least once per 10 calls.
fixtures :words
def test_random
results = []
10.times {results << Word.random.eng}
assert results.include?("yes")
end
Note the 'fixtures :words' line. Edit the 'words.yml' file.
yes:
id: 1
pl: 'tak'
eng: 'yes'
no:
id: 2
pl: 'nie'
eng: 'no'
This will be loaded to the test database before every run of tests. 7. Implement the Word.random method
def self.random
all = Word.find :all
all[rand(all.size)]
end
Warning: The code above could be slow for many words in a database (we retrieve all words only for selecting a random element). It's good enough for our needs. 8. Generate the Words controller with a 'learn' action
script/generate controller Words learn
9. Write a test for the learn method Just as there is a one-to-one ratio between unit tests and models, so there is between functional tests and controllers. The Controller's responsibility is to retrieve objects from the Model layer and pass them to the View. Let's test the View part first. We use the 'assigns' collection which contains all the objects passed to the View.
def test_learn_passes_a_random_word
get 'learn'
assert_kind_of Word, assigns('word')
end
10. Make the Test Pass
def learn
@word = Word.new
end
11. Write more tests in the words_controller_test How can we test that controller uses the Word.random method? We don't want to duplicate the tests for the Word.random method. Mocks to the rescue! We will only test that the controller calls the Word.random method. The returned value will be faked with a prepared word. Let's install the mocha framework:
gem install mocha
Now we can use 'expects' and 'returns' methods. 'expects' is used for setting an expectation on an object or a class. In this case we expect that the 'random' method will be called. We also set a return value by using 'returns' method. Setting a return value means faking (stubbing) the real method. The real Word.random won't be called. If an expectation isn't met the test fails.
require 'mocha'

def test_learn_passes_a_random_word
random_word = Word.new
Word.expects(:random).returns(random_word)
get 'learn'
assert_equal random_word, assigns('word')
end
'rake test' now fails. The Word.method wasn't called. 12. Rewrite the implementation
def learn
@word = Word.random
end
'rake test' now passes. 13. Test that a word is displayed: Extend the existing test with assert_tag calls.
def test_learn_passes_a_random_word
random_word = Word.new(:pl=>'czesc', :eng=>'hello')
Word.expects(:random).returns(random_word)
get 'learn'
assert_equal random_word, assigns('word')
assert_tag :tag=>'div', :child => /czesc/
assert_tag :tag=>'div', :child => /hello/
end
14. Implement the view - learn.rhtml
<div>
<%= @word.eng %>
<%= @word.pl %>
</div>
15. Manual testing
script/server
Go to 'http://localhost:3000/words/learn'. Refresh several times. Related articles ... and some more TDD steps with Rails Testing Rails controllers with mock objects If you want to read more about testing in Rails go to the Guide To Testing The Rails.

If you read this far you should Follow andrzejkrzywda on Twitter and subscribe to my RSS

Thursday, April 19, 2007

Test your web application with Selenium

Thanks to Skills Matter I attended a Selenium session tonight. Selenium is a tool for automated web testing. The session was led by Erik Doernenburg. He's a ThoughtWorker, so I knew the presentation was going to be good quality. Not only did he present what Selenium is, but he also presented many good techniques for using it with your project.
Some of my notes below:
  • All important browsers are supported
  • All important languages are supported
    • He used Java at the talk
  • SeleniumIDE is the tool for recording tests
    • It's not necessary to use it, you can write tests in your favourite language
    • SeleniumIDE is only for Firefox
  • You need to start the Selenium server in order to run tests
  • Many methods available:
    • createCookie
    • dragdrop
    • fireEvent
    • selenium.click("btnG")
    • assertTitle
  • There is a concept of locators
    • It means the way to locate your DOM elements
    • name, id, identifier
    • document.forms['myForm'].myDropDown
    • XPath
    • link text
    • css selectors, css = a[href="#id3"]
  • Selenium server sets the proxy in your browser, so your browser asks the server for everything
  • It's bad for performance testing
  • Good for testing multiple browsers
  • There was some great advice, which I think applies for all kinds of functional tests
    • "Test what you want to test"
    • Which means, if you have already tested creating users in a functional way, you don't need to test it functionally again
    • So, you can actually use your production code to change the state and then functionally test what you're actually testing
    • Saves time
    • Makes your tests less fragile
    • Cool!
  • It's a good pattern to have classes for all of your pages
    • WelcomePage
    • DailyViewPage
  • And one class for your test
    • AddItemToBasket
    • CreateUser
  • Your test classes will then use your page classes
    • welcomePage = WelcomePage()
    • welcomePage.setDailyView()
  • There are other ways of functionally testing your web application
    • HttpUnit which asks your web server, not the browser
    • Use a Presentation pattern
      • have only thin presentation layer
      • test only your "event handlers" methods
  • It's common practice for QA people to create Selenium scenarios
  • You can start your TDD session with a failing Selenium test

Thursday, March 8, 2007

Andrzej on Test Driven Development

Test Driven Development helps me creating better software. TDD is not only about testing. It's more about designing and managing scope. I'll try to show what I mean by using a simple example based on tabbedimages application.

Tabbedimages is a simple image viewer. We are going to add drag and drop feature to it.
I start with a requirement, which I analyze and spike for a working solution. Based on the analysis I create a user story and an automated acceptance test. The acceptance test "drives" me when I'll add unit tests and the production code.
  • Requirement
    • tabbedimages is a simple image viewer.
    • We want to add a new feature to tabbedimages.
  • Title.
    • 'Drag&drop support'.
    • TIP: It's good to have a short title for a user story.
  • Analysis: let's list things to worry about:
    • Single image files
    • Multiple image files
    • Directories
    • Non-image files
    • Already open image.
  • Spiking
    • TIP: This part should give us better understanding of the problem
      • If we're sure how to implement the new feature we can skip this step.
    • tabbedimages is implemented using IronPython and Windows Forms.
      • The code is availalable here.
    • Google for 'drag and drop windowsforms' and see some code examples.
    • Check out the fresh version of tabbedimages.
    • Try to add the required feature to our code base (without tests).
    • Discover that Windows Forms has support for DragDropEffect.
      • Which displays a 'plus' sign if the thing that we're trying to drag is acceptable.
    • Add the DragDropEffect to the list of things to worry about.
  • User story:
    • Marten wants to drag and drop his images from his desktop to tabbedimages.
    • He starts tabbedimages.
    • He then drags the 'Faye001.jpg' file over the application.
    • The plus sign appears.
    • He drops it.
    • A new tab is created with a label saying 'Faye001.jpg'
    • 'She's cute' he thinks
    • Marten realizes that there are more Faye's pictures.
    • He drags 'Faye001.jpg' (again) and 'Faye002.jpg'.
    • He drops them.
    • Two new tabs are created.
    • He drags and drops readme.txt file.
    • The message box appears saying 'readme.txt doesn't appear to be a valid image file'
    • He quits tabbedimages.
  • Functional test:
    • Write the ideal code (DSL-like) that follows the user story steps:
    • marten.starts()
      he.asserts_number_of_tabs(0)
      he.drags('Faye001.jpg')
      assert shows_plus()
      he.drops('Faye001.jpg')
      he.asserts_number_of_tabs(1)
      he.asserts_tab_labels(['Faye001.jpg'])
      fayes_pictures = ['Faye001.jpg', 'Faye002.jpg']
      marten.drags_and_drops(fayes_pictures)
      marten.drops(fayes_pictures)
      he.asserts_number_of_tabs(3)
      he.asserts_tab_labels(['Faye001.jpg', Faye001.jpg', Faye002.jpg'])
      he.drags_and_drops('readme.txt')
      he.sees_message_box("readme.txt doesn't appear to be a valid image file")
      he.quits()
  • Implementation
    • Run the Functional Test (FT).
    • Whenever FT fails or you can think of any edge cases not covered by FT:
      • Write appropriate Unit Test that reflects the problem.
      • Add the implementation to fix the problem.
After the last phase the drag & drop feature should be ready to use. Of course, this example presented a very simple problem. Hopefully, the Test Driven Development as shown here should be easy to understand. In the near future, I'll try to write more about the implementation phase, including some refactoring techniques, so stay tuned!