Chapter:Classes - Testing your classes

From Juneday education
Jump to: navigation, search

Meta information about this chapter

Expand using link to the right to see the full content.

Introduction

This lecture is about testing that the classes the student writes work the way she intended. We show some ways to test a class, like if-statements and printouts and assertions.

Purpose

The purpose is to make the student aware of testing and some things to think about when testing.

Goal

After this chapter, the student shall understand:

  • How to write a simple test of a class using if-statements and printouts
  • The assert statement syntax and how to use it (including how to enable assertions in the JVM)
  • That tests can't prove the absence of bugs, but help finding some
  • That extreme values are a common source of bugs (null, 0, negative numbers, extremely large or small numbers)
  • That bugs you find while testing improves the code (if you fix them)
  • That one approach to testing is to let the test program crash on failure so that failed tests are fixed in sequence

Instructions to the teacher

Common problems

  • Students forgetting to enable assertions when running tests
  • Test code which crashes - e.g. an equals test generates a null pointer exception
  • Explaining whose responsibility it is to handle problematic data - the method or the calling code?
    • Exceptions, which comes later in the course material, solves this question in an organized way
    • Explain that checking in-data is never wrong, the problem is how to act when a method "discovers" bad input - exceptions will solve this later
  • Stress that the example class is just a simple example and that e.g. the test for valid email is just a simplification
    • A real email validity check would typically use regular expressions but there's no time for that and it's not the focus of this lecture

Classes: Testing if your classes work

How do we know a class working the way we want it to? We need to test it. There are several test strategies and methods which could be the basis of a separate course. In this course we'll provide some tips and tricks for you when writing code.

Let's look at a version of our Member class again:

package net.supermegacorp.orgmanager;

public class Member {

  private String name;
  private String email;
  private static final String separator = ";";

  public Member(String name, String email) {
    this(name);
    setEmail(email);
  }

 
  public Member(String name) {
    this.name = name;
  }

  public void setEmail(String email) {
    if (email.contains("@")) {
      this.email = email;
    }
  }
  
  public String toString() {
    return name + separator + email;
  }
    
  public String name() {
    return name;
  }
  
  public String email() {
    return email;
  }


  public static String getSeparator() {
    return separator;
  }
}

What do we need to test (or verify) in this class? In the first constructor we have two parameters. The second argument is only valid if there's an "@" in it so let's write code to:

  • create a Member object with a valid email address
  • create a Member object with an invalid email address
  • create a Member object with null as an email address

let's do this first and then write code to make sure the values were set accordingly.

So let's start with the following test class:

package net.supermegacorp.orgmanager.test;

import net.supermegacorp.orgmanager.Member;

public class MemberTest {

    public static void main(String[] args) {
      Member ada     = new Member("Ada", "ada@lovelace.edu");
      Member charles = new Member("Charles", "charles __AT__ babbage.net");
      Member bert    = new Member("Bert", null);
    }

}

Compile and execute these tests. Wooops, we can compile but get an error when executing it:

Exception in thread "main" java.lang.NullPointerException
	at net.supermegacorp.orgmanager.Member.setEmail(Member.java:20)
	at net.supermegacorp.orgmanager.Member.<init>(Member.java:11)

----
	at net.supermegacorp.orgmanager.test.MemberTest.main(MemberTest.java:11)

Already we have seen a positive effect of our tests and we have just begun. At line 20 in the Member.class something goes wrong. Since Java says it is a NullPointerException we can deduce that emails is null. So we need to change the method setEmail() in our member class slightly. Let's write it like this instead:

1   public void setEmail(String email) {
2     if (email != null && email.contains("@")) {
3       this.email = email;
4     }
5   }

The change here is that we make sure that the email address is not null before we invoke the contains method. Let's compile and execute again.

