Lecture 8-1
Unit Tests and Program Implementation
CS 300 Lecture 8-1
Bart Massey
Unit Tests
Come from pseudocode / detailed design
Written before implementation ("test-driven development")
- and after
Not every unit will be tested
- Every unit should at least be desk-checked
- Interesting units should be inspected by someone else or tested
Really interesting units should get both inspection and testing
Unit Tests, Drivers and Stubs
Need some way to call the unit: "driver"
- Typically use a "unit test framework"
Need some way for the unit to call what it calls
- Develop bottom-up
Make something workable for testing: "stub"
Unit Testing and State
For inferior (not functional) languages, need to manage state accessed by routine
This may involve
- State creation tools
- Capturing and replaying state
This is hard, so the less state a unit depends on, the better
Hence, few global variables
From pseudocode to code
Don't be afraid to rework pseudocode as you go
- Correct errors
- Match implementation language
Tracing should be easy
You will add things not in the pseudocode
- Type declarations
- Assertions
- Memory management
- Instrumentation
Error handling code
Your Code Should Fail Early
Ideally, defective code will not compile
- Turn on all warnings and treat them as errors
At least, it would be nice if errors happened during runtime startup
A defect hit during normal program operation is pretty late
Short-running units / programs are way easier to deal with than long-running ones
You Are Not Using Enough Assertions
Instance of the fail-early principle
Memory Management Is Hard
At least, it is hard in inferior (non-GC) languages
Any non-trivial memory management should be inspected by someone else
Learn to use memory analysis tools, e.g.
valgrind
The Pairing Principle
Every alloc should have a free
Every open should have a close
Every time you do something, think of and note "obligations" produced by it
Ideally, "discharge" those right away
Unit Control Flow Made Easy
Idea: Successive strengthening of invariants
- Start by picking off special cases
- Assertions: fail for unhandled cases
- Recursion: get base case
- Special cases: early return
- Each line of the unit you should know more about what you're working with
- At the end of the routine you should just be able to dispatch stuff
break
andcontinue
are your friends
Idea: Case analysis
- Conditionals and case statements are the same thing
Idea: Hoare clauses and loops
Indentation Is The Enemy
Nesting depth only thing that has been shown to correlate strongly with poor code quality
Early
return
vselse
Splitting out subunits
Idiom Is Your Friend
Write things the way other people write them in your language
- Maybe there's a reason
- Regardless, it means others can work with your code
Don't fight the language
Follow the coding standard
Coupling and Cohesion Again
Every unit tells a story
If a unit tells two unrelated stories, break it up
If a unit needs a lot of information to tell its story, put it with the information
This is the idea behind OOP
Assertions, Dammit
Any time you can reasonably check that things are as you expect with an assertion, add it
- Crashes are better than wrong answers
- You won't hit the assertions anyhow..right?
Your language has good assertion facilities: use them
Instrumentation
If code might be wrong, but you can't stop
If things are complicated and you want to be able to understand and maintain your code
Build instrumentation infrastructure
Error Handling
Your language probably has an exception mechanism
All error returns must be handled
- Many can be handled with assertions, but not all
This is a real design thing
Comments
Clarifying comments at the top of interesting units
Block comments inside unit blocks
Comments should not restate the program
Comment anything that might be mysterious
- Or better yet fix the mystery
Use the comment-doc facilities of your language
At The Top Of Every File
Copyright notice
Copyright (c) 2015 Bart Massey
Any license information
Description of the file
ifdef Is Not A Source Code Management System
Your code is checked into Git
When you delete something, just delete it
- Don't comment it out
- Don't ifdef it out
Dead or irrelevant code makes things incredibly more confusing
Follow The Above Rules
Implementation should be 10-20% of dev effort
This is how you keep it that way
In general, code doesn't have bugs