Download presentation
Presentation is loading. Please wait.
1
CS2013 Lecture 3 John Hurley Cal State LA
2
JUnit Eclipse includes a unit testing framework called JUnit
A test case is a class that contains one or more tests, usually all testing the same target class. Create one or more separate packages in each project for test cases. Tests use assertions of various kinds assertNull(Object o), assertNotNull(Object o) assertEquals(Object o, Object p), assertFalse(boolean) Many others listed here: team/junit/wiki/Assertions A test succeeds if the assertion(s) are true when the test is run and fails if one or more are false The object of a test is to assert something that will be true if the class is working correctly but false if some plausible error occurs
3
JUnit Let's start by writing unit tests for the Vampire class, which implements the Monster interface, and for the Crypt class, which is used by Vampire. Test Crypt first, because it can work without Vampire, but Vampire will not work if Crypt is broken
4
Monster getLocation() is new in this version of Monster
package monsters; public interface Monster { public void setName(String name); public String getName(); public void setLocation(String location); public String getLocation(); public void rampage(); public String getOriginStory(); } getLocation() is new in this version of Monster
5
Crypt package monsters; public class Crypt { private String location;
public Crypt(String location) { this.location = location; } public void setLocation(String location) { public String getLocation() { return location; public String toString(){ return "a mysterious crypt in " + location;
6
Vampire package monsters;
public class Vampire implements Monster, Cloneable { private String name; private Crypt crypt; public Vampire(String name, String location) { this.name = name; crypt = new Crypt(location); } @Override public void setName(String name) { public String getName() { return name; public void setLocation(String location) { crypt.setLocation(location);// TODO Auto-generated method stub public String getLocation(){ return crypt.getLocation();
7
public String getOriginStory() {
@Override public String getOriginStory() { return "undead creature which lives by sucking the blood of living humans"; } public void rampage() { StringBuilder sb = new StringBuilder(name + " arises from " + crypt.toString() + " and "); if (crypt.getLocation().equals("Transylvania")) sb.append("sucks people's blood all night, then returns to a coffin to hide from sunlight"); else if (crypt.getLocation().equals("Burbank")) sb.append("takes over the entire television industry"); else { System.out.println("wreaks unknown havoc in fresh Vampire territory"); return; System.out.println(sb); public Crypt getCrypt() { return crypt; public Object clone() { Vampire newV; try { /* Object clone() returns an Object. It will be a Vampire, but in order to get to anything specific to Vampires, we need to cast it to a Vampire and use a Vampire reference variable */ newV = (Vampire) super.clone(); newV.crypt= new Crypt(crypt.getLocation()); } catch (CloneNotSupportedException e) { e.printStackTrace(); return null; return newV;
8
Unit Testing
9
Unit Testing
10
Unit Testing
11
Unit Testing JUnit tests are identified with the annotation @Test:
public void testCryptCreated(){ String location = "Transylvania"; Crypt c = new Crypt(location); assertNotNull(c); } public void testToString(){ assertNotNull(c.toString());
12
JUnit JUnit starts us off with a test that is directed to fail:
13
JUnit
14
Test Failure
15
Unit Testing JUnit Assertions require static imports
16
Test Success
17
Unit Testing Write assertions that will fail if likely errors occur
Write a separate test case (file containing tests) for each class you need to test, even if they implement common interfaces or superclasses. Keep the tests simple. Most tests have only one assertion each. This way, you can identify problems quickly when assertions fail. Systematically exercise the whole interface In this case, "interface" means the public interface of the class being tested. That may be defined partially or completely by a Java interface, an abstract class, or a concrete superclass, or it may be unique to the class. Unit testing is not used for private methods; if these are wrong, any errors should come to light through the public interface Don't reuse objects in multiple tests; instantiate new ones for each test Tests should not have dependencies on each other, which would cause tests to break if other tests are changed
18
Unit Testing package test; import static org.junit.Assert.*;
import monsters.Crypt; import org.junit.Test; public class CryptTester { @Test public void testCryptCreated(){ String location = "Transylvania"; Crypt c = new Crypt(location); assertNotNull(c); } public void testCryptLocation(){ assertEquals(c.getLocation(), location); public void testSetCryptLocation(){ String firstLocation = "Transylvania"; Crypt c = new Crypt(firstLocation); String secondLocation = "Wisborg"; c.setLocation(secondLocation); assertEquals(c.getLocation(), secondLocation); public void testToString(){ assertNotNull(c.toString());
19
VampireTester Vampire has a Crypt (remember, this is composition).
There is no public method in Vampire that returns the Crypt, so we can’t directly test that it is correctly creating the crypt. This is white box testing, though, and we do know that Vampire.getLocation() gets the location from Crypt.getLocation() @Test public void testLocation() { String name = "Orlok"; String location = "Transylvania"; Vampire v = new Vampire(name, location); assertEquals(v.getLocation(), location); }
20
VampireTester Here are some tests of Vampire.clone() @Test
public void testCloneIsNewVampire(){ String name = "Orlok"; String location = "Transylvania"; Vampire v1 = new Vampire(name, location); Vampire v2 = (Vampire) v1.clone(); //clone() returns an object, but it is a Vampire assertNotSame(v1, v2); } public void testCloneInNewCrypt(){ assertNotSame(v1.getCrypt(), v2.getCrypt()); public void testCloneName(){ assertTrue(v1.getName().equals(v2.getName())); public void testCloneLocation(){ assertTrue(v1.getLocation().equals(v2.getLocation())); public void testCloneChangeLocation(){ Vampire v2 = (Vampire) v1.clone(); v2.setLocation("Burbank"); assertFalse(v1.getLocation().equals(v2.getLocation()));
21
VampireTester To refactor code is to change the implementation
When refactoring a class, don't change the public interface unless you can do a major reorganization of the whole application Refactoring should be completely invisible from outside the class, so that other code does not have to change and other programmers don’t have to learn the internal workings of your code Changing the interface inherently means other code must change Let's refactor Crypt.
22
VampireTester Refactor Crypt without introducing any errors:
public String toString(){ return "a very, very mysterious crypt in " + location; } All test results remain the same
23
VampireTester Let's say we refactor Crypt and make a mistake:
public void setLocation(String location) { location = location; } Both CryptTester and VampireTester contain tests that will now fail
24
VampireTester
25
VampireTester Click on *each* line in the JUnit output indicating a failed test
26
Unit Testing JUnit has many more advanced capabilities. As your applications become more complex, periodically look at team/junit/wiki
27
Data Structures Memorize this definition!
A data structure is a systematic way to organize data in order to improve the efficiency of algorithms that will use the data.
28
Generics Recall that a list is a list of objects of some type. We show that using syntax like this: List<Student> students; List is a generic data structure, meaning that we can have lists of many different types of objects. It is parameterized by the data type that the list will hold. Interfaces may be parameterized in the same way. For example, when we declared that Student implemented Comparable, the interface was parameterized by the same class Student. This means that the compareTo() method compares the current Student to another Student.
29
Generics Many other data structures we will study in this course are also suitable for handling objects of many different types. The same notation is used: Stack <Rectangle>
30
Generics Java is a strongly-typed language, one in which references can only be passed if they are of the same type the receiver expects. It is also statically-typed; type checking is done at compile time, not at run time. Using a type incorrectly, for example by sending a parameter of an incorrect type, is a syntax error. Compare this Java code to the JavaScript on the next slide: public static void main(String[] args) { String myName = "Earl"; printOut(myName); } public static void printOut(String s) { System.out.println(s);
31
Generics JavaScript is dynamically-typed; type checking is done at runtime, not compile time. Sending an incorrect type as a parameter is a runtime error. <script lang = "JavaScript"> function printOut(v){ document.write(v + "<br />"); } var myName = "Earl"; var myAge = 42; printOut(myName); printOut(myAge); </script>
32
Generics We can get more flexibility by being as general as possible with the type of the method parameters public static void main(String[] args) { String myName = "Earl"; printOut(myName); int age = 42; printOut(age); } public static void printOut(Object o) { System.out.println(o); (The compiler and JVM "box" the int automatically into an Integer, which is an object.) The potential problem with this is that it may be *too* general. If we want to write a method with operations that will work with either Students or FacultyMembers, but not with Monsters, we need to be more specific in the method signature.
33
Generics Generic classes and data structures are designed with the capability to incorporate any of a range of other types. They are parameterized by the type that is "plugged in". This is somewhat similar to the concept of an array as a collection of values of the same data type. Unlike arrays, though, generic data types can only use classes or interfaces as their underlying type You can create an ArrayList<Integer>, using the class Integer, but not an ArrayList <int> using the primitive data type int. It is possible to create Lists in Java which are not parameterized and can accept values of any type. This is left over from before generics were introduced to Java. Don’t do this.
34
Generics The generic type is defined with a placeholder that is essentially a variable representing the parameterizing type, for example ArrayList<E>
35
Generics We can and should declare variables with the least-specific type that will work. For example, we can declare a variable of type List<E> and then instantiate an ArrayList<E> and assign this object to the variable. This way, if we decide later to use some other kind of List, it is very easy to change the code. We can even write code in which the type of list depends on things that happen at runtime. This only works if we can limit our use of methods to those that are in the List interface Similarly, we can choose the most general "plugged in" type available and actually add objects of subclasses or implementing classes to the generic data structure
36
Generics The rules for which types can be used in a parameterized data structure are similar to the rules for variable types: If the data structure calls for a type, you can add objects of that type or its subtypes, but not of its supertypes A subtype may be either a subclass, a subinterface, or a class that implements an interface This is easy to understand using some examples: Suppose Monster is a class with two subclasses, Zombie and Demon, which do not have any subclasses of their own An ArrayList<Monster> can hold any combination of Monsters, Zombies, and Demons An ArrayList<Zombie> can only hold Zombies Suppose TVShow is an interface, and the classes GameShow and SitCom implement it and do not have any subclasses An ArrayList<TVShow> can hold any combination of GameShows and SitComs. An ArrayList<GameShow> can only hold GameShows
37
Generic Instantiation
37 Generic Type Although you could achieve similar results by just using the type Object instead of using generics, generics provide stricter compile-time type checking. This replaces runtime errors with syntax errors, which are much easier to deal with. Generic Instantiation Runtime error Improves reliability Compile error
38
38 Generic ArrayList
39
Generic Interfaces public interface MyMath<T> {
Several sections of CS2012 over the past year have completed a lab using the MyMath interface: public interface MyMath<T> { public T add(T o); public T subtract(T o); public T divide(T o); public T multiply(T o); } In implementing MyMath <T>, you chose the type that T represents and applied binary operations like add(), whose operands were the current object and an object of class T
40
Generic Methods public static <E> void print(E[] list) {
40 Generic Methods Methods in non-generic classes may take generic parameters, but the syntax for this may be unexpected: public static <E> void print(E[] list) { for (int i = 0; i < list.length; i++) System.out.print(list[i] + " "); System.out.println(); } Try this with an array of Doubles, Strings, etc. It won’t work with a primitive data type.
41
Generics Note that, when we implement MyMath <T>, we define what type T stands for. Therefore, the parameter types for the methods are determined at compile time, and there is no ambiguity for the compiler. A generic method like public static <E> void print(E[] list) {} is different, because the type of the generic parameter may not be known until runtime.
42
42 Bounded Generic Type Methods that use generic types can limit the possible types to ones that extend a particular class or implement a particular interface For example, if your method finds the largest element in a list by using compareTo(), it will only work if the parameter objects include this method. You can make sure they do by only using objects of classes that implement Comparable.
43
Bounded Generic Type package demos; import java.util.ArrayList;
43 Bounded Generic Type package demos; import java.util.ArrayList; import java.util.List; public class Demo { public static void main(String[] args) { List<String> myList = new ArrayList<String>(); String[] myArray = {"Godzilla", "Dracula", "Frankenstein"}; for(String s:myArray) myList.add(s); System.out.println(min(myList)); } public static <E extends Comparable <E>> E min(List<E> theList) { E smallest = theList.get(0); for (E e: theList) if(e.compareTo(smallest) < 0) smallest = e; return smallest;
44
44 Bounded Generic Type The placeholder E is an arbitrary choice. This min method is equivalent to the one on the last slide: public static <Z extends Comparable <Z>> Z min(List<Z> theList) { Z smallest = theList.get(0); for (Z z: theList) if(z.compareTo(smallest) < 0) smallest = z; return smallest; }
45
Generics The Java collections we have been using are themselves programmed in Java and follow some of the same patterns we are learning. List<E> is an interface There is a List class, but it is the old, pre-generics List. Eclipse or the compiler will produce a warning but let you use it. Don’t. ArrayList<E> is a concrete class that extends Abstract List<E>, which implements List <E> There are various other interfaces and classes involved too
46
Type Erasure and Restrictions on Generics
46 Type Erasure and Restrictions on Generics Generics are implemented using type erasure. The compiler uses the generic type information to compile the code, and in particular to do type checking. The generic type is ignored afterwards. The bounds on the parameterizing type are available to the compiler, but not at run time. This approach enables the generic code to be backward-compatible with legacy code (code in old forms of Java) that uses raw types.
47
47 Compile Time Checking For example, the compiler checks whether the parameterizing type is correct for the following code in (a) and translates it into the equivalent code in (b) for runtime use. The code in (b) uses the Object type.
48
48 Type Erasure A generic class is shared at runtime by all its instances regardless of its actual generic type. GenericStack<String> stack1 = new GenericStack<String>(); GenericStack<Integer> stack2 = new GenericStack<Integer>(); Although GenericStack<String> and GenericStack<Integer> are two types, there is only one class GenericStack loaded into the JVM. Stack1 and stack2 in this example are still two different instances of the GenericStack class
49
49 Type Erasure Type erasure is the reason why you can't construct objects of a generic class. The JVM won’t know at runtime what the class should be: This will *NOT* work: public static <E> void addOne(List<E> theList) { theList.add(new E()); }
50
50 Data Files With .jars Generally, you should ask users for data file paths, not hard code them. If the user needs to be able to use multiple files in similar ways (eg the application can keep separate sets of similar data), s/he needs to be able to choose paths and file names However, if a data file is unlikely to change or holds configuration data internal to the application, it may be appropriate to hard code a file name. This is easy if you are running applications in eclipse or from the .class files at a command line. The next few slides show how to do this with a .jar file
51
51 Data Files With .jars Put the data file in a separate folder in the project, outside src. Create an InputStream this way. The first slash in the path makes the relative path start at the root of the classpath for the application, inside the .jar: InputStream input = getClass().getResourceAsStream("/data/" + wordFile); Use a Scanner that scans from your InputStream: freader = new Scanner(input);
52
52 Data Files With .jars Choose Build Path/Configure Build path, then source. You will see this dialog box
53
53 Data Files With .jars Add the root of the project. This is the base of the classpath for the project. Exclude src but leave the separate reference to it.
54
equals() We have already seen the String method equals()
You can define a method that determines whether two objects of your own class are equal. Often this tests whether all the data variables are equal. If this method overrides the one from Object, it must take the same argument type, namely another Object. This means that you could test two objects of different types for equality. More likely, you will use a cast to make sure you are comparing two objects of your class.
55
equals() package demos; public class Student { private String name;
private Double gpa; public Student(String nameIn, Double gpaIn) { name = nameIn; gpa = gpaIn; } public String toString() { return "Name: " + name + "; GPA: " + gpa; @Override public boolean equals(Object obj) { if (obj == this) return true; if (obj == null || obj.getClass() != this.getClass()) return false; Student otherStudent = (Student) obj; if (name.equals(otherStudent.name) && gpa.equals(otherStudent.gpa)) return true; return false;
56
equals() package demos; import java.util.ArrayList;
import java.util.List; public class GradeBook { public static void main(String[] args) { // create an array list of Students List<Student> students = new ArrayList<Student>(); String[] names = {"Skipper", "Gilligan", "Mary Anne", "Ginger", "Mr. Howell", "Mrs. Howell", "The Professor", "Mary Anne"}; double[] gpas = {2.7, 2.1, 3.9, 3.5, 3.4, 3.2, 4.0, 3.9}; Student currStudent; for(int counter = 0; counter < names.length; counter++){ currStudent=new Student(names[counter], gpas[counter]); students.add(currStudent); } testStudentsForEquality(students.get(0), students.get(4)); testStudentsForEquality(students.get(2), students.get(7)); testStudentsForEquality(students.get(3), students.get(3)); public static void testStudentsForEquality(Student s1, Student s2){ if(s1.equals(s2)) System.out.println(s1 + " = " + s2); else System.out.println(s1 + " != " + s2);
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.