Skip to content

English plugin dev 1 5

semuel edited this page Nov 14, 2011 · 15 revisions

Test Driven Development of Plugins

Introduction

Here we will see how to test your function-tags. which of course brings us to the question:

What are function-tags?

Function-tag is the code responsible for taking a tag, i.e. “<MTEntryTitle>” and replacing the tag with the requested info.

We will make a simple tag, just to see how it works.

What is Test-Driven Development?

Traditionally the testing phase came after the development ended. However, in that method it is difficult to know where the bug is when we get an error. Plus it is possible that a bug in the beginning in the development will require change a lot of code that was written afterwards. it is easier to catch and correct bug as soon as possible.

Test Driven Development makes us write the test even before we write the code, from the spec. then we write the code to make these tests pass.

In Movable Type we have a suite of tests, so we can make sure that new code does not break old features. And you can do it in your plugin too

Creating a test case for the plugin

Test Target

We will make a plugin (MyPlugin03) that have a tag “<MTHelloWorld>” that just prints “Hello, world!”

<MTHelloWorld> => Hello, world!

Setting up testing environment

Before we write a test, we need a testing environment. It is generally not a good idea to test your code against the production database, as it can overload it or delete important information by accident.
The instructions below were written for CentOS5, (which is a Linux distribution) and may be a bit different on your machine.

  • Download the code from Github
$ git clone git://github.com/movabletype/movabletype.git
  • Install the Test::Deep Perl module
# cpan Test::Deep

  The following dependancy modules will be installed. (cpan may ask for your consent)

    Test::Tester
    Test::NoWarnings
    Test::Builder
  • Prepare the Database
mysql> CREATE DATABASE mt_test;
mysql> GRANT ALL PRIVILEGES ON mt_test.* TO mt@localhost;
  • Install php-mysql and php-gd using the yum command
# yum install php-mysql php-gd

Test files location

Please create a “t” directory for your tests under the plugin’s script directory

i.g.)00-compile.t, 01-tags.t

$MT_DIR/
|__ plugins/
   |__ MyPlugin03/
      |__ t
         |_ 00-compile.t
         |_ 01-tags.t

Test case #0 – class loading test, 00-compile.t

Our first test case verify that the plugin’s modules can be loaded at all

use strict;
use lib qw( t/lib lib extlib );
use warnings;
use MT;
use Test::More tests => 5;
use MT::Test;

ok(MT->component ('MyPlugin03'), "MyPlugin03 plugin loaded correctry");

require_ok('MyPlugin03::L10N');
require_ok('MyPlugin03::L10N::ja');
require_ok('MyPlugin03::L10N::en_us');
require_ok('MyPlugin03::Tags');

1;

This test checks the following:

  1. Was MyPlugin03 successfully loaded?
  2. Can the MyPlugin03::L10N module be ‘require’-ed without error?
  3. Can the MyPlugin03::L10N::ja module be ‘require’-ed without error?
  4. Can the MyPlugin03::L10N::en_us module be ‘require’-ed without error?
  5. Can the MyPlugin03::L10N::Tags module be ‘require’-ed without error?

The line “use Test::More tests => 5;” tells the testing framework to expect five tests. So if the test crashes halfway, it will know to tell you that not all the tests ran

Test case #1 – Testing tag behavior, 01-tags.t

As this test’s supporting code is quite long, we don’t include all of it here, just the important part. You can find the complete example in the complete example download at the end of this page.
The parts that you need to edit are between the “#= Edit here” line and the “#=” mark.

The test checks your tags in both the Perl publisher and the PHP publisher. This is why the tests are written in JSON – a data representation that can be used by both languages

In the test below, three things are tested:

  1. For empty string we expect empty response
  2. For the “<MTHelloWorld>” tag we expect to receive “Hello, world!”
  3. For the “<MTHelloWorld lower_case=”1">" tag we expect to receive hello, world!"

