The Bumblebee Documentation

Index

Bumblebee 1.1.0 documentation

Bumblebee is a framework that builds on top of JUnit to create documentation by example. It is open source and Apache 2 licensed.

A quick example

Bumblebee takes code like this:

public class ASimpleExample {
    @Test
    public void createTimeReporter() throws Exception {
        /*!
        To create a time reporter object, simply invoke the *default constructor*
        */
        TimeReporter timeReporter = new TimeReporter();
        /*!*/
    }
}

and turns it into documentation like this:


A simple example

Create time reporter

To create a time reporter object, simply invoke the default constructor

TimeReporter timeReporter = new TimeReporter();

Bumblebee is especially suited for creating documents where code snippets or runtime data are necessary. Some of the features and benefits:

The general idea is to retrieve as much human-friendly documentation out of your code base as possible, with

The documentation is an abstracted view of the system, generated from the details. This differs Bumblebee from many other approaches to code-integrated documentation that tries to create details from an abstract view which is difficult, at best. This document, the Bumblebee documentation, is created from the acceptance test suite of Bumblebee.

Bumblebee can also be used to document Web GUIs (preferably using Selenium) and Swing GUIs, please see examples posted at the Bumblebee Google Group

This release: 1.1.0

See the Roadmap section for current and coming features, as well as some information about the Bumblebee project.

Getting support

There is a Bumblebee Google Group where you can post questions or see what others have had issues with, and how their issues were resolved.

About this document

Since the purpose of the documentation is to demonstrate how to use the different features of Bumblebee, most sections will have a structure where the method that is the base for the sections is presented as code, and it is the same method that is actually rendering that section. This can be a bit awkward/meta/recursive, but you will get the hang of it.

Configuration

Getting bumblebee

The latest version of Bumblebee is available at the Bumblebee download area at Agical

There is a bumblebee-all jar including Bumblebee and all dependencies. The bumblebee-all-no-junit jar contains everything except the JUnit jar. The bumblebee-all zip file contains the condensed version of the entire Bumblebee source tree, including all the dependencies.

The entire codebase is available as open source at Bumblebee Launchpad Bazaar DVCS

Setting bumblebee up

Create a JUnit4 test suite like this:

package com.agical.bumblebee.acceptance.examples.simple;

import org.junit.runner.RunWith;
import org.junit.runners.Suite.SuiteClasses;

import com.agical.bumblebee.collector.BumblebeeCollectors;
import com.agical.bumblebee.junit4.BumbleBeeSuiteRunner;
import com.agical.bumblebee.ruby.RubyCollector;

@RunWith(BumbleBeeSuiteRunner.class)
@SuiteClasses({TheSimplestTestCase.class})
@BumblebeeCollectors({RubyCollector.class})
public class RubyCollectorTestSuite {
    /*!!
    #{configuration.target_file='target/site/simple-setup.html';''}
    */
}

In this example there are some things that are not as the standard JUnit4 test-suite setup:

  1. @RunWith is configured with the Bumblebee runner
  2. @BumblebeeCollectors has to be configured with the desired Bumblebee collectors (more on collectors below).

@SuiteClasses is configured as for any other suite.

Create the JUnit test case and run the suite. If everything is correctly setup you will get the documentation in: target/site/simple-setup.html. And you are up and running!

The ruby collector

The RubyCollector used in the example allows you to:

  1. Add text to the document by adding comments to your code
  2. Include code snippets easily
  3. Format the text easily using the muse wiki-syntax
  4. Use serializable values from execution in your documentation
  5. Include whole or parts of texts from files

...and generally use Ruby or Java thanks to the JRuby interpretation of the comments.

Configuring sources

By default, it is assumed that the sources are structured according to the Standard Directory Structure if you want to include code snippets. If that doesn't suit your needs just make your own subclass of the RubyCollector and change or add source folders to fit your needs. Use the constructor that takes a File[]

You will have to do this if you want to include sources from other projects than the current.

Bumblebee will also assume that it is run in the root of the project. If not, you will also have to specify for Bumblebee where your sources are.

Document target name and directory

The default target for the documentation is target/site/documentation.html, but if you want to change that you can put a line like the following in the Bumblebee comment of your root suite:

    #{configuration.target_file='target/site/bumblebee_doc.html';''}

Stylesheet

Bumblebee will provide a default stylesheet, but if you want to have a special stylesheet, add a line like this:

#{configuration.stylesheet='src/site/css/mystylesheet.css'}

It will be copied to the same folder as the target_file and linked to in the document.

Bumblebee will always write the bumblebee-default-stylesheet.css to the target folder, and you can include it and extend it from your CSS like this:

@import url(bumblebee-default-stylesheet.css);

pre {
	  background: #bbbbee;
}

Images and other resources

To copy more resources to e.g. the output folder, use the Bumblebee copy utility:

#{copy('src/site/dummyresource.txt', 'target/tmp/dummyresource.txt')}

It will create the output directory if it doesn't exist.

The Ruby API can of course be used for more advances tasks.

Using a nested suite structure

If you have a nested suite structure, subsuites need not specify any runner, just the SuiteClasses annotation. Bumblebee will assume them to be Bumblebee subsuites since they reside within a Bumblebee suite.

This is the (somewhat obvious) way to do it:

package com.agical.bumblebee.acceptance.helpers.extension;

import org.junit.runners.Suite.SuiteClasses;

@SuiteClasses(AddRubyExtensions.class)
public class ExtendingBumblebee {
    /*!!
    There are several ways in which to extend Bumblebee.
    #{assert.contains 'Extending bumblebee', 'De-camelcasing of suite class name'}
    */
}

Previously, sub suites were configured with the @RunWith(BumbleBeeSubSuiteRunner.class). Keeping that configuration won't render any errors at the moment, but the class will eventually be removed.

Using maven

If you are a Maven2 user, add http://www.agical.com/maven2 to your repository list and the following dependency to your pom.xml

<dependency>
    <groupId>com.agical.bumblebee</groupId>
    <artifactId>bumblebee-all</artifactId>
    <version>1.1.0</version>
    <scope>test</scope>
</dependency>

The agile dox collector

The AgileDoxCollector is a simple Bumblebee implementation that just prints out the de-camelcased names of the suites, test classes and tests methods in an HTML list item hierarchy. If used as is, it produces the HTML-file target/site/agile-dox.html, and it can be configured to write the file anywhere on your file system by extending it and calling the superclass constructor with the desired output file. The AgileDoxCollector cannot retrieve any comments, in order to do so you must use the RubyCollector.

Demonstrating method comments

One of the key points with Bumblebee is to keep the code close to the comments, and to let the comments become the documentation. With that in mind, the most natural place to keep the comments is of course in the methods. Comments can also reside on a test-class or suite-class level.

This section is a demonstration of basic usage of method comments together with the Ruby execution of the comments.

Probably the simplest comment

This is the simplest comment possible

The simplest comment explained

This is the code that generated the section above:

@Test
public void probablyTheSimplestComment() throws Exception {
    /*!
    This is the simplest comment possible
    */
}

It will simply output the comment text as it is, and the section header is the de-camelcased version of the method name.

Make assertions about the output

It is possible to make assertions about what the resulting document should contain. This is an example from the previous section:

