MarkUs Blog

MarkUs Developers Blog About Their Project

Unit Testing

with one comment

“The primary goal of unit testing is to take the smallest piece of testable software in the application, isolate it from the remainder of the code, and determine whether it behaves exactly as you expect”1. Unit tests are done before integrating the unit into the system to assure quality and save time due to errors.

While unit testing, other units are considered bug free. Unfortunately, we don’t live in a perfect world and it’s impossible to ensure bug free units. For this reason, other units can affect the behavior of the tested unit. To solve this situation, isolation is required and since all the unit tests have to run under 5 minutes, it also helps to boost the speed.

In this example, while testing the Student class, calls done to the Repository, Grouping and Database have to be stubbed and mocked.

Unit Test Isolation
All the next examples are coded in Ruby and will use the mocha framework.
http://mocha.rubyforge.org
How to test
Assertions are used to test the behavior of the code.
For example, this is the test of a method changing the “hidden” attribute of the provided students to true.

def test_hide_students
student1 = users(:student1)
student2 = users(:student2)

student_id_list = [student1.id, student2.id]

Student.hide_students(student_id_list)

students = Student.find(student_id_list)

# The test fails if the assertion is false
assert_equal(true, students[0].hidden)
assert_equal(true, students[1].hidden)
end

How to achieve isolation

Stub objects are used to bypass a call and return the expected result. For example, to test this method in the Student class, stubs are required.

def self.hide_students(student_id_list)
...
# A stub will bypass the call to repository_external_commits_only?
#to return false or true depending on the test case.
if group.repository_external_commits_only? &&membership.grouping.is_valid?
begin
group.repo.remove_user(student.user_name) # revoke repo permissions
rescue Repository::UserNotFound
# ignore case when user isn't there any more
end
...
end

In the previous code, to ensure the students are removed from the repository, stubs are used to enter the “if” condition.

Here is the code testing this method :

def test_hide_students_repo_remove_user
student1 = users(:student1)
student2 = users(:student2)

# Stubs to enter into the if
# Calls to the method “repository_external_commits_only?”
for any Group instance will return true
Group.any_instance.stubs(:repository_external_commits_only?).returns(true)
Grouping.any_instance.stubs(:is_valid?).returns(true)

student_id_list = [student1.id, student2.id]
assert Student.hide_students(student_id_list)
end

How to guarantee behavior with mock objects
Mock objects are used to assure the unit calls an expected method with expected arguments.

Let’s go back to the previous example, hide_students(student_id_list) in the Student Class. To guarantee the method removes the correct user from the repository, a mock object expecting to be called will be used.

To confirm this method is called :

group.repo.remove_user(student.user_name) # revoke repo permissions

a mock object is used :

# Mock the repository and expect :remove_user with the student's
mock_repo = mock('Repository::AbstractRepository')

#Stub to return true
mock_repo.stubs(:remove_user).returns(true)
mock_repo.expects(:remove_user).with(any_of(student1.user_name, student2.user_name)).at_least(2)
Group.any_instance.stubs(:repo).returns(mock_repo)

During the test, if “remove_user” is not called at least 2 times with either “student1.user_name” or “student2.user_name”, the test fails.

Advantages
-It helps to think about all the possible cases;
-It is easier to refactor the code when a net (tests) can catch all the possible errors;
-Boost the confidence in your code.

Best practices
-Never commit code that isn’t tested;
-Always run all the tests before committing;
-If you change something and the tests fail, correct the situation;
-Clean tests are the best!

Examples in MarkUs
Look at student_test.rb and annotation_categories_controller_test.rb

Useful links
Unit testing
XUnit Patterns
Mocks aren’t stubs

Frameworks used in MarkUs :
Shoulda
Mocha

All the next examples are coded in Ruby and will use the mocha framework.
http://mocha.rubyforge.org/
1 Unit Testing – http://msdn.microsoft.com/en-us/library/aa292197(VS.71).aspx

Written by simonlg

October 3rd, 2009 at 12:51 am

Posted in Uncategorized

One Response to 'Unit Testing'

Subscribe to comments with RSS or TrackBack to 'Unit Testing'.

  1. Thanks Simon!

    Tara Clark

    7 Oct 09 at 5:38 pm

Leave a Reply