Skip to content

Instantly share code, notes, and snippets.

@jeremyckahn
Last active April 9, 2018 14:58
Show Gist options
  • Save jeremyckahn/77f6aa8f855a9a76e6299e5c1f4fcda7 to your computer and use it in GitHub Desktop.
Save jeremyckahn/77f6aa8f855a9a76e6299e5c1f4fcda7 to your computer and use it in GitHub Desktop.
Getting Started With Unit Tests

Getting Started With Unit Tests

This is not meant to be a comprehensive guide to testing, but rather a high-level introduction. It is meant to help you become conversant in unit testing, not a master. That will come later!

Case Study: rekapi-timeline's computeHighlightedKeyframe and computeDescaledPixelPosition functions:

Why

  • Sanity: Code that is tested is code that you don't have to worry about (as much).
  • Stability: Testing affords a level of automated quality assurance and functional verification. It also improves maintainabilty by enabling greater confidence in making a change.
  • Simplicity: Code that is testable tends to be more clear, modular, and reusable.

What

  • Business logic: This should be your primary focus. A test suite should comprehensively verify that the core of your project solves the problem that it is intended to solve.
  • Rendering logic: This gets a lower priority, because rendering tends to change frequently and is often tangential to whether the code actually works or not. Clear pass/fail characteristics of rendering logic should be tested, such as whether something on the UI shows up or not.

How

  • Test organization
    • Separate tests logically by file, component, and function: The test output should tell a sort of narrative around what the code is proven to do (see 2-test-output.txt).
  • Test anatomy
    • Setup: Perform any work that is necessary to enable a valuable assertion to be directly made.
    • Assertion: A clear, isolated pass/fail expectation of the result of running a piece of code.
    • Cleanup: Any work that is needed to revert the environment back to what it was before the test ran.
  • Philosophy
    • Isolation: A test should run the same way regardless of where in the test suite it is, and regardless of what has or has not happened before it is ran.
    • Minimalism: A given test should focus on as little as possible in terms of setup and assertions. If a test isn't covering everything you want to verify, add more tests. Preferring to test smaller, isolated pieces of behavior helps ensure that this happens. You tend to end up with simpler tests by focusing on lower-level chunks of behavior (assuming your code is already decoupled and small to begin with). (Thanks to daveschinkel for help with this!)
  • Things to assert:
    • What should happen, does: All of your expected use cases should be accounted for.
    • What shouldn't happen, doesn't: Expected negative cases, such as sane default return values and early returns.
    • Error handling: Realistic failure cases are handled gracefully.
  • Think not to assert:
    • Unexpected parameter types: Your documentation should define an implicit contract of valid data types so your tests don't have to. It's expected that if the data types are invalid then your program will crash, so you don't have to explicitly test for that. (Thanks to @hillelogram for help with this!)
    • External libraries: Assuming you are using robust external libraries, your tests should only focus on the code you are authoring.
    • Unrealistic scenarios: There is infinite number of potential scenarios your code may run under, but a finite set of realistic ones. Focus on the latter.
$ npm test
> [email protected] test /Users/jeremykahn/oss/rekapi-timeline
> mocha -r jsdom-global/register ./node_modules/babel-core/register.js test/setup.js test/index.js
utils
computeHighlightedKeyframe
✓ is a function
return values
when propertyCursor is empty
✓ returns empty object
when rekapi has an actor and propertyCursor is empty
✓ returns empty object
when propertyCursor references a property track that does not exist
✓ returns empty object
when propertyCursor references a property that does not exist
✓ returns empty object
when propertyCursor references a property that does exist
✓ returns KeyframeProperty data
computeDescaledPixelPosition
✓ de-scales a scaled pixel value against a normalized value
✓ rounds returned values down
8 passing (52ms)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment