Skip to content

Commit e3897fe

Browse files
committed
WIP: reorganizing based on proofreader feedback
1 parent e38e5c7 commit e3897fe

File tree

1 file changed

+186
-80
lines changed

1 file changed

+186
-80
lines changed

docs/pages/principles/testing.md

Lines changed: 186 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,42 @@ parent: Principles
1010

1111
# Testing recommendations
1212

13-
In the guide, we will classify two kingdoms of test: external and internal.
14-
External tests view the module from the perspective of a user of the module, and
15-
are concerned that the public-facing features behave as expected. Internal tests
16-
view the module from the perspective of code inside of the module, and ensure
17-
that the components that make up our package work as expected, and interact with
18-
each other properly.
19-
20-
### Any test case is better than none
13+
In this guide, we will provide a roadmap and best-practices for creating test
14+
suites for python projects.
15+
16+
We will describe the most important types of test suites, the purposes they
17+
serve and differences between them. They will be presented in OutSide -> In
18+
order, which is our recommend approach. Starting with
19+
[Public Interface tests](#user-interface-or-public-api-testing), which test your
20+
code from the perspective of your users, focusing on the behavior of the public
21+
interface and the Features that your project provides. Then we will cover
22+
[Package Level Integration tests](#package-level-integration-tests), which test
23+
that the various parts of your package work together, and work with the other
24+
packages it depends on. Finally we will cover the venrable
25+
[Unit Test](#unit-tests), which test the correctness of your code from a
26+
perspective internal to your codebase, tests individual units in isolation, and
27+
are optimized to run quickly and often.
28+
29+
These 3 test suites will cover the bulk of your testing needs and help get your
30+
project to a reliable and maintainable state. We will also discuss some more
31+
specialized and advanced types of test cases in our
32+
[Taxonomy of Test Cases](#taxonomy-of-test-cases) section.
33+
34+
## Advantages of Testing
35+
36+
- Trustworthy code: Well tested code, is code that you can trust to behave as
37+
expected.
38+
- Living Documentation: A good test is a form of documentation, which tells us
39+
how the code is expected to behave, communicates the intent of the author, and
40+
is validated every time the test is run.
41+
- Preventing Failure: Tests provide safety against many ways code can fail, from
42+
errors in implementation, to unexpected changes in upstream dependencies.
43+
- Confidence when making changes: A thorough suite of tests allows developers to
44+
add features, fix bugs, and refactor code, with a degree of confidence that
45+
their changes do not break existing features, or cause unexpected
46+
side-effects.
47+
48+
## Any test case is better than none
2149

2250
When in doubt, write the test that makes sense at the time.
2351

@@ -31,7 +59,7 @@ bogged down in the taxonomy of test types. As you write and use your test suite,
3159
the reason for classifying and sorting some types of tests into different test
3260
suites will become apparent.
3361

34-
### As long as that test is correct...
62+
## As long as that test is correct...
3563

3664
It can be surprisingly easy to write a test that passes when it should fail,
3765
especially when using complicated mocks and fixtures. The best way to avoid this
@@ -45,14 +73,20 @@ the test-case to make sure it fails when the code is broken.
4573
is better to write many test cases for a single function or class, than one
4674
giant case.
4775

48-
## External or outside-in testing
76+
## User Interface and Public API testing
4977

5078
A good place to start writing tests is from the perspective of a user of your
5179
module or library, as described in the [Test
5280
Tutorial]({% link pages/tutorials/test.md %}), and [Testing with pytest
53-
guide]({% link pages/guides/pytest.md %}). These test cases live outside your
54-
code, and include many styles or types of test that you may have heard of
55-
(behavioral, fuzz, end-to-end, feature, etc., etc.).
81+
guide]({% link pages/guides/pytest.md %}).
82+
83+
- These test cases live outside of your source code.
84+
- Test the code as you expect your users to interact with it.
85+
- Keep these tests simple, and easily readable, so that they provide good
86+
documentation when a user asks "how should I use this feature"
87+
- Focus on the supported use-case, and avoid extensive edge-case testing
88+
(edge-case and exhaustive input testing will be handled in a separate test
89+
suite)
5690

5791
{: .highlight-title }
5892

@@ -63,80 +97,36 @@ code, and include many styles or types of test that you may have heard of
6397
> your test suite(s) grow, taxonomy of test cases, the and the use/need for
6498
> different kinds of tests will become more clear.
6599
66-
### Taxonomy of outside-in tests
67-
68-
A non-exhaustive discussion of some common types of tests.
69-
70-
^_^ Dont Panic ^_^
71-
72-
Depending on your project, you may not need many, or most of these kinds of
73-
tests.
74-
75-
- A library project probably does not need to test integration with
76-
microservices.
77-
- A library with no 3rd party dependencies, does not need test them.
78-
- Fuzz testing is for critical code, that many users rely on.
79-
80-
#### Behavioral, Feature, or Functional Tests:
81-
82-
High-level tests, which ensure a specific feature works. Used for testing things
83-
like:
84-
85-
- Loading a file works
86-
- Setting a debug flag results in debug messages being printed
87-
- A configuration option affects the behavior of the code as expected
88-
89-
#### Fuzz Tests
90-
91-
Fuzz tests attempt to test the full range of possible inputs to a function. They
92-
are good for finding edge-cases, where what should be valid input causes a
93-
failure. [Hypothesis](https://hypothesis.readthedocs.io/en/latest/) is an
94-
excellent tool for this, and a lot of fun to use.
95-
96-
- SLOW TESTS: fuzz tests can take a very long time to run, and should usually be
97-
placed in a test suite which is run separately from faster tests.
98-
[see: fail fast](https://en.wikipedia.org/wiki/Fail-fast_system)
99-
- Reserve fuzz testing for the few critical functions, where it really matters.
100-
101-
#### Integration Tests
102-
103-
The word "Integration" is a bit overloaded, and can refer to many levels of
104-
interaction between your code, its dependencies, and external systems.
105-
106-
- Code level
107-
- Test the integration between your software and external / 3rd party
108-
dependencies.
109-
- Low-level testing of your code-base, where you run the code imported from
110-
dependencies without mocking it.
100+
## Project Level Integration Testing
111101

112-
- Environment level
113-
- Testing that your software works in the environments you plan to run it in.
114-
- Running inside of a docker container
115-
- Using GPU's or other specialized hardware
116-
- Deploying it to cloud servers
117-
118-
- System level
119-
- Testing that it interacts with other software in a larger system.
120-
- Interactions with other services, on local or cloud-based platforms
121-
- Micro-service, Database, or API connections and interactions
102+
The term "Integration Test" is
103+
unfortunately overloaded, and used to describe testing that various components
104+
integrate with each other, at many levels of the system. These tests will loosely
105+
follow the "Detroit School" of test design.
122106

123-
#### End to End Tests
107+
- Write tests which view the code from an outside-in perspective, like
108+
[Public Interface]() tests
109+
- Avoid Mocks/Fakes/Patches as much as possible
110+
- Test that the components of your code all work together (inner-package
111+
integration)
112+
- Test that your code works with its dependencies (dependency integration)
124113

125-
The slowest, and most brittle, of all tests. Here, you set up an entire
126-
production-like system, and run tests against it.
114+
These tests can be a good place for more extensive edge-case, and fuzzy input
115+
testing.
127116

128-
- Create a Dev / Testing / Staging environment, and run tests against it to make
129-
sure everything works together
130-
- Fake user input, using tools like
131-
[Selenium](https://www.selenium.dev/documentation/)
132-
- Processing data from a pre-loaded test database
133-
- Manual QA testing
117+
The intended audience for these tests developers working on the project, or
118+
debugging issues they encounter as opposed to Public Interface tests, which
119+
should be helpful for users of the package.
134120

135121
## Unit Tests
136122

137-
Internal tests, which test that individual units/components of the code behave
138-
as expected in isolation. Some examples of units are: A single function, an
139-
attribute of an object, a method or property of a class.
123+
Unit tests loosely follow the "London School" of testing, where the smallest unit
124+
of code is tested in isolation.
125+
126+
These tests are written from an internal perspective, so they are a good place
127+
to test aspects of the codebase which are "private" not directly exposed to
128+
users, but which still need to be tested. Some examples of units are: A single
129+
function, an attribute of an object, a method or property of a class.
140130

141131
### Advantages of unit testing:
142132

@@ -362,12 +352,128 @@ def test_pytest(mocker):
362352
dangerous_sideffects()
363353
```
364354

355+
### A Brief Taxonomy Test Suites
356+
357+
A non-exhaustive discussion of some common types of tests.
358+
359+
^_^ Dont Panic ^_^
360+
361+
Depending on your project, you may not need many, or most of these kinds of
362+
tests.
363+
364+
- A library project probably does not need to test integration with
365+
microservices.
366+
- A library with no 3rd party dependencies, does not need test them.
367+
- Fuzz testing is for critical code, that many users rely on.
368+
369+
#### Behavioral, Feature, or Functional Tests:
370+
371+
High-level tests, which ensure a specific feature works. Used for testing things
372+
like:
373+
374+
- Loading a file works
375+
- Setting a debug flag results in debug messages being printed
376+
- A configuration option affects the behavior of the code as expected
377+
378+
#### Fuzz Tests
379+
380+
Fuzz tests attempt to test the full range of possible inputs to a function. They
381+
are good for finding edge-cases, where what should be valid input causes a
382+
failure. [Hypothesis](https://hypothesis.readthedocs.io/en/latest/) is an
383+
excellent tool for this, and a lot of fun to use.
384+
385+
- SLOW TESTS: fuzz tests can take a very long time to run, and should usually be
386+
placed in a test suite which is run separately from faster tests.
387+
[see: fail fast](https://en.wikipedia.org/wiki/Fail-fast_system)
388+
- Reserve fuzz testing for the few critical functions, where it really matters.
389+
390+
#### Integration Tests
391+
392+
The word "Integration" is a bit overloaded, and can refer to many levels of
393+
interaction between your code, its dependencies, and external systems.
394+
395+
- Code level
396+
- Test the integration between your software and external / 3rd party
397+
dependencies.
398+
- Low-level testing of your code-base, where you run the code imported from
399+
dependencies without mocking it.
400+
401+
- Environment level
402+
- Testing that your software works in the environments you plan to run it in.
403+
- Running inside of a docker container
404+
- Using GPU's or other specialized hardware
405+
- Deploying it to cloud servers
406+
407+
- System level
408+
- Testing that it interacts with other software in a larger system.
409+
- Interactions with other services, on local or cloud-based platforms
410+
- Micro-service, Database, or API connections and interactions
411+
412+
#### End to End Tests
413+
414+
The slowest, and most brittle, of all tests. Here, you set up an entire
415+
production-like system, and run tests against it.
416+
417+
- Create a Dev / Testing / Staging environment, and run tests against it to make
418+
sure everything works together
419+
- Fake user input, using tools like
420+
[Selenium](https://www.selenium.dev/documentation/)
421+
- Processing data from a pre-loaded test database
422+
- Manual QA testing
423+
424+
### Other Kinds of Internal Tests
425+
426+
The thing that distinguishes Internal tests is their perspective on the code,
427+
where External tests focus on the way users will interact with the package (or
428+
the public API) and "avoid testing implementation details". Internal tests exist
429+
to test that those critical implementation details work correctly.
430+
431+
#### Testing Edgecases
432+
433+
While writing unit tests, you may be tempted to test edgecases. You may have a
434+
critical private function or algorithm, which is not part of the public API, so
435+
not a good candidate for External tesing, and you are concerned about many
436+
edgecases that you want to defend against using tests.
437+
438+
It is perfectly valid to write extensive edgecase testing for private code, but
439+
these tests should be kept separate from the unit test suite. Extensive edgecase
440+
testing makes tests long, and difficult to read (tests are documentation). They
441+
can slow down execution, we want unit tests to run first, fast, and often.
442+
443+
- Place them in separate files from unit tests, to improve readability
444+
- [mark them](https://docs.pytest.org/en/stable/how-to/mark.html) so that they
445+
can be run as a separate test suite, after your unit test pass
446+
447+
#### Fuzz Tests and other slow tests
448+
449+
Testing random input, using tools like Hypothesis, is similar to testing edge
450+
cases, but running these tests can take a very long time, and they can often be
451+
much more complex and difficult to read for new developers.
452+
453+
- Place them in their own test files
454+
- [mark them](https://docs.pytest.org/en/stable/how-to/mark.html) so that they
455+
can be run as a separate test suite, once all of the faster test suites have
456+
succeeded.
457+
365458
## Diagnostic Tests
366459

367460
Diagnostic tests are used to verify the installation of a package. They should
368461
be runable on production systems, like when we need to ssh into a live server to
369462
troubleshoot problems.
370463

464+
A diagnostic test suite may contain any combination of tests you deem pertinent.
465+
You could include all the unit tests, or a specific subset of them. You may want
466+
to include some integration tests, and feature tests. Consider them Smoke Tests,
467+
a select sub-set of tests, meant to catch critical errors quickly, not perform a
468+
full system check of the package.
469+
470+
- Respect the user's environment!
471+
- Diagnostic tests should not require additional dependencies beyond what the
472+
package requires.
473+
- Do not create files, alter a database, or change the state of the system
474+
- Run quickly, select tests that can be run in a few moments
475+
- provide meaningful feedback
476+
371477
### Advantages of Diagnostic Tests
372478

373479
- Diagnostic tests allow us to verify an installation of a package.

0 commit comments

Comments
 (0)