Showing posts with label ruby on rails. Show all posts
Showing posts with label ruby on rails. Show all posts

Saturday, June 23

RSpec: TDD 1

Test Driven-Development, is used to write test for desired functions of your program before you write actual code. General steps would be:

  1. write test code so that they will fail when executed (since you haven't implemented your function)
  2. write the simplest function code to make the test pass
  3. after passing all test, try to fill and refactor your function code

In this case, your tests would lead you through development, to write your functions. Assuming your tests are consistent with your requirement, TDD will reduce bugs in your code because they ensure that you are building the program correctly.

In Ruby, RSpec is used to do this TDD task. Actually it is also involved in BDD, that is why I think TDD and BDD should be worked together. In fact I am not 100% sure what is TDD, what is BDD. Anyway, in rspec, tests are mostly like this:


This is a test for the function to search movie titles in the TMDB. Line 3 tells us it is a test for MoviesController, a controller file. Line 4 is to indicate our desired function, to "search TMDb". Then line 8, 18, 21, sentences that started with "it" are actually desired behaviors for our function. They will represent three blocks as shown, and each of the block will contains a series of smaller test. Note by this time we don't have any codes to realize such behaviors. It looks a lot like Cucumber syntax, but somehow more concise, more focus on general i/o than detail steps. Line 5-7 is a block that would be executed every time in the following three blocks. These are similar to background steps in Cucumber. Inside the before block, we create a fake result using mock method, which create 2 fake Movie object. I think this is another play of convention over configuration because the method does not explicitly state Movie, maybe this is test for MoviesController so it automatically creates Movie object. These fake objects are used to test whether there will be a method called.

In line 8-12, it is the first test, to "call the model method that perform TMDb search". Like I say above, to pass the test, we should have a model method, also if we pass some parameters it could return something, that is basically what line 9-10 says. Note although here is a model method, but we test on our controller. Thus, we have to include a call to a model method (it even does not need to exist in models.rb) in controller explicitly. What is more, even you have a model method with this name in the model file, it will get overwritten by RSpec during the test. All we do is only to pass the test. Line 11 is the action, to make a post request with given parameters.

After the first block, you could see line 13-24 is actually a nested block contains 2 tests. Because they have common steps so we create this to avoid duplicate code. Note in both 2 tests, the requirement turns from should_receive to stub. Stub also creates a model method, but it does not require it to be called. We use it because in the latter 2 tests we don't care about whether the method is called or not ( it is the 1st test's job). In 2nd test for example, we only care about if the corresponding search tmdb template is being rendered or not. For the 3rd test, we use assign method to get whatever the program send to the instance variable @movies (again, convention), because we want to know if search results are correctly sent to the template.

At lase, to make the test run we use rspec spec_file_name, or execute autotest at the project root, so that all tests would be executed automatically every time you change codes that would affect the test result. Also make sure there is a database for test; TDD of course belongs to the test environment.

Codes are not hard. But some concepts are tricky. I will continue tomorrow.

Thursday, June 14

BDD and Cucumber


Behavior Driven Development (BDD) to me is like we develop the software to achieve some specific behaviors that are derived from customers requirements. It is popular because it clears the goal of development: what you need to do is to make the software behaves as required.

There are generally three steps.
1. engineers and customers work together to create user stories. They often look like this:
Feature: Add a movie to Rotten Potatoes
  As a movie fan [a kind of stakeholder]
  So that I can share a movie with other movie fans [achieve some goals]
  I want to add a movie to Rotten Potatoes database [by doing some tasks]
They focus on "what could the software do" but with more details. Actually there is a theory called SMART user story that requires participants to create stories that are Specific, Measurable, Achievable, Relevant and Timeboxed.

2. expand user stories into steps that involve several scenarios and lo-fi UIs. The former is specific to the Cucumber, the BDD tool used by Rails. In Cucumber, an user story is defined by several steps like:

Feature: User can manually add movie
Scenario: Add a movie
  Given I am on the RottenPotatoes home page
  When I follow "Add new movie"
  Then I should be on the Create New Movie page
  When I fill in "Title" with "Men In Black"
  And I select "PG-13" from "Rating"
  And I press "Save Changes"
  Then I should be on the RottenPotatoes home page
  And I should see "Men In Black"
A feature could behave differently in different scenarios, that is why we could have multiple scenarios in one feature. Lo-fi UIs are sketches to achieve two goals: (1). to obtain a look for this feature; (2). to connect this feature with others.

3. Iterate cucumber and improve your code until it accepts all steps.

For the course SaaS, it actually teaches us BDD before TDD, result in that we are building features based on what we code instead of building code based on features. Therefore in this post, I only write about how cucumber works.

Cucumber is actually a gem that needs to be included in your Rails project. By including that we will have a new directory called features, in which we store .feature files. Features directory has a subfolder called step_definitions, in which we store .rb files that define every step we will take. The step definition uses regular expression to catch a step in feature file, and then define the acton to test this step.

Some notes:

1. At the first time to execute cucumber, you have to create a test database by rake db:test:prepare. Cucumber runs in the test environment of Rails.
2. To reuse and compress the code, when different scenarios have several steps the same, they could be extracted into a new section called Background under that feature.
3. In Cucumber you are not dealing with objects and data you created for production or development environment anymore. Every time it runs it generates data it needs.
4. To debug in cucumber, set debugger flag as usual in rb files. In debug mode, use page.body, which is the HTML page generated by Capybara (the test tool used by Cucumber) to check your data instead of model object.