Testing Your Plugin - Redmine Plugin Extension and Development (2014)

Redmine Plugin Extension and Development (2014)

Chapter 8. Testing Your Plugin

Rails-based projects are structured to allow software tests to be easily incorporated. Most open source projects such as Redmine include tests, and contributors are requested (if not required) to submit tests with their patches. This is especially true for projects written in dynamic languages such as Ruby.

The Redmine core project has excellent test coverage, and if our plugin relies on the core features of Redmine, writing tests is a good way to quickly detect Redmine code changes.

The assumption while going into this chapter is that we are interested in writing tests that can be run in the same environment as the Redmine core project's test suite. Whether we feel that Test Driven Development is beneficial or detrimental to our project and what constitutes a good test are out of the scope of this book as they are extremely subjective topics.

For a good introduction to Test Driven Development as it relates to Rails applications, visit http://andrzejonsoftware.blogspot.ca/2007/05/15-tdd-steps-to-create-rails.html or see the Rails guide to testing applications at http://guides.rubyonrails.org/testing.html.

The following topics will be covered in this chapter:

· Laying out the test directory for your plugin

· Patching the test case classes to allow core and custom fixtures to coexist

· Rake tasks available for running tests

· An overview of the different types of supported tests

· Running tests

· Hooking our plugin into the Travis continuous integration system

Testing infrastructure layout

As we initially used the Redmine plugin generator (see Chapter 1, Introduction to Redmine Plugins) when we created our plugin, we should already have a skeletal test directory available for our plugin.

The basic structure of this test folder should be folders named fixtures, functional, and unit, and a test_helper.rb file.

$ tree test

test

├── fixtures

│ ├── kb_articles.yml

│ └── kb_categories.yml

├── functional

│ ├── articles_controller_test.rb

│ ├── categories_controller_test.rb

├── integration

│ ├── accessing_content_test.rb

├── test_helper.rb

└── unit

├── article_test.rb

└── category_test.rb

The test_helper.rb file that Redmine generates is populated with default configuration, which loads Redmine's main test helper.

Basics of test fixtures

Fixtures are basically just sample data we set up to be used with our tests. They are written as individual entries in YAML files, the files being named after the model they represent. For example, since our article model is stored in a physical kb_article.rb file, the associated fixture would be named kb_articles.yml and could contain something similar to the following:

one:

id: 1

category_id: 1

title: "Sample Article One"

summary: "Summary of Sample Article One"

content: "Lorem Ipsum …"

Each fixture is named (one, in the preceding example) and is then followed by an indented list of key/value pairs. For a much more detailed dive into fixtures, I would recommend the guide at http://guides.rubyonrails.org/testing.html#the-low-down-on-fixtures.

Note

For more information on Redmine's plugin generators, visit http://www.redmine.org/projects/redmine/wiki/Plugin_Tutorial#Creating-a-new-Plugin.

Working around a Redmine testing issue

This book is based on the Redmine 2.3.3 final release, and as such, the scenario described here may not be relevant in future versions of Redmine.

At the time of this writing, if any plugin fixtures are used, the test helper doesn't load them properly. We know our fixtures aren't loaded properly when we add a custom fixture to our test layout and get an error similar to the following on running our tests:

# Running tests:

[1/1] ArticlesControllerTest#test_index = 0.02 s

1) Error:

test_index(ArticlesControllerTest):

Errno::ENOENT: No such file or directory - /path/to/redmine/test/fixtures/kb_articles.yml

Thanks to the tip at http://www.redmine.org/boards/3/topics/35164?r=37565#message-37565, we can monkey patch ActionController::TestCase to work properly for us.

The necessary patch can just be added to our test_helper.rb:

module Redmine

module PluginFixturesLoader

def self.included(base)

base.class_eval do

def self.plugin_fixtures(*symbols)

ActiveRecord::Fixtures.create_fixtures(File.dirname(__FILE__) + '/fixtures/', symbols)

end

end

end

end

end

unless ActionController::TestCase.included_modules.include?(Redmine::PluginFixturesLoader)

ActionController::TestCase.send :include, Redmine::PluginFixturesLoader

End

Now that ActionController::TestCase is patched, we can include the Redmine core fixtures as well as our plugin's fixtures in our tests.

Note that we'll want to repeat the monkey patch for ActiveSupport::TestCase so the same functionality is available when we write our unit tests.

