I recently gave the students in my Arduino-based programming class a project to write some simple unit tests and fix the bugs that they encountered as they did. The class in question mostly stores a bunch of Arduino-style String objects, with various getters and setters and a couple of constructors.
The test code that they're supposed to write involved creating two instances of the class (on the stack) and verifying that the constructors set the values correctly. When I went through the exercise myself, everything worked.
When the students repeated the exercise, the first object worked. The second object returned empty strings. If they commented out the first object, the second object began working. I couldn't find any obvious mistakes that could explain these failures.
After staring at their code for about ten minutes to make sure I wasn't losing it, I compared Arduino IDE versions to make sure my machine was running the same version. It was.
Next, I unplugged one of their Arduino Uno devices and plugged in my Mega 2560. The exact same source code passed the unit tests as expected. That means the test failures were caused by some subtle difference between the Arduino Uno and the Mega 2560—either in the hardware or in the compiler.
Before I file a bug against the Arduino Uno compiler (v1.6.7, though I don't see anything in the 1.6.8/1.6.9 release notes that imply any compiler changes since then), I wanted to make sure I wasn't missing something subtle. Could somebody take a look at this and see if I missed anything?
I've included a condensed version of the class in question below (with irrelevant bits removed). You can see the complete set of exercises at:
https://sites.google.com/a/cabrillo.edu/cs-11m/schedule/unit-testing-exercises
The tests failed unexpectedly after they added the second AddressBookEntry
object.
Class Declaration (partial)
class AddressBookEntry {
public:
AddressBookEntry(void);
AddressBookEntry(String title, String firstName,
String middleName, String lastName,
String suffix);
String title(void);
String firstName(void);
...
private:
String _title; // Mr.
String _firstName; // Barack
String _middleName; // Hussein
String _lastName; // Obama
String _suffix; // Sr.
String _addressLine1; // The White House
String _addressLine2; // 1600 Pennsylvania Ave.
String _city; // Washington
String _state; // DC
String _zip; // 20500
String _plusFour; // 0001 (from 20500-0001)
String _homePhone; // Unknown
String _mobilePhone; // Unknown
String _workPhone; // 202-456-1414
Implementation (partial)
#define assert(condition) { \
if (!(condition)) { \
Serial.print(__FILE__); \
Serial.print(":"); \
Serial.print(__LINE__); \
Serial.println(": Assertion failed: " #condition); \
while (1); \
} \
}
bool AddressBookEntry::runTests(void)
{
Serial.println("Running tests.\n"); Serial.flush();
AddressBookEntry testEntry("Mr.", "Jim", "Bob", "Murphy", "Sr.");
Serial.println(testEntry.title());
Serial.flush();
assert(testEntry.title() == "Mr.");
assert(testEntry.firstName() == "Jim");
assert(testEntry.middleName() == "Bob");
assert(testEntry.lastName() == "Murphy");
assert(testEntry.suffix() == "Sr.");
testEntry.setMiddleName("Phil");
assert(testEntry.middleName() == "Phil");
AddressBookEntry obamaEntry("Mr.", "Barack", "Hussein", "Obama", "");
assert(obamaEntry.title() == "Mr."); // fails
assert(obamaEntry.firstName() == "Barack"); // fails
assert(obamaEntry.middleName() == "Hussein"); // stopped checking here.
assert(obamaEntry.lastName() == "Obama");
assert(obamaEntry.suffix() == "");
Serial.println("All tests passed.\n"); Serial.flush();
}
AddressBookEntry::AddressBookEntry()
{
_title = "";
_firstName = "";
_middleName = "";
_lastName = "";
_suffix = "";
_addressLine1 = "";
_addressLine2 = "";
_city = "";
_state = "";
_zip = -1; // Deliberate bug
_plusFour = -1; // Deliberate bug
_homePhone = "";
_mobilePhone = "";
_workPhone = "";
}
AddressBookEntry::AddressBookEntry(String title, String firstName,
String middleName, String lastName,
String suffix)
{
_title = title;
_firstName = firstName;
_middleName = middleName;
_lastName = lastName;
_suffix = suffix;
_addressLine1 = "";
_addressLine2 = "";
_city = "";
_state = "";
_zip = -1; // Deliberate bug
_plusFour = -1; // Deliberate bug
_homePhone = "";
_mobilePhone = "";
_workPhone = "";
String str = "";
Serial.println(str + "title is \"" + title + "\"");
}
String AddressBookEntry::title()
{
return _title;
}
String AddressBookEntry::firstName()
{
return _firstName;
}
... // remaining getters are similar
Calling Code (from setup() function)
Serial.begin(9600);
AddressBookEntry myEntry;
myEntry.runTests();