COMP6700: Lectures Block 6

Alexei B Khorev

May 2013

Topics of the Block 6

This part is devoted to Java code-documentation system (javadoc utility), annotations — a mechanism to add information about program which (without affecting what the program is) may add specific meaning to what it does when it’s compiled, executed, documented, deployed and managed by IDE tools. We will also discuss unit testing, software quality and life-cycle, and elemants of UML notation to design software.

javadoc: Tool for self-documenting programs

Java programs have three types of comments (we know that). The third type is the so called Doc comments — everything inside the /** … */. These comments and declarations in the program are parsed by the javadoc tool, and the output is written into a collection of HTML interlinked pages. The Java API documentation pages are generated precisely this way, and every program writer should document their code like this, too.

By default, the identifiers featured in the documentation are public and protected classes, inner classes (but not anonymous classes), interfaces, constructors, methods, and fields. You can run the javadoc tool on entire packages, individual source files or both:

javadoc -d docs/ -author -private -sourcepath src [packagename]

The syntax of the javadoc command can be found by reading the output of man javadoc. These arguments can for convenience be packed into args file and the command run as

javadoc @args @files 

where files is a file with the full list of source files (same is useful for compiling).

javadoc Output

If multiple packages are processed, the document has three HTML frames for: - the list of packages (P) - the list of classes (C) - the main class page

--------------                 --------------       
| C | Detail |                 | P | Detail |       
|   |        |                 |   |        |       
|   |        |                 |---|        |       
|   |        |                 | C |        |       
|   |        |                 |   |        |       
--------------                 --------------       
javadoc *.java                 javadoc java.lang java.awt  

The look and feel of the documentation pages (and even the output format) can be determined by using a special format file called a doclet.

Types of the Doc comments

One Doc comment must be placed before class or interface declaration, it’s a class header:

/** The root class of the Clock application. Run the program like follows:
 *    <pre>
 *       java -Xdock:name="Comp6700 Clock" Clock
 *    </pre>
 * It creates Observable model, Observer view, and registers view with model 
 * @author abx, 2007 adopted for a GUI assignment and javadoc tags added */

public class Clock {...}

method or constructor declaration — method comment:

/** implements Observer interface by repainting the panel 
  * in accordance with the latest data from the model
  * @param o observable object (model)
  * @param arg additional data passed to notifyObservers(arg) */

public void update(Observable o, Object arg) {
    ........
}

or field declaration — field comment.

Doc comments rules

Inside these comments the code writer may include tags. They allow the javadoc tool autogenerate a complete, well-formatted API from the source code. The tags start with an “at” sign (@) and are case-sensitive. A tag must start at the beginning of a line (after any leading spaces and an optional asterisk) or it is treated as normal text. By convention, tags with the same name are grouped together. Eg, put all @see tags on adjoint lines. Tags come in two types:

There two important rules which must be followed:

The doc comment syntax

where the content of body can run over several lines and can contain HTML tags; its first line is used in a short version of class attribute description placed at the top of the documentation page.

Doc comments tags

Doc comments tags are:

Java Doclets

Customising javadoc output

“Doclets are programs written in the Java programming language that use the doclet API to specify the content and format of the output of the javadoc tool. By default, javadoc uses the standard doclet provided by Sun™ (correction — a new doclet is now used to highlight the Oracle™) to generate API documentation in HTML form. However, you can supply your own doclets to customise the output of Javadoc as you like. You can write the doclets from scratch using the doclet API, or you can start with the standard doclet and modify it to suit your needs.”

To obtain a customised information about the class MyClass, use javadoc with a doclet class MyDoclet as follows:

javadoc -doclet MyDoclet -docletpath "path-to-MyDoclet"  MyClass.java

(the doclet class must be compiled). An example of defining a (devastatingly simple) Doclet class and its use in obtaining the information (formatted according to a particular definition; here, the basic information about the class constructors and methods is printed on the standard output) about a Java class is given by the ListParams.java class (taken from Java’s API documentation) can be found in the examples/reflections subdirectory.

