CSC 309 Lecture Notes Weeks 6 and 7
Design Refinement
Introduction to Code Coverage Measures



  1. Administrative matters.

    1. Midterm on Monday 18 May.

    2. M4 also due on Monday 18 May.


  2. The "P" part of "MVP".

    1. The "P" is for "Process".

    2. Process classes define data and methods that are used to support the efficient implementation of Models

    3. The conceptually easiest way to distinguish a Process class from a Model class is that a Process classes and methods do not trace directly to the abstract objects and operations in the user-level specification.

    4. When using a rich class library, such as Java's, a lot of process classes will be from the library, or relatively simple extensions of library classes.

      1. This is particularly true in the case of Java collection classes.

      2. These classes are suitable for a wide variety of applications in the kind of information processing tools we're building in 309.


  3. An example of data structure refinement.

    1. All of the 309 projects have some interesting data structures that need to be defined.

      1. These will use classes from libraries, such as java.util, or the equivalent for other programming languages.

      2. The underlying data representation details are part of the process component of the design.

    2. The Calendar Tool example has details of a key process-level refinement of the calendar data structure.

    3. The focus of the refinements is choosing an appropriate data representation for a user calendar.

    4. Design decisions are based on the fact that scheduled items need to be accessed individually by unique key, as well as in ordered sequences for days, weeks, and months.

    5. Based on these requirements, a TreeMap is chosen as the representation for the collection of scheduled items in a UserCalendar.

    6. See in particular the UserCalendar and ItemKey classes in the Milestone 4 example.


  4. Using java.io.File and javax.swing.JFileChooser for platform-independent file access.

    1. Useful general operations of class File include:

      1. canRead

      2. canWrite

      3. createNewFile

      4. delete

      5. isDirectory

      6. isFile

      7. mkdir

    2. Package java.io provides very classes to read and write file data.

      1. For output, the classes are FileOutputStream and ObjectOutputStream

      2. For input, the classes are FileInputStream and ObjectInputStream

      3. These classes are used in conjunction with serializable objects.

      4. E.g., suppose we have
        public class SomeModelClass
            extends Model {
        
             ...
        }
        

      5. To write out such a model object do the following:

        model = new SomeModelClass(); /* Put some data in model ... */ FileOutputStream outFile = new FileOutputStream( "model.dat"); ObjectOutputStream outStream = new ObjectOutputStream(outFile); outStream.writeObject(model);

      6. To read the object back in:
        FileInputStream inFile =
            new FileInputStream("model.dat");
        
        ObjectInputStream inStream =
            new ObjectInputStream(inFile);
        
        model = (SomeModelClass)
            inStream.readObject();
        

    3. javax.swing.JFileChooser provides a platform-independent UI for file selection; particularly useful methods:

      1. showOpenDialog

      2. showSaveDialog

      3. getSelectedFile

      4. setSelectedFile


  5. The observer/observable design pattern.

    1. In Java, this pattern is defined with the Observable class and the Observer interface.

      1. Do take the time to through the Java library documentation for this class and interface pair, even if you're not using Java as your project implementation language

      2. The Java documentation is a good example of the design pattern, whatever language you're implementing in.

    2. It is particularly useful when two or more views need to change in response to a change in the model.

    3. The MVP Model and View classes extend these.

    4. I.e.,
      public class View implements Observer {
      
              . . .
      }
      
      public class Model extends Observable implements Serializable {
      
              . . .
      }
      

    5. Here's an example of model/view classes participating in the Observer/Observable pattern:
      public class UserCalendar extends Model {
      
      . . .
      public void add(ScheduledItem item) {
      . . .
      items.add(item); setChanged(); } } . . . public class OKScheduleEVentButtonListener implements ActionListener { public void actionPerformed() {
      try { schedule.scheduleEvent( new caltool.schedule.Event( ... ) ); } . . . schedule.notifyObservers(); . . . } } . . . public class MonthlyAgenda extends View {
      public MonthlyAgenda(caltool.view.View view, UserCalendar userCalendar) {
      . . .
      userCalendar.addObserver(this) } public void update(Observable o, Object arg) { /* Get items for this month from MonthylyAgenda model and update display */ } }

    6. The key aspects of this example are the following:

      1. Observable classes extend java.util.Observable. This extension can be direct, or in the example above, indirect via mvp.Model.

      2. Observer classes implement java.util.Observer. This implementation can be direct, or in the example above, indirect via mvp.View.

      3. The constructor of an observer class calls addObserver on all the model classes it wants to observe.

      4. In observable model classes, all mutating methods call Observable.setChanged when they perform a mutation.

        1. A mutating method is any that changes the state of the data in its class.

        2. In the above example, it's the UserCalendar.add method, which adds an item to a calendar.

        3. Examples of other mutating methods are those that delete or modify items from the calendar, i.e., UserCalender.delete and UserCalender.change.

      5. In companion view classes, the actionPerformed method that calls a mutating model method subsequently calls Observable.notifyObservers, when the mutating methods succeeds.

        1. This separation of calls to setChanged and notifyObservers enforces a clean separation of model/view duties.

        2. The model calls setChanged in all of its mutating methods, but not notifyObservers.

        3. The view calls notifyObservers after a successful call to a mutating model method.

        4. By not calling notifyObservers, the model maintains its independence from its views, and other classes that are observing it.

        5. By not calling setChanged, the view maintains its independence from the model classes that know when and when not to signify changes in the state of model data.

      6. In observing view classes, the Observable.update method is invoked via notifyObservers; when invoked, update calls appropriate model methods to query the model state and access model data.


  6. A client/server design pattern.

    1. In java, client/server applications can be written using the facilities for the java.rmi package, which provides remote method invocation.

    2. An example of a simple remote server and companion client are in ~gfisher/classes/309/examples/rmi

    3. Group members who are implementing some aspect of client/server processing can look at the example for help in getting started.


    -- Now onto Some General Topics of Design Refinement --



  7. Coupling and cohesion between modules, i.e., classes.

    1. "Coupling" and "cohesion" are terms used to denote module interconnectedness.

    2. Coupling refers to how much inter-module connection exists in a design; specific measures of coupling are:

      1. the number of classes that a given class inherits from;

      2. the number of classes that a given class references as data members;

      3. the number of methods that a given method calls;

      4. the number of parameters that a given method has.

    3. Cohesion refers to how much intra-module cohesiveness a design has; measures of cohesion are:

      1. the degree to which all methods in a class perform a logically-related aspect of system functionality

      2. the degree to which a method performs a single specific function

    4. In general, coupling should be minimized and cohesion should be maximized.


  8. Techniques to reduce coupling

    1. Limit the number of classes that communicate with (i.e, call methods of) other classes.

    2. Limit the number of calls between classes that do need to communicate with each other.

      1. E.g., in the Calendar Tool, the dialog button listeners communicate directly with the model, without going through a parent view class.

      2. E.g., OKScheduleEventListener.actionPerformed calls schedule.scheduleEvent directly.

      3. A more highly-coupled alternative would be some chain-of-command call such as ScheduleEventDialog.getSchedule().scheduleEvent.

    3. Limit the number of public methods in each class.

      1. In general, make a method public only if there is a demand for it from some other class.

      2. Do not add public get methods "in case" they may be needed; the same goes for public set methods.

      3. E.g., since ScheduleEventDialog.getSchedule is never used, it should not be provided.

    4. Limit the types of method parameters and return values to the smallest type necessary.

      1. Consider the UserCalendar.getItems operation.

      2. It returns zero or more scheduled items, which could be returned as full UserCalendar object, or a simpler ScheduledItem[] object.

      3. The ScheduledItem[] is preferable, since callers of getItems do not need all of the information in a full UserCalendar, but only the item list.

    5. Design to limit the amount of change needed if data representations change.

      1. When using external process classes (e.g., C++ from Java), design wrapper classes that define a generic process class that hides details of external class.

      2. With a properly designed wrapper class, little or no changes to a Model class are necessary when changing underlying data representations in Process classes.

    6. Use exception handling wisely.

      1. Consider again the use of exception handling to communicate between models and views.

      2. The PrecondViolation exception classes provide a generic, string-based representation that can be used uniformly for error communication.


  9. Techniques to increase cohesion.

    1. Generally, coupling measures are easier to pinpoint exactly.

    2. Limiting the coupling between classes in turn increases cohesiveness, since decoupling eliminates unnecessary items from classes.

    3. To address cohesion directly means to design each class to represent a single, clearly identifiable piece of data and/or encapsulate a single clearly identifiable area of functionality.

    4. To a large extent, the MVP design technique promotes cohesion, since it has well-defined functionality for each category of design class.

    5. Limiting the size of each individual scoping unit (class or method), per our design and implementation conventions, is also a way to foster cohesion.

    6. I.e., it's harder to do too much in a class or method if you can't do that much there in the first place.


  10. Ease of debugging -- a major rationale for limiting coupling.

    1. Consider the debugging question: "If something goes wrong in module X, where do I look for the problem?"

    2. First, look in module X for the problem.

    3. If it's caused immediately there, then look at all other modules that connect to X.

    4. Repeat this process until the source of the problem is located.

    5. Clearly, this problem isolation process will be made more orderly, and potentially shorter if the number of directly connected modules is limited.


    -- And Finally, a Brief Introduction to Code Coverage
    With More to Follow in the Week 8 Lecture Notes



  11. What is code coverage?

    1. It is a measure of how program code is covered for a given program execution.

    2. Coverage is typically measured at the level of textual lines of code.

    3. When a program is run to completion, the coverage measure states the percentage of program lines that are covered, i.e., executed, during the run.

    4. If all lines of code are executed, coverage is 100%; if only half the lines are executed, coverage is 50%.


  12. How code goes "uncovered".

    1. Lines of code can go unexecuted for a number of reasons, including the following.

      1. Uninvoked functions -- code in a function body is not covered if the function is never called during a program run

      2. Untaken conditional branches -- depending on the values assigned to program variables, not all alternative branches of conditional statements may be covered in a particular program run.

      3. Unexecuted loop bodies -- if a loop test never evaluates to true the loop body will not be executed.

    2. In a testing context, uncovered code means there are insufficient test cases to fully exercise the code being tested.


  13. Coverage Tool Resources

    1. See the 309/doc/ page.

    2. And note that code coverage is NOT required for Milestone 7, though it will be for a later milestone.


  14. Where code coverage fits into testing.

    1. Code coverage is used to ensure that black box tests are adequately cover code.

    2. There are many different coverage measures.

    3. The bottom line is to ensure some measure of coverage.

    4. During and after a test execution run, the coverage measures are applied to determine how much of the tested code is covered.

    5. What follows is a discussion of the different coverage measures, from weakest to strongest.


  15. Code coverage measures.

    1. Function (method) coverage.

    2. Statement coverage

    3. Branch coverage

    4. Decision coverage

    5. Loop coverage

    6. Define-use (d-u) coverage

    7. All path coverage

    8. Exhaustive coverage