CSE 374 Programming Concepts and Tools
Lecture 14 - Pre- and Post-Conditions, Testing
Next Week
Today
More on Variable Types and Storage
Memory Architecture
C++ Intro
Testing
Interfaces in Software
High level: What is an interface?
Definition: the place at which independent and often unrelated systems meet and act on or communicate with each other (Merriam-Webster)
At its most basic, an interface is how two systems can interact
Discussion
Turn and talk:
What are some examples of interfaces that you can think of? Categories include software, hardware, user interfaces, etc.
Examples of Interfaces
Interfaces in Software: Functions!
Think of your functions as a black box, where input goes in and output comes out
Interface = what kind of input and output your black box has
Implementation = the code to make your black box work
Remember the Java keywords: interface, implements?
7
Interfaces in C
In C, the function declaration serves as the interface
char* strncpy (char* des, const char* source, size_t num);
What does this tell us? What doesn't it tell us?
8
Interface Design
Designing interfaces in software is hard.
9
Many programming languages define their own tools to help define reliable interfaces
Pointer Write Permissions
Pointer types can sometimes be confusing
int foo(int* p);
10
int foo(int* p) {
*p = 374;
return 0;
}
int foo(int* p) {
return *p;
}
const Pointers
Add const to the pointer type to make the pointer read-only: i.e., cannot modify!
int foo(const int* p);
11
// Doesn't compile!
int foo(const int* p) {
*p = 374;
return 0;
}
// OK, compiles
int foo(const int* p) {
return *p;
}
Aside: Backward Compatibility
We want to design these contracts so that they are future proof, or resilient to change.
A change and/or addition to an interface is backward compatible if any old code will continue to work as-is.
12
Aside: Backward Compatibility in C
In C, and many other languages, a common strategy is to always add fields, but never take away.
We can add the index field, and all old code that used value and next will work!
As long as you use sizeof to allocate the Node, the size will seamlessly update.
13
typedef struct Node {
int value;
struct Node* next;
} Node;
Node* make_list_from_arr(int* arr, int len);
typedef struct Node {
int value;
struct Node* next;
int index;
} Node;
Node* make_list_from_arr(int* arr, int len);
Demo: Interfaces in C
14
Pre- and Post-Conditions
How do we know our program works?
Some indicators:
What can we do to help us know the program behaves the way it's supposed to?
16
Input
What type?
What needs to be true about our input?
This is called a "pre-condition"
17
Polling Time!
Please answer in the LP14 assignment on Gradescope!
Consider the function free(void* p);
If you were explaining how to use free, what would the precondition be?
18
man 3 free
free has multiple manual pages, one is a command line tool
Use man 3 free to look up "free" as a C function
19
The free() function frees the memory space pointed to by ptr,
which must have been returned by a previous call to malloc() or
related functions. Otherwise, or if ptr has already been freed,
undefined behavior occurs. If ptr is NULL, no operation is
performed.
Output
What's type?
What needs to be true about our output?
This is called a "post-condition"
20
Contracts
A function's pre- and post-conditions form a contract
The user and the function must uphold their end of the deal
Undefined behavior: calling a function with input which doesn't satisfy the pre-condition
21
Hyrum's Law
With a sufficient number of users of an API,
it does not matter what you promise in the contract:
all observable behaviors of your system
will be depended on by somebody.
It's vital that we design our interfaces, pre-conditions, and post-conditions very carefully! The more programmatic guards we can use (like const), the better.
22
Summary So Far
Valid input = input satisfying the pre-condition
Correct output = output satisfying the post-condition
Correct functions generate correct output for every valid input
We don't care what happens on invalid input
Ideally, we want to fail fast if we're following defensive programming (for Hyrum!)
Specification = list of pre- and post-conditions for every function in the program
23
Example: pow()
int pow(int base, int exp);
Pre: exp >= 0
Post: returned value is equal to base to the power of exp
24
Polling Time!
Please answer in the LP14 assignment on Gradescope!
Part 1:�If pow(-10, 3) != -1000 = (-10)3, whose fault is it?
Part 2:�If pow(1, -2) != 1 = 1-2,�whose fault is it?
25
Testing
Testing
We can write tests to check if our functions generate correct output for valid input
Writing a test:
* There used to be a very clear separation between testers & developers. Today (and probably for the past decade) it's very common for developers to be testers too!
27
Testing Isn't Perfect
Program testing can be used to show the presence of bugs, but never to show their absence!
– Edsger Dijkstra, 1970 (1972 Turing Award Winner)
In other words,
“Absence of evidence is not evidence of the absence.”
“Just because you did not detect a bug, doesn't mean you don't have one.”
“Even when you don't detect any bugs through testing, you can still have one.”
28
Tests are (usually) Good Enough
Tests can never 100% prove that your program is correct
However, tests are usually good enough to give you confidence
Ideally, your tests should cover all the different "ways" your program can be used
Together, these tests form a test suite
29
Assert
In many programming languages, we can assert whether a condition is true
In C, assert() is defined in <assert.h>
It can be used like a function, but it is technically a macro
30
#include <assert.h>
int main() {
void* pointer = 0;
assert(pointer);
return 0;
}
Demo: Assert
31
Kinds of Tests
32
Unit tests
Integration tests
Black box tests
Clear box tests
Unit vs. Integration Testing
Unit testing = testing one module/function by itself
Integration testing = testing many modules/functions together
33
Black vs. Clear Box Testing
Black box testing = tests designed using only information from the specification
Clear box testing = tests influenced by the implementation
35
How Much Testing?
Writing tests is expensive in time & money
How much should we test before we're satisfied?
Many heuristics exist to answer this question
Code coverage = was each line of code executed?
36
Polling Time!
Please answer in the LP14 assignment on Gradescope!
I have the header files (.h) and object files (.o) of some program and I want to test my functions working together. What kind of tests should I use in this situation?
37
Regression Tests
As you develop larger projects, you may edit code that you wrote earlier
You want tests to make sure that any fixes/edits don't introduce further bugs (i.e. a regression)
Regression testing is where you test against old output to make sure that the behavior has not changed
Good practice is to create at least one regression test each time you fix a bug, to make sure it doesn't come back
38
Testing Frameworks
Various programming languages have various frameworks that make testing easier
For CSE 374, we will give you a testing framework, safe_assert.h
Testing framework has its own syntax, which is different from C's syntax
39
Using safe_assert.h
In test files, instead of a main() function, there is a suite() block
Inside of the suite() are similar test() blocks
suite("My test suite") {
test("Test case 1") {
int res = pow(10, 2);
safe_assert(res == 100);
}
}
40
The safe_assert() macro
safe_assert() is very similar to assert(), except that if you segfault inside of an assert, it will tell you!
Assert failed (test.c:14): `*null_ptr'
The test suite process will survive the segfault
41
Demo: Testing Framework
42
Assignment Reminders
EX12 due Monday 7/28 @10:49am
HW4: Debugging is due tonight @11:59pm
HW5: T9 Testing releases today and is due next Friday 8/1 @11:59pm
Mid-Course Survey due on Sunday 7/27 @11:59pm
Make sure to submit LP14 before the next class as well.
All of these assignments can be found on our course's Gradescope page.