The Java’s powerful MIF Doclet can generate the API documentation in MIF (the Adobe FrameMaker’s interchange format), FrameMaker and PDF formats. More Doclets can be found elsewhere in the virtual world.

Doclets can be used not only to generate documentation, but to generate the source files too, thus realising the code generating facility.

Digression on Literate Programming

Combining the code source and its detailed description in one readable document, which is achieved in Java through the use of Doc comments and javadoc tool, is an older idea of literate programming, first suggested by Donald Knuth in 1980s in the system called WEB (not “www”-Web!).

Literate_WEB

“A traditional computer program consists of a text file containing program code. Scattered in amongst the program code are comments which describe the various parts of the code. In literate programming the emphasis is reversed. Instead of writing code containing documentation, the literate programmer writes documentation containing code. No longer does the English commentary injected into a program have to be hidden in comment delimiters at the top of the file, or under procedure headings, or at the end of lines. Instead, it is wrenched into the daylight and made the main focus. The”program" then becomes primarily a document directed at humans, with the code being herded between “code delimiters” from where it can be extracted and shuffled out sideways to the language system by literate programming tools" (Ross Williams).

(In Java, you cannot divide the definition of a class or a method into chunks, and document them in separate parts of your program — in C++, you can).

Java’s Annotations

Metadata in Java code

Metadata — data about data — is the way to provide additional information about a program that is not part of the program itself, ie, they do not change the code’s compilation or run-time behaviour. But this information can be useful for:

If the software development environment allows you do to these things, we say that it has metadata facility.

Doc comments are not suitable for these tasks except for generating structured documentation. Since JDK 1.5, Java has the language feature called annotations used as metadata facility. Annotations are special (parametrized) kind of modifiers, that you can add to your code and apply to all declaration (from package to local variables). Annotations are types and modifiers at the same time. There are few annotation types defined in the Java standard API (in java.lang.annotation package), and there are standard rules (similar to interface definitions) to write custom annotation types.

Built-in annotations

The standard annotations you can use (from java.lang package):

As a good programming style, the use of annotations must be explained in a Doc comment:

public interface House {
  /** @deprecated use of open is discouraged, use
    *  openFrontDoor or openBackDoor instead. */
   @Deprecated
   public void open();
   public void openFrontDoor();
   public void openBackDoor();
}

Custom annotations

You can create your own annotation by defining it as an interface.

In annotations with a single element (aka, annotation parameters), the element should be named value, as shown below:

/** Associates a copyright notice with the annotated API element. */
public @interface Copyright {
    String value();
}

For a single-element annotation whose element name is value, the element name and equals sign (=) can be omitted:

@Copyright("u1234567")
public class AlarmClock { ... }

Another useful annotation:

/** Annotation type to indicate a task still needs to be completed */
public @interface TODO {
    String value();
}

“Serious” custom annotations

import java.lang.annotation.*; // import this to use @Documented
//annotate it with @Documented, then @ClassPreamble is picked up by javadoc
@Documented 
@interface ClassPreamble {
   String author(); // these are "annotation type elements"
   String date();
   int currentRevision() default 1; // there are optional default values
   String lastModified() default "N/A";
   String lastModifiedBy() default "N/A";
}

The annotation body above contains annotation type element declarations (they look like methods, and they may contain optional default values). Once an annotation is defined, you can “instantiate” annotations of that type, with the values filled in:

@ClassPreamble (
   author = "ian",
   date = "01.04.2007",
   currentRevision = 1,
   lastModified = "01.04.2013",
   lastModifiedBy = "abx"
)
public class AlarmClockModel extends Model { ... }

Annotations benefits and pitfalls

Annotation processing tool (apt) and general remarks

“Without standards in the use of annotations, their effective use could be stifled.” (J. Gosling)

Software Testing

The software product is normally subject to the following kinds of testing:

