Summary
A simple tool which makes refactoring safer.
Advertisement
In my last blog, I described sensing variables and how you can use them to write tests for gnarly long methods when you want to refactor. As I mentioned, it’s a useful technique, but it is a pain to set up. A few weeks ago, I wrote a little tool which makes the process much easier. It’s called Vise and it uses the metaphor of a vise, a clamp that you use to hold down mechanical parts when you are working on them.
Here’s how it works...
// Exhibit A – Big Gnarly Method
public class RPRequest {
...
public int process(int level, RPPacket packet) {
if (...) {
if (...) {
...
} else {
...
bar_args[1] += list.size();
packet.add(new Subpacket(list, arrivalTime));
if (packet.calcSize() > 2)
bar_args[1] += 2;
}
} else {
int reqLine = -1;
bar_args[0] = packet.calcSize(reqLine);
...
}
}
}
We’d like to do some refactoring inside this method, but we know that some of the things that we want to do (changing the order of statements, etc) could affect the contents of the bar_args array if we make a mistake. Sadly, we don’t have access to the bar_args array; it’s used internally in the method and inaccessible to us. What can we do? Well, we can use sensing variables, but there is another alternative. We can use Vise.
Essentially, Vise is a tool that helps you record a set of values that occur in your code and then use those values as a behavioral invariant. Once the values are recorded, if they change the next time the code executes, Vise throws an exception.
Here’s how we can use Vise to clamp down the code above. The first thing we do is go back to our code and add a series of calls to a static method named grip on a class called Vise:
import vise.tool.*;
public class RPRequest {
...
public int process(int level, RPPacket packet) {
if (...) {
if (...) {
...
} else {
...
bar_args[1] += list.size();
Vise.grip(bar_args[1]);
packet.add(new Subpacket(list, arrivalTime));
if (packet.calcSize() > 2)
bar_args[1] += 2;
Vise.grip(bar_args[1]);
}
} else {
int reqLine = -1;
bar_args[0] = packet.calcSize(reqLine);
Vise.grip(bar_args[0]);
...
}
}
}
Next, we write a series of tests which call the process() method with various inputs:
// Note that the test class inherits from ViseTest..
public class RPRequest extends ViseTest {
public void testProcessSimpleRequest() {
final int BASE_LEVEL = 0;
new RPRequest().process(BASE_LEVEL, new RequestPacket(“local”));
}
public void testProcessLoginRequest() {
final int TARGET_LEVEL = 1;
new RPRequest().process(TARGET_LEVEL, new LoginPacket(“mike”, “default”));
}
...
}
Then we execute the tests, and they all pass.
They are pretty silly looking tests, aren’t they? They don’t have any assertions. Well, here’s why: The first time that you execute a test method that calls grip(), Vise checks to see if there is a vise file for that method on your system. If there isn’t, it creates a file and then takes each value that you pass to grip and saves it to that file. If the file already exists, Vise will open it and read the next value from it each time grip is called. If the file value matches the gripped value, all is fine. If there is a difference, Vise throws an exception that indicates that the behavior of your code has changed since you recorded those values.
The algorithm for using Vise is simple:
Look at a chunk of code that you need to modify or refactor, and figure out what intermediate values you’d like to preserve.
Add grip calls
Write tests which exercise those paths
Refactor or make your changes
Delete the grip calls from your code
When I’ve used it, I’ve used it to convince myself that I’m preserving behavior as I start to get code under test. It makes the work easier.
Vise has a few more ease of use features. There is a method on Vise called Vise.inspect() which throws an exception that shows you all of the values that have been gripped in the current test method. When the exception is caught by JUnit, it gives you a nice listing of values. This can be very handy when you are trying to figure out whether particular tests are exercising a particular chunk of code. Vise also has a method named Vise.release() which deletes the persisted file for each method. In practice, however, I’ve found it easier just to keep a window open on my desktop which shows all of the vise files for a set of tests. When I want to re-record, I just delete one or more of the files.
Is Vise ideal? No. I think it would be cool to have an IDE plug-in that does this. I can imagine going into my IDE, selecting a set of expressions across my code base, and then associating them with a set of tests. Once I’ve associated them I should be able to use some set of buttons on my IDE to clamp down the current behavior or release it. With that I could always know whether I’m inadvertently changing behavior I care about.
You can get a copy of the Vise code in a zip here. It contains a readme file, but be forewarned: the tool hasn't seen extensive use. Giovanni Asproni and I are piecing together a version for C++. Please let me know if you give the Java version a spin and find it useful (or not).
Sounds like a very cool utility (and I also enjoyed your previous blog entry about "sensing variables"). However it appears that the link to the zip containing the code is invalid. It's pointing to a GIF image, not a zip file...
> Sounds like a very cool utility (and I also enjoyed your > previous blog entry about "sensing variables"). However > it appears that the link to the zip containing the code is > invalid. It's pointing to a GIF image, not a zip file...
Hmm... I just downloaded it again, and it seems to be working for me. Can you try again?
After spending almost a year developing using mocks, JUnit and TestNG - it seems to me that most (non-web) unit testing boil down to verifying the outputs for a particular set of inputs and what external calls are made (which is where mocks comes in). Much of writing tests with mocks is drudgery - but is essential to allow proper unit testing of individual methods in individual classes. Combining the Vise idea inside an IDE which could generate JUnit code + test data (ideally stored within the code) and prompt the user to enter in the expected return values for calls outside of the individual component being tested would be fantastic. Even more so if this can be set up so the tests can be debugged and fixed up with the same editor as changes are made to the underlying code - replacing the expected results as required.
Vice.grip() looks a lot like Logger.log(). I wonder if you could do something similar by adding log statements to your code and having a LogTest class that automatically verifies that each test logs the same messages as before? That way these extra calls could also be used for diagnostics when in production.
> Vice.grip() looks a lot like Logger.log(). I wonder if > you could do something similar by adding log statements to > your code and having a LogTest class that automatically > verifies that each test logs the same messages as before? > That way these extra calls could also be used for > r diagnostics when in production.
Actually, there is something that does that. It is called TextTest and it was written by Geoff and Emily Bache. The link's not coming up right now but here it is: http://texttest.carmen.se/
It's great when you already have logging. I suspect that the choice between using that and something like vise depends on whether you have logging, and how permanent you want your invariants to be.
I was wondering.. those grip calls need to stay in your code after you refactor your code ? I wouldn't want to pollute the code with automated testing stuff..
> I was wondering.. those grip calls need to stay in your > code after you refactor your code ? I wouldn't want to > pollute the code with automated testing stuff..
No. You take them out. That's the hardest thing to explain about this. People aren't used to putting code in temporarily and ripping it out when they work.
The ideal thing with vise is to have it be an IDE feature so that you can select grip points in the IDE just like you can select breakpoints in a debugger.
... found a working link on the objectmentor page and tried it on a current project. Results:
- There is a lot of Vise errors that seem to arise because Vise is in an inconsistent state. - When setting up the tests I have to refactor my legacy code so much that I end up with a lot of hooks where regular unit testing can be applied as well (i.e. assertEqual(expected, actual) instead of Vise(currentState)) - which is even better, since it serves as documentation on what exactly the state of interest is. - What vises would be really useful for is "history" regression testing, a.k.a. diff debugging: I have documented undesired behavior in the current codebase by means of a test or a "vise config" or a mixture of techniques and want to know if there was a point in the past when this behavior did not show up. With "local" version management systems (git, Bazaar et al) this would be quite fast (the bottleneck is probably your build). Then the test system would pinpoint the class(es) that changed between working and non-working versions.