@@ -10,14 +10,37 @@ 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 suites for python projects.
14+
15+ We will describe the most important types of test suites, the purposes they serve
16+ and differences between them.
17+ They will be presented in OutSide -> In order, which is our recommend approach.
18+ Starting with [ Public Interface tests] ( #user-interface-or-public-api-testing ) ,
19+ which test your code from the perspective of your users,
20+ focusing on the behavior of the public interface and the Features that your project provides.
21+ Then we will cover [ Package Level Integration tests] ( #package-level-integration-tests ) , which test that the various parts of your package
22+ work together, and work with the other packages it depends on.
23+ Finally we will cover the venrable [ Unit Test] ( #unit-tests ) , which test the correctness of your code from a perspective
24+ internal to your codebase, tests individual units in isolation, and are optimized to run quickly and often.
25+
26+ These 3 test suites will cover the bulk of your testing needs and help get your project to a reliable
27+ and maintainable state. We will also discuss some more specialized and advanced types of test cases
28+ in our [ Taxonomy of Test Cases] ( #taxonomy-of-test-cases ) section.
29+
30+
31+ ## Advantages of Testing
32+ - Trustworthy code: Well tested code, is code that you can trust to behave as expected.
33+ - Living Documentation: A good test is a form of documentation,
34+ which tells us how the code is expected to behave, communicates the intent of the author,
35+ and is validated every time the test is run.
36+ - Preventing Failure: Tests provide safety against many ways code can fail, from errors in implementation,
37+ to unexpected changes in upstream dependencies.
38+ - Confidence when making changes: A thorough suite of tests allows developers to add features, fix bugs,
39+ and refactor code, with a degree of confidence that their changes do not break existing features,
40+ or cause unexpected side-effects.
41+
42+
43+ ## Any test case is better than none
2144
2245When in doubt, write the test that makes sense at the time.
2346
@@ -31,7 +54,7 @@ bogged down in the taxonomy of test types. As you write and use your test suite,
3154the reason for classifying and sorting some types of tests into different test
3255suites will become apparent.
3356
34- ### As long as that test is correct...
57+ ## As long as that test is correct...
3558
3659It can be surprisingly easy to write a test that passes when it should fail,
3760especially when using complicated mocks and fixtures. The best way to avoid this
@@ -45,14 +68,20 @@ the test-case to make sure it fails when the code is broken.
4568 is better to write many test cases for a single function or class, than one
4669 giant case.
4770
48- ## External or outside-in testing
71+ ## User Interface and Public API testing
4972
5073A good place to start writing tests is from the perspective of a user of your
5174module or library, as described in the [ Test
5275Tutorial] ({% 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.).
76+ guide] ({% link pages/guides/pytest.md %}).
77+
78+ - These test cases live outside of your source code.
79+ - Test the code as you expect your users to interact with it.
80+ - Keep these tests simple, and easily readable, so that they provide good documentation when
81+ a user asks "how should I use this feature"
82+ - Focus on the supported use-case, and avoid extensive edge-case testing
83+ (edge-case and exhaustive input testing will be handled in a separate test suite)
84+
5685
5786{: .highlight-title }
5887
@@ -63,74 +92,21 @@ code, and include many styles or types of test that you may have heard of
6392> your test suite(s) grow, taxonomy of test cases, the and the use/need for
6493> different kinds of tests will become more clear.
6594
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:
95+ ## Project Level Integration Testing
8496
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
97+ The term "Integration Test" is unfortunately overloaded, and used to describe testing that various components
98+ integrate with each other, at many levels of the system. These tests will loosly follow the "Detroit School" of test design.
8899
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.
100+ - Write tests which view the code from an outside-in perspective, like [ Public Interface] ( ) tests
101+ - Avoid Mocks/Fakes/Patches as much as possible
102+ - Test that the components of your code all work together (inner-package integration)
103+ - Test that your code works with its dependencies (dependency integration)
95104
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.
105+ These tests can be a good place for more extensive edge-case, and fuzzy input testing.
100106
101- #### Integration Tests
107+ The intended audience for these tests developers working on the project, or debugging issues they encounter
108+ as opposed to Public Interface tests, which should be helpful for users of the package.
102109
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.
111-
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
122-
123- #### End to End Tests
124-
125- The slowest, and most brittle, of all tests. Here, you set up an entire
126- production-like system, and run tests against it.
127-
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
134110
135111## Unit Tests
136112
@@ -362,12 +338,110 @@ def test_pytest(mocker):
362338 dangerous_sideffects()
363339```
364340
341+ ### A Brief Taxonomy Test Suites
342+
343+ A non-exhaustive discussion of some common types of tests.
344+
345+ ^_ ^ Dont Panic ^_ ^
346+
347+ Depending on your project, you may not need many, or most of these kinds of
348+ tests.
349+
350+ - A library project probably does not need to test integration with
351+ microservices.
352+ - A library with no 3rd party dependencies, does not need test them.
353+ - Fuzz testing is for critical code, that many users rely on.
354+
355+ #### Behavioral, Feature, or Functional Tests:
356+
357+ High-level tests, which ensure a specific feature works. Used for testing things
358+ like:
359+
360+ - Loading a file works
361+ - Setting a debug flag results in debug messages being printed
362+ - A configuration option affects the behavior of the code as expected
363+
364+ #### Fuzz Tests
365+
366+ Fuzz tests attempt to test the full range of possible inputs to a function. They
367+ are good for finding edge-cases, where what should be valid input causes a
368+ failure. [ Hypothesis] ( https://hypothesis.readthedocs.io/en/latest/ ) is an
369+ excellent tool for this, and a lot of fun to use.
370+
371+ - SLOW TESTS: fuzz tests can take a very long time to run, and should usually be
372+ placed in a test suite which is run separately from faster tests.
373+ [ see: fail fast] ( https://en.wikipedia.org/wiki/Fail-fast_system )
374+ - Reserve fuzz testing for the few critical functions, where it really matters.
375+
376+ #### Integration Tests
377+
378+ The word "Integration" is a bit overloaded, and can refer to many levels of
379+ interaction between your code, its dependencies, and external systems.
380+
381+ - Code level
382+ - Test the integration between your software and external / 3rd party
383+ dependencies.
384+ - Low-level testing of your code-base, where you run the code imported from
385+ dependencies without mocking it.
386+
387+ - Environment level
388+ - Testing that your software works in the environments you plan to run it in.
389+ - Running inside of a docker container
390+ - Using GPU's or other specialized hardware
391+ - Deploying it to cloud servers
392+
393+ - System level
394+ - Testing that it interacts with other software in a larger system.
395+ - Interactions with other services, on local or cloud-based platforms
396+ - Micro-service, Database, or API connections and interactions
397+
398+ #### End to End Tests
399+
400+ The slowest, and most brittle, of all tests. Here, you set up an entire
401+ production-like system, and run tests against it.
402+
403+ - Create a Dev / Testing / Staging environment, and run tests against it to make
404+ sure everything works together
405+ - Fake user input, using tools like
406+ [ Selenium] ( https://www.selenium.dev/documentation/ )
407+ - Processing data from a pre-loaded test database
408+ - Manual QA testing
409+
410+
411+ ### Other Kinds of Internal Tests
412+ The thing that distinguishes Internal tests is their perspective on the code,
413+ where External tests focus on the way users will interact with the package (or the public API)
414+ and "avoid testing implementation details".
415+ Internal tests exist to test that those critical implementation details work correctly.
416+
417+ #### Testing Edgecases
418+ While writing unit tests, you may be tempted to test edgecases. You may have a critical private function or algorithm, which is not part of the public API, so not a good candidate for External tesing, and you are concerned about many edgecases that you want to defend against using tests.
419+
420+ It is perfectly valid to write extensive edgecase testing for private code, but these tests should be kept separate from the unit test suite. Extensive edgecase testing makes tests long, and difficult to read (tests are documentation). They can slow down execution, we want unit tests to run first, fast, and often.
421+ * Place them in separate files from unit tests, to improve readability
422+ * [ mark them] ( https://docs.pytest.org/en/stable/how-to/mark.html ) so that they can be run as a separate test suite, after your unit test pass
423+
424+ #### Fuzz Tests and other slow tests
425+ Testing random input, using tools like Hypothesis, is similar to testing edge cases, but running these tests can take a very long time, and they can often be much more complex and difficult to read for new developers.
426+ * Place them in their own test files
427+ * [ mark them] ( https://docs.pytest.org/en/stable/how-to/mark.html ) so that they can be run as a separate test suite, once all of the faster test suites have succeeded.
428+
429+
365430## Diagnostic Tests
366431
367432Diagnostic tests are used to verify the installation of a package. They should
368433be runable on production systems, like when we need to ssh into a live server to
369434troubleshoot problems.
370435
436+ A diagnostic test suite may contain any combination of tests you deem pertinent. You could include all the unit tests, or a specific subset of them. You may want to include some integration tests, and feature tests.
437+ Consider them Smoke Tests, a select sub-set of tests, meant to catch critical errors quickly, not perform a full system check of the package.
438+
439+ * Respect the user's environment!
440+ * Diagnostic tests should not require additional dependencies beyond what the package requires.
441+ * Do not create files, alter a database, or change the state of the system
442+ * Run quickly, select tests that can be run in a few moments
443+ * provide meaningful feedback
444+
371445### Advantages of Diagnostic Tests
372446
373447- Diagnostic tests allow us to verify an installation of a package.
0 commit comments