Running tests

Redmine offers some rake tasks to facilitate interacting with a plugin's test suite. These tasks are shown in the following command:

$ rake -T | grep plugins:test

rake redmine:plugins:test

rake redmine:plugins:test:functionals

rake redmine:plugins:test:integration

rake redmine:plugins:test:units

Running any of these rake tasks will run the tests for all installed plugins. In order to limit the tests for our plugin, we need to provide a NAME environment variable.

$ rake redmine:plugins:test:functionals NAME=redmine_knowledgebase

Run options:

# Running tests:

..

Finished tests in 0.118963s, 16.8119 tests/s, 16.8119 assertions/s.

2 tests, 2 assertions, 0 failures, 0 errors, 0 skips

The rake tasks for running plugin tests are standard Rake::TestTask instances (http://rake.rubyforge.org/classes/Rake/TestTask.html), so passing options through a TESTOPTS environment variable will work the same as if the parameters were provided directly.

$ rake redmine:plugins:test:functionals NAME=redmine_knowledgebase TESTOPTS="-v"

Run options: -v

# Running tests:

ArticlesControllerTest#test_truth = 0.10 s = .

CategoriesControllerTest#test_truth = 0.01 s = .

Finished tests in 0.115128s, 17.3719 tests/s, 17.3719 assertions/s.

2 tests, 2 assertions, 0 failures, 0 errors, 0 skips

Writing functional tests

Test cases that target our controller actions are referred to as functional tests. Web requests are received, and the desired response is generally a rendered view.

The Rails guide indicates that some ideal functional test types would be as follows:

· Whether a web request succeeded

· Whether the user was redirected to the correct page

· Whether the user was authenticated

· Whether the proper template was rendered as a response

· Whether the correct message shows in a view

As we'll be using test cases that derive from ActionController::TestCase (http://api.rubyonrails.org/classes/ActionController/TestCase.html), each functional test case should only test a single controller method.

Here is an example of a functional test for our ArticlesController:

require File.dirname(__FILE__) + '/../test_helper'

class ArticlesControllerTest < ActionController::TestCase

fixtures :projects, :roles, :users

plugin_fixtures :kb_articles, :enabled_modules

def setup

User.current = User.find(1)

@request.session[:user_id] = 1

@project = Project.find(1)

end

def test_index

Role.find(1).add_permission! :view_kb_articles

get :index, :project_id => @project.id

assert_response :success

assert_template 'index'

end

end

A few useful methods have been included in this code, which we'll briefly summarize as follows:

· fixtures: The fixtures method allows us to include fixtures from Redmine core's test suite (for example, :issues, :roles, :users, :projects, and so on)

· plugin_fixtures: This is the method we monkey-patched into the various TestCase classes so that we could interact with Redmine's fixtures as well as our own custom fixtures

· @request.session[:user_id] = 1: If we need to force membership into a project for a specific user, we provide it directly to the session

· Role.find(1).add_permission! :view_kb_articles: If we need the current user to have a particular permission in place in order to fulfil a request, we can explicitly add it

Writing integration tests

When we want to test more than one component and examine how they'll function together, or if we want to test the behavior, we write integration tests.

For a more in depth look at Rails integration tests, including what helpers are available, visit http://guides.rubyonrails.org/testing.html#integration-testing.

The following is an example of an integration test that accesses a category with an explicitly defined whitelist:

require File.dirname(__FILE__) + '/../test_helper'

class AccessingContentTest < ActionController::IntegrationTest

fixtures :projects, :users

plugin_fixtures :kb_articles, :kb_categories

def setup

@project = Project.find(1)

@user_1 = User.find(1)

@user_2 = User.find(2)

end

test "access category with an explicit whitelist defined" do

cat_wl = KbCategory.find(2)

assert !cat_wl.user_whitelist.blank?, "Category Whitelist expected to be populated"

assert !cat_wl.blacklisted?(@user_1), "User 1 is supposed to be whitelisted"

assert cat_wl.blacklisted?(@user_2), "User 2 is supposed to not be whitelisted"

end

end

This is not meant to be an example of how to write a good test, just a very basic integration test.

Writing unit tests

Unit tests within Ruby on Rails applications tend to involve writing tests for models. A good practice is to include tests for all validations, and at least one test per method. Ideally though, tests should be written for anything that could possibly break.

If we were writing the tests first, we would start with something like the following code:

require File.dirname(__FILE__) + '/../test_helper'

class CategoryTest < ActiveSupport::TestCase

plugin_fixtures :kb_categories

test "should not save category without title" do

category = KbCategory.new

assert !category.save, "Saved the category without a title"

end

end

If we had yet to configure our model, this test would fail until we added a presence validation to our category model.

Preparing a test database

If this is the first time tests are being run against Redmine, we'll need to first initialize the testing environment.

A test database should first be defined in the path /path/to/redmine/config/database.yml, and then the following rake tasks can be run to set up the database:

rake db:drop db:create db:migrate redmine:plugins:migrate redmine:load_default_data RAILS_ENV=test

The first command drops the tests database, creates a fresh one, and then runs the Redmine core migrations and all migrations for any installed plugins.

The second command is used to seed the test database with Redmine's default data. For a peek into what constitutes default data, see the contents of /path/to/redmine/lib/redmine/default_data/loader.rb.

Once the test database has been initialized, we can use the rake tasks introduced at the beginning of this chapter to run the tests for our plugin.

Note that running the full suite can take a bit of time, so if you're trying to just run a single test case, you can execute it directly.

As an example, if we wanted to run the ArticlesControllerTest test case for our knowledgebase plugin, we would execute the following command from the root of our Redmine installation:

ruby $(pwd)/plugins/redmine_knowledgebase/test/functional/articles_controller_test.rb

Continuous integration with Travis

Travis CI (https://travis-ci.org/) is a hosted continuous integration service for the open source community. It is integrated with GitHub and offers first class support for a number of languages, including Ruby.

Travis is generally meant to run tests against a standalone application, but since we're building plugins for Redmine, we'll need a bit of help in order to bootstrap the process.

Using the samples from https://github.com/alexbevi/redmine_plugins_travis-ci, we can configure our plugin to be tested against the latest version of Redmine.

In order to actually integrate our plugin with Travis, the sample files we downloaded from the repository we just mentioned needs to be added to our plugin's root folder, checked into our Git repository, and pushed to GitHub.

More information on the configuration of Travis CI Redmine helpers can be found at https://github.com/alexbevi/redmine_plugins_travis-ci/blob/master/README.md.

With the basic tests that we've written for our plugin, once Travis runs, we can check the results on whose website to check whether our tests passed or failed.

Continuous integration with Travis

The basic layout of a Travis CI test run is broken down into two sections. The first is basic information about which commit triggered the build and the status of the tests run against that build. The second is a build matrix that outlines some information about the test jobs that were run.

If our tests failed, we can drill down further using the build matrix, which lists the various Travis jobs that are associated with the current configuration we've defined for our test environment (visit http://docs.travis-ci.com/user/build-configuration/#The-Build-Matrix). The details provided here by Travis should be similar, if not identical, to what we were seeing when running the tests locally.

Continuous integration with Travis

Now that we have our plugin integrated with the Travis CI service, we can update our README.md file with a badge that indicates the current status of our plugin's tests.

Wherever we want the badge to appear within the README.md file, just add the following sample markdown formatted text:

[![Build Status](https://travis-ci.org/<user>/<project>.png)](https://travis-ci.org/<user>/<project>)

The <user> and <project> values should be replaced by your GitHub username and the project name on GitHub that represents the Redmine plugin we're working on. For our knowledgebase, we will be using alexbevi/redmine_knowledgebase.

Continuous integration with Travis

Summary

Test Driven Development and testing in general are very popular among Ruby and Ruby on Rails developers. The fact that any new Rails project that you generate automatically includes basic tests as part of the scaffolding and generators serves as some pretty good reinforcement of that.

This chapter was not meant to serve as an introduction to Test Driven Development or testing or even to try to reinforce the value of writing tests. If the testing tools provided by Rails are not suitable to our needs, there are numerous testing frameworks available that can be used instead. There are also a myriad breakdowns of what types of tests should be written for what types of scenarios and under what circumstances.

Redmine has very good code coverage and provides a lot of excellent examples of the basic test types in its own test/ directory. Our tests in this chapter were meant to be examples of how to tie plugin testing into Redmine's testing infrastructure and how Redmine test assets could be accessed therein.

In our final chapter, we'll be gaining some insight into the process of releasing our plugin to the Redmine community as well as some tips to encourage community participation.