This site is from a past semester! The current version will be here when the new semester starts.
CS2103/T 2020 Aug-Dec
  • Full Timeline
  • Week 1 [Mon, Aug 10th]
  • Week 2 [Fri, Aug 14th]
  • Week 3 [Fri, Aug 21st]
  • Week 4 [Fri, Aug 28th]
  • Week 5 [Fri, Sep 4th]
  • Week 6 [Fri, Sep 11th]
  • Week 7 [Fri, Sep 18th]
  • Week 8 [Fri, Oct 2nd]
  • Week 9 [Fri, Oct 9th]
  • Week 10 [Fri, Oct 16th]
  • Week 11 [Fri, Oct 23rd]
  • Week 12 [Fri, Oct 30th]
  • Week 13 [Fri, Nov 6th]
  • Textbook
  • Admin Info
  • Dashboards
  •  Individual Project (iP):
  • Individual Project Info
  • iP Upstream Repo
  • iP Showcase
  • iP Code Dashboard
  • iP Progress Dashboard

  •  Team Project (tP):
  • Team Project Info
  • Addressbook-level3
  • Team List
  • tP Code Dashboard
  • tP Progress Dashboard
  • Report Bugs
  • Forum
  • Gitter (Chat)
  • Instructors
  • Announcements
  • Files
  • Tutorial Schedule
  • Java Coding Standard
  • Git Conventions
  • Forum Activities Dashboard
  • Participation Dashboard
  • Week 8 [Fri, Oct 2nd] - Topics

    • [W8.1] [Revisiting] Drawing Class/Object Diagrams - Basics
    • [W8.1a] Design → Modelling → Modelling Structure → Class diagrams - basic

    • [W8.1b] Design → Modelling → Modelling Structure → Object diagrams

    • [W8.1c] Tools → UML → Object versus class diagrams

    • [W8.2] [Revisiting] Drawing Class/Object Diagrams - Intermediate
    • [W8.2a] Tools → UML → Notes

    • [W8.2b] Tools → UML → Constraints : OPTIONAL

    • [W8.2c] Tools → UML → Class Diagrams → Associations as attributes

    • [W8.2d] Design → Modelling → Modelling Structure → Class diagrams - intermediate

    • [W8.2e] Paradigms → OOP → Associations → Association classes

    • [W8.3] [Revisiting] Drawing Sequence Diagrams
    • [W8.3a] Design → Modelling → Modelling Behaviors Sequence diagrams - basic

    • [W8.3b] Design → Modelling → Modelling Behaviors Sequence diagrams - intermediate

    • [W8.3c] Tools → UML → Sequence Diagrams → Reference frames

    • [W8.3d] Tools → UML → Sequence Diagrams → Parallel paths

    • [W8.4] Testing: Types

       Integration Testing

    • [W8.4a] Quality Assurance → Testing → Integration Testing → What

    • [W8.4b] Quality Assurance → Testing → Integration Testing → How

       System Testing

    • [W8.4c] Quality Assurance → Testing → System Testing → What

    • [W8.4d] Quality Assurance → Testing → Test Automation → Automated testing of GUIs

       Acceptance Testing

    • [W8.4e] Quality Assurance → Testing → Acceptance Testing → What

    • [W8.4f] Quality Assurance → Testing → Acceptance Testing → Acceptance versus system testing

       Alpha/Beta Testing

    • [W8.4g] Quality Assurance → Testing → Alpha/Beta Testing → What

       Exploratory vs Scripted Testing

    • [W8.4h] Quality Assurance → Testing → Exploratory and Scripted Testing → What

    • [W8.4i] Quality Assurance → Testing → Exploratory and Scripted Testing → When

    • [W8.5] Testing: Intermediate Concepts

       Dependency Injection

    • [W8.5a] Quality Assurance → Testing → Dependency Injection → What

    • [W8.5b] Quality Assurance → Testing → Dependency Injection → How : OPTIONAL

       Testability

    • [W8.5c] Quality Assurance → Testing → Introduction → Testability

       Test Coverage

    • [W8.5d] Quality Assurance → Testing → Test Coverage → What

    • [W8.5e] Quality Assurance → Testing → Test Coverage → How

       TDD

    • [W8.5f] Quality Assurance → Testing → Test-Driven Development → What


    Guidance for the item(s) below:


    [image source]

    In an earlier week you learned how to interpret class diagrams and object diagrams. Let's go back to that topic but this time let's learn how to draw those diagrams to match code. You'll need to draw class/object diagrams when you document the design of your tP.

    But note that while in the tP you'll be using diagramming tools to draw the diagram, here, we focus on hand drawing. The reason is, we also want you to be able to quickly hand-draw UML diagrams to aid discussions (e.g., draw a diagram on a whiteboard while explaining a design decision to colleagues).

    The relevant UML topics are repeated further down in this page, for reference. In addition, here are somme worked-examples/exercise of drawing class/object/sequence diagrams:

    Video Drawing class/object diagrams (basic) - Box, Item, Lid

    Video Drawing class/object diagrams (intermediate) - Action, Task, History

    Video Drawing class/object diagrams (intermediate) - Person, Inbox, Message

    Video Drawing class/object diagrams (intermediate) - Person, Project, Task

    Video Drawing sequence diagrams (basic) - Item creation

    Video Drawing sequence diagrams (intermediate) - TaskList#generateTask()

    Video Drawing sequence diagrams (intermediate) - create Quote

    [W8.1] [Revisiting] Drawing Class/Object Diagrams - Basics

    Video

    W8.1a

    Design → Modelling → Modelling Structure → Class diagrams - basic

    Can use basic-level class diagrams

    Contents of the panels given below belong to a different chapter; they have been embedded here for convenience and are collapsed by default to avoid content duplication in the printed version.

    Classes form the basis of class diagrams.

    Associations are the main connections among the classes in a class diagram.

    The most basic class diagram is a bunch of classes with some solid lines among them to represent associations, such as this one.

    An example class diagram showing associations between classes.

    In addition, associations can show additional decorations such as association labels, association roles, multiplicity and navigability to add more information to a class diagram.

    Here is the same class diagram shown earlier but with some additional information included:

    Which association notations are shown in this diagram?

    • a. association labels
    • b. association roles
    • c. association multiplicity
    • d. class names

    (a) (b) (c) (d)

    Explanation: '1’ is a multiplicity, ‘mentored by’ is a label, and ‘mentor’ is a role.

    Explain the associations, navigabilities, and multiplicities in the class diagram below:

    Draw a class diagram for the code below. Show the attributes, methods, associations, navigabilities, and multiplicities.

    class Box {
    private Item[] parts = new Item[10];
    private Item spareItem;
    private Lid lid; // lid of this box
    private Box outerBox;

    public void open() {
    // ...
    }
    }
    class Item {
    public static int totalItems;
    }
    class Lid {
    Box box; // the box for which this is the lid
    }

    W8.1b

    Design → Modelling → Modelling Structure → Object diagrams

    Can use basic object diagrams

    Object diagrams can be used to complement class diagrams. For example, you can use object diagrams to model different object structures that can result from a design represented by a given class diagram.

    This question is based on the following question from another topic:

    Draw a class diagram for the code below. Show the attributes, methods, associations, navigabilities, and multiplicities.

    class Box {
    private Item[] parts = new Item[10];
    private Item spareItem;
    private Lid lid; // lid of this box
    private Box outerBox;

    public void open() {
    // ...
    }
    }
    class Item {
    public static int totalItems;
    }
    class Lid {
    Box box; // the box for which this is the lid
    }

    Draw an object diagram to match the code. Include objects of all three classes in your object diagram.

    W8.1c

    Tools → UML → Object versus class diagrams

    Can distinguish between class diagrams and object diagrams

    Compared to the notation for class diagrams, object diagrams differ in the following ways:

    • Show objects instead of classes:
      • Instance name may be shown
      • There is a : before the class name
      • Instance and class names are underlined
    • Methods are omitted
    • Multiplicities are omitted

    Furthermore, multiple object diagrams can correspond to a single class diagram.

    Both object diagrams are derived from the same class diagram shown earlier. In other words, each of these object diagrams shows ‘an instance of’ the same class diagram.

    Which of these class diagrams match the given object diagram?

    • 1
    • 2

    (1) (2)

    Explanation: Both class diagrams allow one Unit object to be linked to one Item object.

    [W8.2] [Revisiting] Drawing Class/Object Diagrams - Intermediate

    Video

    W8.2a

    Tools → UML → Notes

    Can use UML notes

    UML notes can augment UML diagrams with additional information. These notes can be shown connected to a particular element in the diagram or can be shown without a connection. The diagram below shows examples of both.

    Example:

    W8.2b : OPTIONAL

    Tools → UML → Constraints

    Can specify constraints in UML diagrams

    A constraint can be given inside a note, within curly braces. Natural language or a formal notation such as OCL (Object Constraint Language) may be used to specify constraints.

    Example:

    W8.2c

    Tools → UML → Class Diagrams → Associations as attributes

    Can show an association as an attribute

    An association can be shown as an attribute instead of a line.

    Association multiplicities and the default value can be shown as part of the attribute using the following notation. Both are optional.

    name: type [multiplicity] = default value

    The diagram below depicts a multi-player Square Game being played on a board comprising of 100 squares. Each of the squares may be occupied with any number of pieces, each belonging to a certain player.

    A Piece may or may not be on a Square. Note how that association can be replaced by an isOn attribute of the Piece class. The isOn attribute can either be null or hold a reference to a Square object, matching the 0..1 multiplicity of the association it replaces. The default value is null.

    The association that a Board has 100 Squares can be shown in either of these two ways:

    Show each association as either an attribute or a line but not both. A line is preferred as it is easier to spot.

    W8.2d

    Design → Modelling → Modelling Structure → Class diagrams - intermediate

    Can use intermediate-level class diagrams

    A class diagram can also show different types of relationships between classes: inheritance, compositions, aggregations, dependencies.

    Modeling inheritance

    Modeling composition

    Modeling aggregation

    Modeling dependencies

    A class diagram can also show different types of class-like entities:

    Modeling enumerations

    Modeling abstract classes

    Modeling interfaces

    Which of these statements match the class diagram?

    • a. A Snake must belong to at least one Board.
    • b. A SnakeHeadSquare can contain only one Snake head.
    • c. A Square can contain a Snake head.
    • d. A Snake head can be in more than one SnakeHeadSquare.
    • e. The Board has exactly 5 Snakes.

    (a)(b)(c)(d)(e)

    Explanation:

    (a) does not match because a Snake may or may not belong to a Board (multiplicity is 0..1)
    (b) matches the diagram because the multiplicity given is 1
    (c) matches the diagram because SnakeHeadSquare is a Square (due to inheritance)
    (d) matches the diagram because the multiplicity given is *
    (e) matches the diagram because the multiplicity given is 5

    Explain the meaning of various class diagram notations in the following class diagram:

    Consider the code below:

    public interface Billable {
    void bill();
    }
    public abstract class Item
    implements Billable {
    public abstract void print();
    }
    public class StockItem extends Item {
    private Review review;
    private String name;

    public StockItem(
    String name, Rating rating) {

    this.name = name;
    this.review = new Review(rating);
    }

    @Override
    public void print() {
    //...
    }

    @Override
    public void bill() {
    //...
    }
    }
    public enum Rating {
    GOOD, OK, POOR
    }
    public class Review {
    private final Rating rating;

    public Review(Rating rating) {
    this.rating = rating;
    }
    }
    import java.util.List;

    public class Inventory {
    private List<Item> items;

    public int getItemCount() {
    return items.size();
    }

    public void generateBill(Billable b) {
    // ...
    }

    public void add(Item s) {
    items.add(s);
    }
    }

    (a) Draw a class diagram to represent the code. Show all attributes, methods, associations, navigabilities, visibilities, known multiplicities, and association roles. Show associations as lines.
    (b) Draw an object diagram to represent the situation where the inventory has one item named spanner and a review of POOR rating
    i.e., new Inventory().add(new StockItem("spanner", new Review(Rating.POOR))).

    W8.2e

    Paradigms → OOP → Associations → Association classes

    Can explain the meaning of association classes

    An association class represents additional information about an association. It is a normal class but plays a special role from a design point of view.

    A Man class and a Woman class are linked with a ‘married to’ association and there is a need to store the date of marriage. However, that data is related to the association rather than specifically owned by either the Man object or the Woman object. In such situations, an additional association class can be introduced, e.g. a Marriage class, to store such information.

    Implementing association classes

    There is no special way to implement an association class. It can be implemented as a normal class that has variables to represent the endpoint of the association it represents.

    In the code below, the Transaction class is an association class that represents a transaction between a Person who is the seller and another Person who is the buyer.

    class Transaction {

    //all fields are compulsory
    Person seller;
    Person buyer;
    Date date;
    String receiptNumber;

    Transaction(Person seller, Person buyer, Date date, String receiptNumber) {
    //set fields
    }
    }

    Which of these are suitable as an Association Class?

    • a
    • b
    • c
    • d

    (a)(b)(c)(d)

    Explanation: Mileage is a property of the car, and not specifically about the association between the Driver and the Car. If Mileage was defined as the total number of miles that car was driven by that driver, then it would be suitable as an association class.

    [W8.3] [Revisiting] Drawing Sequence Diagrams

    W8.3a

    Design → Modelling → Modelling Behaviors Sequence diagrams - basic

    Video

    Can draw basic sequence diagrams

    Explain in your own words the interactions illustrated by this Sequence Diagram:

    Consider the code below:

    class Person {
    Tag tag;
    String name;

    Person(String personName, String tagName) {
    name = personName;
    tag = new Tag(tagName);
    }
    }
    class Tag {
    Tag(String value) {
    // ...
    }
    }

    class PersonList {
    void addPerson(Person p) {
    // ...
    }
    }

    Draw a sequence diagram to illustrate the object interactions that happen in the code snippet below:

    PersonList personList = new PersonList();
    while (hasRoom) {
    Person p = new Person("Adam", "friend");
    personList.addPerson(p);
    }

    Find notation mistakes in the sequence diagram below:

    W8.3b

    Design → Modelling → Modelling Behaviors Sequence diagrams - intermediate

    Video

    Can draw intermediate-level sequence diagrams

    Method calls to static (i.e., class-level) methods are received by the class itself, not an instance of that class. You can use <<class>> to show that a participant is the class itself.

    In this example, m calls the static method Person.getMaxAge() and also the setAge() method of a Person object p.

    Here is the Person class, for reference:

    What’s going on here?

    • a. Logic object is executing a parallel thread.
    • b. Logic object is executing a loop.
    • c. Logic object is creating another Logic instance.
    • d. One of Logic object’s methods is calling another of its methods.
    • e. Minefield object is calling a method of Logic.

    (d)

    Explain the interactions depicted in this sequence diagram.

    First, the createParser() method of an existing ParserFactory object is called. Then, ...

    Draw a sequence diagram to represent this code snippet.

    if (isFirstPage) {
    new Quote().print();
    }

    The Quote class:

    class Quote {

    String q;

    Quote() {
    q = generate();
    }

    String generate() {
    // ...
    }

    void print() {
    System.out.println(q);
    }

    }
    • Show new Quote().print(); as two method calls.
    • As the created Quote object is not assigned to a variable, it can be considered as 'deleted' soon after its print() method is called.

    W8.3c

    Tools → UML → Sequence Diagrams → Reference frames

    Can interpret sequence diagrams with reference frames

    UML uses ref frame to allow a segment of the interaction to be omitted and shown as a separate sequence diagram. Reference frames help you to break complicated sequence diagrams into multiple parts or simply to omit details you are not interested in showing.

    Notation:

    The details of the get minefield appearance interactions have been omitted from the diagram.

    Those details are shown in a separate sequence diagram given below.

    W8.3d

    Tools → UML → Sequence Diagrams → Parallel paths

    Can interpret sequence diagrams with parallel paths

    UML uses par frames to indicate parallel paths.

    Notation:

    Logic is calling methods CloudServer#poll() and LocalServer#poll() in parallel.

    If you show parallel paths in a sequence diagram, the corresponding Java implementation is likely to be multi-threaded because a normal Java program cannot do multiple things at the same time.

    Guidance for the item(s) below:

    You've already been doing some testing in your project work. It turns out there are many ways to categorize testing. Let's learn some of the more common categorizations of testing.

    [W8.4] Testing: Types


    Integration Testing

    W8.4a

    Quality Assurance → Testing → Integration Testing → What

    Video

    Can explain integration testing

    Integration testing : testing whether different parts of the software work together (i.e. integrates) as expected. Integration tests aim to discover bugs in the 'glue code' related to how components interact with each other. These bugs are often the result of misunderstanding what the parts are supposed to do vs what the parts are actually doing.

    Suppose a class Car uses classes Engine and Wheel. If the Car class assumed a Wheel can support a speed of up to 200 mph but the actual Wheel can only support a speed of up to 150 mph, it is the integration test that is supposed to uncover this discrepancy.

    W8.4b

    Quality Assurance → Testing → Integration Testing → How

    Video

    Can use integration testing

    Integration testing is not simply a case of repeating the unit test cases using the actual dependencies (instead of the stubs used in unit testing). Instead, integration tests are additional test cases that focus on the interactions between the parts.

    Suppose a class Car uses classes Engine and Wheel. Here is how you would go about doing pure integration tests:

    a) First, unit test Engine and Wheel.
    b) Next, unit test Car in isolation of Engine and Wheel, using stubs for Engine and Wheel.
    c) After that, do an integration test for Car by using it together with the Engine and Wheel classes to ensure that Car integrates properly with the Engine and the Wheel.

    In practice, developers often use a hybrid of unit+integration tests to minimize the need for stubs.

    Here's how a hybrid unit+integration approach could be applied to the same example used above:

    (a) First, unit test Engine and Wheel.
    (b) Next, unit test Car in isolation of Engine and Wheel, using stubs for Engine and Wheel.
    (c) After that, do an integration test for Car by using it together with the Engine and Wheel classes to ensure that Car integrates properly with the Engine and the Wheel. This step should include test cases that are meant to unit test Car (i.e. test cases used in the step (b) of the example above) as well as test cases that are meant to test the integration of Car with Wheel and Engine (i.e. pure integration test cases used of the step (c) in the example above).

    Note that you no longer need stubs for Engine and Wheel. The downside is that Car is never tested in isolation of its dependencies. Given that its dependencies are already unit tested, the risk of bugs in Engine and Wheel affecting the testing of Car can be considered minimal.


    System Testing

    W8.4c

    Quality Assurance → Testing → System Testing → What

    Can explain system testing

    System testing: take the whole system and test it against the system specification.

    System testing is typically done by a testing team (also called a QA team).

    System test cases are based on the specified external behavior of the system. Sometimes, system tests go beyond the bounds defined in the specification. This is useful when testing that the system fails 'gracefully' when pushed beyond its limits.

    Suppose the SUT is a browser that is supposedly capable of handling web pages containing up to 5000 characters. Given below is a test case to test if the SUT fails gracefully if pushed beyond its limits.

    Test case: load a web page that is too big
    * Input: load a web page containing more than 5000 characters.
    * Expected behavior: abort the loading of the page and show a meaningful error message.

    This test case would fail if the browser attempted to load the large file anyway and crashed.

    System testing includes testing against non-functional requirements too. Here are some examples:

    • Performance testing – to ensure the system responds quickly.
    • Load testing (also called stress testing or scalability testing) – to ensure the system can work under heavy load.
    • Security testing – to test how secure the system is.
    • Compatibility testing, interoperability testing – to check whether the system can work with other systems.
    • Usability testing – to test how easy it is to use the system.
    • Portability testing – to test whether the system works on different platforms.

    W8.4d

    Quality Assurance → Testing → Test Automation → Automated testing of GUIs

    Video

    Can explain automated GUI testing

    If a software product has a GUI (Graphical User Interface) component, all product-level testing (i.e. the types of testing mentioned above) need to be done using the GUI. However, testing the GUI is much harder than testing the CLI (Command Line Interface) or API, for the following reasons:

    • Most GUIs can support a large number of different operations, many of which can be performed in any arbitrary order.
    • GUI operations are more difficult to automate than API testing. Reliably automating GUI operations and automatically verifying whether the GUI behaves as expected is harder than calling an operation and comparing its return value with an expected value. Therefore, automated regression testing of GUIs is rather difficult.
    • The appearance of a GUI (and sometimes even behavior) can be different across platforms and even environments. For example, a GUI can behave differently based on whether it is minimized or maximized, in focus or out of focus, and in a high resolution display or a low resolution display.

    Moving as much logic as possible out of the GUI can make GUI testing easier. That way, you can bypass the GUI to test the rest of the system using automated API testing. While this still requires the GUI to be tested, the number of such test cases can be reduced as most of the system will have been tested using automated API testing.

    There are testing tools that can automate GUI testing.

    Some tools used for automated GUI testing:

    • TestFX can do automated testing of JavaFX GUIs

    • Visual Studio supports the ‘record replay’ type of GUI test automation.

    • Selenium can be used to automate testing of web application UIs

      This video shows automated testing of the TEAMMATES web app using Selenium.

    GUI testing is usually easier than API testing because it doesn’t require any extra coding.

    False


    Acceptance Testing

    W8.4e

    Quality Assurance → Testing → Acceptance Testing → What

    Can explain acceptance testing

    Acceptance testing (aka User Acceptance Testing (UAT): test the system to ensure it meets the user requirements.

    Acceptance tests give an assurance to the customer that the system does what it is intended to do. Acceptance test cases are often defined at the beginning of the project, usually based on the use case specification. Successful completion of UAT is often a prerequisite to the project sign-off.

    W8.4f

    Quality Assurance → Testing → Acceptance Testing → Acceptance versus system testing

    Video

    Can explain the differences between system testing and acceptance testing

    Acceptance testing comes after system testing. Similar to system testing, acceptance testing involves testing the whole system.

    Some differences between system testing and acceptance testing:

    System Testing Acceptance Testing
    Done against the system specification Done against the requirements specification
    Done by testers of the project team Done by a team that represents the customer
    Done on the development environment or a test bed Done on the deployment site or on a close simulation of the deployment site
    Both negative and positive test cases More focus on positive test cases

    Note: negative test cases: cases where the SUT is not expected to work normally e.g. incorrect inputs; positive test cases: cases where the SUT is expected to work normally

    Requirement specification versus system specification

    The requirement specification need not be the same as the system specification. Some example differences:

    Requirements specification System specification
    limited to how the system behaves in normal working conditions can also include details on how it will fail gracefully when pushed beyond limits, how to recover, etc. specification
    written in terms of problems that need to be solved (e.g. provide a method to locate an email quickly) written in terms of how the system solves those problems (e.g. explain the email search feature)
    specifies the interface available for intended end-users could contain additional APIs not available for end-users (for the use of developers/testers)

    However, in many cases one document serves as both a requirement specification and a system specification.

    Passing system tests does not necessarily mean passing acceptance testing. Some examples:

    • The system might work on the testbed environments but might not work the same way in the deployment environment, due to subtle differences between the two environments.
    • The system might conform to the system specification but could fail to solve the problem it was supposed to solve for the user, due to flaws in the system design.

    Choose the correct statements about system testing and acceptance testing.

    • a. Both system testing and acceptance testing typically involve the whole system.
    • b. System testing is typically more extensive than acceptance testing.
    • c. System testing can include testing for non-functional qualities.
    • d. Acceptance testing typically has more user involvement than system testing.
    • e. In smaller projects, the developers may do system testing as well, in addition to developer testing.
    • f. If system testing is adequately done, we need not do acceptance testing.

    (a)(b)(c)(d)(e)(f)

    Explanation:

    (b) is correct because system testing can aim to cover all specified behaviors and can even go beyond the system specification. Therefore, system testing is typically more extensive than acceptance testing.

    (f) is incorrect because it is possible for a system to pass system tests but fail acceptance tests.


    Alpha/Beta Testing

    W8.4g

    Quality Assurance → Testing → Alpha/Beta Testing → What

    Can explain alpha and beta testing

    Alpha testing is performed by the users, under controlled conditions set by the software development team.

    Beta testing is performed by a selected subset of target users of the system in their natural work setting.

    An open beta release is the release of not-yet-production-quality-but-almost-there software to the general population. For example, Google’s Gmail was in 'beta' for many years before the label was finally removed.


    Exploratory vs Scripted Testing

    W8.4h

    Quality Assurance → Testing → Exploratory and Scripted Testing → What

    Can explain exploratory testing and scripted testing

    Here are two alternative approaches to testing a software: Scripted testing and Exploratory testing.

    1. Scripted testing: First write a set of test cases based on the expected behavior of the SUT, and then perform testing based on that set of test cases.

    2. Exploratory testing: Devise test cases on-the-fly, creating new test cases based on the results of the past test cases.

    Exploratory testing is ‘the simultaneous learning, test design, and test execution’ [source: bach-et-explained] whereby the nature of the follow-up test case is decided based on the behavior of the previous test cases. In other words, running the system and trying out various operations. It is called exploratory testing because testing is driven by observations during testing. Exploratory testing usually starts with areas identified as error-prone, based on the tester’s past experience with similar systems. One tends to conduct more tests for those operations where more faults are found.

    Here is an example thought process behind a segment of an exploratory testing session:

    “Hmm... looks like feature x is broken. This usually means feature n and k could be broken too; you need to look at them soon. But before that, you should give a good test run to feature y because users can still use the product if feature y works, even if x doesn’t work. Now, if feature y doesn’t work 100%, you have a major problem and this has to be made known to the development team sooner rather than later...”

    Exploratory testing is also known as reactive testing, error guessing technique, attack-based testing, and bug hunting.

    Exploratory Testing Explained, an online article by James Bach -- James Bach is an industry thought leader in software testing.

    Scripted testing requires tests to be written in a scripting language; manual testing is called exploratory testing.

    False

    Explanation: “Scripted” means test cases are predetermined. They need not be an executable script. However, exploratory testing is usually manual.

    Which testing technique is better?

    (e)

    Explain the concept of exploratory testing using Minesweeper as an example.

    When we test Minesweeper by simply playing it in various ways, especially trying out those that are likely to be buggy, that would be exploratory testing.

    W8.4i

    Quality Assurance → Testing → Exploratory and Scripted Testing → When

    Video

    Can explain the choice between exploratory testing and scripted testing

    Which approach is better – scripted or exploratory? A mix is better.

    The success of exploratory testing depends on the tester’s prior experience and intuition. Exploratory testing should be done by experienced testers, using a clear strategy/plan/framework. Ad-hoc exploratory testing by unskilled or inexperienced testers without a clear strategy is not recommended for real-world non-trivial systems. While exploratory testing may allow us to detect some problems in a relatively short time, it is not prudent to use exploratory testing as the sole means of testing a critical system.

    Scripted testing is more systematic, and hence, likely to discover more bugs given sufficient time, while exploratory testing would aid in quick error discovery, especially if the tester has a lot of experience in testing similar systems.

    In some contexts, you will achieve your testing mission better through a more scripted approach; in other contexts, your mission will benefit more from the ability to create and improve tests as you execute them. I find that most situations benefit from a mix of scripted and exploratory approaches. --[source: bach-et-explained]

    Exploratory Testing Explained, an online article by James Bach -- James Bach is an industry thought leader in software testing.

    Scripted testing is better than exploratory testing.

    False

    Explanation: Each has its pros and cons. Relying on only one is not recommended. A combination is better.

    Guidance for the item(s) below:

    One of the primary goals of a software engineer is to avoid bugs. You certainly don't want to be the person responsible for causing a major bug that caused heavy damages to some party. That's why we need to focus heavily on testing -- one of the main defences against bugs.

    The next few sections cover some intermediate level testing topics that you are very likely to encounter in software engineering.

    [W8.5] Testing: Intermediate Concepts


    Dependency Injection

    Video

    W8.5a

    Quality Assurance → Testing → Dependency Injection → What

    Can explain dependency injection

    Dependency injection is the process of 'injecting' objects to replace current dependencies with a different object. This is often used to inject stubs to isolate the Software Under TestSUT from its objects it depends ondependencies so that it can be tested in isolation.

    Quality Assurance → Testing → Unit Testing →

    Stubs

    A proper unit test requires the unit to be tested in isolation so that bugs in the code the unit depends ondependencies cannot influence the test i.e. bugs outside of the unit should not affect the unit tests.

    If a Logic class depends on a Storage class, unit testing the Logic class requires isolating the Logic class from the Storage class.

    Stubs can isolate the Software Under Test (in this case, the unit being tested)SUT from its dependencies.

    Stub: A stub has the same interface as the component it replaces, but its implementation is so simple that it is unlikely to have any bugs. It mimics the responses of the component, but only for a limited set of predetermined inputs. That is, it does not know how to respond to any other inputs. Typically, these mimicked responses are hard-coded in the stub rather than computed or retrieved from elsewhere, e.g. from a database.

    Consider the code below:

    class Logic {
    Storage s;

    Logic(Storage s) {
    this.s = s;
    }

    String getName(int index) {
    return "Name: " + s.getName(index);
    }
    }
    interface Storage {
    String getName(int index);
    }
    class DatabaseStorage implements Storage {

    @Override
    public String getName(int index) {
    return readValueFromDatabase(index);
    }

    private String readValueFromDatabase(int index) {
    // retrieve name from the database
    }
    }

    Normally, you would use the Logic class as follows (note how the Logic object depends on a DatabaseStorage object to perform the getName() operation):

    Logic logic = new Logic(new DatabaseStorage());
    String name = logic.getName(23);

    You can test it like this:

    @Test
    void getName() {
    Logic logic = new Logic(new DatabaseStorage());
    assertEquals("Name: John", logic.getName(5));
    }

    However, this logic object being tested is making use of a DataBaseStorage object which means a bug in the DatabaseStorage class can affect the test. Therefore, this test is not testing Logic in isolation from its dependencies and hence it is not a pure unit test.

    Here is a stub class you can use in place of DatabaseStorage:

    class StorageStub implements Storage {

    @Override
    public String getName(int index) {
    if (index == 5) {
    return "Adam";
    } else {
    throw new UnsupportedOperationException();
    }
    }
    }

    Note how the StorageStub has the same interface as DatabaseStorage, but is so simple that it is unlikely to contain bugs, and is pre-configured to respond with a hard-coded response, presumably, the correct response DatabaseStorage is expected to return for the given test input.

    Here is how you can use the stub to write a unit test. This test is not affected by any bugs in the DatabaseStorage class and hence is a pure unit test.

    @Test
    void getName() {
    Logic logic = new Logic(new StorageStub());
    assertEquals("Name: Adam", logic.getName(5));
    }

    In addition to Stubs, there are other type of replacements you can use during testing, e.g. Mocks, Fakes, Dummies, Spies.

    • Mocks Aren't Stubs by Martin Fowler -- An in-depth article about how Stubs differ from other types of test helpers.

    Stubs help us to test a component in isolation from its dependencies.

    True

    A Foo object normally depends on a Bar object, but you can inject a BarStub object so that the Foo object no longer depends on a Bar object. Now you can test the Foo object in isolation from the Bar object.

    W8.5b : OPTIONAL

    Quality Assurance → Testing → Dependency Injection → How

    Can use dependency injection

    Polymorphism can be used to implement dependency injection, as can be seen in the example given in [Quality Assurance → Testing → Unit Testing → Stubs] where a stub is injected to replace a dependency.

    Quality Assurance → Testing → Unit Testing →

    Stubs

    A proper unit test requires the unit to be tested in isolation so that bugs in the code the unit depends ondependencies cannot influence the test i.e. bugs outside of the unit should not affect the unit tests.

    If a Logic class depends on a Storage class, unit testing the Logic class requires isolating the Logic class from the Storage class.

    Stubs can isolate the Software Under Test (in this case, the unit being tested)SUT from its dependencies.

    Stub: A stub has the same interface as the component it replaces, but its implementation is so simple that it is unlikely to have any bugs. It mimics the responses of the component, but only for a limited set of predetermined inputs. That is, it does not know how to respond to any other inputs. Typically, these mimicked responses are hard-coded in the stub rather than computed or retrieved from elsewhere, e.g. from a database.

    Consider the code below:

    class Logic {
    Storage s;

    Logic(Storage s) {
    this.s = s;
    }

    String getName(int index) {
    return "Name: " + s.getName(index);
    }
    }
    interface Storage {
    String getName(int index);
    }
    class DatabaseStorage implements Storage {

    @Override
    public String getName(int index) {
    return readValueFromDatabase(index);
    }

    private String readValueFromDatabase(int index) {
    // retrieve name from the database
    }
    }

    Normally, you would use the Logic class as follows (note how the Logic object depends on a DatabaseStorage object to perform the getName() operation):

    Logic logic = new Logic(new DatabaseStorage());
    String name = logic.getName(23);

    You can test it like this:

    @Test
    void getName() {
    Logic logic = new Logic(new DatabaseStorage());
    assertEquals("Name: John", logic.getName(5));
    }

    However, this logic object being tested is making use of a DataBaseStorage object which means a bug in the DatabaseStorage class can affect the test. Therefore, this test is not testing Logic in isolation from its dependencies and hence it is not a pure unit test.

    Here is a stub class you can use in place of DatabaseStorage:

    class StorageStub implements Storage {

    @Override
    public String getName(int index) {
    if (index == 5) {
    return "Adam";
    } else {
    throw new UnsupportedOperationException();
    }
    }
    }

    Note how the StorageStub has the same interface as DatabaseStorage, but is so simple that it is unlikely to contain bugs, and is pre-configured to respond with a hard-coded response, presumably, the correct response DatabaseStorage is expected to return for the given test input.

    Here is how you can use the stub to write a unit test. This test is not affected by any bugs in the DatabaseStorage class and hence is a pure unit test.

    @Test
    void getName() {
    Logic logic = new Logic(new StorageStub());
    assertEquals("Name: Adam", logic.getName(5));
    }

    In addition to Stubs, there are other type of replacements you can use during testing, e.g. Mocks, Fakes, Dummies, Spies.

    • Mocks Aren't Stubs by Martin Fowler -- An in-depth article about how Stubs differ from other types of test helpers.

    Stubs help us to test a component in isolation from its dependencies.

    True

    Here is another example of using polymorphism to implement dependency injection:

    Suppose you want to unit test Payroll#totalSalary() given below. The method depends on the SalaryManager object to calculate the return value. Note how the setSalaryManager(SalaryManager) can be used to inject a SalaryManager object to replace the current SalaryManager object.

    class Payroll {
    private SalaryManager manager = new SalaryManager();
    private String[] employees;

    void setEmployees(String[] employees) {
    this.employees = employees;
    }

    void setSalaryManager(SalaryManager sm) {
    this.manager = sm;
    }

    double totalSalary() {
    double total = 0;
    for (int i = 0; i < employees.length; i++) {
    total += manager.getSalaryForEmployee(employees[i]);
    }
    return total;
    }
    }


    class SalaryManager {
    double getSalaryForEmployee(String empID) {
    // code to access employee’s salary history
    // code to calculate total salary paid and return it
    }
    }

    During testing, you can inject a SalaryManagerStub object to replace the SalaryManager object.

    class PayrollTest {
    public static void main(String[] args) {
    // test setup
    Payroll p = new Payroll();
    p.setSalaryManager(new SalaryManagerStub()); // dependency injection
    // test case 1
    p.setEmployees(new String[]{"E001", "E002"});
    assertEquals(2500.0, p.totalSalary());
    // test case 2
    p.setEmployees(new String[]{"E001"});
    assertEquals(1000.0, p.totalSalary());
    // more tests ...
    }
    }


    class SalaryManagerStub extends SalaryManager {
    /** Returns hard coded values used for testing */
    double getSalaryForEmployee(String empID) {
    if (empID.equals("E001")) {
    return 1000.0;
    } else if (empID.equals("E002")) {
    return 1500.0;
    } else {
    throw new Error("unknown id");
    }
    }
    }

    Choose the correct statements about dependency injection.

    • a. It is a technique for increasing dependencies
    • b. It is useful for unit testing
    • c. It can be done using polymorphism
    • d. It can be used to substitute a component with a stub

    (a)(b)(c)(d)

    Explanation: It is a technique we can use to substitute an existing dependency with another, not increase dependencies. It is useful when you want to test a component in isolation but the SUT depends on other components. Using dependency injection, we can substitute those other components with test-friendly stubs. This is often done using polymorphism.


    Testability

    W8.5c

    Quality Assurance → Testing → Introduction → Testability

    Can explain testability

    Testability is an indication of how easy it is to test an SUT. As testability depends a lot on the design and implementation, you should try to increase the testability when you design and implement software. The higher the testability, the easier it is to achieve better quality software.


    Test Coverage

    Video

    W8.5d

    Quality Assurance → Testing → Test Coverage → What

    Can explain test coverage

    Test coverage is a metric used to measure the extent to which testing exercises the code i.e., how much of the code is 'covered' by the tests.

    Here are some examples of different coverage criteria:

    • Function/method coverage : based on functions executed e.g., testing executed 90 out of 100 functions.
    • Statement coverage : based on the number of lines of code executed e.g., testing executed 23k out of 25k LOC.
    • Decision/branch coverage : based on the decision points exercised e.g., an if statement evaluated to both true and false with separate test cases during testing is considered 'covered'.
    • Condition coverage : based on the boolean sub-expressions, each evaluated to both true and false with different test cases. Condition coverage is not the same as the decision coverage.

    if(x > 2 && x < 44) is considered one decision point but two conditions.

    For 100% branch or decision coverage, two test cases are required:

    • (x > 2 && x < 44) == true : [e.g. x == 4]
    • (x > 2 && x < 44) == false : [e.g. x == 100]

    For 100% condition coverage, three test cases are required:

    • (x > 2) == true , (x < 44) == true : [e.g. x == 4]
    • (x < 44) == false : [e.g. x == 100]
    • (x > 2) == false : [e.g. x == 0]
    • Path coverage measures coverage in terms of possible paths through a given part of the code executed. 100% path coverage means all possible paths have been executed. A commonly used notation for path analysis is called the Control Flow Graph (CFG).
    • Entry/exit coverage measures coverage in terms of possible calls to and exits from the operations in the SUT.

    Which of these gives us the highest intensity of testing?

    (b)

    Explanation: 100% path coverage implies all possible execution paths through the SUT have been tested. This is essentially ‘exhaustive testing’. While this is very hard to achieve for a non-trivial SUT, it technically gives us the highest intensity of testing. If all tests pass at 100% path coverage, the SUT code can be considered ‘bug free’. However, note that path coverage does not include paths that are missing from the code altogether because the programmer left them out by mistake.

    W8.5e

    Quality Assurance → Testing → Test Coverage → How

    Can explain how test coverage works

    Measuring coverage is often done using coverage analysis tools. Most IDEs have inbuilt support for measuring test coverage, or at least have plugins that can measure test coverage.

    Coverage analysis can be useful in improving the quality of testing e.g., if a set of test cases does not achieve 100% branch coverage, more test cases can be added to cover missed branches.

    Measuring code coverage in Intellij IDEA


    TDD

    Video

    W8.5f

    Quality Assurance → Testing → Test-Driven Development → What

    Can explain TDD

    Test-Driven Development(TDD) advocates writing the tests before writing the SUT, while evolving functionality and tests in small increments. In TDD you first define the precise behavior of the SUT using test cases, and then write the SUT to match the specified behavior. While TDD has its fair share of detractors, there are many who consider it a good way to reduce defects. One big advantage of TDD is that it guarantees the code is testable.

    A) In TDD, we write all the test cases before we start writing functional code.

    B) Testing tools such as JUnit require us to follow TDD.

    A) False

    Explanation: No, not all. We proceed in small steps, writing tests and functional code in tandem, but writing the test before we write the corresponding functional code.

    B) False

    Explanation: They can be used for TDD, but they can be used without TDD too.

    Follow up notes for the item(s) above:

    You are welcome to, but not required to, follow the TDD approach in your project.