Test Driven Development
falling into the pit of success
Newbies
How do I critically examine the way I write software? How can I use software engineering techniques to make myself more productive?
Veterans
How does the way Will designs software compare with the way I design software? If I don't use TDD, does it look like it would be useful to me?
Takeaways
What I'm Going to Say
- Tooling
- Forming Good Habits
- Breaking Bad Habits
Tooling
Unit Test Frameworks
- JUnit Style
- What I was familiar with.
- RSpec Style?
- Ruby is a foreign land to me.
//The preparation macro. Add it to the start of main()
#define WILLUNIT_PREP testSuiteData data; data.testNames = (char**)malloc(sizeof(char**)); data.functPtrs = (willTest*)malloc(sizeof(willTest*)); data.maxRuntimes = (long*)malloc(sizeof(long*)); data.numTests = 0;
//This trick is from
//http://stackoverflow.com/questions/11761703/overloading-macro-on-number-of-arguments
#define WILLUNIT_TEST(...) OVERLOAD(__VA_ARGS__, TEST_WITH_TIME, TEST)(__VA_ARGS__)
//If there's just one argument in __VA_ARGS__,
//we'll get OVERLOAD(arg, TEST_WITH_TIME, TEST) so TEST gets selected
//If there's two arguments, TEST_WITH_TIME gets selected
#define OVERLOAD(_1,_2,NAME,...) NAME
#define TEST(name) addTest(&data,&name,#name,-1);
#define TEST_WITH_TIME(name, time) addTest(&data,&name,#name,time);
#define WILLUNIT_DEFINE(name) void name(testResults* results)
#define WILLUNIT_RUN runTests(&data);
#include "willUnit.h"
WILLUNIT_DEFINE(sortSmallInput)
{
//Setup
int toSort[10] = {3,4,1,67,4,3,9,-4,34,8};
int toCheck[10] = {-4,1,3,3,4,4,8,9,34,67};
//Execute Unit Under Test
START_TIMER
krsort(toSort,0,9);
STOP_TIMER
//See if UUT succeeded
if (0 == memcmp(toCheck,toSort,sizeof(int)*10))
{
FAILURE("Failed to sort integers in ascending order.")
}
}
Problems
- Making a unit testing framework is nontrivial
- OS projects were more "prototypes" than products
Golden Opportunity!
- Web development project
- Team of three working for months
- Decent-sized code base
Disillusionment
- Could never configure the web framework for unit testing
- Banged my head against a wall for ~6 hours
- TA banged his head against the wall. Maybe more successfully.
Silver Lining
- Learned how to use unittest module
- Learned how to use nosetests
Good Habits
Programming by Wishful Thinking
def test_compulsory_miss(self):
access = Access('123','R')
pTable = self.pTable;
pTable.request(access)
self.assertTrue(pTable.table[self.page_num].is_valid)
def test_evict(self):
pTable = self.pTable
access_list = [Access(hex(x*4096 + 1),'R') for x in range(0,9)]
#Eviction function that always evicts page #1
def evict_arbitrary():
pTable.valid_count -= 1
return 1
#Monkey patch!
pTable.select_victim = evict_arbitrary
#Access one more page than our page table's capacity
for access in access_list:
pTable.request(access)
#Every page numbered two or greater should still be valid
for page_num in range(2,9):
self.assertTrue(pTable.table[page_num].is_valid)
#Page 1 should have been evicted
self.assertFalse(pTable.table[1].is_valid)
Living Documentation
Getting in the Flow
- Write a test
- Make it pass
- Repeat
"If you edit as you go along, there are no first, second, third drafts. There is only one draft, and when it’s done, it’s done. Who can find anything bad to say about the last day of a novel? It’s a feeling of happiness that knocks me clean out of adjectives. I think sometimes that the best reason for writing novels is to experience those four and a half hours after you write the final word."
-Zadie Smith
Sweet Victory
Bad Habits
People who smoke cigarettes, they say "You don't know how hard it is to quit smoking." Yes I do. It's as hard as it is to start flossing.
People who smoke cigarettes, they say "You don't know how hard it is to quit smoking." Yes I do. It's as hard as it is to start flossing.
-Mitch Hedberg
Defining a Unit
class RandTests(unittest.TestCase):
def test_evict(self):
runner = vmsim.Runner()
runner.run_single(Random,8,'12.trace')
page_num_list = [1055,81758,24184,1141,201544,1187,28687,1182,1218,208986,24070]
num_valid = 0
for page_num in page_num_list:
if runner.pTable.table[page_num].is_valid:
num_valid += 1
self.assertEqual(num_valid,8)
Dependency Injection
class ClockTests(unittest.TestCase):
def test_evict_lru(self):
runner = vmsim.Runner()
runner.run_single(Clock,8,'clock.trace')
rightful_victim = runner.pTable.table[81758]
self.assertFalse(rightful_victim.is_valid)
rightful_victim = runner.pTable.table[24184]
False Confidence
- A bad test is worse than no test
- False positive made me blind to a nasty bug
Is It Worth It?
Yes
- Made the project more fun
- Exposed bad habits
- The cool kids do it
Learning
"The history of Western Civilization is a series of failures approximating success"
-Paolo Palmieri
Misadventures in TDD
By Will Engler
Misadventures in TDD
Presented at the Pitt Computer Science Club on Nov. 10, 2014.
- 703