Unless the stubs were constructed because the real component wasn't available yet, you should expect to retain them
past deployment. The tests they support will likely be important during product maintenance. Stubs, therefore, need to
be written to higher standards than throwaway code. While they don't need to meet the standards of product code - for
example, most do not need a test suite of their own - later developers will have to maintain them as components of the
product change. If that maintenance is too hard, the stubs will be discarded, and the investment in them will be lost.
Especially when they're to be retained, stubs alter component design. For example, suppose your component will use a
database to store key/value pairs persistently. Consider two design scenarios:
Scenario 1: The database is used for testing as well as for normal use. The existence of the database needn't be
hidden from the component. You might initialize it with the name of the database:
public Component(
String databaseURL)
{
try
{
databaseConnection = DriverManager.getConnection(databaseURL);
...
} catch (SQLException e) {...}
}
And, while you wouldn't want each location that read or wrote a value to construct a SQL statement, you'd certainly have
some methods that contain SQL. For example, component code that needs a value might call this component method:
public String get(String key)
{
try
{
Statement stmt = databaseConnection.createStatement();
ResultSet rs = stmt.executeQuery(
"SELECT value FROM Table1 WHERE key=" + key);
...
} catch (SQLException e) {...}
}
Scenario 2: For testing, the database is replaced by a stub. The component code should look the same whether it's
running against the real database or the stub. So it needs to be coded to use methods of an abstract interface:
interface KeyValuePairs
{
String
get(String key);
void
put(String key, String value);
}
Tests would implement KeyValuePairs with something simple like a hash table:
class FakeDatabase implements KeyValuePairs
{
Hashtable table = new Hashtable();
public String
get(String key) {return (String) table.get(key); }
public void
put(String key, String value) {table.put(key, value); }
}
When it is not being used in a test, the component would use an adapter object that
converted calls to the KeyValuePairs interface into SQL statements:
class DatabaseAdapter implements KeyValuePairs
{
private Connection databaseConnection;
public DatabaseAdapter(String databaseURL)
{
try
{
databaseConnection = DriverManager.getConnection(databaseURL);
...
} catch (SQLException e) {...}
}
public String
get(String key)
{
try
{
Statement stmt = databaseConnection.createStatement();
ResultSet rs = stmt.executeQuery("SELECT value FROM Table1 WHERE key=" + key);
...
} catch (SQLException e) {...}
}
public void
put(String key, String value) {... }
}
Your component might have a single constructor for both tests and other clients. That constructor would take an object that
implements KeyValuePairs. Or it might provide that interface only for tests, requiring that ordinary
clients of the component pass in the name of a database:
class Component
{
public Component(String databaseURL)
{
this.valueStash = new DatabaseAdapter(databaseURL);
} // For testing.
protected
Component(KeyValuePairs valueStash)
{
this.valueStash = valueStash;
}
}
So, from the point of view of client programmers, the two design scenarios yield the same API, but one is more readily
testable. (Some tests might use the real database and some might use the stub database.) |