| By Ted Husted | Article Rating: |
|
| September 30, 2006 04:00 PM EDT | Reads: |
19,245 |
Focusing on the execute method, only two of the four result partitions would ever happen, so we mark the global forwards "registration," "logoff," and "welcome" as unexpected. For a normal return, only the observation "@RETURN!=!null" seems like a worthy assertion.
However, all trials returned logon and none succeeded. Looking at the code, we can see that a User object isn't being found, and so all the trials are being returned to the logon outcome. This makes perfect sense; we just have to provide a valid User object to some of the trials as we saw done in the webinar. In the webinar, an omniscient architect had already provided a session factory that created a User object. This being real life, we'll have to roll our own.
The execute method calls a helper to fetch the User object. Looking at the code coverage panel for the getUser helper method, we can see that the database parameter is usually null, and when it's not, the User object is null. As a result, the code always returns a message, and the execute method always forwards to logon instead of success.
Hmmm. Why is the database parameter ever not null? We haven't set up a session factory for it and the methods that could read it from a file have been disabled. What gives?
True, right now, when we call getUser from the execute method, the database parameter would always be null, but Agitator also tests each method in isolation. When we ran a full Agitation against the code base, Agitator also called getUser itself, and used its own default factory to create the database parameter. When Agitator called getUser, it mixed up the calls null with default instantiations of MemoryUserDase and TestUserDatabase. We didn't tell Agitator to do that. This is the automatic default behavior we get out of the box.
We already have a TestUserDatabase object that exercises exception handling. To that we can add a static getTestUserDatabase method that will return an instance of TestUserDatabase populated with User and Subscription objects. Creating the method means a bit more coding, but not so much, and now we'll be able to test the product independently of the XML document parsing.
Once the method is coded, we can right-click on the Factory Assignment view, choose "Assign a Factory form a Constructor or Method," and specify our new method. The code under test does validate that the database is not null, so we should pass some nulls to exercise that code. A simple solution is to leave the original factory running, but specify a weight of 1, and change the weight on our own factory to 9. This way, we will get some nulls and empty databases, but most often we will get a copy of our seed database.
After Agitating, we have more coverage, but we're still ending up with a null User on every trial. Checking the Snapshot, it's easy to see that the username and password parameters need factories too. When we right-click on the factory assignment for username, this time we're offered the chance to "Assign a list of values" to our string parameter. The values item leads directly to the Factory Settings dialog, where we can quickly add a couple of usernames. We repeat the process for password, so that the first items on each list are valid username and password credentials. The factories will mix these entries up, so that we get a spread of valid and invalid logon attempts. Since the code doesn't validate the username and password variables, we disable the default factories by setting the weight to zero.
With these changes, we now have 100% coverage on the getUser helper, but from the Outcomes view we can see that some trials are still throwing exceptions. Checking the Observations, we see that every time an Exception is thrown errors is null. The API contract for this sub-routine implies that errors should never be null.
We make that change to the default setting for the errors parameter and the unexpected Exceptions disappear. To seal the contract, we promote the Observation "@PRE(errors.messages) != null" to an Assertion. Now if the contract is ever broken due to future code modifications, Agitator will mark the getUser method with a red mark and exclaim "errors == null."
If we Agitate the LogonAction execute method, we see that getUser is still returning errors each time. Of course this is because, in this case, the database, username, and password values are still coming from the default factories. As shown in the webinar, we promote our new factories to package scope so that we can use them with other methods.
Under the Factory Assignments for LogonExecute, we find that the Expert has correctly wired a LogonForm factory to the "form" parameter. Problem is we need the factory to include both correct and incorrect credentials. After some trial and error, we manage to replace the default factories with new Form Property Factories that return usernames and passwords from the lists we created for getUser and promoted to package scope.
Note: When creating factories be sure to scroll down and review all the available fields. For example, the last field on the Form Property Factory form is classname, which must be set to org.apache.struts.config.FormPropertyConfig.
One red mark remains. Agitator noticed that the getUser helper can throw an ExpiredPasswordException, but none of our trials threw that exception. As it happens, the TestUserDatabase class is designed to thrown that exception if a certain username value is passed, so all we need to do is add that magic value to our factory. One last Agitation, and LogonAction is clear. The code coverage for the class is not 100% mainly because the logging statements aren't being reached, but all the domain code is being exercised.
Using the Agitator, we've gone from a standing start to having solid code coverage and a series of tests. We haven't written any JUnit code, but with Agitator's assertions we have a set of test assets that will automatically evolve as code is updated. Looking around in the manuals, it becomes clear that if somebody decided to write JUnit tests, those would work in tandem with Agitators and show up in the same summary statistics.
Agitator isn't a bad way to answer the question, "How are we going to create tests for our code base?"
Published September 30, 2006 Reads 19,245
Copyright © 2006 SYS-CON Media, Inc. — All Rights Reserved.
Syndicated stories and blog feeds, all rights reserved by the author.
More Stories By Ted Husted
Ted Husted (http://husted.com/ted/) is a software engineer and an active member of several open source projects hosted by the Apache Software Foundation, including Struts and iBATIS. His books include JUnit in Action, Struts in Action, and Professional JSP Site Design. Ted is also a consultant for Agitar Software, Inc.
- Kindle 2 vs Nook
- Why IBM’s Server Chief Got Busted
- Is Cloud Computing Like Teenage Sex?
- Industry Experts Discuss the State of Cloud Computing
- Performance Tuning Essentials for Java
- Confessions of a Ulitzer Addict
- Tactical Cloud Computing Panel at 1st Annual GovIT Expo
- It's the Java vs. C++ Shootout Revisited!
- Cloud Computing Can Revitalize Your Career as Software Developer
- IBM Could "Reinvent" Java: Mills
- Oracle & Cloud Computing: Exclusive Q&A with SVP Richard Sarwal
- A Brief History of Cloud Computing
- Kindle 2 vs Nook
- Cloud CEOs, CTOs & SVPs to Speak at 4th International Cloud Computing Expo
- Why IBM’s Server Chief Got Busted
- Is Cloud Computing Like Teenage Sex?
- Industry Experts Discuss the State of Cloud Computing
- Performance Tuning Essentials for Java
- The Difference Between Web Hosting and Cloud Computing
- Cloud Computing Expo: Exclusive Q&A with Yahoo! SVP Cloud Computing
- Ajax in RichFaces 3.3, JSF 2 and RichFaces 4
- Confessions of a Ulitzer Addict
- My Thoughts on Ulitzer
- Tactical Cloud Computing Panel at 1st Annual GovIT Expo
- A Cup of AJAX? Nay, Just Regular Java Please
- Java Developer's Journal Exclusive: 2006 "JDJ Editors' Choice" Awards
- The i-Technology Right Stuff
- JavaServer Faces (JSF) vs Struts
- Rich Internet Applications with Adobe Flex 2 and Java
- Java vs C++ "Shootout" Revisited
- Bean-Managed Persistence Using a Proxy List
- Reporting Made Easy with JasperReports and Hibernate
- Creating a Pet Store Application with JavaServer Faces, Spring, and Hibernate
- What's New in Eclipse?
- Why Do 'Cool Kids' Choose Ruby or PHP to Build Websites Instead of Java?
- i-Technology Predictions for 2007: Where's It All Headed?





































