RU beehive logo ITEC dept promo banner
ITEC 120
2008spring
ibarland,
jdymacek

homeinfoexamslectureslabshws
RecipeLawsliessyntaxjava.lang docsjava.util docs

lab06a
Unit testing

For today's lab, we'll all follow along together. We'll learn about writing automated test suites, so that after writing a test case once, you can press a single button to run all existing tests.

Unit Testing

  1. Open your PizzaServer-with-fields, from last lab.
  2. While people are starting up, we'll discuss what sort of test cases we used for the TeamStat's record and toInfoString methods, from homework.
  3. Okay, write a stub function for one of the tasks you hadn't yet finished -- say, spend. (What is the signature for spend? As for its test cases -- that's what we're about to do in the next few steps of today's lab.)
  4. If you don't already see a greyed-out “record test cases” panel on your screen, then you'll need to turn on BlueJ's test-case tool: To do this, select Options > Preferences… > Miscellaneous > Show Unit Testing Tools.
    (On Mac, the Preferences… item is under the main BlueJ menu.)
  5. Now right-click on your class: In addition to the options like compile and new PizzaServer(), there is now an option Create Test Class.
    Choose this option. This will create a khaki “unit-test” class which is attached to your primary class. (We won't edit the test class manually; instead, as we use BlueJ to record tests, BlueJ will put code inside this (currently empty) unit-test class.)
  6. Right-click on the test class, and select Create Test Method…. Give it a name, testSpend. (We might make several test suites, each stressing a certain feature or set-of-features of our program.)

    By convention, each test begins with the four letters “test”. This begins recording your actions, which can be replayed in the future.

  7. Go ahead and test your method: Create a new PizzaServer instance, and right-click on the red instance to call setBalance, setting the balance to (say) $100. Then, call spend, spending $25. How do we test if it worked? Well, call getBalance.

    As we've already seen, BlueJ will return the answer from our function (currently just an uninformative stub function). However there is also something new, since we are recording a test case: we can assert that the result should be equal to 75.0. That is, we get a chance to enter what the intended result is. Always use the equal to assertion. (For doubles, use equal to (float or double). doubles require this special comparison, since we need to allow a small amount of tolerance, to compensate for round-off error.)

  8. Still recording, run a couple more test cases: call spend again with 0, then check the balance is still as expected, then call spend with (say) the remaining $75.00, and check that the balance is now zero.

    Discuss: What if we try to spend into a negative balance -- should this be allowed?
    I can think of several reasonable courses of action, if a PizzaServer is asked to spend more than they have. What do you think is most appropriate?

    In real life, when there are several plausible courses of action, you have to decide for yourself, what your program will do. Note that test cases are forcing us to decide this policy before we start writing a bunch of code!

    So: Make a test case which involves a negative balance, and you get to assert what the intended answer should be!

    For teaching purposes, I'll have my test-cases decide that if you try to spend more than you have, then nothing happens.

  9. When you think you have a sufficient set of tests for spend, go ahead and click to End recording.
  10. Now, whenever you want to run that entire suite of tests, you can click Run Tests. Try it now. Presumably your tests will fail (at least, most of them).
  11. Optional: Double-click on the khaki unit-test file which BlueJ created for you. You will find the actual Java code corresponding to your creating a new object and calling a method and testing against various outcomes.
    Other IDEs may not have BlueJ's “record” feature, but they do provide other tools to help you write the unit-testing code you see inside that file.

  12. Let's go write the function! A version which doesn't use a local variable: In either case, use the getters and setters to access/mutate the balance, rather than accessing the field directly. (See solution)

    Once you write the code for the method, testing is now easy: just click on Run Tests, and if everything is green, then you're happy!

    My own first attempt at the code, alas, fails.
    (You probably realized that while my code lets the balance go negative, I had told the test-cases to expect something different. But pretend that we're not fully sure why we don't pass our test cases, and let's see how to go about debugging.) What do I do?

    1. If I look at the test-case-window and click on the “testSpend” (which has an “×” next to it), it will report something like: “expected 10.0, but got -5.0”.

      Moreover, if I click on Show Source, BlueJ will open up the test-case and show me the specific test case which failed.

      The important part to note is that we passed two tests, before failing the third. This is valuable information — my code does sometimes give the correct answer.

      In fact, when I re-work through the thinking of my test cases, I realize that the test which is failing is where I try to spend more than I have. The test-harness is telling me “expected 10.0, but got -5.0” … Aha! I bet that I'm letting the balance go to -5.0, which contradicts my earlier assertion that the test case should be unchanged.

      I'll add an if-else statement to my program, to take care of this: (See solution)

    2. Finally, note that our attempted fix (solution) still doesn't pass the test cases: In fact, by clicking on Show source, we see that things seem worse — our very first test case doesn't pass! … After thinking through in our head what is happening, we realize that we had < instead of >

    3. … or should we use >=? Our test cases pass either way, uh-oh! One of those two versions should be wrong!1 This means that even though our program might have a bug, our test cases will certify it as correct. Oops! It means we didn't make thorough enough test cases (and therefore missed points before we ever started writing the body of the method!).

      The adventurous might edit the unit-test's source-code directly (even though BlueJ had generated it for us). The safer among us might just create a second test-case, which specifically checks that it's okay to spend exactly as much as you have.

  13. You may have noticed similar testing-technology already used for practice problems at javabat.com.
  14. Self-practice: Modify your setSalary method, so that it's impossible to set a PizzaServer's salary to be less than minimum wage.

Here's a different tutorial you can look at, if you like: Unit Testing with BlueJ tutorial.


Using Checkstyle:

Note: when using checkStyle on the lab computers (from BlueJ, when the project window is showing, select Tools > Checkstyle), it will complain about having fields. That's because fields is an intermediate topic, and Checkstyle is currently configured for beginners. It's time to graduate!

From Preferences > Extensions > Checkstyle, change the configuration file to …intermediate_learner…. instead of …beginning_learner….
(The full URL of the file is “http://www.radford.edu/itec120/2008spring/Lib/intermediate.xml”)

Summary

To create test cases:
  1. Make your project has the 'run tests' button on the left-hand-side. (If not, set preferences to 'Show Unit Test Tools')
  2. Create a unit-test class. (Do this once for each class you have … of course, so far we've never had more than one class in a project.)
  3. For each function you want to test,
    1. Create a test and name it. (Right-click on the khaki test class. By convention, the name of a test is the four letters “test” followed by which method you are testing.)
    2. Create an object or two.
    3. Test the one particular method. (Typically: calls to the method, interleaved with calls to getters/setters.)
    4. Click Stop recording.
      (Myself, I obsessively click Run Tests immediately, knowing that it will fail.)
    5. Write the body of the method.
    6. Click Run Tests; if any tests fail, then go back .
    7. Optional: sometimes it's necessary to actually go back and change the tests (you made an incorrect assertion, or more likely you forgot about some specific test). If you are adventurous, you can double-click on the khaki test class, and edit its source code directly!
      Less perilously, you can make a second test-case (e.g.testMyMethod2”).
One thing to note: There is no history between tests! That is, if you Run Test twice in a row, any PizzaServers are destroyed and re-created between tests. This is good; the results of one test don't require that you have previously run certain other tests in some specific order.

1Er, at least one of those two versions is wrong.

Two men say they're Jesus;
One of them must be wrong
— Dire Straits, Industrial Disease
     

homeinfoexamslectureslabshws
RecipeLawsliessyntaxjava.lang docsjava.util docs


©2008, Ian Barland, Radford University
Last modified 2008.Feb.26 (Tue)
Please mail any suggestions
(incl. typos, broken links)
to iba�rlandrad�ford.edu
Powered by PLT Scheme