The unit testing is the only kind of testing which can and should be exercised by the code writers themselves. Without making sure that smallest individual pieces of code (units) are correct, it is not possible to attain correctness of an integrated system. The traditional unit testing techniques like debugging, however, are inevitably disruptive (even when carried out with the best tools), because they are not like coding, they require different mind set. Also, they cannot be easily composed to execute multiple tests at a time, and they require human attention to assess their results.

In the unit testing, the code is tested in isolation. The testing involves writing and running the additional testing code which is written by the developer himself, and exercises a (usually small) specific functionality of the tested code. Thus, for every piece of functionality (production code), the code writer also creates the testing counter-part (test code).

Unit Testing

How the production code and the test code can be organised? There are two ways:

junitnit Testing Framework allows the developer to define a reciprocal test class for every class in the development software. The test classes can be extended incrementally, organised in suitable collections (called suites) for testing a particular aspect of the unit behaviour. The test suites can include ever increasing number of test cases which can be devised from the specification and contracts, by analysing the equivalence classes and boundary cases etc. Often, the test data (expected, or required, values) represent assets (it may be costly to obtain them, eg, using real experiments etc). The JUnit approach allows to use these assets effectively (selection of tests). JUnit also provides special Runner classes for conducting the test runs, using either an easy to use GUI, or generating test reports (in a standard format, like XML) which can be processed using additional tools (calculating statistics, bottlenecks etc).

JUnit testing framework

Before we delve in:

Digression: what is a software framework?

A software framework is a reusable design for a software system (or subsystem). This is expressed as a set of abstract classes and the way their instances collaborate for a specific type of software. All software frameworks are object-oriented designs. Although designs don’t have to be implemented in an object-oriented language, they usually are. … It is the skeleton upon which various objects are integrated for a given solution (Wiki). Frameworks can be HUGE (Swing, Java EE, .NET) or small (JUnit).

JUnit: Advantages and Features

Testing with the JUnit framework offers these remarkable advantages:

JUnit features include:

Steps in JUnit testing

  1. Create a subclass of the TestCase abstract class for every production class, eg, for the class Complex create the class TestComplex (the name of the text class can be arbitrary, but this one is GOOD). The file TestComplex.java should like this:

    import junit.framework.*; // for JUnit-4 import org.junit.*;
    public class TestComplex extends TestCase {
        .... .....
    }
  2. For every method in the production code, write one (or several) test method(s) that asserts expected results on the object under test:

    @Test 
    public void isAdiitionGood() { 
       Complex c  = new Complex(2,2);
       Complex c1 = new Complex(2,0);
       Complex c2 = new Complex(0,2);
    
       assertEquals("Addition operation is broken",c, c1.add(c2));
       assertEquals("Commutativity broken.",c1.add(c2), c2.add(c1));
       .... more assert statements ....
     }

In older JUnit, test methods must have had names staring with test, eg testAddition().

JUnit assert methods

Every test method contains one or more assert calls defined in the Assert class. assert’s are polymorphic methods which represent “atomic” acts of testing. They check the equality of two bits of data (actual and required return values of a method, or actual or required state of an object), and if the equality holds — success, if not — failure. Example of assert-methods:

Some assert methods have Not counterparts, eg assertNotSame() and assertFalse(). The assertEquals() has a variant with an additional argument float tolerance, which should be used if compared values are floats or doubles. The fail() assertion is provided to immediately abort testing to mark a section of code that should not be reached in the normal execution thread. .. (eg, after an exception is expected).

When one of the assert calls fails, that test method is aborted (no remaining assertions in it will be executed), but this is not a failure of the test, this is a success — we found a bug (provided our test was correct)! The test methods which follow the one with the failed assertion will be executed during the same run, such that all assertion failures will be identified and flagged. After the run is complete, we can examine the reported messages. Since different functionalities are isolated in different tests, to find the cause of a particular failure is easy.

