Creating tests to assess the functional correctness of your DailyProjectData subclass generally involves two steps: (1) creating test data, and (2) writing unit tests.
One way to test your DailyProjectData subclass is to find existing data within the "testdataset" user that serves as appropriate data for testing, or create and add new data for this test user for use in testing your class. This can work reasonably well, although it suffers from brittleness: if someone else adds new data at some future point to the testdataset user, then it is possible that the new data could alter the results from your tests.
A slightly more complicated, but more robust approach is to define a new test user with a dataset specific to the needs of testing your class. This involves two steps: creating a set of subdirectories containing the new test user's account and data, and then copying this data to the hackystat data directory during system build.
Example 20.2, “DailyProjectFileMetrics test directories and files” illustrates the directory structure for the DailyProjectFileMetrics class, showing the directories and files defining the user and data for testing this class.
Example 20.2. DailyProjectFileMetrics test directories and files
hackySdt_FileMetric/src/org/hackystat/sdt/filemetric/dailyproject/
testdailyprojectfilemetric/
user.default.xml
data/
FileMetric/
2006-05-29.xml
As you can see, we create a new directory within the FileMetric dailyproject/ directory called "testdailyprojectfilemetric". This will be the name of the new user. This directory contains a user.default.xml file, as well as a data/ directory. The data directory contains sensor data for only a single sensor data type, FileMetric, and data for only a single day, 2006-05-29, which turns out to be sufficient for the purposes of testing this class.
Once this test data directory structure is defined, it must be copied to the hackystat data directory during the system build process. Example 20.3, “DailyProjectFileMetrics hackySdt_FileMetric.install.pre-sensorshell target” illustrates the target within the local.build.xml file used to accomplish this task.
Example 20.3. DailyProjectFileMetrics hackySdt_FileMetric.install.pre-sensorshell target
<target name="hackySdt_FileMetric.install.pre-sensorshell" if="hackySdt_FileMetric.available"
description="Copy over web app definitions and the test data sets.">
<!-- Copy over SDT, sensor, docbook, jsp, html, etc. files to their appropriate places in the webapp. -->
<makeUpdateWebAppContent module.src.dir="${hackySdt_FileMetric.src.dir}"/>
<!-- If this module contains telemetry code, use this to install it. -->
<hackyCore_Telemetry.installXmlDefinitions module.src.dir="${hackySdt_FileMetric.src.dir}" />
<!-- Copy over test data sets. -->
<copy todir="${install.testdataset.dir}" preservelastmodified="true">
<fileset dir="${hackySdt_FileMetric.src.dir}/org/hackystat/sdt/filemetric/testdataset" />
</copy>
<copy todir="${install.testdata.users.dir}">
<fileset dir="${hackySdt_FileMetric.src.dir}/org/hackystat/sdt/filemetric/dailyproject" includes="testdailyprojectfilemetric/**" />
</copy>
</target>
In this target, it is the last "copy" task that is responsible for copying over the testdailyprojectfilemetric directory to the appropriate place in the hackystat data directory.
Writing unit tests to assess the functional correctness of your DailyProjectData subclass can be done in two ways: (1) write "server side" tests that invoke server commands such as DailyProjectDetails, and then extract from the output the data associated with the running of your DailyProjectData subclass and check it for correctness, or (2) write "client side" tests that instantiate an instance of your DailyProjectData subclass, invoke its public API, and check the return values. Although both approaches have strengths and weaknesses, and although the best case scenario would be to use both approaches, we suggest that you start by using client side testing. The advantages to client-side testing is that it is easier to test the full public API, and it is easier to set up and run the tests within Eclipse, which simplifies development and testing by allowing you, for example, to use the debugger to step through the execution of a failing test case.
Example 20.4, “TestDailyProjectFileMetrics class (excerpt)” illustrates part of the TestDailyProjectFileMetric class used to test the DailyProjectFileMetric subclass.
Example 20.4. TestDailyProjectFileMetrics class (excerpt)
public class TestDailyProjectFileMetric extends TestCase {
protected void setUp() throws Exception {
this.startDay = Day.getInstance("29-May-2006");
this.testUser = UserManager.getInstance().getUser(testUserKey);
Workspace workspace1 = WorkspaceCache.getInstance().getWorkspace(this.testUser, path1);
Workspace workspace2 = WorkspaceCache.getInstance().getWorkspace(this.testUser, path2);
TreeSet workspaceSet = new TreeSet();
workspaceSet.add(workspace1);
workspaceSet.add(workspace2);
// TestProject1 is a "normal" project with some FileMetric data.
this.testProject1 = this.projectManager.createTestProjectClientSide(this.testProjectName + "1",
this.testUser, this.startDay, this.startDay.inc(3), workspaceSet);
// TestProject2 has no data associated with it, because it starts the day after.
this.testProject2 = this.projectManager.createTestProjectClientSide(this.testProjectName + "2",
this.testUser, this.startDay.inc(1), this.startDay.inc(3), workspaceSet);
}
public void testProjectNoData() throws Exception {
DailyProjectFileMetric daily = DailyProjectFileMetric.getInstance(this.testProject2, this.startDay.inc(1));
assertEquals("Checking for no data", false, daily.hasSensorData());
assertEquals("Checking no total lines", 0L, daily.getTotalLines(null, null));
assertEquals("Checking wildcard", 0L, daily.getTotalLines("Java", new FilePattern("**")));
assertEquals("Checking totalLines", 0L, daily.getSizeMetric(null, null, "totalLines"));
assertEquals("Checking all fileTypes", 0, daily.getAllFileTypes().size());
List summary = daily.getSummaryStrings(this.testUser);
assertEquals("Checking summary size", 1, summary.size());
String[] strs = (String[]) summary.get(0);
assertEquals("Checking summary string array", 2, strs.length);
assertEquals("Checking first element of the array", daily.getName(), strs[0]);
assertEquals("Checking no data message", "No data for this day", strs[1]);
List drillDown = daily.getDrillDowns(this.testUser);
assertEquals("Checking drilldown return value", 0, drillDown.size());
}
public void testProjectIllegalDay() throws Exception {
DailyProjectFileMetric daily = DailyProjectFileMetric.getInstance(this.testProject2, this.startDay.inc(-1));
assertEquals("Checking no data", false, daily.hasSensorData());
assertEquals("Checking total lines", 0L, daily.getTotalLines(null, null));
assertEquals("Checking file types", 0L, daily.getAllFileTypes().size());
}
public void testProjectInitialCache() throws Exception {
// Guarantee that the instance has not already been initialized.
// We assume single threaded test execution!!!! :-)
DailyProjectDataCache.getInstance().clearCache(this.testUser);
// Now do the test.
DailyProjectFileMetric daily = DailyProjectFileMetric.getInstance(this.testProject1, this.startDay);
// Check that we can retrieve basic metrics provided in initialized cache.
assertEquals("Checking has data", true, daily.hasSensorData());
assertEquals("Checking FileMetric xml numFiles", 1, daily.getSizeMetric("xml", this.fileMetricFP, "numFiles"));
assertEquals("Checking FileMetric hTMl SOURCElines", 3, daily.getSizeMetric("hTMl", this.fileMetricFP, "SOURCElines"));
assertEquals("Checking Build java totallines", 118, daily.getSizeMetric("java", this.buildFP, "totallines"));
// Check the summary and drilldowns built using the initial cache.
List summaryList = daily.getSummaryStrings(this.testUser);
String[] summaryArray = (String[]) summaryList.get(0);
assertEquals("Checking summary string", "6/189/309 (files/sloc/loc)", summaryArray[1]);
List drillDowns = daily.getDrillDowns(this.testUser);
assertEquals("Checking drilldown table size", 5, ((List) drillDowns.get(0)).size());
}
}
The full test class contains instance variables and several additional unit test methods, but the above provides an illustration of the basic approach. A setup() method is used to create two "client-side" Project instances, and the testProjectNoData(), testProjectIllegalDay(), and testProjectInitialCache() methods create instances of the DailyProjectFileMetric subclass under various conditions to test the public API.
Running this test class under Eclipse requires some configuration, which is explained in Section 13.6.3, “Running individual unit tests in Eclipse”. Of course, this test will also be run under Ant as part of the normal system testing process.