Unit Tests
Test-driven design is composed by the idea that we can first write case scenarios that a function will then have to produce - this is one of the main concepts behind the “how to code” templates The idea here is that a test case is really just an example that we compare against, and in writing said example we are able to then extract the required information to write a function.
Although test-driven design can appear tedious, it significantly reduces code complexity down the line. Additionally, time spent creating unit tests to serve as reference material for a function allows us to do incremental testing and to fail quickly rather than encounter bugs when the application has become extremely complex.
To quote Gregor Kiczales, creating examples before the function itself allows us:
- to figure out what we want the function to do
- to remember the names and types of the primitives we need
- to be accurate with details, like order of arguments
Creating the unit test first means we can adjust our functions to fit the data instead of wrangling data to fit a function. This allows us to figure out the function with specific cases rather than starting with the more difficult general case
Example
Using an example where we are trying to graph schools and tuition costs in Racket we might arrive at the following data definitions:
Data definition
Transclude of Reference-Rule#example
Unit tests
These unit tests are complex, but the approach to writing them is sequential. Given a reference image, slowly build up the unit test to match the required specification - begin, for example, generating text on the screen. Then rotate it, then colour it, then add a background, etc.
Note that when tests start to become more complex, we may define constants to avoid rewriting the same code over and over. Additionally, as programs get larger and larger, we may send the tests to a different file, wherein the constants should be written as well.
;; ================
;; Functions
;; ListOfSchool -> Image
;; produce bar chart showing names and tuitions of consumed schools
(check-expect (chart empty) (square 0 "solid" "white"))
(check-expect (chart (cons (make-school "UBC" 8000) empty))
(beside (overlay/align "center" "bottom"
(rotate 90 (text "UBC" FONT-SIZE FONT-COLOR))
(rectangle BAR-WIDTH (* 8000 Y-SCALE) "outline" "black")
(rectangle BAR-WIDTH (* 8000 Y-SCALE) "solid" BAR-COLOR))
(square 0 "solid" "white")))
(check-expect (chart (cons (make-school "SFU" 12000) (cons (make-school "UBC" 8000) empty)))
(beside/align "bottom"
(overlay/align "center" "bottom"
(rotate 90 (text "SFU" FONT-SIZE FONT-COLOR))
(rectangle BAR-WIDTH (* 12000 Y-SCALE) "outline" "black")
(rectangle BAR-WIDTH (* 12000 Y-SCALE) "solid" BAR-COLOR))
(overlay/align "center" "bottom"
(rotate 90 (text "UBC" FONT-SIZE FONT-COLOR))
(rectangle BAR-WIDTH (* 8000 Y-SCALE) "outline" "black")
(rectangle BAR-WIDTH (* 8000 Y-SCALE) "solid" BAR-COLOR)
)
(square 0 "solid" "white")))
#;
(define (chart los) (square 0 "solid" "white"))
(define (chart los)
(cond [(empty? los) (square 0 "solid" "white")]
[else
(beside/align "center" "bottom"
(make-bar (first los)) ; use helper for reference
(chart (rest los)))])) ; use natural recursion
;; School -> Image
;; produce the bar for a single school in the bar chart
;; !!!
(define (make-bar s) (square 0 "solid" "white"))References
- How to code: Simple data. edX. Retrieved January 22, 2022 from https://www.edx.org/course/how-to-code-simple-data