Note:

  • If you have test that your want to disable for now, change the ‘r’ parameter to 0: ‘{"r" : “0”,’
  • The last line of the JSON data can not have a comma
  • You can extend this test by adding more lines to the JSON data
use strict;
use warnings;
use IPC::Open2;

BEGIN {
    $ENV{MT_CONFIG} = 'mysql-test.cfg';
}

$| = 1;

use lib 't/lib', 'lib', 'extlib';
use MT::Test qw(:db :data);
use Test::More;
use JSON -support_by_pp;
use MT;
use MT::Util qw(ts2epoch epoch2ts);
use MT::Template::Context;
use MT::Builder;

require POSIX;

my $mt = MT->new();

#===== Edit here
my $test_json = <<'JSON';
[
{ "r" : "1", "t" : "", "e" : ""},
{ "r" : "1", "t" : "<MTHelloWorld>", "e" : "Hello, world!"},
{ "r" : "1", "t" : "<MTHelloWorld lower_case=\"1\">", "e" : "hello, world!"}
]
JSON
#=====

$test_json =~ s/^ *#.*$//mg;
$test_json =~ s/# *\d+ *(?:TBD.*)? *$//mg;

(excerpt)

Running the tests

We haven’t written anything that the tests are trying to verify, so lets run them and see what happens:

  $ cd $MT_DIR
  $ perl plugins/MyPlugin03/t/00-compile.t 
  1..5
  not ok 1 - MyPlugin03 plugin loaded correctry
  #   Failed test 'MyPlugin03 plugin loaded correctry'
  #   in plugins/MyPlugin03/t/00-compile.t at line 11.
  not ok 2 - require MyPlugin03::L10N;
  #   Failed test 'require MyPlugin03::L10N;'
  #   in plugins/MyPlugin03/t/00-compile.t at line 13.
  #     Tried to require 'MyPlugin03::L10N'.
  #     Error:  Can't locate MyPlugin03/L10N.pm in @INC (以下略)

  $ perl plugins/MyPlugin03/t/01-tags.t 
  1..7
  ok 1 - 'blog-name' template found
  ok 2 - Test blog loaded
  ok 3 - Test entry loaded
  
  ok 4 - perl test 1
  # -- error compiling: <MTHelloWorld> at line 1 is unrecognized.
  Use of uninitialized value in concatenation (.) or string at plugins/MyPlugin03/t/01-tags.t line 87.
  (snip)

As expected, almost everything ended up as errors. (except some setup-tests in the second test)

As it is no fun to run tests one by one, or to hunt down errors in a long output, we use the ‘prove’ command to do all this for us. just tell it which files to run (wildchars are OK) and it will run them all and output a report in the end. then you can scan the output for errors, as you already know where they are

  $ prove plugins/MyPlugin03/t/*.t
  plugins/MyPlugin03/t/00-compile....NOK 1                                     
  #   Failed test 'MyPlugin03 plugin loaded correctry'
  #   in plugins/MyPlugin03/t/00-compile.t at line 11.
  plugins/MyPlugin03/t/00-compile....NOK 2                                     
  #   Failed test 'require MyPlugin03::L10N;'
  #   in plugins/MyPlugin03/t/00-compile.t at line 13.
  #     Tried to require 'MyPlugin03::L10N'.
  (中略)
  plugins/MyPlugin03/t/01-tags.......dubious                                   
          Test returned status 2 (wstat 512, 0x200)
  DIED. FAILED tests 5-6
          Failed 2/7 tests, 71.43% okay
  Failed Test                       Stat Wstat Total Fail  Failed  List of Failed
  -------------------------------------------------------------------------------
  plugins/MyPlugin03/t/00-compile.t    5  1280     5    5 100.00%  1-5
  plugins/MyPlugin03/t/01-tags.t       2   512     7    2  28.57%  5-6
  Failed 2/2 test scripts, 0.00% okay. 7/12 subtests failed, 41.67% okay.

Developing a function-tag – Perl

We start from the plugin created in the last chapter, MyPlugin02

config.yaml

For adding our <MTHelloWorld> tag, we need to add to the config.yaml the record: “tags”=> “function”=> “Tag name” => $PluginName::Handler

id: MyPlugin03
key: MyPlugin03
name: <__trans phrase="Sample Plugin Test Driven">
version: 1.0
description: <__trans phrase="_PLUGIN_DESCRIPTION">
author_name: <__trans phrase="_PLUGIN_AUTHOR">
author_link: http://www.example.com/about/
doc_link: http://www.example.com/docs/
l10n_class: MyPlugin03::L10N

tags:
    function:
        HelloWorld: $MyPlugin03::MyPlugin03::Tags::_hdlr_hello_world

L10N.pm

For now, in the localization modules, only the name of the plugin was changed:

package MyPlugin03::L10N;
use strict;
use base 'MT::Plugin::L10N';

1;

L10N/en_us.pm

Likewise, here too we changed the plugin name, but also the description

package MyPlugin03::L10N::en_us;

use strict;
use base 'MyPlugin03::L10N';
use vars qw( %Lexicon );

%Lexicon = (
    '_PLUGIN_DESCRIPTION' => 'Sample Test Driven test plugin',
    '_PLUGIN_AUTHOR' => 'Plugin author',
);

1;

L10N/ja.pm

And similar changes to the Japanese translation file:

package MyPlugin03::L10N::ja;

use strict;
use base 'MyPlugin03::L10N::en_us';
use vars qw( %Lexicon );

%Lexicon = (
    'Sample Plugin Test Driven' => 'サンプルプラグイン テストドリブン',
    '_PLUGIN_DESCRIPTION' => 'テストドリブン テストプラグイン',
    '_PLUGIN_AUTHOR' => 'プラグイン作者',
);

1;

Tags.pm

A new module, with a function _hdlr_hello_world. this function will be called by MT when it encounter the “<MTHelloWorld>” tag, and we want it to return “Hello, world!”

package MyPlugin03::Tags;
use strict;

sub _hdlr_hello_world {
    my ($ctx, $args) = @_;

    return "Hello, world!";
}

1;

Current directory structure

$MT_DIR/
|__ plugins/
   |__ MyPlugin03/
      |__ config.yaml
      |__ lib/
      |  |_ MyPlugin03/
      |     |__ L10N.pm
      |     |_ L10N/
      |     |  |_ en_us.pm
      |     |  |_ ja.pm
      |     |_ Tags.pm
      |__ t/
         |_00-compile.t
         |_01-tags.t

Developing a function-tag – PHP

now lets not forget our PHP-loving friends, and support dynamic publishing

function.mthelloworld.php

The file name expressing that this is a function tag, whose name is ‘MTHelloWorld’

<?php
    function smarty_function_mthelloworld($args, &$ctx) {
        return 'Hello, world!';
    }
?>

The file name and function name is according to the smarty PHP framework. It will be used when this tag will be encountered

Current directory structure

$MT_DIR/
|__ plugins/
   |__ MyPlugin03/
      |__ config.yaml
      |__ lib/
      |  |_ MyPlugin03/
      |     |__ L10N.pm
      |     |_ L10N/
      |     |  |_ en_us.pm
      |     |  |_ ja.pm
      |     |_ Tags.pm
      |__ php/
      |  |_function.mthelloworld.php
      |__ t/
         |_00-compile.t
         |_01-tags.t

Test Running

Lets test the new plugin

00-compile.t

$ cd $MT_DIR
$ perl plugin/MyPlugin03/t/00-compile.t
perl plugins/MyPlugin03/t/00-compile.t 

1..5
ok 1 - MyPlugin03 plugin loaded correctry
ok 2 - require MyPlugin03::L10N;
ok 3 - require MyPlugin03::L10N::ja;
ok 4 - require MyPlugin03::L10N::en_us;
ok 5 - require MyPlugin03::Tags;

01-tags.t

$ perl plugins/MyPlugin03/t/01-tags.t
1..7
ok 1 - 'blog-name' template found
ok 2 - Test blog loaded
ok 3 - Test entry loaded

ok 4 - perl test 1
Hello, world!
ok 5 - perl test 2
hellow, world!
ok 6 - perl test 3
ok 7 - ok - php test 1 ok - php test 2 ok - php test 3 

Or with the prove command

$ prove plugins/MyPlugin03/t/*.t
plugins/MyPlugin03/t/00-compile....ok                                        
plugins/MyPlugin03/t/01-tags.......ok                                        
All tests successful.
Files=2, Tests=12, 25 wallclock secs (14.38 cusr +  4.03 csys = 18.41 CPU)

Understanding test errors

Now everything rosy, and all test passed. but sometimes there are bugs, and we get errors. Lets change the test files a bit, so we can get errors and explain them

Before the change

#===== Edit here
my $test_json = <<'JSON';
[
{ "r" : "1", "t" : "", "e" : ""},
{ "r" : "1", "t" : "<MTHelloWorld>", "e" : "Hello, world!"},
{ "r" : "1", "t" : "<MTHelloWorld lower_case=\"1\">", "e" : "hello, world!"}
]
JSON
#=====

After the change

#===== Edit here
my $test_json = <<'JSON';
[
{ "r" : "1", "t" : "", "e" : ""},
{ "r" : "1", "t" : "<MTHelloWorld>", "e" : "Hello, World!"},
{ "r" : "1", "t" : "<MTHelloWorld upper_case=\"1\">", "e" : "hello, world!"}
]
JSON
#=====

We changed ‘lower_case’ to ‘upper_case’, and made the ‘w’ in the second JSON line to upper ‘W’.

Test Output

$ perl plugins/MyPlugin03/t/01-tags.t 
1..7
ok 1 - 'blog-name' template found
ok 2 - Test blog loaded
ok 3 - Test entry loaded

ok 4 - perl test 1
Hello, world!
not ok 5 - perl test 2
#   Failed test 'perl test 2'
#   in plugins/MyPlugin03/t/01-tags.t at line 88.
#          got: 'Hello, world!'
#     expected: 'Hello, World!'
HELLO, WORLD!
not ok 6 - perl test 3
#   Failed test 'perl test 3'
#   in plugins/MyPlugin03/t/01-tags.t at line 88.
#          got: 'HELLO, WORLD!'
#     expected: 'hello, world!'
ok 7 - ok - php test 1 not ok - php test 2\#     expected: Hello, World!\#          
got: Hello, world!not ok - php test 3\#     expected: hello, world!\#          got: HELLO, WORLD!
# Looks like you failed 2 tests of 7.

Looking at the results reveals that both Perl and PHP has encountered the following error.

  1. In test 2 “Hello, World!” was expected but “Hello, world!” came back
  2. In test 3 “hello, world!” was expected but “HELLO, WORLD!” came back

Summary

I know, I know. creating tests is a boring task, and you want to zoom ahead and create your cool plugin. But with each test that you create and run, you gain confidence that it is working OK, and there whatever changes you did right now did not corrupted existing functionality.

And never forget to test your tests – by changing them a bit and see them failing. this helps eliminate the possibility of wrong tests that just pass without actually testing what is important to you.

Please release the tests with the plugin itself. It may help your users, when debugging a problem, to verify that the plugin is installed and working as intended

Plugin Download

MyPlugin03.zip(5.87KB)

Navigation

Prev:Plugin localization << Index >> Next:Developing Global Modifier Plugin

Clone this wiki locally