Nice, it seems to work or at least not crash. So let's continue writing code to check that the values were set accordingly. We give an example on how to do this below:

    public static void main(String[] args) {
      Member ada     = new Member("Ada", "ada@lovelace.edu");
      Member charles = new Member("Charles", "charles __AT__ babbage.net");
      Member bert    = new Member("Bert", null);

      System.out.println("ada:      " + ada.email());
      System.out.println("charles:  " + charles.email());
      System.out.println("bert:     " + bert.email());
    }

This is ok, but it is a bit tedious that the developer has to manually check the email addresses. Since we're (or at least trying to become) developers we want to automate this, so let's write the following text code to verify that the constructor does its job:

      // Let's check that we have managed to store "ada@lovelace.edu" 
      if ( !ada.email().equals("ada@lovelace.edu")) {
          System.err.println("Ada's email " + ada.email() + ") is faulty: ");
      }

      // Passing the email address "charles __AT__ babbage.net" should result
      // in the email (instance) variable being assigned null.
      //
      // Let's verify this (that the email address is null).
      //
      // Let's check that the constructor works (i.e assignes email to null), 
      //    so, if not null (!=null) then we should output an error message
      if ( charles.email() != null ) {
          System.err.println("charles' email is faulty. Current value: \"" + charles.email + "\"   should be null");
      } 
      // We could potentially have added this, but we want a silent check
      // - that is, don't print anything if "ok"
      /*else {
          System.out.println("charles' email is null, which is ok");
      } 
      */

      // Similar to the above
      if ( bert.email() != null ) {
          System.err.println("bert's email  is faulty: ");
      }

Now we get an error message if something is not ok. If all is well, Nothing is printed. This is a better approach but still not good. Let's use assertions (Programming With Assertions (Oracle)). An Assertion is a statement that let's you test your code in a structured way. If we for example want to verify that a variable email has the value ada@lovelace.net we write

1    assert email.equals("ada@lovelace.net") : "Ada's email was wrong";

Think of this as "saying" to Java: I am assuming that the variable email has the value "ada@lovelace.net". Can you check that for me. If I am wrong, force an error with a message to occur.

If we translate the above (rather short) introduction to our MemberTest class we could write:

  public static void main(String[] args) {
    Member ada     = new Member("Ada", "ada@lovelace.edu");
    Member charles = new Member("Charles", "charles __AT__ babbage.net");
    Member bert    = new Member("Bert", null);

    assert ada.email().equals("ada@lovelace.edu") : "Ada's email was wrong";
    System.out.println("ada passed the email equals test.");
    assert charles.email()==null : "Charle's email wasn't null";
    System.out.println("charles passed the email is null test.");
    assert bert.email()==null : "Bert's email wasn't null";
    System.out.println("bert passed the email is null test.");
  }

Note that the print statements about tests having passed will not be printed unless the tests pass! This is because the assert statements will crash the code before anythings is printed if the tests do not pass.

To compile the classes: javac net/supermegacorp/orgmanager/Member.java net/supermegacorp/orgmanager/test/MemberTest.java

To execute the class java -ea net.supermegacorp.orgmanager.test.MemberTest

Note: to enable the assertions you have to use -ea (enable assertions)

It is important to discuss tests a bit. What is it we've verified? Have we made sure our code (the Member class) always works as it should? Are we sure that our test code is correct? These are all questions that require, as we said above, a separate course or book so we will settle with some short remarks. With the tests above you have made sure the code does not crash when the tests were executed

You cannot say that the code always will work. But the tests above are at least better than nothing since they made us find some bugs. A rule of thumb is: Testing can never prove the absence of bugs, but it can help find them.

Chapters about classes

Here are the chapters about classes in this book, so that you can keep a check on your progress:

Links

Video and slides

Java - Classes - Testing (Full playlist) | Java - Classes - Testing 1/3 | 2/3 | 3/3 | Testing your classes (pdf)

Related pages

Further reading

Where to go next

Next page has exercises for this chapter: Classes_-_Testing_your_classes_-_Exercises

« PreviousBook TOCNext »