JUnit test runs

  1. Override the runTest() method. This can be done in two ways:

    (a) the static way — override runTest() method explicitly using an anonymous inner class, TestCase object can be instantiated as follows:

    TestCase testComplexAddition = new TestComplex("test complex addition") {
       public void runTest() {
          testAddition();
       } 
    };

    Every test method is turned into a named test case. The test class will have as many (or even more) instances of TestCase class as there are methods for testing in the production class.

    (b) the dynamic way relies on the reflection mechanism to implement runTest(). The name of the test object should be tied up to the name of the test method:

    TestCase testComplexConjugate= new TestComplex("testConjugate");

    By using the test object name, JUnit (via reflection, at runtime) finds and invokes the correct test method.

  2. Either way, to run a single test and the get the result, execute:

    TestResult result = testComplexAddition.run();
    TestResult result1 = (new TestComplex("testMultiplication")).run();

JUnit test suites

  1. You can run the tests one at a time “by hands” (like in pre-JUnit days). JUnit provides an object of the TestSuite class which can run any number of test cases together. The test suite object is created and “stuffed” with test objects and/or other test suites.

    TestSuite suite = new TestSuite("Complex Tests");
    suite.addTest(testComplexAdditon);
    suite.addTest(new TestComplex("testAbs"));
    /* ... and so on for all the methods the test class ...*/
    TestResult result = suite.run();

    Adding many tests manually is cumbersome; instead, all test methods from a test class can be added to a suite in one go (again, reflections are used):

    TestSuite suite = new TestSuite(TestComplex.class);
    TestResult result = suite.run();

    Finally, test suites responsible for different classes and (sub-)packages can be placed into one big suite which covers the whole system (such nesting of suites can be done several times — the Russian Doll structure of test suites):

    TestSuite mySuite = new TestSuite("My JUnit Tests");
    TestSuite aSuite = new TestSuite("Algebra Tests");
    TestSuite suite1 = new TestSuite(TestComplex.class);   
    aSuite.addTest(suite1);
    mySuite.addTest(aSuite);
    ... ...

Running tests in JUnit

  1. Once you have a test suite, you can run it and collect the results. JUnit provides tools to run a suite and to display its results. You make your suite accessible to a TestRunner tool with a static method suite that returns a Test suite. So finally, the stand alone class which will run the tests may look as follows:

    import junit.framework.*; // to use JUnit 3.8 runners
    //import org.junit.runner.*; // to use JUnit 4.x
    
    public class MyJunitTests {
        public static void main(String[] args) { // the main method 
            junit.textui.TestRunner.run(suite());// for standalone run
            // org.junit.runner.JUnitCore.run(suite())
        }
    
        public static Test suite() {
            TestSuite mySuite = new TestSuite("My JUnit Tests");
            .... all the code from the previous slide example ....     
            return mySuite;
        }
    }

    The tests are run through the TestRunner, or as standalone

    > java junit.textui.TestRunner MyJunitTests
    ...
    > java MyJunitTests

JUnit run results

The output of the tests — when none test fails — should look as follows:

......................
Time: 0.007

OK (22 tests)

or — if there are broken tests:

....F..F..
Time: 0.106
There were 2 failures:
1) testSimilarity(TestTriangle)junit.framework.AssertionFailedError
    at TestTriangle.testSimilarity(TestTriangle.java:50)
2) testDegenerate(TestTriangle)junit.framework.AssertionFailedError
    at TestTriangle.testDegenerate(TestTriangle.java:69)

FAILURES!!!
Tests run: 8,  Failures: 2,  Errors: 0

Note: Old JUnit included GUI test runners, but JUnit 4.x includes only command-line ones. However, all modern IDEs have excellent plugins to run JUnit tests and display their results.

JUnit Test Fixtures

Tests need to run against the background of a known set of objects. This set of objects is called a test fixture. When you are writing tests you will often find that you spend more time writing the code to set up the fixture than you do in actually testing values. The time and effort can be saved by sharing fixture code. Often, you will be able to use the same fixture for several different tests. Each case will send slightly different messages or parameters to the fixture and will check for different results.

When you have a common fixture, here is what you do:

  1. Create a subclass of TestCase (as explained above)
  2. Add an instance variable for each part of the fixture
  3. Override setUp() to initialise the variables
  4. Override tearDown() to release any permanent resources you allocated in setUp

