1

I have a class Inventory which has 6 methods. I am supposed to implement tests for each on of these 6 methods in a test class called InventoryTEST. The class object is a hashmap.

Here are the methods for the class

 /**
   * Return the number of Records.
   */
  public int size() { }

  /**
   * Return a copy of the record for a given Video.
   */
  public Record get(VideoObj v) {  }

  /**
   * Return a copy of the records as a collection.
   * Neither the underlying collection, nor the actual records are returned.
   */
  public Collection toCollection() {}

  /**
   * Add or remove copies of a video from the inventory.
   * If a video record is not already present (and change is
   * positive), a record is created. 
   * If a record is already present, <code>numOwned</code> is
   * modified using <code>change</code>.
   * If <code>change</code> brings the number of copies to be less
   * than one, the record is removed from the inventory.
   * @param video the video to be added.
   * @param change the number of copies to add (or remove if negative).
   * @throws IllegalArgumentException if video null or change is zero
   * @postcondition changes the record for the video
   */
  public void addNumOwned(VideoObj video, int change) {

  }

  /**
   * Check out a video.
   * @param video the video to be checked out.
   * @throws IllegalArgumentException if video has no record or numOut
   * equals numOwned.
   * @postcondition changes the record for the video
   */
  public void checkOut(VideoObj video) {
  }

  /**
   * Check in a video.
   * @param video the video to be checked in.
   * @throws IllegalArgumentException if video has no record or numOut
   * non-positive.
   * @postcondition changes the record for the video
   */
  public void checkIn(VideoObj video) {

  }

  /**
   * Remove all records from the inventory.
   * @postcondition <code>size() == 0</code>
   */
  public void clear() {

  }

Now in order to test these methods I will need to create an object with some records in it. One way I could do this is call the default constructor which will give me an empty hashmap and then call addNumOwned to add records to the hashmap. Another way I could do this is create an overloaded constructor which can add records in the hashmap at the time of creation.

The issue I see with the first method is addNumOwned is a method (and unit) being tested in the test class. So if this unit fails all the other ones will as well. Should a unit test be suspectible to the failure of another unit test?

I suppose if I had to make a unit test for the constructor the same argument would hold. I do not have a unit test for the constructor, however.

Raedwald
  • 46,613
  • 43
  • 151
  • 237
Mattreex
  • 189
  • 2
  • 17
  • Your methods are void methods, so the only way to write a unit test for them is to inspect the state change of VideoObj object. – Yash Bansal Oct 17 '19 at 01:17
  • The class object is a hashmap. Would it be better to create various states of the hashmap with a constructor, or the method AddNumOwn? – Mattreex Oct 17 '19 at 01:20
  • If you do not need that constructor in your project, then you do not need to create one only for testing. You can use AddNumOwn method to populate the HashMap. – Yash Bansal Oct 17 '19 at 01:22
  • I'm not sure that I need a constructor, but it seemed to be good design choice because AddNumOwn does more than add video:records, it also removes them, and modifies fields of a record. It is also a "unit" in the 6 unit tests that are required. I'm not sure if it is good practice to use one unit to test another... – Mattreex Oct 17 '19 at 01:37

2 Answers2

2

If you are writing a suite of tests for a class, it is OK for the set up code of some of your tests to use methods of that class. In fact, it is practically impossible to avoid doing that.

If you want your tests to be comprehensive, you will want to test the constructor of your class. But of course all the tests of the methods of your class will have to first create an object of your class, as part of their set up phase.

public ThingTest // We have a class named Thing we want to test, this is its unit tests
{

   @Test
   public void testIncrement() // Test of the Thing.increment method
   {
      // Set up:
      var thing = new Thing(); // Using part of the Thing class in the set up phase!

      // Exercise:
      thing.increment();

      // Verify:
      assertEquals(1, thing.count());
   }
...

You are worried, I guess, because you want your tests to be arranged so that a bug in the code you are testing causes only one test failure, so debugging the test failure is easy. That is an ideal, worth striving for, but it is only an ideal. In practice, some kinds of bugs and tests can not be so simple.

You can mitigate this difficulty by ensuring you have comprehensive tests, and by testing bottom-up. That is, it the set up phase of a test relies on a particular mutator or constructor operating correctly, you must have unit tests of that mutator or constructor.

For complicated code, you can consider using Java level assert statements (my preference) or JUnit assumptions to check that the set up phase of a test has completed correctly before exercising the method you are testing. Then instead of having a difficult to debug test failure, you will get an AssertionError or AssumptionViolatedException or TestAbortedException that more clearly points towards the cause of the problem.

   @Test
   public void testIncrement()
   {
      var thing = new Thing();
      assert thing.count() == 0;

      thing.increment();

      // Verify:
      assertEquals(1, thing.count());
   }
...
Raedwald
  • 46,613
  • 43
  • 151
  • 237
1

If I understand your question correctly you are trying to balance between making test cases easy to understand and maintain by using the constructors, setters, getters etc. available in the class versus making each unit test a single test i.e. only able to fail due to one reason.

This is definitely a balancing act. Some of it comes down to a bit of personal (or organisational) choice. However, in the example you give I would happily use the constructor and addNumOwned methods to setup the test.

The reason I am willing to use these methods is that typically constructors, setters, getters and add methods do not contain much (if any) business logic. Therefore the risk of these breaking is low. Based on this the benefit of having tests which use the standard public interface to the class outweighs the risk of the tests breaking due to a change in the methods used to setup the test.

Any questions on this please let me know.

James Wilson
  • 1,541
  • 7
  • 20