Those who augment their development practice to include unit-testing--as I hope you have if you are following this series--can help insure they continue to embrace the discipline of 'test first' by saving their understanding of the software-under-test (SUT) within the actual test. This practice guards against you or another developer modifying the SUT without informing the test. For example, if someone adds a new property or function your test can alert you. This practice provides a little insurance to keep you honest to your promise to test first.
This practice can also check the implicit functions found within the SUT. Generally, I only check for the functions/methods I explicitly define within the SUT and ignore the implicit assessors provided by ColdFusion 9. But, the samples below can check for both if desired.
Save Your Understanding Of The Software
import coldbox.system.testing.*;
/** @hint Unit Test for the User Object. */
component extends="mxunit.framework.TestCase" {
/** Constructor */
public void function setUp() {
// Mocks
MockBox = new MockBox();
// SUT
User = MockBox.createMock(classname='shared.models.objects.User',clearMethods=false).init();
}
/** Test that verifies the User object has not been given any new properties. */
public void function guard() {
// Register all our known properties here.
var known_properties = [
'User_ID'
,'User_Name'
,'First_Name'
,'Last_Name'
,'Notes'
,'Phone'
,'Skype'
,'UUID'
,'Password'
,'Active'
,'Company'
,'IM'
,'Email'
,'External_Position'
,'last_login'
,'External_URL'
,'RequirePasswordChange'
,'Roles'
,'Languages'
,'Language'
,'BeanFactory'
,'Password_Confirm'
,'UserDAO'];
var explicit_functions = [
'addRole'
,'clearRoles'
,'getPassword'
,'getPasswordPlain'
,'init'
,'isAllowedAccess'
,'isPopulated'
,'languageExists'
,'onDIComplete'
,'populate'
,'roleExists'
,'setBeanFactory'
,'setPassword'
,'setPassword_Confirm'];
var test = TestHelper.checkEvolution(object=User,properties=known_properties,functions=explicit_functions,checkImplicitFunctions=false);
if (test.fail)
fail(test.message);
assertTrue(true);
}
}In the above code sample you can see I am registering the properties and functions I expect to find within the SUT. I am not verifying that the implicit assessors created by ColdFusion 9 exists; however, the helper method can check for the existences of getters/setters if you desire.
/** @hint Utility methods for testing. */
component name='TestHelper' {
/** Checks to see if the SUT has evolved. Returns a struct with a 'fail' flag and a 'message'. Fails if
any properties/functions were added/removed compared to expectations provided via arguments.
@Object Pass any object one desires to test for changes.
@Properties Pass an array of properties understood to be within the object.
@Functions Pass an array of functions understood to be within the object.
@checkImplicitFunctions If true all functions are compared. Otherwise, only functions with names not-uppercase are checked. */
public struct function checkEvolution (required any Object, required array Properties, required array Functions, boolean checkImplicitFunctions=false) {
var md = GetMetaData(arguments.Object);
var i = 1;
var foundProperties = [];
var foundFunctions = [];
var failures = {
properties = {added = [], removed = []}
,functions = {added = [], removed = []} };
// Simplify reference to the properties within the Object.
for (i=1;i<=arrayLen(md.properties); i++)
arrayAppend(foundProperties, md.properties[i].name);
// Simplify reference to the function names within the Object.
for (i=1;i<=arrayLen(md.functions); i++) {
var functionName = md.functions[i].name;
if (checkImplicitFunctions) {
arrayAppend(foundFunctions, functionName);
} else {
// If these two strings match the func name is all caps
// and is considered an implicit function. We only
// desire non-matched function names.
if (compare(functionName, ucase(functionName)) != 0)
arrayAppend(foundFunctions, functionName);
}
}
// Check for added properties to the Object.
for (i=1;i<=arrayLen(foundProperties); i++)
if (!arrayContains(arguments.properties, foundProperties[i]))
arrayAppend(failures.properties.added, foundProperties[i]);
// Check for properties that have gone missing.
for (i=1;i<=arrayLen(arguments.properties); i++)
if (!arrayContains(foundProperties, arguments.properties[i]))
arrayAppend(failures.properties.removed, arguments.properties[i]);
// Check for added functions to the Object.
for (i=1;i<=arrayLen(foundFunctions); i++)
if (!arrayContains(arguments.Functions, foundFunctions[i]))
arrayAppend(failures.functions.added, foundFunctions[i]);
// Check for functions that have gone missing.
for (i=1;i<=arrayLen(arguments.Functions); i++)
if (!arrayContains(foundFunctions, arguments.Functions[i]))
arrayAppend(failures.functions.removed, arguments.Functions[i]);
// Prepare the return.
var result = { message = '', fail = false };
if (!arrayIsEmpty(failures.properties.added) OR !arrayIsEmpty(failures.properties.removed) OR
!arrayIsEmpty(failures.functions.added) OR !arrayIsEmpty(failures.functions.removed)) {
result.message = 'The SUT has evolved. ';
result.message &= (arrayIsEmpty(failures.properties.added)) ? '' : 'Unregistered properties: ' & arrayToList(failures.properties.added, ', ');
result.message &= (arrayIsEmpty(failures.properties.removed)) ? '' : 'Removed properties: ' & arrayToList(failures.properties.removed, ', ');
result.message &= (arrayIsEmpty(failures.functions.added)) ? '' : 'Unregistered functions: ' & arrayToList(failures.functions.added, ', ');
result.message &= (arrayIsEmpty(failures.functions.removed)) ? '' : 'Removed functions: ' & arrayToList(failures.functions.removed, ', ');
result.fail = true;
}
return result;
}
}
