hledger/doc/TESTS.md

246 lines
8.3 KiB
Markdown
Raw Normal View History

2023-10-28 19:27:19 +03:00
# TESTS
2021-09-27 14:56:13 +03:00
<div class=pagetoc>
2021-09-27 14:56:13 +03:00
<!-- toc -->
</div>
About testing in the hledger project, as of 201809.
## Kinds of tests
<div style="margin:1em 2em; font-style:italic;">
"Here, then, is a list of properties of tests. Not all tests need to exhibit all properties. However, no property should be given up without receiving a property of greater value in return.
- Isolated — tests should return the same results regardless of the order in which they are run.
- Composable — if tests are isolated, then I can run 1 or 10 or 100 or 1,000,000 and get the same results.
- Fast — tests should run quickly.
- Inspiring — passing the tests should inspire confidence
- Writable — tests should be cheap to write relative to the cost of the code being tested.
- Readable — tests should be comprehensible for reader, invoking the motivation for writing this particular test.
- Behavioral — tests should be sensitive to changes in the behavior of the code under test. If the behavior changes, the test result should change.
- Structure-insensitive — tests should not change their result if the structure of the code changes.
- Automated — tests should run without human intervention.
- Specific — if a test fails, the cause of the failure should be obvious.
- Deterministic — if nothing changes, the test result shouldnt change.
- Predictive — if the tests all pass, then the code under test should be suitable for production."
--[Kent Beck](https://medium.com/@kentbeck_7670/test-desiderata-94150638a4b3)
</div>
1. Unit tests
Unit tests exercise small chunks of functionality. In hledger, that
means a function. So, many of our functions have one or more unit
tests. These are mostly in hledger-lib, with a few in hledger.
Our unit tests use the
[tasty](https://hackage.haskell.org/package/tasty) test runner,
[tasty-hunit](https://hackage.haskell.org/package/tasty-hunit) HUnit-style tests,
and some helpers from
[Hledger.Utils.Test](https://github.com/simonmichael/hledger/blob/master/hledger-lib/Hledger/Utils/Test.hs),
such as:
- `tests` and `test` aliases for `testGroup` and `testCase`
- `assert*` helpers for constructing various kinds of assertions
We would like our unit tests to be:
- easy to read (clear, concise)
- easy to write (low boilerplate, low cognitive load)
- easy to maintain (easy to edit, easy to refactor, robust)
- easy to associate with the code under test (easy to view/jump
between code & test, easy to estimate coverage)
- and scalable (usable for all devs, easy to run and select,
suitable for small/large modules/packages).
Here\'s the current pattern (let us know if you see a better way):
``` haskell
module Foo (
...
tests_Foo -- export this module's and submodules' tests
)
where
import Hledger -- provides Hledger.Utils.Test helpers
import Bar -- submodules, providing tests_Bar etc.
import Baz
functionA = ...
functionB = ...
functionC = ...
functionD = ...
tests_Foo = tests "Foo" [ -- define tests at the end of each module
-- a group of several named tests for functionA
tests "functionA" [
test "a basic test" $ assertBool "" SOMEBOOL
,test "a pretty equality test" $ SOMEEXPR @?= EXPECTEDVALUE
,test "a pretty parsing test" $ assertParseEq PARSER INPUT EXPECTEDRESULT
,test "a multiple assertions test" $ do
A @?= B
doSomeIO
C @?= D
]
-- a single test containing multiple unnamed assertions for functionB
,test "functionB" $ do
assertBool "" BOOL
EXPR @?= VALUE
,tests_Foo -- aggregate submodule tests
,tests_Bar
]
```
Here are
[some](https://github.com/simonmichael/hledger/blob/master/hledger-lib/Hledger/Data/Posting.hs#L296)
real-world
[examples](https://github.com/simonmichael/hledger/blob/master/hledger-lib/Hledger/Read/JournalReader.hs#L579).
The unit tests are shipped as part of the hledger executable, and
2022-12-11 07:56:47 +03:00
can always be run via the [test](https://hledger.org/hledger.html#test)
command (`hledger test`).
2. Doc tests
Like unit tests, but defined inside functions\' haddock
documentation, in the style of a GHCI transcript. These test
functionality, provide usage examples in the API docs, and test
those examples, all at once. They are a bit more finicky and slower
than unit tests. See
[doctest](https://hackage.haskell.org/package/doctest) for more.
doctests [do not work on Mac with GHC
8.4+](https://github.com/sol/doctest/issues/199), out of the box.
See
[ghc\#15105](https://ghc.haskell.org/trac/ghc/ticket/15105#comment:10)
for current status and a workaround.
3. Functional tests
Functional tests test the overall functioning of the program. For
hledger, that means running `hledger` with various inputs and
options and checking for the expected output. This exercises
functionality in the hledger and hledger-lib packages. We do this
with
[shelltestrunner](https://hackage.haskell.org/package/shelltestrunner).
Tests are defined in files named `*.test` under
[hledger/test/](https://github.com/simonmichael/hledger/tree/master/hledger/test),
grouped by *component* (command or topic name).
For more about these, see the README there.
4. Code tests
We have some tests aimed at testing eg code quality, generally runnable via just.
Eg `just haddocktest`, `just hlinttest`.
5. Package test suites
Haskell tools like stack and cabal recognise test suites defined in
a package\'s cabal file (or package.yaml file). These can be run via
`stack test`, `cabal test` etc., and they are required to build and
pass by services like Stackage. Here are the currently hledger
package test suites:
------------- ------------ ---------------------------------------------------------------
package test suite what it runs
hledger-lib doctests doctests
hledger-lib easytests unit tests
hledger test builtin test command (hledger\'s + hledger-lib\'s unit tests)
hledger-ui
hledger-web
------------- ------------ ---------------------------------------------------------------
## Coverage
This means how thoroughly the code is tested - both in breadth (are all
parts of the code tested at least a little ?) and in depth (are all
possible code paths, states, situations tested ?).
Our current test coverage can be summarised like so:
------------- ------ ----- ------------
package unit doc functional
hledger-lib X X X
hledger X X
hledger-ui
hledger-web
------------- ------ ----- ------------
There are ways to generate detailed coverage reports for haskell unit
tests, at least. It would be useful to set this up for hledger.
## How to run tests
Run unit tests:
``` example
$ just unittest
```
Run doctests:
``` example
$ just doctest
```
Run functional tests (and unit tests, now):
``` example
$ stack install shelltestrunner
$ just functest
```
Run the package tests (unit tests, maybe doctests, but not functional
tests) of all or selected packages.
``` example
$ stack test [PKG]
```
Run \"default tests: package plus functional tests\":
``` example
$ just test
```
Test generation of haddock docs:
``` example
$ just haddocktest
```
Run built-in hledger/hledger-lib unit tests via hledger command:
``` example
$ hledger test # test installed hledger
$ stack build hledger && stack exec -- hledger test # test just-built hledger
$ hledger test --help
test [TESTPATTERN] [SEED]
Run the unit tests built in to hledger-lib and hledger,
printing results on stdout and exiting with success or failure.
Tests are run in two batches: easytest-based and hunit-based tests.
If any test fails or gives an error, the exit code will be non-zero.
If a pattern argument (case sensitive) is provided, only easytests
in that scope and only hunit tests whose name contains it are run.
If a numeric second argument is provided, it will set the randomness
seed for easytests.
```
Rebuild and rerun hledger/hledger-lib doc tests via ghcid:
``` example
$ just ghcid-doctest
```
See all test-related just rules:
``` example
$ just h test
```