## Unit Testing Made Simple
Benjamin Newman -
[email protected]
Bitreich Con 2019
## About Your Presenter
* Full-time quality assurance "engineer"
* Part-time master's student in computer science
* Someone who can’t say, "No", to Christoph...
## Naïve SDLC for a Software Developer
1. Determine what the business people _need_ (not want) the computer to do.
2. Making the computer do that.
## A Day in My Work-Life: Quality Assurance
Making sure that:
1. The _business people_ know what they _need_ the computer to do.
2. The _programmer people_ know what the computer needs to do.
3. The _computer_ actually does that.
## The Computer Actually Does That?
Two kinds of (dynamic) verification:
1. Manual testing
* Boring and expensive
* Hopefully done only once
2. Automated testing
* Quick and cheap
* Can be done frequently
## The Testing Pyramid
* A model for automated tests
* At the bottom: fastest and most isolated
* At the top: slowest and least isolated
^.
/ \*.
/UI \**.
/_____\***.
/ Inte- \****.
/ gration \****|
/___________\***|
/ \**|
/ Unit \*|
/_________________\|
KCK,
http://ascii.co.uk/art/pyramid
## More Advantages of Unit Testing
_All_ automated tests:
* Confidence when changing implementation
* Easily repeatable
Unit tests:
* More helpful when they fail
* Find bugs earlier
* Can improve the implementation
* More modular
* Documentation
MORE RELIABLE IMPLEMENTATION!
## Contrived Example: The Problem
Need a function, is_odd:
* Input: a number
* Output: True if and only if the input is odd
## Contrived Example: Stub Implementation and Test Cases
TDD-esque (Test-Driven Development)
assert(n, expected) →
if (is_odd(n) = expected)
PASS
else
FAIL
assert(-1, TRUE)
assert(0, FALSE)
assert(3, TRUE)
assert(4, FALSE)
is_odd(n) → FALSE
## Contrived Example: Test the Stub
is_odd(n) → FALSE
assert(-1, TRUE) → FAIL
assert(0, FALSE) → PASS
assert(3, TRUE) → FAIL
assert(4, FALSE) → PASS
## Contrived Example: The (Initial, Incorrect) Implementation
is_odd(n) → (n%2 = 0)
## Contrived Example: Initial Results
is_odd(n) → (n%2 = 0)
assert(-1, TRUE) → FAIL
assert(0, FALSE) → FAIL
assert(3, TRUE) → FAIL
assert(4, FALSE) → FAIL
Oh, no!
## Contrived Example: Fix the Implementation
is_odd(n) → (n%2 = 1)
## Contrived Example: Re-Run the Tests
is_odd(n) → (n%2 = 1)
assert(-1, TRUE) → PASS
assert(0, FALSE) → PASS
assert(3, TRUE) → PASS
assert(4, FALSE) → PASS
Much better!
## How to Start?
Find a Framework:
1. In the codebase
2. In the ecosystem
3. Roll your own
This is the big hurdle.
## When to Write Tests 1/2
All the time:
* Writing new code/adding features
* Refactoring
## When to Write Tests 2/2
| |
Fixing bugs \\_V_//
\/=|=\/
[=v=]
BEFORE __\___/_____
/..[ _____ ]
and /_ [ [ M /] ]
/../.[ [ M /@] ]
After <-->[_[ [M /@/] ]
/../ [.[ [ /@/ ] ]
_________________]\ /__/ [_[ [/@/ C] ]
<_________________>>0---] [=\ \@/ C / /
___ ___ ]/000o /__\ \ C / /
\ / /....\ \_/ /
....\||/.... [___/=\___/
. . . . [...] [...]
. .. . [___/ \___]
. 0 .. 0 . <---> <--->
/\/\. . . ./\/\ [..] [..]
/ / / .../| |\... \ \ \ _[__] [__]_
/ / / \/ \ \ \ [____> <____]
https://www.asciiart.eu/computers/bug
## What to Test (Non-Exhaustive Examples)
* Happy paths
* Sad paths (input that cause errors)
* Boundary conditions
* BUG FIXES
. ' .
' .( '.) '
_ ('-.)' (`'.) '
|0|- -(. ')`( .-`) (-')
.--`+'--. . (' -,).(') .
|`-----'| (' .) - ('. )
| | . (' `. )
| .-. | ` . `
| (0.0) |
| >|=|< |
| `"` |
| |
jgs| |
`-.___.-'
https://asciiart.website/index.php?art=animals/insects/other
## Good Tests in a Nutshell
Are useful:
* Simple
* Failable
* Independent
## Unit Test Smells
Are not useful:
* Complicated
* Multiple concerns
* Unreliable
* Define implementation
## Tell Me More About this Art
Bowes, David, Tracy Hall, Jean Petrić, Thomas Shippey, and Burak Turhan. 2017. “How Good Are My Tests?” In _Proceedings of the 8th Workshop on Emerging Trends in Software Metrics_, 9–14. WETSoM ’17. Piscataway, NJ, USA: IEEE Press.
https://doi.org/10.1109/WETSoM.2017.2. (
https://bura.brunel.ac.uk/bitstream/2438/14816/1/FullText.pdf)
* 16 principles of unit tests
* Metrics
* Test smells
## Questions?
___ ___ ___ ___
|__ \ |__ \ |__ \ |__ \
/ / / / / / / /
|_| |_| |_| |_|
(_) (_) (_) (_)
## The Main Question Was....
Question:
How do you start writing tests if you already have a big codebase and it's not super-simple to use? For example, if it needs an N amount of daemons running, and requires some kind of continuous user input.
------
Answer:
If it would require n>0 daemon to be running, it would be an integration test. User input can be mocked, but "continuous user input" makes it sound like an integration test as well.
Even if unit testing were feasible, integration (and UI) testing still have value; in this case, however, integration testing is even more valuable than it would be otherwise. In addition to the advantages of automated testing if the codebase continues as it is, it would also reduce the risk if it was decided to to refactor or rewrite the implementation such that unit testing would be possible.
In any case, if the implementation has lots of smells, the test suite will as well.