Once you have the fixture in place, you can write as many test cases as you’d like — the fixture’s setUp() code will be executed immediately before and the fixture’s tearDown() immediately after every method in the test class (JUnit 4 also lets you to define “global” fixtures, which will be executed once at the beginning and once at the end of every test class run; in the “old” JUnit, you have to write this yourself).

The Unit Test Principle: “Whenever you are tempted to type something into a print statement or a debugger expression, write it as a test instead” (M. Fowler).

Software Development Cycle

A software project normally begins because a client (or a computer hacker, or whoever) has a problem to solve and money (or time) to spend. If not abandoned midway, the software undergoes the following life cycle:

  1. Analysis
  2. Design
  3. Implementation
  4. Integration and Testing (V&V, performance, usability etc)
  5. Deployment
  6. Maintenance/service/support

In the analysis phase, the requirements of your software are defined. You try to understand the problem that the customer has given you in as much detail as you can. Many large software projects will not have a single clearly defined problem to solve but will be required to exhibit certain behaviour for particular use cases. The output of this phase is a software requirements specification (SRS) document written in compliance with the industry standards (eg, IEEE Std 830-1998).

The most important part of the requirements are functional requirements, which specify the intended behaviour of the system. Analysis should not focus on how the program will satisfy the requirements, but it can define performance criteria such as speed and memory requirements (they represent non-functional requirements — performance constraints, availability, accessibility etc).

Software Design

Design is concerned with a plan about how you might implement your software system. Design phase begins with formulating the so called Use Cases, which is a technique for capturing functional requirements of systems. “Use cases allow description of sequences of events that, taken together, lead to a system doing something useful” (Bittner and Spence). Each use case provides one or more scenarios that convey how the system should interact with the users called actors to achieve a specific business goal or function.

The use case analysis allows the designers to establish and define (if they use OO approach) the system classes, their relationships and their data attributes and their methods (which must be described in terms of their contact and the most essential elements of implementation). The output of this phase is the Class Diagrams, which constitute the static structure of the system under development.

The dynamic structure is captured in the State Machine Diagrams. The State Machine is an abstraction of object’s life cycle, akin to a class being an abstraction to all its instances. There are other useful diagrammatic techniques for representing the behaviour of objects, class collaborations and so on). Most often used are so called Sequence and Collaboration Diagrams. These are complimentary ways to realise the use cases using the structural and behavioural characteristics of the system, and thus to test the design against the functional requirements.

The would be informal (somewhat artistic rather then scientific or engineering) approach to design software can be made more precise and better defined (at least for communication) via the use of design notations. The de facto industry standard for the design notations is represented by the Unified Modelling Language (UML).

Software Development Methodologies, 1

In the implementation phase you write and compile program code. The output of this phase is the completed program. The unit testing is to be done during this phase!

In the integration and testing phase, you run integration tests to verify that the program as whole works correctly and that it satisfies all of the requirements. The output of this phase is a report describing the tests that you carried out and their results.

In the deployment phase, the program gets installed and used in its target environment. (Some deployment characteristics can be also formalised in the design, the UML has a specialised set of notation for this, too.)

The nature of a software project is that it can get out of control very easily. Because of this, software engineers put much effort into thinking about managing process. When formal software processes were first introduced in the 1970s, engineers had a very simple model of these phases which they called the waterfall model. The output of one phase was meant to spill into the next like water falling down a set of pools as in the figure.

The waterfall model is is too rigid: It is very difficult to immediately come up with a perfect requirements specification (partly because customers do not actually know what they really want!). It was quite common to discover in the design phase that the requirements were inconsistent or that a small change in the requirements would result in a much better system. But the analysis phase was over, so the engineers had to build the inferior system with its errors and all! When the design was actually implemented, it was often found that the design was not perfect and so on.

Waterfall Model

image

Software Development Methodologies, 2