@Test
public void theSimplestCommentExplained() throws Exception {
    /*!
    This is the code that generated the section above:
    >>>>
    #{clazz.probablyTheSimplestComment}
    <<<<
    It will simply output the comment text as it is, and the section header is the 
    de-camelcased version
    of the method name.
    #{assert.contains 'This is the simplest' +' comment possible', 'Can include comments'}
    */
}

Asserts will be present here and there in the documentation, since the documentation also serves as acceptance tests for Bumblebee.

To avoid having the assertion match its own assertion string (the underlying comments are present as part of the document) the '+' is added in the assertion string through-out the document. Assertions are made on the wiki text produced by the comments in the current method, not globally on the document (this has changed between the 0.3 and 0.4 releases).

To produce a bounded code box, write >>>> on a line by itself, followed by one or more lines with snippet text (or calls), followed by <<<< on a line by itself.

A note on mixing testing and documentation

Some people argue that testing and documenting are separate responsibilities. However, splitting them up would result in a lot of duplication, thus breaking the DRY (don't repeat yourself) principle.

The SRP (single responsibility principle) is often defined as a module should only have one reason to change, and by that definition sharing documentation and acceptance testing could be done in the same module. Since the the correlation is strong between the documented features and the tested acceptance features, and splitting them up would result in breaking the DRY principle, tests and documentation is in one place in Bumblebee.

Override default header

By default, Bumblebee will generate a section header by de-camelcasing the method name or the simple name of the class (when there is no method, e.g. for test suites). To override this for occasional headlines, just set the header.

For this section the title would have been 'Name that will be overridden' but instead we set it to 'Override default header'. This is how you override the method name:

@Test
public void nameThatWillBeOverridden() throws Exception {
    /*!
    By default, Bumblebee will generate a section header by de-camelcasing the 
    method name or the simple name of the 
    class (when there is no method, e.g. for test suites).
    To override this for occasional headlines, just set the header. 
    #{set_header 'Override default header'}
    
    For this section the title would have been 'Name that will be  overridden' 
    but instead we set it to 'Override default header'.
    This is how you override the method name:
    >>>>
    #{meth}
    <<<<
    #{assert.not_contains 'Name'+' that will be overridden', 'Can replace headlines'}
    */
}

Excluding methods from documentation

Sometimes you don't want all of your tests to be part of your documentation, but you still want the tests to run. In those cases you can use the @Exclude annotation, like in:

@Test
@Exclude
public void excludedMethod() throws Exception {
    /*!
    #{raise Exception.new('I must not be executed')}
    But the test must be run.
    */
    excludedMethodRan = true;
}

This method won't make it into the documentation, but it will be run by JUnit.

If you want the test to be ignored altogether, just use the JUnit @Ignore annotation

@Test
@Ignore
public void ignoredMethod() throws Exception {
    /*!
    #{raise Exception.new('I must not be executed')}
    And the test must not be run.
    */
    ignoredMethodRan = true;
}

Getting java snippets

The Java snippet extraction DSL

The DLS helps extracting data from various sources of information. "Out-of-the-box" it currently supports getting various parts of source code, but since it is written in Ruby it can be easily extended at any level to customize functionality. This section focuses on getting Java-code into your documentation.

Auto snippeting

Bumblebee will include as snippets any code in the test methods that resides between comments in a test case (new feature from 1.0.2). The resulting document will more resemble the code you write.

The whole idea is to get a more natural flow in your tests, where you first write some comments...

String thenSomeCode = "that automatically gets includes as snippet";

...and then some more comments...

List<String> andThenMoreCode = new ArrayList<String>();

...and the last comment in the method marks the end of what will be included. In this case it is just an empy comment. This method looks like this:

@Test
public void autoSnippeting() throws Exception {
    String thisIsOutsideTheFirstComment = "and will not be included";
    /*!
    Bumblebee will include as snippets any code in the test methods that resides between 
    comments in a test case (new feature from 1.0.2). The resulting document will more 
    resemble the code you write. 
    
    The whole idea is to get a more natural flow in your tests, where you first 
    write some comments...
    */
    String thenSomeCode = "that automatically gets includes as snippet";
    /*!
    ...and then some more comments... 
    */
    List<String> andThenMoreCode = new ArrayList<String>();
    /*!
    ...and the last comment in the method marks the end of what will be included. In this 
    case it is just an empy comment.
    This method looks like this:
    >>>>
    #{meth}
    <<<<
    Here comes the last part:
    */
    String troublesomeString = "&<>#{}\n";
    /*!*/
    String outsideTheLastComment = "since it is after the last comment";
}

Here comes the last part:

String troublesomeString = "&<>#{}\n";

Include the currently executing method

This is how to include the current method using the DSL:

@Test
public void includeTheCurrentlyExecutingMethod() throws Exception {
    /*!
    This is how to include the current method using the DSL:
    >>>>
    #{meth}
    <<<<
    
    =meth= represents the currently executing method.
    
    #{assert.contains 'public'+' void includeTheCurrentlyExecutingMethod', 
    'Can include current method'}
    */
}

meth represents the currently executing method.

Include other method from current class

It is possible to include other methods from the currently executing class by using the clazz object. This is what the output looks like:

public void demonstratingUsingMethodMissing() {
    // Demo method
}

...and this is how it is done:

@Test
public void includeOtherMethodFromCurrentClass() throws Exception {
    /*!
    It is possible to include other methods from the currently executing class 
    by using the =clazz= object. This is what the output looks like:
    >>>>
    #{clazz.demonstratingUsingMethodMissing}
    <<<<
    ...and this is how it is done:
    >>>>
    #{meth}
    <<<<
    This feature uses the *method_missing* feature in Ruby, i.e. when the 
    =clazz= object gets the call to =demonstratingUsingMethodMissing= 
    and no such method is found, it assumes that the call is intended to 
    return the method object of the specified =clazz= with the name 
    =demonstratingUsingMethodMissing= and returns it. 
    
    #{assert.contains 'public'+' void demonstratingUsingMethodMissing', 
    'Can include method from clazz object using method_missing'}
    */
}

This feature uses the method_missing feature in Ruby, i.e. when the clazz object gets the call to demonstratingUsingMethodMissing and no such method is found, it assumes that the call is intended to return the method object of the specified clazz with the name demonstratingUsingMethodMissing and returns it.

Avoid naming collision when including method

Sometimes there is a naming collision between the methods on the Ruby class and the ones in the Java class, and the above approach won't work. In those cases you can use the more explicit form clazz.meth 'methodName'

@Test
public void avoidNamingCollisionWhenIncludingMethod() throws Exception {
    /*!
    Sometimes there is a naming collision between the methods on the Ruby 
    class and the ones in the Java class, and the above approach won't work. 
    In those cases you can use the more explicit form =clazz.meth 'methodName'=:
    >>>>
    #{meth}
    <<<<
    The output will be similar to that of the previous example:
    >>>>
    #{clazz.meth('explicitlyGettingMethod')}
    <<<<
    #{assert.contains 'public'+' void explicitlyGettingMethod', 
    'Can include method from clazz with explicit method'}
    */
}

The output will be similar to that of the previous example:

public void explicitlyGettingMethod() {
    // Demo method
}

Inline entire source file

It is possible to include an entire file in the result:

package com.agical.bumblebee.acceptance.helpers;

import java.util.HashMap;

public class ShortClass {
    public void ps() {
        new HashMap();
    }
}

This is how it is done:

@Test
public void inlineEntireSourceFile() throws Exception {
    /*!m1
    It is possible to include an entire file in the result:
    >>>>
    #{clazz('com.agical.bumblebee.acceptance.helpers.ShortClass')}
    <<<<
    This is how it is done:
    >>>>
    #{meth}
    <<<<
    Note that it is the same =clazz= method, and this time with a parameter telling which 
    class to represent. 
    
    #{assert.contains 'public'+' class ShortClass', 'Can include entire source file'}
    */
}

Note that it is the same clazz method, and this time with a parameter telling which class to represent.

Link to source file

An alternative is to link to the file:

Link to file

This is how it is done:

@Test
public void linkToSourceFile() throws Exception {
    /*!m1
    An alternative is to link to the file:
    
    [[#{clazz('com.agical.bumblebee.acceptance.helpers.ShortClass').source_link}][Link to file]]
    
    This is how it is done:
    >>>>
    #{meth}
    <<<<
    =source_link= will cause Bumblebee to paste the class source into an HTML file and return 
    the link to it.
    
    #{assert.contains 'ShortClass.java', 'Can make link to file'}
    */
}

source_link will cause Bumblebee to paste the class source into an HTML file and return the link to it.

Get code from method in other class

Now consider the following method:

@Test
public void getCodeFromMethodInOtherClass() throws Exception {
    /*!m1
    Now consider the following method:
    >>>>
    #{meth}
    <<<<
    The =clazz('package.ClassName').methodName= method helps you retrieve methods 
    from other classes:
    >>>>
    #{clazz('com.agical.bumblebee.acceptance.helpers.MethodTester').testingMethod}
    <<<<
    #{assert.contains 'public'+' void testingMethod', 'Can include other methods'}
    */
}

The clazz('package.ClassName').methodName method helps you retrieve methods from other classes:

public void testingMethod() {
    // Demo method
}

Smaller code snippets from other classes or methods

By adding Bumblebee comments in other methods or classes than the current you can slice out interesting parts without relying on e.g. line numbers and such. Consider:

public void methodWithComments() {
    String codeBeforeIncludedPart = "";
    /*!m1*/
    String includedCode = "";
    /*!m2*/
    String codeAfterIncludedPart = "";
}

#{clazz.methodWithComments.from.m1.to.m2} retrives the code between the comments with the markers (m1 and m2 in this case) and the result is:

String includedCode = "";

Note that the last variable is not in this snippet. Note also that there can be several comments in one file, but the comments can be empty (contain only the marker) to serve as markers

Include variable current class

It is possible to include a variable declaration in almost the same way as methods:

private Runnable runnable = new Runnable() {
    public void run() {
        // do stuff
    }
};

...and this is how it is done:

@Test
public void includeVariableCurrentClass() throws Exception {
    /*!
    It is possible to include a variable declaration in almost the same way as 
    methods:
    >>>>
    #{clazz.variable('runnable')}
    <<<<
    ...and this is how it is done:
    >>>>
    #{meth}
    <<<<
    #{assert.contains 'new'+' Runnable', 'Can include variable'}
    */
}

Include overloaded method

When the method to be included is available with several signatures you need to provide the signature to select what method to use, to produce something like this:

public void methodWithSignature(String s, int i) {
    // Demo method String, int
    System.out.println(s + i);
}

We use the current class here, but it can be done for any class by specifying the desired class as argument to the clazz method.

This is what the method that produced this section looks like:

@Test
public void includeOverloadedMethod() throws Exception {
    /*!m1
    When the method to be included is available with several signatures you need to provide 
    the signature to select what method to use, to produce something like this:
    >>>>
    #{clazz.methodWithSignature('java.lang.String', 'int')}
    <<<<
    We use the current class here, but it can be done for any class by specifying the desired 
    class as argument to the =clazz= method.
    
    This is what the method that produced this section looks like:
    >>>>
    #{meth}
    <<<<
    #{assert.contains 'Demo'+' method String, int', 
    'Can include methods with overloaded signatures'}
    */
}

Unique method name

If the method you'd like to include has arguments, but isn't overloaded, then you don't need to specify the arguments:

public void methodNotOverloadedButWithParameters(Integer i, String s) {
    // Method with arguments that is included without having to specify parameters
    System.out.println(s + i);
}

This is generally something that is nice since specifying all those parameters can be pretty tiring.

This is what the method looks like:

@Test
public void uniqueMethodName() throws Exception {
    /*!m1
    If the method you'd like to include has arguments, but isn't overloaded, then you don't 
    need to specify the arguments:
    >>>>
    #{clazz.methodNotOverloadedButWithParameters}
    <<<<
    This is generally something that is nice since specifying all those parameters can be 
    pretty tiring.
    
    This is what the method looks like:
    >>>>
    #{meth}
    <<<<
    #{assert.contains('public'+' void methodNotOverloadedButWithParameters', 
    'Can include methods with signature without specifying its arguments')}
    */
}

Get code from inner class

Inner classes are more difficult to parse, and cannot be used in the same way as normal classes (at the moment). What can be done is to simply extract the class using a regular expression and some comment markers:

@Test
public void getCodeFromInnerClass() throws Exception {
    /*!m1
    Inner classes are more difficult to parse, and cannot be used in the same way as 
    normal classes (at the moment). What can be done is to simply extract the class using a regular 
    expression and some comment markers:
    >>>>
    #{meth}
    <<<<
    For the entire class:
    >>>>
    #{clazz.to_s.match(Regexp.new('//Start'+'tag(.*)//Endtag', Regexp::MULTILINE))[1]}
    <<<<
    #{assert.contains 'public class InnerClass', 'Can include inner classes'}
    */
}

For the entire class:


    public class InnerClass {
        public void someMethod() {
            String doing = "something";
        }
    }
    

Including slices and dices of text

Bumblebee tries to utilize Rubys excellent string manipulation functionality.

Include arbitrary file

This is how to include the contents of an arbitrary file into the documentation, here as a snippet:

This is a Bumblebee example file.
@Test
public void includeArbitraryFile() throws Exception {
    /*!m1
    This is how to include the contents of an arbitrary file 
    into the documentation, here as a snippet:
    >>>>
    #{File.new('src/test/resources/demofile.txt').read}
    <<<<
    
    >>>>
    #{meth}
    <<<<
    
    The filename can be relative to the execution directory 
    or an absolute path (not recommended).
    #{assert.contains('This'+' is a Bumblebee example file.', 
    'Can include arbitrary files')}
    */
}

The filename can be relative to the execution directory or an absolute path (not recommended).

Include line ranges

This is how you include certain lines:

@Test
public void includeLineRanges() throws Exception {
    /*!m1
    This is how you include certain lines:
    >>>>
    #{meth}
    <<<<
    The first argument to =lines= is the starting line (one-based), and 
    the second argument is how many lines 
    to include.
    
    This is what it looks like for a file:
    >>>>
    #{File.new('src/test/resources/multiline.txt').read.lines(1,3)}
    <<<<
    
    =lines= is a method added onto the Ruby =String= object, and can be used 
    on Strings through-out.
    
    #{assert.contains 'Line 1'+' of multiline.txt', 
    'Can include specific line range from content' }
    */
}

The first argument to lines is the starting line (one-based), and the second argument is how many lines to include.

This is what it looks like for a file:

Line 1 of multiline.txt
Line 2 of multiline.txt
Line 3 of multiline.txt

lines is a method added onto the Ruby String object, and can be used on Strings through-out.

Include text based on regular expression

Ruby has support for regular expressions, and they can be used as-is in Bumblebee:

@Test
public void includeTextBasedOnRegularExpression() throws Exception {
    /*!m1
    Ruby has support for regular expressions, and they can be used as-is in Bumblebee:
    >>>>
    #{meth}
    <<<<
    The output looks like this:
    >>>>
    #{File.new('src/test/resources/multiline.txt').read.match('Line 9(.*)')}
    <<<<
    
    #{assert.contains 'Line'+' 9 of multiline.txt', 
    'Can use regexp to include parts of content'}
    */
}

The output looks like this:

Line 9 of multiline.txt

Using wiki syntax

To enable simple, but powerful, formatting, a wiki dialect is used: The Muse wiki syntax

Basic inline formatting

The basic inline formattings available are emphasize, strong, fixed font and underlined. It is accomplished like this:

@Test
public void basicInlineFormatting() throws Exception {
    /*!
    The basic inline formattings available are *emphasize*,  **strong**, 
    =fixed font= and _underlined_. It is accomplished like this:
    >>>>
    #{meth}
    <<<<
    (for some strange reason the double-asterisk of strong requires two 
    leading spaces to not clog together with the preceeding
    emphasize)
    */
}

(for some strange reason the double-asterisk of strong requires two leading spaces to not clog together with the preceeding emphasize)

A note on indenting comments

Since wiki syntax is sensitive for indents and other subtle formattings, it is very important to indent your comments consistently. Each line in the comment will be stripped with the same indenting character sequence as the first line in the comment is indented with. I.e. do not leave the first line of a comment empty

If you are getting strange results, the reason is often that different lines are indented differently, e.g. one line is indented with spaces and another line with tabs etc.

Cross referencing within the document

Bumblebee will write anchor names for all suites, classes and methods in the document. This is what enables the index generation, and it can be used when you want to make cross references within the document. To do this, use a link tag like this:

[[#com.agical.bumblebee.acceptance.helpers.UsingWikiSyntax.basicInlineFormatting][Basic inline formatting]]

and it will render this link: Basic inline formatting

The common format is:

[[#classname.methodname][Link text]]

Headings

Headlines are accomplished with leading asterisks.

First level

Second level

Third level

Fourth level (lowest level)

It is accomplished like this:

@Test
public void headings() throws Exception {
    /*!
    Headlines are accomplished with leading  asterisks.
    
    * First level
    
    ** Second level
    
    *** Third level
    
    **** Fourth level (lowest level)
    
    It is accomplished like this:
    >>>>
    #{meth}
    <<<<
    Note the necessary space after the last asterisk and the necessary empty 
    line before each headline.
    
    These headings are parsed by the Wiki-syntax processor, and they may or 
    may not be part of the structural information of the document, depending 
    on how well the parser can be integrated with. So, until further notice, 
    consider these Wiki-headings as a presentational sugar rather than
    structural information.
    */
}

Note the necessary space after the last asterisk and the necessary empty line before each headline.

These headings are parsed by the Wiki-syntax processor, and they may or may not be part of the structural information of the document, depending on how well the parser can be integrated with. So, until further notice, consider these Wiki-headings as a presentational sugar rather than structural information.

Tables

Tables can also be used:

column heading 1 column heading 2
1.1 1.2
2.1 2.2
And the code looks like this:

@Test
public void tables() throws Exception {
    /*!
    Tables can also be used:
    column heading 1||column heading 2
    1.1|1.2
    2.1|2.2
    And the code looks like this:
    >>>>
    #{meth}
    <<<<
    */
}

Using runtime data

Passing runtime data to use in documentation

It is possible to pass runtime variables to use in the method comments. The data is inlined in the text when called. This is the an example where direct parameter access is used, i.e. the toString() method is called rendering:

Result of toString method from MyObject

This method looks like this:

@Test
public void passingRuntimeDataToUseInDocumentation() throws Exception {
    /*!m1
    It is possible to pass runtime variables to use in the method comments. The data is 
    inlined in the text when called. This is the an example where direct parameter 
    access is used, i.e. the =toString()= method is called rendering: 
    
    *#{myKey2}*

    This method looks like this:
    >>>>
    #{meth}
    <<<<
    =store= is a static method on the class =Storage=, and it is a good candidate for a 
    static import.
      
    #{assert.contains 'Result'+' of toString method from MyObject', 
        'Can include runtime variables'}
     */
    store("myKey2", new MyObject("The text"));
}

store is a static method on the class Storage, and it is a good candidate for a static import.

Rules and limitations for keys and values

The data stored must implement java.io.Serializable. This is largely a measure to enable storing data on disk if needed without breaking existing code.

To use direct access to the stored data, keys must conform to the Ruby symbol syntax, and also be accessed in such way.

If you would like to store data with other key strings, e.g. 'non-symbol key:' you can use the accessor method #{get_value(key)}

Calling methods on stored objects

The nice thing with JRuby is that you can call the method on the Java objects you store from within your Ruby code. This method looks like this:

@Test
public void callingMethodsOnStoredObjects() throws Exception {
    /*!m1
    The nice thing with JRuby is that you can call the method on the 
    Java objects you store from within your Ruby code. This method looks like this:
    >>>>
    #{meth}
    <<<<
    and we can get the =getContainedData()= from the =MyObject=:  *#{myKey2.getContainedData()}*
    
    MyObject looks like this:
    >>>>
    #{clazz('com.agical.bumblebee.acceptance.helpers.MyObject')}
    <<<<
    
    JRuby has the feature that it converts Java-style method names to Ruby-style, and in this case we
    could have called 
    
    =contained_data= 
    
    just as well as 
    
    =getContainedData()= 
    
    However, since it 
    can be rather confusing mixing Ruby and Java code, it is recommended to call included 
    Java objects by their Java names.
    
    #{assert.contains 'The'+' text', 'Can call methods on runtime variables'}
     */
   store("myKey2", new MyObject("The" + " text"));
}

and we can get the getContainedData() from the MyObject: The text

MyObject looks like this:

package com.agical.bumblebee.acceptance.helpers;

import java.io.Serializable;

public class MyObject implements Serializable {
    private static final long serialVersionUID = -9047583716063916445L;
    private String string;

    public MyObject(String string) {
        this.string = string;
    }

    public String getContainedData() {
        return string;
    }
    
    public String toString() {
        return "Result of toString method from MyObject";
    }
}

JRuby has the feature that it converts Java-style method names to Ruby-style, and in this case we could have called

contained_data

just as well as

getContainedData()

However, since it can be rather confusing mixing Ruby and Java code, it is recommended to call included Java objects by their Java names.

Using images

By adding images to your document the whole experience gets more vibrant.

Since Bumblebee can include runtime data, also runtime data such as screen-shots and similar can be extracted. The goal of this package is that it also should be easy to add information such as explanations and balloons as overlays in the images.

Add an image to your document

Images are included using the link tag:

[[images/bumblebee.jpeg][]]

The site will by default be placed in target/site, hence images with relative URLs should reside there or in a sub folder.

The wiki-parser recognizes images of types jpeg ( not jpg) and png and possibly some more formats.

Copying of resources are handled in Images and other resources

Create uml diagrams

Bumblebee has some functionality for creating UML diagrams, demonstrated below. The interface is intended to be as lightweight, yet helpful, as possible.

On a technical note, Bumblebee integrates Umlspeed for creating UML diagrams. Umlspeed creates UML diagrams in the SVG format from a proprietary textual description. The SVG is transformed into a PNG using Apache Batik. The implementation of Umlspeed is a bit awkward to use, hence the choice of Umlspeed might be changed in the future, but the Bumblebee interface is likely to remain.

The jars for Umlspeed and Batik are not included in the bumblebee-core jar. You need to add the following;


umlspeed:umlspeed:jar:0.19
batik:batik-rasterizer:jar:1.6
batik:batik-awt-util:jar:1.6
batik:batik-bridge:jar:1.6
batik:batik-css:jar:1.6
batik:batik-dom:jar:1.6
batik:batik-ext:jar:1.6
batik:batik-gvt:jar:1.6
batik:batik-parser:jar:1.6
batik:batik-script:jar:1.6
batik:batik-svg-dom:jar:1.6
batik:batik-transcoder:jar:1.6
batik:batik-util:jar:1.6
batik:batik-xml:jar:1.6
xerces:xercesImpl:jar:2.5.0
xml-apis:xmlParserAPIs:jar:2.0.2

Umlspeed is not in any maven repository, but it is included in the bumblebee-all-x.y.z.zip file.

Draw a class diagram of classes in a method

Suppose you have some code like this in a method, for instance a test method:

SuperClass objectUnderTest = new CentralObject();

Now you want to create a UML diagram like this: This is how you do it:

@Test
public void drawAClassDiagramOfClassesInAMethod() throws Exception {
    /*!
    Suppose you have some code like this in a method, for instance a test method: 
    */
    SuperClass objectUnderTest = new CentralObject();
    /*!
    Now you want to create a UML diagram like this:
    #{uml.classdiagram.focused_on('com.agical.bumblebee.acceptance.uml.CentralObject')}
    This is how you do it:
    >>>>
    #{meth}
    <<<<
    */
}

Draw a class diagram of specific classes

If you want to include some specific classes in a class diagram, do like this:

@Test
public void drawAClassDiagramOfSpecificClasses() throws Exception {
    /*!
    If you want to include some specific classes in a class diagram, do like this:
    >>>>
    #{meth}
    <<<<
    and the result is this:  
    #{uml.classdiagram.for_all(classesForDiagram)}
    The focus will be on the first class in the provided array.
    */
    Storage.store("classesForDiagram", new Class<?>[] {
            CentralObject.class, 
            DependentObject.class, 
            SuperClass.class,
            CreateUmlDiagrams.class});
}

and the result is this: The focus will be on the first class in the provided array.

Using JUnit3

JUnit3.x testcases can be used standalone or together with JUnit4 testcases.

So far, no limitations in that use

Thanks to JUnit4's handling of JUnit3.x test cases they work seamlessly with the Suites, hence allowing you to recycle you old test cases.

package com.agical.bumblebee.acceptance.helpers;

import junit.framework.TestCase;

public class JUnit3TestCase extends TestCase {
    /*!!
    #{set_header 'Using JUnit3'}
    JUnit3.x testcases can be used standalone or together with JUnit4 testcases.
    */
    public void testIfJUnit3AlsoWorks() throws Exception {
        /*!m1
        #{set_header 'So far, no limitations in that use'}
        Thanks to JUnit4's handling of JUnit3.x test cases they work seamlessly
        with the Suites, hence allowing you 
        to recycle you old test cases.
        >>>>
        #{clazz}
        <<<<
        #{assert.contains 'So far,'+' no limitations in that use', 'JUnit3 is included'}
        */
    }
    
}

Using bumblebee with swing

This section explains how to use Bumblebee to document a Swing application. The purpose could be to create a manual for the application, or to create a domain tutorial using the application to exemplify.

Bumblebee will have a simple DSL (Domain Specific Language) to allow you do extract and manipulate screen-shots from the application, thus allowing you to robustly generate documentation for your Java Swing GUI.

Including screenshots in the documentation

Having pictures in the documentation makes it so much nicer to read. On the other hand, updating the pictures every time you change something is a true pain.

By using Bumblebee to generate the documentation you will get away from that pain. Some goodies in the DSL will also help you do things you wouldn't even consider if you used manual screen shots.

Let's stop talking and start walking.

Getting a shot of your application

First, lets create a Swing application:

application = new Application();

Feed that frame (or actually any javax.swing.Component) to the Picter shot method:

Picter picter = new Picter();
picter
    .getAppShot(application.getFrame())
    .writeAs("fullApp");

This is the shot written:

This whole section output is created from a test method that looks like this:

@Test
public void gettingAShotOfYourApplication() throws Exception {
    /*!
    First, lets create a Swing application:
    */
    application = new Application();
    /*!
    Feed that frame (or actually any =javax.swing.Component=) to the =Picter= shot method:
    */
    Picter picter = new Picter();
    picter
        .getAppShot(application.getFrame())
        .writeAs("fullApp");
    /*!
    This is the shot written:
    
    #{fullApp}
    
    This whole section output is created from a test method that looks like this: 
    >>>>
    #{meth}
    <<<<
    Note that the link reference cannot collide with any (J)Ruby keyword, nor can it collide with 
    any Bumblebee configuration parameter or snippet DSL names, e.g. =clazz= or =meth=.
    If you get strange results, change the name, preferrably to something more explicit.
    
    The application used in these examples looks 
    [[#{clazz('com.agical.bumblebee.acceptance.swing.application.Application').source_link}][like this]].
    */
}

Note that the link reference cannot collide with any (J)Ruby keyword, nor can it collide with any Bumblebee configuration parameter or snippet DSL names, e.g. clazz or meth. If you get strange results, change the name, preferrably to something more explicit.

The application used in these examples looks like this

Highlighting parts of the shot

Oftentimes you want to highlight a part of the screen. This is how you do it:

picter
    .getAppShot(application.getFrame())
    .highlight(new Rectangle(30, 40, 100, 120))
    .writeAs("highlightedApp");

This is the result:

Hmm, that's impressive, but not too useful. We should probably use the coordinates from the Swing components to highlight cohesive parts. Here we use the reference to the form component of the application.

picter
    .getAppShot(application.getFrame())
    .highlight(application.getForm())
    .writeAs("highlightedFromComponent");

Now, that looks better!

Here, we used the strategy of explicitly exposing relevant components in the application. Another strategy is to traverse the Swing/AWT component hierarchy from a root component until you find the component of interest.

Cropping

Every now and then you want to crop out and show only a part of the application. Here's how you do that

picter
    .getAppShot(application.getFrame())
    .crop(application.getForm())
    .writeAs("croppedForm");

This is the result:

Callouts

When you want to add detailed information in a screenshot, you can add callouts to describe a flow or just different parts in the picture.

picter
    .getAppShot(application.getFrame())
    .callout(application.getNameLabel(), "The Name field", "nameField")
    .callout(application.getEmailLabel(), "The Email field", "emailField")
    .callout(application.getPhoneLabel(), "The Phone field", "phoneField")
    .crop(application.getForm())
    .writeAs("calloutForm");

This is the result:

The Name field

The Email field

The Phone field

You can get the details from each reference:

Reference Output Description
#{nameField.icon} The icon in the image
#{nameField.text} The Name field The text
#{nameField.nr} 1 The number of the text
#{nameField} The Name field The icon and text in one

This method looks like this:

@Test
public void callouts() throws Exception {
    application = new Application();
    Picter picter = new Picter();
    /*!
    When you want to add detailed information in a screenshot, you can 
    add callouts to describe a flow or just different parts in the picture.
    */
    picter
        .getAppShot(application.getFrame())
        .callout(application.getNameLabel(), "The Name field", "nameField")
        .callout(application.getEmailLabel(), "The Email field", "emailField")
        .callout(application.getPhoneLabel(), "The Phone field", "phoneField")
        .crop(application.getForm())
        .writeAs("calloutForm");
    /*!
    This is the result:
    
    #{calloutForm}
    
    #{nameField}
    
    #{emailField}
    
    #{phoneField}
    
    You can get the details from each reference:
    Reference||Output||Description
    #{'#'}{nameField.icon} | #{nameField.icon} | The icon in the image
    #{'#'}{nameField.text} | #{nameField.text} | The text
    #{'#'}{nameField.nr} | #{nameField.nr} | The number of the text
    #{'#'}{nameField} | #{nameField} | The icon and text in one
    
    This method looks like this:
    >>>>
    #{meth}
    <<<<
    */
}

Using bumblebee with selenium

This is a tutorial on how to use Bumblebee together with Selenium to document your site.

Bumblebee will have a simple DSL (Domain Specific Language) to allow you do extract and manipulate screen-shots from the browser, thus allowing you to robustly generate documentation for your web GUI. The Selenium jars are not included in the bumblebee-core to prevent it from getting too big. You need the following:


org.openqa.selenium.core:selenium-core:jar:0.8.3
org.openqa.selenium.client-drivers:selenium-java-client-driver:jar:0.9.2
org.openqa.selenium.server:selenium-server:jar:0.9.2
org.openqa.selenium.server:selenium-server:jar:standalone:0.9.2
org.openqa.selenium.server:selenium-server-coreless:jar:0.9.2

Including screenshots in the documentation

Getting a snapshot of a tested site

First, you need to start the site you are testing. I hope you know how to do that.

For this example we use a simple, static site that runs in JettyController. It has one main page and two sub pages.

Secondly, you need to setup Selenium:

@BeforeClass
public static void startSelenium() throws Exception {
    int port = SeleniumServer.DEFAULT_PORT;
    seleniumServer = new SeleniumServer(port, false, true);
    seleniumServer.start();
    selenium = new DefaultSelenium("localhost", port, "*firefox", "http://localhost:8080");
    selenium.start();
}

Then, feed the selenium variable to the DSL root-class Picter. This integration will use selenium to extract portions of a page, write it to file and create a link for you to use in the text. Do whatever you want to do with selenium, in this case we open the index.html, and then start working with the DSL.

selenium.open("index.html");
Picter picter = new Picter();
picter.browserShot(selenium).writeAs("linkReference");

This will retrieve the full browser window in an image on disk. You can link it with #{linkReference}, i.e. the same name that you provided the method. This is the result:

Note that the link reference cannot collide with any (J)Ruby keyword, nor can it collide with any Bumblebee configuration parameter or snippet DSL names, e.g. clazz or meth. If you get strange results, change the name, preferrably to something more explicit.

The method that produced this looks like this:

@Test
public void gettingASnapshotOfATestedSite() throws Exception {
    /*!
    First, you need to start the site you are testing. I hope you know how to do that. 
    
    For this example we use a simple, static site that runs in JettyController. 
    It has one main page and two sub pages.
    
    Secondly, you need to setup Selenium:
    >>>>
    #{clazz('com.agical.bumblebee.acceptance.selenium.SimpleSiteAndSelenium').startSelenium}
    <<<<
    Then, feed the =selenium= variable to the DSL root-class =Picter=. This integration 
    will use selenium to extract portions of a page, write it to file and create a link for 
    you to use in the text. 
    Do whatever you want to do with selenium, in this case we open the index.html, and then 
    start working with the DSL.
    */
    selenium.open("index.html");
    Picter picter = new Picter();
    picter.browserShot(selenium).writeAs("linkReference");
    /*!
    This will retrieve the full browser window in an image on disk. 
    You can link it with =#{'#'}{linkReference}=, i.e. the same name that you provided
    the method. This is the result:
    
    #{linkReference} 
    
    Note that the link reference cannot collide with any (J)Ruby keyword, nor can it collide with 
    any Bumblebee configuration parameter or snippet DSL names, e.g. =clazz= or =meth=.
    If you get strange results, change the name, preferrably to something more explicit.
    
    The method that produced this looks like this:
    >>>>
    #{meth}
    <<<<
    */
}

Intelligent cropping

As you probably saw, the image in the first example was huge. Most of the time you are only interested in a portion of the page, i.e. you want to crop the page:

selenium.open("index.html");
Picter picter = new Picter();
picter.browserShot(selenium).crop(new Rectangle(40, 40, 300, 300)).writeAs("staticallyCropped");

This is the result:

As you can see, just cropping an image with absolute coordinates is pretty tricky. It is also not very tolerant to changes in the site layout and design. We would like those coordinates to be extracted from the elements in the page instead:

picter.browserShot(selenium).crop("menu").writeAs("croppedFromPageCoordinates");

You can use any Selenium locators in the crop method:

picter.browserShot(selenium).crop("xpath=//h1").writeAs("headerFromXPath");

...resulting in:

Note that using the Selenium locators can only be used on the BrowserShot class, since they rely on the positionings in the browser.

Highlighting and cropping

Sometimes you want to highlight parts of the picture and crop a larger part:

picter.browserShot(selenium)
    .highlight("menu", "xpath=//h1")
    .highlight("xpath=//p")
    .crop("bodytable")
    .writeAs("highlighted");

highlight(...) will take one or several Selenium locators and highlight them in the image:

The highlight(...) method will return a BrowserShot object, hence it is still possible to manipulate it based on Selenium locators, e.g. cropping out bodytable.

Detailing the screen shots

Bumblebee also allow you to put more detailed descriptions in your snapshots.

Using callouts

By adding call-outs to a picture, you can explain several coherent parts in the same image, or a flow of events.

picter.browserShot(selenium)
    .callout("xpath=//h1", "The header", "headerRef")
    .callout("bodytable", "The body table", "bodytableRef")
    .callout("home", "The link to the first page", "homeRef")
    .callout("something", "The link to something", "somethingRef")
    .callout("nothing", "The link to nothing", "nothingRef")
    .callout("xpath=//p", "The text", "textRef")
    .crop("bodytable")
    .writeAs("annotated");

The header

The body table

The link to the first page

The link to something

The link to nothing

The text

Each call-out will be added as a numbered balloon just outside of the image, with a line to the element referenced. The call-out will be available to use in the text, e.g:

Reference Output Description
#{headerRef.icon} The icon in the image
#{headerRef.text} The header The text
#{headerRef.nr} 1 The number of the text
#{headerRef} The header The icon and text in one

This method looks like this:

@Test
public void usingCallouts() throws Exception {
    selenium.open("index.html");
    Picter picter = new Picter();
    /*!
    By adding call-outs to a picture, you can explain several coherent parts in the 
    same image, or a flow of events. 
    */
    picter.browserShot(selenium)
        .callout("xpath=//h1", "The header", "headerRef")
        .callout("bodytable", "The body table", "bodytableRef")
        .callout("home", "The link to the first page", "homeRef")
        .callout("something", "The link to something", "somethingRef")
        .callout("nothing", "The link to nothing", "nothingRef")
        .callout("xpath=//p", "The text", "textRef")
        .crop("bodytable")
        .writeAs("annotated");
    /*!
    #{annotated}
    
    #{headerRef}
    
    #{bodytableRef}
    
    #{homeRef}
    
    #{somethingRef}
    
    #{nothingRef}
    
    #{textRef}
    
    Each call-out will be added as a numbered balloon just outside of the image,
    with a line to the element referenced. The call-out will be available to use in
    the text, e.g:
    
    Reference||Output||Description
    #{'#'}{headerRef.icon} | #{headerRef.icon} | The icon in the image
    #{'#'}{headerRef.text} | #{headerRef.text} | The text
    #{'#'}{headerRef.nr} | #{headerRef.nr} | The number of the text
    #{'#'}{headerRef} | #{headerRef} | The icon and text in one
    
    This method looks like this:
    >>>>
    #{meth}
    <<<<
    */
}

Extending bumblebee

There are several ways in which to extend Bumblebee.

Add ruby extensions

Execute ruby code directly in comment

Now consider the following method:

@Test
public void executeRubyCodeDirectlyInComment() throws Exception {
    /*!m1
    Now consider the following method:
    >>>>
    #{meth}
    <<<<
    
    The comments are string templates that get executed in Ruby. Therefore 
    most Ruby string features can be used within the comment. In this case 
    we output the numbers 0 through 9.
    
    *#{s = "";10.times {|i| s+=i.to_s};s;}*
    
    This is often rather awkward, and one of the extension suggestions below will 
    probably be a better option when you want to execute more than the Ruby 
    code that comes with Bumblebee.
    #{assert.contains '0123456789', 'Outputs results of Ruby evaluation'}
    */
}

The comments are string templates that get executed in Ruby. Therefore most Ruby string features can be used within the comment. In this case we output the numbers 0 through 9.

0123456789

This is often rather awkward, and one of the extension suggestions below will probably be a better option when you want to execute more than the Ruby code that comes with Bumblebee.

Add a script in the comment using require

To include a script, just use the Ruby require statement.

@Test
public void addAScriptInTheCommentUsingRequire() throws Exception {
    /*!m1
    #{require('com/agical/bumblebee/acceptance/helpers/extension');''}
    
    To include a script, just use the Ruby =require= statement.
    >>>>
    #{meth}
    <<<<
    Since the require method returns =true= we have to prevent that from making it to the output
    by adding a =;''= 
    
    The file is named =extension.rb= and looks like this:
    >>>>
    #{File.new('src/test/resources/com/agical/bumblebee/acceptance/helpers/extension.rb').read}
    <<<<
    
    The method of the extension script can be invoked like any other method, 
    in this case the output is: =#{extension_method}=
    
    #{assert.contains 'result of extension method', 'Can use extension methods'}
    */
}

Since the require method returns true we have to prevent that from making it to the output by adding a ;''

The file is named extension.rb and looks like this:

  def extension_method
    'result of extension method'
  end

The method of the extension script can be invoked like any other method, in this case the output is: result of extension method

Execute a script from a comment and include result

Now we want to run the following Ruby script (i.e. program) and include the result in the documentation:

"The result of script evaluation: " + (4712-1).to_s

We just invoke Ruby's instance_eval on the contents of a from a file and get the result:

The result of script evaluation: 4711

This method looks like this:

@Test
public void executeAScriptFromACommentAndIncludeResult() throws Exception {
    /*!m1
    Now we want to run the following Ruby script (i.e. program) 
    and include the result in the documentation:
    >>>>
    #{File.new('src/test/resources/com/agical/bumblebee/acceptance/helpers/evaluate.rb').read}
    <<<<
    We just invoke Ruby's =instance_eval= on the contents of a from a file and get the result:
    
    #{instance_eval(File.new('src/test/resources/com/agical/bumblebee/acceptance/helpers/evaluate.rb').read)}
    
    This method looks like this: 
    >>>>
    #{meth}
    <<<<
    
    #{assert.contains( 'The result of script evaluation: 4711', 
    'Can evaluate scripts and include results')}
    */
}

Experimental

This section contains features that are of experimental status. They may or may not be there in the next release, and they may or may not change API and functionality. That said, even the other features of Bumblebee may change, but these are almost guaranteed to.

Invocation information

Invocation information is information regarding the method execution time and what method was called, and whether it returned normally or with an exception.

For the JUnit case this is sufficient, but if you would like to use Bumblebee in a more generic situation, there are some more data for those cases.

Get execution meta information

This example shows how to retrieve different kinds of meta information around the execution of a certain method.

Member time[ms]
getExecutionMetaInformation 21
InvocationInformation 26
Experimental 57
BumblebeeDocumentation 29701

How it is done is shown here:

@Test
public void getExecutionMetaInformation() throws Exception {
    /*!
    #{current_execution=execution.getMethod();''}
    #{parent_class=parent.execution.getExecutingClass();''}
    #{grand_parent_class=parent.parent.execution.getExecutingClass();''}
    #{great_grand_parent_class=parent.parent.parent.execution.getExecutingClass();''}
    
    This example shows how to retrieve different kinds of meta information
    around the execution of a certain method.
    
    Member || time[ms]
    #{execution.getMethod().getName()} | #{execution.getExecutionTime()}
    #{parent_class.getSimpleName()} | #{parent.execution.getExecutionTime()}
    #{grand_parent_class.getSimpleName()}| #{parent.parent.execution.getExecutionTime()}
    #{great_grand_parent_class.getSimpleName()}| #{parent.parent.parent.execution.getExecutionTime()}
    
    How it is done is shown here:
    >>>>
    #{meth}
    <<<<
    Here we make use of a new feature, to add arbitrary parameters to a 
    node (=current_execution= etc). 
    
    The =execution= variable is holding the current execution information. 
    On a class/suite there is the java =Class= and the local execution time. 
    For the methods there is the java =Method= object and the execution time, 
    as shown above.
    
    */
    Thread.sleep(20);
}

Here we make use of a new feature, to add arbitrary parameters to a node (current_execution etc).

The execution variable is holding the current execution information. On a class/suite there is the java Class and the local execution time. For the methods there is the java Method object and the execution time, as shown above.

Getting failure information

When a test fails there is additional information on the execution variable.

An example of a failing suite can be found here

Parameterized tests

Parameterized tests can also be used. JUnit will execute the methods once for each parameter, and Bumblebee will get the corresponding calls. A good recommendation for large series of tests is probably to have the header to contain the parameter:

@Test
public void oneParameterizedTest() throws Exception {
    /*!
    #{set_header(data + ' First test')}
    */
    store("data", data);
}

As we see, you can store the actual data used in the test to display it in the report.

data1 First test

data1 Second test

data2 First test

data2 Second test

Using theories

A theory

It is also possible to use JUnit theories, an experimental feature in JUnit 4.4. In difference from using the Parameterized runner, each test will only be reported by JUnit once, so you need to store the data from each run in the test:

package com.agical.bumblebee.acceptance.helpers.experimental;

import static com.agical.bumblebee.junit4.Storage.store;
import static org.junit.Assume.assumeThat;

import java.util.ArrayList;

import org.hamcrest.core.IsEqual;
import org.junit.experimental.theories.DataPoint;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.runner.RunWith;

@RunWith(Theories.class)
public class UsingTheories {
    @DataPoint public static final String aString = "string 1";
    @DataPoint public static final String anotherString = "string 2";
    
    private static ArrayList<String> data = new ArrayList<String>();
    private static ArrayList<String> multiData = new ArrayList<String>();
    
    @Theory
    public void aTheory(String theStringToTest) throws Exception {
        /*!
        It is also possible to use JUnit theories, an experimental feature in JUnit 4.4. 
        In difference from using the Parameterized runner, each test will only be reported 
        by JUnit once, so you need to store the data from each run in the test:
        >>>>
        #{clazz}
        <<<<
        This is a simple single argument theory. These are the data points submitted.
        #{theory_data.collect {|datapoint| datapoint + ' | '}}
        */
         data.add(theStringToTest);
         store("theory_data", data);
    }
    
    @Theory
    public void anAssumingMultiArgumentTheory(String first, String second) throws Exception {
        assumeThat(first, new IsEqual<String>(aString));
        /*!
        You can use =assumeThat(...)= to filter which tests to run to the end, 
        and hence what data is stored:
        >>>>
        #{meth}
        <<<<
        In this case we use *assumeThat* to let the test continue for data sets 
        where the first argument is =\"string 1\"=:
        #{theory_data.collect {|datapoint| datapoint + ' | '}}
        */
        multiData.add(first + "-" + second);
        store("theory_data", multiData);
    }
    
}

This is a simple single argument theory. These are the data points submitted.

string 1 string 2

An assuming multi argument theory

You can use assumeThat(...) to filter which tests to run to the end, and hence what data is stored:

@Theory
public void anAssumingMultiArgumentTheory(String first, String second) throws Exception {
    assumeThat(first, new IsEqual<String>(aString));
    /*!
    You can use =assumeThat(...)= to filter which tests to run to the end, 
    and hence what data is stored:
    >>>>
    #{meth}
    <<<<
    In this case we use *assumeThat* to let the test continue for data sets 
    where the first argument is =\"string 1\"=:
    #{theory_data.collect {|datapoint| datapoint + ' | '}}
    */
    multiData.add(first + "-" + second);
    store("theory_data", multiData);
}

In this case we use assumeThat to let the test continue for data sets where the first argument is "string 1"

string 1-string 1 string 1-string 2

Excluding content

Exclude test from report

This is how to exclude a test from the resulting document:

@Test
public void thisIsTheExcludedTest() throws Exception {
    /*!
    #{exclude}
    This comment won't be in the resulting document.
    
    #{configuration.traversers.wiki2doc.assert(self, 'Test can be excluded from the resulting document') {|doc|
        !doc.include?('This'+' is the excluded test')
    }}
    */
    
}

You need to exclude it from both the wiki2doc and the htmlindex traverser in order to make it disappear totally from the document.

This is also an example of how to make asserts on the resulting document. The result in the JUnit report is not very nice at the moment, but at least there is an error.

Roadmap

This section contains the history and the backlog of Bumblebee.

Bumblebee is starting to shake in place, and the documented features can be considered rather stable. User input will have a big say in the future development, and feedback from usage of Bumblebee is much appreciated in order to make it a better product. I will do my best to add any features requested to Bumblebee, or help you accomplish what you want in some other way.

You can email the Bumblebee Google group at bumblebee-tool@googlegroups.com or join it at Bumblebee group and start a discussion there.

Releases

Release 1.1.0 - GUI documentation utilities - 2009-05-07

Bumped the minor a notch due to several large new features and a new jar structure.

Release 1.0.6 - UML generation - 2009-04-10

Release 1.0.5 - Changed QDox to PMD for parsing files - 2009-03-07

Release 1.0.4 - New version of QDox - 2009-02-01

Release 1.0.3 - Bugfix: HTML-tags in autosnippeted code - 2008-12-19

Release 1.0.2 - Autosnippeting promotion - 2008-11-30

Release 1.0.1 - Autosnippeting - 2008-08-20

From now on I'll use a maintenance/build version number for the micro-releases, and I'll push the other two up when there is a good reason for it.

Release 1.0 - Simplified suite configuration - 2008-07-17

Please, note that release 1.0 is merely the successor to 0.9, it does not imply anything special above the fact that all releases are aimed at full functioning and stability.

Release 0.9 - Improved stylesheet and resource management - 2008-07-06

Release 0.8 - JUnit4.5beta compatibility - 2008-07-01

Bumblebee is now JUnit 4.5beta compatible.

Release 0.7 - Simplified runtime data inclusion - 2008-06-27

Simpler storage and retrieval methods for runtime data.

Release 0.6 - Web and Swing documentation enablers - 2008-06-25

This is the first micro-release of Bumblebee.

Release 0.5 - Better feedback - 2008-06-21

This release has focused on getting better error messages and feedback from Bumeblebee to the user.

Packaging has improved, and now includes a bumblebee-all jar and a bumblebee-all-no-junit jar. For some reason the jars are not compressed by Buildr, which results in rather large download size. I'm sorry for any inconvenience.

This is the last aggregated release. Future releases will be made whenever a feature is ready, and hopefully that will result in a more steady flow, and this will eliminate the need for SNAPSHOTS releases.

New and changed features

Bugfixes and internal work

Release 0.4 - Get the Node model in place - 2008-02-26

This release had the goal of creating and exposing a more consistent node model. The release should only require minor changes in existing applications using Bumblebee, but the possibilities to retrieve data, to configure and style the documentation has improved significantly.

These improvements are partially explained in the documentation where needed, but the implementation has mostly been a refactoring of existing functionality, hence all new possibilities haven't been explored enough yet.

One of the important-to-remember changes is that class- and suite level comments are now commented with two !! instead of one ! to simplify parsing.

Below is a list of changes, improvements and bug-fixes.

New and changed features

Bugfixes and internal work

Release 0.3 - Multiple input formats - 2007-12-01

Release 0.2 - Object-orient the information extraction model - 2007-11-11

Release 0.1 - Get it out - 2007-11-01

Backlog

The sprints was abandoned from version 0.5, and new releases will be published as soon as any minimum marketable feature is implemented. This is the queue of possible items, and it will be affected by any user input given:

Queue

Background

The history of Bumblebee

Bumblebee springs from the experiences from TDDoc, a documentation add-on to RMock. The ideas and concepts are pretty much the same, but the implementation approach is different.

Projects using Bumblebee

Need more support?

Bumblebee is an open source project that I work on when I'm not consulting or otherwise tending my company Agical. If you desperately need some feature, please contact me at daniel.brolund@agical.com and we'll work something out.

© 2007-2009 Agical AB