One way to remedy the waterfall model deficiencies is the iterative waterfall model (“waterfall with up-flow”). This model is taught to our software engineers in their project management course. Students are encouraged to translate these phases into a timeline for their specific project.

Yet another improvement is the spiral model (introduced by Barry Boehm). The early phases of this model focus on the construction of prototypes. These are small systems which illustrate the behaviour of one part of the larger project. For example, a GUI prototype could be built to show the customer how it might look (without implementing any functionality). Other prototypes could be built to test out complicated algorithms, to test out integration with external systems, or to profile system performance.

One of the dangers of the spiral model is that an excessive reliance on prototypes may cause engineers to be too relaxed about actually delivering on the overall system requirements. Another alternative is the “Rational Unified Process” (RUP) — see Grady Booch, James Rumbaugh and Ivar Jacobson, ``The Unified Modeling Language User Guide’‘, 2nd Ed., Addison Wesley, 2005,) which was developed by the inventors of the Unified Modelling Language (UML). This has a complicated set of “activity levels” as shown in Figure 3, Chapter 16 of Horstmann. A related to RUP approach is the so called Capability Maturity Model (CMM) (Shayne Flint is an expert). Another quite trendy methodology is “extreme programming” (see Kent Beck, ``Extreme Programming Explained’’, Addison-Wesley, 1999) which focuses on a set of programming practices rather than a formal process. The methodology envisions a constant interaction with a representative of the customer and regular releases of useful systems which complete part of the requirements.

Spiral Model

image

Product Software Quality

All software methodologies aim at achieving software productivity and software quality enhancement.

Software quality is a two-face Janus, its meaning is somewhat different for users and developers. The users (in broad sense, which may include developers too) are concerned with the product quality, while developers are affected by the code quality.

As product, software can be assessed against:

Code Software Quality

As code, software can be assessed against:

There are well developed techniques (supported by tools) to improve the code quality, eg, Refactoring (code modification without changing its external behaviour, the classical exposition in Martin Fowler’s book Refactoring, and on his web site, link above).

What Software Design Involves

Set of rules to obey and steps to make

  1. Discovering classes (tangible things, roles, incidents, interactions, specifications) — they must be abstractions!
  2. Defining non-functional attributes of classes — abstractions of “atomic” (non-derivable) structural characteristics of a class (notional slots, value holders)
  3. Defining operational attributes of classes — behavioural characteristics of a class (actions and functions); derived attribute values must be computed with functions for consistency
  4. Relating classes (dependencies, associations, constraints)
  5. Discovering behaviour of classes (object states) — not here, not now!
  6. Communicating objects (state machines, signals, events) — not here, not now!
  7. Verifying the design: sequence and collaboration diagrams

How to begin

Fear of coding and how to avoid it

Unified Modelling Language

UML is a set of graphical notations (diagrams) backed by a single meta-model to describe and design software systems, especially SS constructed via Object Oriented technology. Actually, the UML means three different things:

UML Class Diagrams

class-diagrams

UML Class Associations

associations

UML Aggregation and Composition

aggregation

UML Classification and Generalisation

generalisation

UML Dependencies and Constraints

class-constraint

Anything (class, association, operation etc) can be a subject to constraint. The constraint can be shown inside a UML note within a pair of braces {..}. Constraints can be expressed in a natural language, pseudo-code or (better, yet) in Object Constraint Language. A simple example of an operation constraint (precondition) is shown on the previous slide “UML Classification and Generalisation” in red.

JUnit Class Design

A case study for UML use in the software design is represented by the JUnit framework.

The small design for a small (but so effective) system includes a number of classes and interfaces, various kinds of association, and design patterns which are brief recipe-like solutions to recurrent design problems. There are 23 (was in 1995, now it’s more than that) standard design patterns for the OO design of general-purpose systems. Earlier, we have encountered the Observer design pattern.

junit-design

The detailed design of JUnit (in the larger context of the Eclipse IDE) is discussed in the E. Gamma and K. Beck’s book “Contributing to Eclipse. Principles, patterns, plugins”.