diff --git a/Python/Module2_EssentialsOfPython/Functions.md b/Python/Module2_EssentialsOfPython/Functions.md
index 2ad15dda..2d670f3d 100644
--- a/Python/Module2_EssentialsOfPython/Functions.md
+++ b/Python/Module2_EssentialsOfPython/Functions.md
@@ -137,7 +137,7 @@ Write a function named `count_even`. It should accept one input argument, named
## The `return` Statement
-In general, any Python object can follow a function's `return` statement. Furthermore, an **empty** `return` statement can be specified, or the **return** statement of a function can be omitted altogether. In both of these cases, *the function will return the `None` object*.
+In general, any Python object can follow a function's `return` statement. Furthermore, an **empty** `return` statement can be specified, or the **return** statement of a function can be omitted altogether. In both of these cases, *the function will return the* `None` *object*.
```python
# this function returns `None`
diff --git a/Python/Module3_IntroducingNumpy/Broadcasting.md b/Python/Module3_IntroducingNumpy/Broadcasting.md
index 5c8a96fc..cd83934a 100644
--- a/Python/Module3_IntroducingNumpy/Broadcasting.md
+++ b/Python/Module3_IntroducingNumpy/Broadcasting.md
@@ -5,7 +5,7 @@ jupyter:
extension: .md
format_name: markdown
format_version: '1.2'
- jupytext_version: 1.3.0rc1
+ jupytext_version: 1.3.0
kernelspec:
display_name: Python 3
language: python
@@ -486,16 +486,16 @@ Performing this computation using for-loops proceeds as follows:
def pairwise_dists_looped(x, y):
""" Computing pairwise distances using for-loops
- Parameters
- ----------
- x : numpy.ndarray, shape=(M, D)
- y : numpy.ndarray, shape=(N, D)
+ Parameters
+ ----------
+ x : numpy.ndarray, shape=(M, D)
+ y : numpy.ndarray, shape=(N, D)
- Returns
- -------
- numpy.ndarray, shape=(M, N)
- The Euclidean distance between each pair of
- rows between `x` and `y`."""
+ Returns
+ -------
+ numpy.ndarray, shape=(M, N)
+ The Euclidean distance between each pair of
+ rows between `x` and `y`."""
# `dists[i, j]` will store the Euclidean
# distance between `x[i]` and `y[j]`
dists = np.empty((5, 6))
@@ -545,18 +545,18 @@ Voilà! We have produced the distances in a vectorized way. Let's write this out
def pairwise_dists_crude(x, y):
""" Computing pairwise distances using vectorization.
- This method uses memory-inefficient broadcasting.
-
- Parameters
- ----------
- x : numpy.ndarray, shape=(M, D)
- y : numpy.ndarray, shape=(N, D)
-
- Returns
- -------
- numpy.ndarray, shape=(M, N)
- The Euclidean distance between each pair of
- rows between `x` and `y`."""
+ This method uses memory-inefficient broadcasting.
+
+ Parameters
+ ----------
+ x : numpy.ndarray, shape=(M, D)
+ y : numpy.ndarray, shape=(N, D)
+
+ Returns
+ -------
+ numpy.ndarray, shape=(M, N)
+ The Euclidean distance between each pair of
+ rows between `x` and `y`."""
# The use of `np.newaxis` here is equivalent to our
# use of the `reshape` function
return np.sqrt(np.sum((x[:, np.newaxis] - y[np.newaxis])**2, axis=2))
@@ -628,23 +628,23 @@ In total, we have successfully used vectorization to compute the all pairs of di
```python
def pairwise_dists(x, y):
- """ Computing pairwise distances using memory-efficient
- vectorization.
-
- Parameters
- ----------
- x : numpy.ndarray, shape=(M, D)
- y : numpy.ndarray, shape=(N, D)
-
- Returns
- -------
- numpy.ndarray, shape=(M, N)
- The Euclidean distance between each pair of
- rows between `x` and `y`."""
+ """ Computing pairwise distances using memory-efficient
+ vectorization.
+
+ Parameters
+ ----------
+ x : numpy.ndarray, shape=(M, D)
+ y : numpy.ndarray, shape=(N, D)
+
+ Returns
+ -------
+ numpy.ndarray, shape=(M, N)
+ The Euclidean distance between each pair of
+ rows between `x` and `y`."""
dists = -2 * np.matmul(x, y.T)
- dists += np.sum(x**2, axis=1)[:, np.newaxis]
+ dists += np.sum(x**2, axis=1)[:, np.newaxis]
dists += np.sum(y**2, axis=1)
- return np.sqrt(dists)
+ return np.sqrt(dists)
```
diff --git a/Python/Module5_OddsAndEnds/Modules_and_Packages.md b/Python/Module5_OddsAndEnds/Modules_and_Packages.md
index 3324669e..471e1a4e 100644
--- a/Python/Module5_OddsAndEnds/Modules_and_Packages.md
+++ b/Python/Module5_OddsAndEnds/Modules_and_Packages.md
@@ -5,7 +5,7 @@ jupyter:
extension: .md
format_name: markdown
format_version: '1.2'
- jupytext_version: 1.3.0rc1
+ jupytext_version: 1.3.0
kernelspec:
display_name: Python 3
language: python
@@ -406,15 +406,15 @@ It must be mentioned that we are sweeping some details under the rug here. Insta
### Installing Your Own Python Package
-Suppose that we are happy with the work we have done on our `face_detector` project. We will want to install this package - placing it in our site-packages directory so that we can import it irrespective of our Python interpreter's working directory. Here we will construct a basic setup script that will allow us to accomplish this.
+Suppose that we are happy with the work we have done on our `face_detector` project. We will want to install this package - placing it in our site-packages directory so that we can import it irrespective of our Python interpreter's working directory. Here we will construct a basic setup script that will allow us to accomplish this. For completeness, we will also indicate how one would include a test suite alongside the source code in this directory structure.
We note outright that the purpose of this section is strictly to provide you with the minimum set of instructions needed to install a package. We will not be diving into what is going on under the hood at all. Please refer to [An Introduction to Distutils](https://docs.python.org/3/distutils/introduction.html#an-introduction-to-distutils) and [Packaging Your Project](https://packaging.python.org/tutorials/packaging-projects/#packaging-your-project) for a deeper treatment of this topic.
Carrying on, we will want to create a setup-script, `setup.py`, *in the same directory as our package*. That is, our directory structure should look like:
```
-- setup.py
-- face_detection/
+- setup.py # script responsible for installing `face_detection` package
+- face_detection/ # source code of `face_detection` package
|-- __init__.py
|-- utils.py
|-- database.py
@@ -423,23 +423,34 @@ Carrying on, we will want to create a setup-script, `setup.py`, *in the same dir
|-- __init__.py
|-- calibration.py
|-- config.py
+- tests/ # test-suite for `face_detection` package (to be run using pytest)
+ |-- conftest.py # optional configuration file for pytest
+ |-- test_utils.py
+ |-- test_database.py
+ |-- test_model.py
+ |-- camera/
+ |-- test_calibration.py
+ |-- test_config.py
```
+A `tests/` directory can be included at the same directory level as `setup.py` and `face_detection/`.
+This is the recommended structure for using [pytest](https://docs.pytest.org/en/latest/) as our test-runner.
+
The bare bones build script for preparing your package for installation, `setup.py`, is as follows:
```python
# contents of setup.py
-import setuptools
+from setuptools import find_packages, setup
-setuptools.setup(
+setup(
name="face_detection",
- version="1.0",
- packages=setuptools.find_packages(),
+ version="1.0.0",
+ packages=find_packages(exclude=["tests", "tests.*"]),
+ python_requires=">=3.5",
)
```
-
-
+The `exclude` expression is used to ensure that specific directories or files are not included in the installation of `face_detection`. We use `exclude=["tests", "tests.*"]` to avoid installing the test-suite alongside `face_detection`.
If you read through the additional materials linked above, you will see that there are many more fields of optional information that can be provided in this setup script, such as the author name, any installation requirements that the package has, and more.
@@ -447,7 +458,7 @@ If you read through the additional materials linked above, you will see that the
Armed with this script, we are ready to install our package locally on our machine! In your terminal, navigate to the directory containing this setup script and your package that it being installed. Run
```shell
-python setup.py install
+pip install .
```
and voilà, your package `face_detection` will have been installed to site-packages. You are now free to import this package from any directory on your machine. In order to uninstall this package from your machine execute the following from your terminal:
@@ -456,10 +467,10 @@ and voilà, your package `face_detection` will have been installed to site-packa
pip uninstall face_detection
```
-One final but important detail. The installed version of your package will no longer "see" the source code. That is, if you go on to make any changes to your code, you will have to uninstall and reinstall your package before your will see the effects system-wide. Instead you can install your package in develop mode, such that a symbolic link to your source code is placed in your site-packages. Thus any changes that you make to your code will immediately be reflected in your system-wide installation. Thus, instead of running `python setup.py install`, execute the following to install a package in develop mode:
+One final but important detail: the installed version of your package will no longer "see" the source code. That is, if you go on to make any changes to your code, you will have to uninstall and reinstall your package before your will see the effects system-wide. Instead, you can install your package in "development mode", such that a symbolic link to your source code is placed in your site-packages. Thus, any changes that you make to your code will immediately be reflected in your system-wide installation. You can add the `--editable` flag to pip to install a package in development mode:
```shell
-python setup.py develop
+pip install --editable .
```
diff --git a/Python/Module5_OddsAndEnds/Writing_Good_Code.md b/Python/Module5_OddsAndEnds/Writing_Good_Code.md
index 1fb91532..43be9158 100644
--- a/Python/Module5_OddsAndEnds/Writing_Good_Code.md
+++ b/Python/Module5_OddsAndEnds/Writing_Good_Code.md
@@ -636,7 +636,7 @@ To be more concrete, let's revisit our `count_vowels` function:
```python
def count_vowels(x: str, include_y: bool = False) -> int:
- """Returns the number of vowels contained in `in_string`"""
+ """Returns the number of vowels contained in `x`"""
vowels = set("aeiouAEIOU")
if include_y:
vowels.update("yY")
@@ -774,14 +774,16 @@ def pairwise_dists(x: np.ndarray, y: np.ndarray) -> np.ndarray:
Parameters
----------
x : numpy.ndarray, shape=(M, D)
- An optional description of ``x``
+ An array of M, D-dimensional vectors.
+
y : numpy.ndarray, shape=(N, D)
- An optional description of ``y``
+ An array of N, D-dimensional vectors.
Returns
-------
numpy.ndarray, shape=(M, N)
- The pairwise distances
+ The pairwise distances between the M rows of ``x`` and the N
+ rows of ``y``.
Notes
-----
@@ -829,7 +831,7 @@ def compute_student_stats(grade_book: Dict[str, Iterable[float]],
Parameters
----------
- grade_book : Dict[str, List[float]]
+ grade_book : Dict[str, Iterable[float]]
The dictionary (name -> grades) of all of the students'
grades.
diff --git a/Python/Module6_Testing/Hypothesis.md b/Python/Module6_Testing/Hypothesis.md
new file mode 100644
index 00000000..119f6b31
--- /dev/null
+++ b/Python/Module6_Testing/Hypothesis.md
@@ -0,0 +1,131 @@
+---
+jupyter:
+ jupytext:
+ text_representation:
+ extension: .md
+ format_name: markdown
+ format_version: '1.2'
+ jupytext_version: 1.3.0
+ kernelspec:
+ display_name: Python [conda env:.conda-jupy] *
+ language: python
+ name: conda-env-.conda-jupy-py
+---
+
+
+.. meta::
+ :description: Topic: Writing tests for your code, Difficulty: Easy, Category: Section
+ :keywords: test, automated, pytest, parametrize, fixture, suite, decorator, clean directory
+
+
+
+# Describing Data with Hypothesis
+
+It is often the case that the process of *describing our data* is by far the heaviest burden that we must bear when writing tests. This process of assessing "what variety of values should I test?", "have I thought of all the important edge-cases?", and "how much is 'enough'?" will crop up with nearly every test that we write.
+Indeed, these are questions that you may have been asking yourself when writing `test_count_vowels_basic` and `test_merge_max_mappings` in the previous sections of this module.
+
+[Hypothesis](https://hypothesis.readthedocs.io/) is a powerful Python library that empowers us to write a _description_ (specification, to be more precise) of the data that we want to use to exercise our test.
+It will then *generate* test cases that satisfy this description and will run our test on these cases.
+
+Let's look at a simple example of Hypothesis in action.
+In the preceding section, we learned to use pytest's parameterization mechanism to test properties of code over a set of values.
+For example, we wrote the following trivial test:
+
+```python
+import pytest
+
+# A simple parameterized test that only tests a few, conservative inputs.
+# Note that this test must be run by pytest to work properly
+@pytest.mark.parametrize("size", [0, 1, 2, 3])
+def test_range_length(size):
+ assert len(range(size)) == size
+```
+
+which tests the property that `range(n)` has a length of `n` for any non-negative integer value of `n`.
+Well, it isn't *really* testing this property for all non-negative integers; clearly it is only testing the values 0-3.
+We should probably also check much larger numbers and perhaps traverse various orders of magnitude (i.e. factors of ten) in our parameterization scheme.
+No matter what set of values we land on, it seems like we will have to eventually throw our hands up and say "okay, that seems good enough."
+
+Instead of manually specifying the data to pass to `test_range_length`, let's use Hypothesis to simply describe the data:
+
+
+
+```python
+from hypothesis import given
+
+# Hypothesis provides so-called "strategies" for us
+# to describe our data
+import hypothesis.strategies as st
+
+# Using hypothesis to test any integer value in [0, 10 ** 10]
+@given(size=st.integers(min_value=0, max_value=1E10))
+def test_range_length(size):
+ assert len(range(size)) == size
+```
+
+
+
+Here we have specified that the `size` value in our test should take on any integer value within $[0, 10^{10}]$.
+We did this by using the `integers` "strategy" that is provided by Hypothesis: `st.integers(min_value=0, max_value=1E10)`.
+When we execute the resulting test (which can simply be run within a Jupyter cell or via pytest), this will trigger Hypothesis to generate test cases based on this specification;
+by default, Hypothesis will generate 100 test cases - an amount that we can configure - and will evaluate our test for each one of them.
+
+```python
+# Running this test once will trigger Hypothesis to
+# generate 100 values based on the description of our data,
+# and it will execute the test using each one of those values
+>>> test_range_length()
+```
+
+With great ease, we were able to replace our pytest-parameterized test, which only very sparsely tested the property at hand, with a much more robust, hypothesis-driven test.
+This will be a recurring trend: we will generally produce much more robust tests by _describing_ our data with Hypothesis, rather than manually specifying test values.
+
+The rest of this section will be dedicated to learning about the Hypothesis library and how we can leverage it to write powerful tests.
+
+
+
+
+
+**Hypothesis is _very_ effective...**:
+
+You may be wondering why, in the preceding example, I arbitrarily picked $10^{10}$ as the upper bound to the integer-values to feed to the test.
+I actually didn't write the test that way initially.
+Instead, I wrote the more general test:
+
+```python
+@given(size=st.integers(min_value=0))
+def test_range_length(size):
+ assert len(range(size)) == size
+```
+
+which places no formal upper bound on the integers that Hypothesis will generate.
+However, this test immediately found an issue (I hesitate to call it an outright bug):
+
+```python
+Falsifying example: test_range_length(
+ size=9223372036854775808,
+)
+
+----> 3 assert len(range(size)) == size
+
+OverflowError: Python int too large to convert to C ssize_t
+```
+
+This reveals that the implementation of the built-in `len` function is such that it can only handle non-negative integers smaller than $2^{63}$ (i.e. it will only allocate 64 bits to represent a signed integer - one bit is used to store the sign of the number).
+Hypothesis revealed this by generating the failing test case `size=9223372036854775808`, which is exactly $2^{63}$.
+I did not want this error to distract from what is otherwise merely a simple example, but it is very important to point out.
+
+Hypothesis has a knack for catching these sorts of unexpected edge cases.
+Now we know that `len(range(size)) == size` _does not_ hold for "arbitrary" non-negative integers!
+(I wonder how many of the Python core developers know about this 😄).
+
+
+
+
+
+## Links to Official Documentation
+
+- [Hypothesis](https://hypothesis.readthedocs.io/)
+
+
+## Reading Comprehension Solutions
diff --git a/Python/Module6_Testing/Intro_to_Testing.md b/Python/Module6_Testing/Intro_to_Testing.md
new file mode 100644
index 00000000..35231320
--- /dev/null
+++ b/Python/Module6_Testing/Intro_to_Testing.md
@@ -0,0 +1,565 @@
+---
+jupyter:
+ jupytext:
+ text_representation:
+ extension: .md
+ format_name: markdown
+ format_version: '1.2'
+ jupytext_version: 1.3.0
+ kernelspec:
+ display_name: Python 3
+ language: python
+ name: python3
+---
+
+
+.. meta::
+ :description: Topic: Writing tests for your code, Difficulty: Easy, Category: Section
+ :keywords: test, automated, unit, assert
+
+
+# Introduction to Testing
+
+This section will show us just how simple it is to write rudimentary tests. We need only recall some of Python's basic scoping rules and introduce ourselves to the `assert` statement to write a genuine test function. That being said, we will quickly encounter some important questions to ponder. How do we know that our tests work? And, how do we know that our tests are effective? These questions will drive us deeper into the world of testing.
+
+Before we hit the ground running, let's take a moment to consider some motivations for testing out code.
+
+
+## Why Should We Write Tests?
+
+The fact of the matter is that it is intuitive for most people to test their code to some extent.
+After writing, say, a new function, it is only natural to contrive an input to feed it, and to check that the function returns the output that we expected.
+To the extent that one would want to see evidence that their code works, we need not motivate the importance of testing.
+
+Less obvious are the massive benefits that we stand to gain from automating this testing process.
+By "automating", we mean taking the test scenarios that we were running our code through, and encapsulating them in their own functions that can be run from end-to-end.
+We will accumulate these test functions into a "test suite" that we can run quickly and repeatedly.
+
+There are plenty of practical details ahead for us to learn, so let's expedite this discussion and simply list some of the benefits that we can expect to reap from writing a robust test suite:
+
+**It saves us lots of time**:
+
+> After you have devised a test scenario for your code, it may only take us a second or so to run it; perhaps we need only run a couple of Jupyter notebook cells to verify the output of our code.
+> This, however, will quickly become unwieldy as we write more code and devise more test scenarios.
+> Soon we will be dissuaded from running our tests, except for on rare occasions.
+>
+> With a proper test suite, we can run all of our test scenarios with the push of a button, and a series of green check-marks (or red x's...) will summarize the health of our project (insofar as our tests serve as good diagnostics).
+> This, of course, also means that we will find and fix bugs much faster!
+> In the long run, our test suite will afford us the ability to aggressively exercise (and exorcise) our code at little cost.
+
+**It increases the "shelf life" of our code:**
+
+> If you've ever dusted off a project that you haven't used for years (or perhaps only months or weeks...), you might know the tribulations of getting old code to work.
+> Perhaps, in the interim, new versions of our project's dependencies, like PyTorch or Matplotlib, were released and have incompatibilities with our project's code.
+> And perhaps _we can't even remember_ all of the ways in which our project is supposed to work.
+> Our test suite provides us with a simple and incisive way to dive back into our work.
+> It will point us to any potential incompatibilities that have accumulated over time.
+> It also provides us with a large collection of detailed use-cases of our code;
+> we can read through our tests and remind ourselves of the inner-workings of our project.
+
+
+**It will inform the design and usability of our project for the better:**
+
+> Although it may not be obvious from the outset, writing testable code leads to writing better code.
+> This is, in part, because the process of writing tests gives us the opportunity to actually _use_ our code under varied circumstances.
+> The process of writing tests will help us suss out bad design decisions and redundancies in our code. Ultimately, if _we_ find it frustrating to use our code within our tests, then surely others will find the code frustrating to use in applied settings.
+
+**It makes it easier for others to contribute to a project:**
+
+> Having a healthy test suite lowers the barrier to entry for a project.
+> A contributor can rely on our project's tests to quickly check to see if their changes to our code have broken the project or changed any of its behavior in unexpected ways.
+
+This all sounds great, but where do we even start the process of writing a test suite?
+Let's begin by seeing what constitutes a basic test function.
+
+
+
+## Writing Our First Tests
+
+### Our "Source Code"
+We need some code to test. For the sake of this introduction, let's borrow a couple of functions that may look familiar from previous modules.
+These will serve as our "source code"; i.e. these are functions that we have written for our project and that need to be tested.
+
+```python
+# Defining functions that we will be testing
+
+def count_vowels(x, include_y=False):
+ """Returns the number of vowels contained in `x`.
+
+ The vowel 'y' is included optionally.
+
+ Parameters
+ ----------
+ x : str
+ The input string
+
+ include_y : bool, optional (default=False)
+ If `True` count y's as vowels
+
+ Returns
+ -------
+ vowel_count: int
+
+ Examples
+ --------
+ >>> count_vowels("happy")
+ 1
+ >>> count_vowels("happy", include_y=True)
+ 2
+ """
+ vowels = set("aeiouAEIOU")
+ if include_y:
+ vowels.update("yY")
+ return sum(1 for char in x if char in vowels)
+
+
+def merge_max_mappings(dict1, dict2):
+ """ Merges two dictionaries based on the largest value
+ in a given mapping.
+
+ Parameters
+ ----------
+ dict1 : Dict[str, float]
+ dict2 : Dict[str, float]
+
+ Returns
+ -------
+ merged : Dict[str, float]
+ The dictionary containing all of the keys common
+ between `dict1` and `dict2`, retaining the largest
+ value from common mappings.
+
+ Examples
+ --------
+ >>> x = {"a": 1, "b": 2}
+ >>> y = {"b": 100, "c": -1}
+ >>> merge_max_mappings(x, y)
+ {'a': 1, 'b': 100, 'c': -1}
+ """
+ # `dict(dict1)` makes a copy of `dict1`. We do this
+ # so that updating `merged` doesn't also update `dict1`
+ merged = dict(dict1)
+ for key, value in dict2.items():
+ if key not in merged or value > merged[key]:
+ merged[key] = value
+ return merged
+```
+
+As always, it is useful for us to follow along with this material in a Jupyter notebook.
+We ought to take time to define these functions and run inputs through them to make sure that we understand what they are doing.
+Testing code that we don't understand is a lost cause!
+
+
+### The Basic Anatomy of a Test
+
+Let's write a test for `count_vowels`. For our most basic test, we can simply call `count_vowels` under various contrived inputs and *assert* that it returns the expected output.
+The desired behavior for this test function, upon being run, is to:
+
+- Raise an error if any of our assertions *failed* to hold true.
+- Complete "silently" if all of our assertions hold true (i.e. our test function will simply [return None](https://www.pythonlikeyoumeanit.com/Module2_EssentialsOfPython/Functions.html#The-return-Statement))
+
+Here, we will be making use of Python's `assert` statements, whose behavior will be easy to deduce from the context of this test alone.
+We will be formally introduced to the `assert` statement soon.
+
+```python
+# Writing a rudimentary test function for `count_vowels`
+
+def test_count_vowels_basic():
+ assert count_vowels("aA bB yY", include_y=False) == 2
+ assert count_vowels("aA bB yY", include_y=True) == 4
+```
+
+To run this test, we simply call the function:
+
+```python
+# running our test function
+>>> test_count_vowels_basic() # passes: returns None | fails: raises error
+```
+
+As described above, the fact our function runs and simply returns `None` (i.e. we see no output when we run this test in a console or notebook cell) means that our code has passed this test. We've written and run our very first test! It certainly isn't the most robust test, but it is a good start.
+
+Let's look more carefully at the structure of `test_count_vowels_basic`.
+Note that this function doesn't take in any inputs;
+thanks to [Python's scoping rules](https://www.pythonlikeyoumeanit.com/Module2_EssentialsOfPython/Scope.html), we can reference our `count_vowels` function within our test as long as it is defined in the same "namespace" as `test_count_vowels_basic`.
+That is, we can either define `count_vowels` in the same .py file (or Jupyter notebook, if you are following along with this material in a notebook) as `test_count_vowels_basic`, or we can [import](https://www.pythonlikeyoumeanit.com/Module5_OddsAndEnds/Modules_and_Packages.html#Import-Statements) `count_vowels`, from wherever it is defined, into the file containing our test.
+The latter scenario is by far the most common one in practice.
+More on this later.
+
+
+
+
+
+**Takeaway**:
+
+A "test function" is designed to provide an encapsulated "environment" (namespace to be more precise) in which we can exercise parts of our source code and assert that the code behaves as expected. The basic anatomy of a test function is such that it:
+
+- contains one or more `assert` statements, each of which will raise an error if our source code misbehaves
+- simply returns `None` if all of the aforementioned assertions held true
+- can be run end-to-end simply by calling the test function without passing it any parameters; we rely on Python's scoping rules to call our source code within the body of the test function without explicitly passing anything to said test function
+
+
+
+
+
+
+**Reading Comprehension: Adding Assertions to a Test**
+
+Add an additional assertion to the body of `test_count_vowels_basic`, which tests that `count_vowels` handles empty-string (`""`) input appropriately.
+Make sure to run your updated test to see if it passes.
+
+
+
+
+
+
+**Reading Comprehension: The Basic Anatomy of a Test**
+
+Write a rudimentary test function for `merge_max_mappings`. This should adhere to the basic structure of a test function that we just laid out. See if you can think of some "edge cases" to test, which we may have overlooked when writing `merge_max_mappings`.
+
+
+
+
+## The `assert` Statement
+With our first test functions under our belt, it is time for us to clearly understand how `assert` statements work and how they should be used.
+
+Similar to `return`, `def`, or `if`, the term `assert` is a reserved term in the Python language.
+It has the following specialized behavior:
+
+```python
+# demonstrating the rudimentary behavior of the `assert` statement
+
+# asserting an expression whose boolean-value is `True` will complete "silently"
+>>> assert 1 < 2
+
+# asserting an expression whose boolean-value is `False` raises an error
+>>> assert 2 < 1
+---------------------------------------------------------------------------
+AssertionError Traceback (most recent call last)
+ in
+----> 1 assert 2 < 1
+
+AssertionError:
+
+# we can include an error message with our assertion
+>>> assert 0 in [1, 2, 3], "0 is not in the list"
+---------------------------------------------------------------------------
+AssertionError Traceback (most recent call last)
+ in
+----> 1 assert 0 in [1, 2, 3], "0 is not in the list"
+
+AssertionError: 0 is not in the list
+```
+
+The general form of an assertion statement is:
+
+```python
+assert [, ]
+```
+
+When an assertion statement is executed, the built-in `bool` function is called on the object that is returned by ``; if `bool()` returns `False`, then an `AssertionError` is raised.
+If you included a string in the assertion statement - separated from `` by a comma - then this string will be printed as the error message.
+
+See that the assertion statement:
+```python
+assert expression, error_message
+```
+
+is effectively shorthand for the following code (barring some additional details):
+
+```python
+# long-form equivalent of: `assert expression, error_message`
+if bool(expression) is False:
+ raise AssertionError(error_message)
+```
+
+
+
+
+
+**Reading Comprehension: Assertions**
+
+Given the following objects:
+
+```python
+a_list = []
+a_number = 22
+a_string = "abcdef"
+```
+
+Write two assertion statements with the respective behaviors:
+
+- asserts that `a_list` is _not_ empty
+- asserts that the number of vowels in `a_string` is less than `a_number`; include an error message that prints the actual number of vowels
+
+
+
+
+
+#### What is the Purpose of an Assertion?
+In our code, an assertion should be used as _a statement that is true unless there is a bug in our code_.
+It is plain to see that the assertions in `test_count_vowels_basic` fit this description.
+However, it can also be useful to include assertions within our source code itself.
+For instance, we know that `count_vowels` should always return a non-negative integer for the vowel-count, and that it is illogical for this count to exceed the number of characters in the input string.
+We can explicitly assert that this is the case:
+
+```python
+# an example of including an assertion within our source code
+
+def count_vowels(x: str, include_y: bool = False) -> int:
+ vowels = set("aeiouAEIOU")
+ if include_y:
+ vowels.update("yY")
+ count = sum(1 for char in x if char in vowels)
+
+ # This assertion should always be true: it is asserting that
+ # the internal logic of our function is correct
+ assert isinstance(count, int) and 0 <= count <= len(x)
+ return count
+```
+
+Note that this assertion *is not meant to check if the user passed bad inputs for* `x` *and* `include_y`.
+Rather, it is meant to assert that our own internal logic holds true.
+
+Admittedly, the `count_vowels` function is simple enough that the inclusion of this assertion is rather pedantic.
+That being said, as we write increasingly sophisticated code, we will find that this sort of assertion will help us catch bad internal logic and oversights within our code base.
+We will also see that keen use of assertions can make it much easier for us to write good tests.
+
+
+
+**Disabling Assertions**:
+
+Python code can be run in an "optimized" mode such that *all assertions are skipped by the Python interpreter during execution*.
+This can be achieved by specifying the command line option `-O` (the letter "O", not zero), e.g.:
+
+```shell
+python -O my_code.py
+```
+
+or by setting the `PYTHONOPTIMIZE` [environment variable](https://docs.python.org/3/using/cmdline.html#envvar-PYTHONOPTIMIZE).
+
+The idea here is that we may want assertions within our source code to perform expensive checks to guarantee internal consistency within our code, and that we want the ability to forgo these checks when we are no longer debugging our code.
+Because they can be skipped in this way, *assertions should never be used for practical error handling*.
+
+
+
+
+
+## Testing Our Tests
+
+It is surprisingly easy to unwittingly write a broken test: a test that always passes, or a test that simply doesn't exercise our code in the way that we had intended.
+Broken tests are insidious; they are alarms that fail to sound when they are supposed to.
+They create misdirection in the bug-finding process and can mask problems with our code.
+**Thus, a critical step in the test-writing process is to intentionally mutate the function of interest - to corrupt its behavior so that we can verify that our test works.**
+Once we confirm that our test does indeed raise an error as expected, we restore the function to its original form and re-run the test to see that it passes.
+
+A practical note: we ought to mutate our function in a way that is trivial to undo. We can make use of code-comments towards this end.
+All [IDEs](https://www.pythonlikeyoumeanit.com/Module1_GettingStartedWithPython/Getting_Started_With_IDEs_and_Notebooks.html) have the ability to "block-comment" selected code.
+In order to block-comment code in a Jupyter notebook code cell, highlight the lines of code and press `CTRL + /`.
+The same key-combination will also un-comment a highlighted block of commented code.
+
+
+
+
+
+**Reading Comprehension: Testing Your Test via Manual Mutation**
+
+Temporarily change the body of `count_vowels` such that the second assertion in `test_count_vowels_basic` raises an error.
+Run the test to confirm that the second assertion raises,
+and then restore `count_vowels` to its original form.
+Finally, rerun the test to see that `count_vowels` once again passes all of the assertions.
+
+Repeat this process given the test that you wrote for `merge_max_mappings`.
+Try breaking the function such that it always merges in values from `dict2`, even if those values are smaller.
+
+
+
+
+
+
+
+**Mutation Testing**:
+
+There is an entire subfield of automated testing known as ["mutation testing"](https://en.wikipedia.org/wiki/Mutation_testing), where tools like [Cosmic Ray](https://cosmic-ray.readthedocs.io/en/latest/index.html) are used to make temporary, incisive mutations to your source code - like change a `+` to a `-` or change a `1` to a `-1` - and then run your test suite.
+The idea here is that such mutations *ought to cause one or more of your tests to fail*.
+A mutation that does not trigger at least one test failure is likely an indicator that your tests could stand to be more robust.
+
+Automated mutation testing tools might be a bit too "heavy duty" at this point in our testing journey, but they are great to keep in mind.
+
+
+
+
+## Our Work, Cut Out
+
+We see now that the concept of a "test function" isn't all that fancy.
+Compared to other code that we have written, writing a function that simply runs a handful of assertions is far from a heavy lift for us.
+Of course, we must be diligent and take care to test our tests, but we can certainly manage this as well.
+With this in hand, we should take stock of the work and challenges that lie in our path ahead.
+
+It is necessary that we evolve beyond manual testing.
+There are multiple facets to this observation.
+First, we must learn how to organize our test functions into a test suite that can be run in one fell swoop.
+Next, it will become increasingly apparent that a test function often contains large amounts of redundant code shared across its litany of assertions.
+We will want to "parametrize" our tests to distill them down to their most concise and functional forms.
+Finally, and most importantly, it may already be evident that the process of contriving known inputs and outputs to use in our tests is a highly manual and tedious process; furthermore, it is a process that will become increasingly cumbersome as our source code becomes more sophisticated.
+To combat this, we will seek out alternative, powerful testing methodologies, including property-based testing.
+
+
+## Links to Official Documentation
+
+- [The assert statement](https://docs.python.org/3/reference/simple_stmts.html?highlight=assert#the-assert-statement)
+- [PYTHONOPTIMIZE environment variable](https://docs.python.org/3/using/cmdline.html#envvar-PYTHONOPTIMIZE)
+
+
+## Reading Comprehension Solutions
+
+
+**Adding Assertions to a Test: Solution**
+
+Add an additional assertion to the body of `test_count_vowels_basic`, which tests whether `count_vowels` handles the empty-string (`""`) case appropriately.
+Make sure to run your updated test to see if it passes.
+
+```python
+def test_count_vowels_basic():
+ # test basic strings with uppercase and lowercase letters
+ assert count_vowels("aA bB yY", include_y=False) == 2
+ assert count_vowels("aA bB yY", include_y=True) == 4
+
+ # test empty strings
+ assert count_vowels("", include_y=False) == 0
+ assert count_vowels("", include_y=True) == 0
+```
+
+```python
+# running the test in a notebook-cell: the function should simply return
+# `None` if all assertions hold true
+>>> test_count_vowels_basic()
+```
+
+
+
+**The Basic Anatomy of a Test: Solution**
+
+Write a rudimentary test function for `merge_max_mappings`.
+
+> Let's test the use case that is explicitly documented in the Examples section of the function's docstring.
+> We can also test cases where one or both of the inputs are empty dictionaries.
+> These can often be problematic edge cases that we didn't consider when writing our code.
+
+```python
+def test_merge_max_mappings():
+ # test documented behavior
+ dict1 = {"a": 1, "b": 2}
+ dict2 = {"b": 20, "c": -1}
+ expected = {'a': 1, 'b': 20, 'c': -1}
+ assert merge_max_mappings(dict1, dict2) == expected
+
+ # test empty dict1
+ dict1 = {}
+ dict2 = {"a": 10.2, "f": -1.0}
+ expected = dict2
+ assert merge_max_mappings(dict1, dict2) == expected
+
+ # test empty dict2
+ dict1 = {"a": 10.2, "f": -1.0}
+ dict2 = {}
+ expected = dict1
+ assert merge_max_mappings(dict1, dict2) == expected
+
+ # test both empty
+ dict1 = {}
+ dict2 = {}
+ expected = {}
+ assert merge_max_mappings(dict1, dict2) == expected
+```
+
+```python
+# running the test (seeing no errors means the tests all passed)
+>>> test_merge_max_mappings()
+```
+
+
+
+**Assertions: Solution**
+```python
+a_list = []
+a_number = 22
+a_string = "abcdef"
+```
+
+Assert that `a_list` is _not_ empty:
+
+```python
+>>> assert a_list
+---------------------------------------------------------------------------
+AssertionError Traceback (most recent call last)
+ in
+----> 1 assert a_list
+
+AssertionError:
+```
+
+> You may have written `assert len(a_list) > 0` - this is also correct.
+> However, recall that calling `bool` on any sequence (list, tuple, string, etc.) will return `False` if the sequence is empty.
+> This is a reminder that an assertion statement need not include an explicit logical statement, such as an inequality - `bool` will be called on whatever the provided expression is.
+
+Assert that the number of vowels in `a_string` is fewer than `a_number`; include an error message that prints the actual number of vowels:
+
+```python
+>>> assert count_vowels(a_string) < a_number, f"Number of vowels, {count_vowels(a_string)}, exceeds {a_number}"
+```
+
+> Note that we make use of an [f-string](https://www.pythonlikeyoumeanit.com/Module2_EssentialsOfPython/Basic_Objects.html#Formatting-strings) as a convenient means for writing an informative error message.
+
+
+
+**Testing Your Test via Manual Mutation: Solution**
+
+Temporarily change the body of `count_vowels` such that the _second_ assertion in `test_count_vowels_basic` raises an error.
+> Let's comment out the `if include_y` block in our code. This should prevent us from counting y's, and thus should violate the second assertion in our test.
+
+```python
+# Breaking the behavior of `include_y=True`
+def count_vowels(x: str, include_y: bool = False) -> int:
+ vowels = set("aeiouAEIOU")
+ # if include_y:
+ # vowels.update("yY")
+ return sum(1 for char in x if char in vowels)
+```
+
+```python
+# the second assertion should raise an error
+>>> test_count_vowels_basic()
+---------------------------------------------------------------------------
+AssertionError Traceback (most recent call last)
+ in
+----> 1 test_count_vowels_basic()
+
+ in test_count_vowels_basic()
+ 1 def test_count_vowels_basic():
+ 2 assert count_vowels("aA bB yY", include_y=False) == 2
+----> 3 assert count_vowels("aA bB yY", include_y=True) == 4
+
+AssertionError:
+```
+
+> See that the error output, which is called a "stack trace", indicates with an ASCII-arrow that our second assertion is the one that is failing.
+> Thus, we can be confident that that assertion really does help to ensure that we are counting y's correctly.
+
+Restore `count_vowels` to its original form and rerun the test to see that `count_vowels` once again passes all of the assertions.
+
+> We simply un-comment the block of code and rerun our test.
+
+```python
+# Restore the behavior of `include_y=True`
+def count_vowels(x: str, include_y: bool = False) -> int:
+ vowels = set("aeiouAEIOU")
+ if include_y:
+ vowels.update("yY")
+ return sum(1 for char in x if char in vowels)
+```
+
+```python
+# confirming that we restored the proper behavior in `count_vowels`
+>>> test_count_vowels_basic()
+```
+
diff --git a/Python/Module6_Testing/Pytest.md b/Python/Module6_Testing/Pytest.md
new file mode 100644
index 00000000..cf71bd70
--- /dev/null
+++ b/Python/Module6_Testing/Pytest.md
@@ -0,0 +1,753 @@
+---
+jupyter:
+ jupytext:
+ text_representation:
+ extension: .md
+ format_name: markdown
+ format_version: '1.2'
+ jupytext_version: 1.3.0
+ kernelspec:
+ display_name: Python 3
+ language: python
+ name: python3
+---
+
+
+.. meta::
+ :description: Topic: Introducing the pytest framework, Difficulty: Easy, Category: Section
+ :keywords: test, automated, pytest, parametrize, fixture, suite, decorator, clean directory
+
+
+# The pytest Framework
+
+Thus far, our process for running tests has been an entirely manual one. It is time for us to arrange our test functions into a proper "test suite" and to learn to leverage [the pytest framework](https://docs.pytest.org/en/latest/) to run them.
+We will begin by reorganizing our source code to create an installable [Python package](https://www.pythonlikeyoumeanit.com/Module5_OddsAndEnds/Modules_and_Packages.html#Packages).
+We will then learn how to structure and run a test suite for this Python package, using pytest.
+
+The pytest framework does much more than just run tests;
+for instance, it will enrich the assertions in our tests to produce verbose, informative error messages.
+Furthermore it provides valuable means for enhancing our tests via mechanisms like fixtures and parameterizing decorators.
+Ultimately, all of this functionality helps to eliminate manual and redundant aspects of the testing process.
+
+
+
+
+
+**Note**
+
+It can be useful to [create a separate conda environment](https://www.pythonlikeyoumeanit.com/Module1_GettingStartedWithPython/Installing_Python.html#A-Brief-Introduction-to-Conda-Environments) for the sake of this lesson, so that we can work through this material starting from a blank slate.
+If you do create a new conda environment, be sure to activate that environment and install NumPy and Jupyter notebook: `conda install numpy notebook`
+
+
+
+
+Let's install pytest. Installing from [the conda-forge channel](https://conda-forge.org/) will install the most up-to-date version of pytest. In a terminal where conda can be accessed, run:
+
+```shell
+conda install -c conda-forge pytest
+```
+
+Or, pytest is installable via pip:
+
+```shell
+pip install pytest
+```
+
+
+
+
+**Regarding Alternative Testing Frameworks** (a note from the author of PLYMI):
+
+When sifting through tutorials, blogs, and videos about testing in Python, it is common to see `pytest` presented alongside, and on an equal footing with, the alternative testing frameworks: `nose` and `unittest`.
+This strikes me as... bizarre.
+
+`unittest` is the testing framework that comes with the Python standard library.
+As a test runner, its design is clunky, archaic, and, ironically, un-pythonic.
+While [unittest.mock](https://docs.python.org/3/library/unittest.mock.html) provides extremely valuable functionality for advanced testing, all of its functionality can be leveraged while using pytest as your testing framework.
+
+`nose`, which simply extends the functionality of `unittest`, **is no longer being maintained**.
+There is a project, "Nose2", which is carrying the torch of `nose`. However, this is a fledgling project in comparison with `pytest`.
+As of writing this, `pytest` was downloaded 12 million times last month versus `nose2`'s 150 thousand downloads.
+
+The takeaway here is that, when it comes to picking a testing framework for Python, `pytest` is the clear choice.
+Any discussion that you come across to the contrary is likely outdated.
+
+
+
+## Creating a Python Package with Tests
+
+It's time to create a proper test suite.
+Before proceeding any further, we should reread the material presented in [Module 5 - Import: Modules and Packages](https://www.pythonlikeyoumeanit.com/Module5_OddsAndEnds/Modules_and_Packages.html) and recall the essentials of import statements, modules, and Python packages.
+This material serves as the foundation for this section.
+
+### Organizing our Source Code
+Let's create a Python package, which we will call `plymi_mod6`, with the following directory structure:
+
+```
+project_dir/ # the "parent directory" houses our source code, tests, and all other relevant files
+ - setup.py # script responsible for installing `plymi_mod6` package
+ - plymi_mod6/ # directory containing source code of `plymi_mod6` package
+ |-- __init__.py
+ |-- basic_functions.py
+ |-- numpy_functions.py
+ - tests/ # test-suite for `plymi_mod6` package (to be run using pytest)
+ |-- conftest.py # optional configuration file for pytest
+ |-- test_basic_functions.py
+ |-- test_numpy_functions.py
+```
+
+A reference implementation of this package can be found [in this GitHub repository](https://github.com/rsokl/plymi_mod6).
+Populate the `basic_functions.py` file with the two functions that we were using as our source code in the previous section: `count_vowels` and `merge_max_mappings`.
+In the `numpy_functions.py` module, add the `pairwise_dists` function that appears in [Module 3's discussion of optimized pairwise distances](https://www.pythonlikeyoumeanit.com/Module3_IntroducingNumpy/Broadcasting.html#Optimized-Pairwise-Distances).
+Don't forget to include `import numpy as np` in your script in accordance with how `pairwise_dists` calls NumPy functions.
+
+We have arranged these functions so that they can be imported from the `basic_functions` module and the `numpy_functions` module, respectively, which reside in our `plymi_mod6` package.
+Let's fill out our `setup.py` script and install this package so that we can import it regardless of our current working directory. The content of `setup.py` will be:
+
+```python
+from setuptools import find_packages, setup
+
+setup(
+ name="plymi_mod6",
+ packages=find_packages(exclude=["tests", "tests.*"]),
+ version="1.0.0",
+ author="Your Name",
+ description="A template Python package for learning about testing",
+ install_requires=["numpy >= 1.10.0"],
+ tests_require=["pytest>=5.3", "hypothesis>=5.0"],
+ python_requires=">=3.6",
+)
+```
+
+This setup file dictates that a user must have Python 3.6+ installed - we will bar Python 3.5 and below so that we are free to make use of [f-strings](https://www.pythonlikeyoumeanit.com/Module2_EssentialsOfPython/Basic_Objects.html#Formatting-strings) in our code, which were introduced in Python 3.6. Additionally, we will require pytest and hypothesis for running tests; the Hypothesis library will be introduced in a later section.
+
+Finally, let's install our package locally [in development mode](https://www.pythonlikeyoumeanit.com/Module5_OddsAndEnds/Modules_and_Packages.html#Installing-Your-Own-Python-Package).
+Navigate to the directory containing `setup.py` and run:
+
+```shell
+pip install --editable .
+```
+
+Now, we should be able to start a python console, IPython console, or Jupyter notebook in any directory and import our package:
+
+```python
+# checking that we can import our `plymi_mod6` package
+>>> from plymi_mod6.basic_functions import count_vowels
+>>> count_vowels("Happy birthday", include_y=True)
+5
+```
+
+
+
+## Populating and Running Our Test Suite
+
+pytest's [system for "test discovery"](https://docs.pytest.org/en/latest/goodpractices.html#test-discovery) is quite simple:
+pytest need only be pointed to a directory with files named `test_*.py` in it, and it will find all of the functions in these files _whose names start with the word "test"_ and will run all such functions.
+
+Thus, let's populate the file ``test_basic_functions.py`` with the functions `test_count_vowels_basic` and `test_merge_max_mappings`, which we wrote in the previous section of this module:
+
+```python
+# The contents of test_basic_functions.py
+
+# we must import the functions we are testing
+from plymi_mod6.basic_functions import count_vowels, merge_max_mappings
+
+
+def test_count_vowels_basic():
+ # test basic strings with uppercase and lowercase letters
+ assert count_vowels("aA bB yY", include_y=False) == 2
+ assert count_vowels("aA bB yY", include_y=True) == 4
+
+ # test empty strings
+ assert count_vowels("", include_y=False) == 0
+ assert count_vowels("", include_y=True) == 0
+
+
+def test_merge_max_mappings():
+ # test documented behavior
+ dict1 = {"a": 1, "b": 2}
+ dict2 = {"b": 20, "c": -1}
+ expected = {'a': 1, 'b': 20, 'c': -1}
+ assert merge_max_mappings(dict1, dict2) == expected
+
+ # test empty dict1
+ dict1 = {}
+ dict2 = {"a": 10.2, "f": -1.0}
+ expected = dict2
+ assert merge_max_mappings(dict1, dict2) == expected
+
+ # test empty dict2
+ dict1 = {"a": 10.2, "f": -1.0}
+ dict2 = {}
+ expected = dict1
+ assert merge_max_mappings(dict1, dict2) == expected
+
+ # test both empty
+ dict1 = {}
+ dict2 = {}
+ expected = {}
+ assert merge_max_mappings(dict1, dict2) == expected
+
+```
+
+As described before, `count_vowels` and `merge_max_mappings` must both be imported from our `plymi_mod6` package, so that our functions are in the same namespace as our tests.
+A reference implementation of `test_basic_functions.py` can be viewed [here](https://github.com/rsokl/plymi_mod6/blob/master/tests/test_basic_functions.py).
+Finally, add a dummy test - a test function that will always pass - to `test_basic_numpy.py`.
+We will replace this with a useful test later.
+
+Without further ado, let's run our test suite! In our terminal, with the appropriate conda environment active, we navigate to the root directory of the project, which contains the `tests/` directory, and run `pytest tests/`.
+The following output should appear:
+
+
+```
+$ pytest tests/
+============================= test session starts =============================
+platform win32 -- Python 3.7.5, pytest-5.3.2, py-1.8.0, pluggy-0.12.0
+rootdir: C:\Users\plymi_user\plymi_root_dir
+collected 3 items
+
+tests\test_basic_functions.py .. [ 66%]
+tests\test_basic_numpy.py . [100%]
+
+============================== 3 passed in 0.04s ==============================
+```
+
+
+This output indicates that three test-functions were found across two files and that all of the tests "passed"; i.e. the functions ran without raising any errors.
+The first two tests are located in `tests/test_basic_functions.py`; the two dots indicate that two functions were run, and the `[66%]` indicator simply denotes that the test-suite is 66% (two-thirds) complete.
+The following reading comprehension problem will lead us to see what it looks like for pytest to report a failing test.
+
+
+
+
+**Reading Comprehension: Running a Test Suite**
+
+Temporarily add a new "broken" test to `tests/test_basic_functions.py`.
+The name that you give this test should adhere to pytest's simple rules for test-discovery.
+Design the test function so that is sure to fail when it is run.
+
+Rerun your test suite and compare its output to what you saw before - is it easy to identify which test failed and what caused it to fail?
+Make sure to remove this function from your test suite once you are finished answering this question.
+
+
+
+
+
+We can also direct pytest to run the tests in a specific .py file. For example, executing:
+
+```shell
+pytest tests/test_basic_functions.py
+```
+
+will cue pytest to only run the tests in `test_basic_functions.py`.
+
+A key component to leveraging tests effectively is the ability to exercise one's tests repeatedly and rapidly with little manual overhead.
+Clearly, pytest is instrumental toward this end - this framework makes the process of organizing and running our test suite exceedingly simple!
+That being said, there will certainly be occasions when we want to run a _specific_ test function.
+Suppose, for instance, that we are writing a new function, and repeatedly want to run one of our tests that is pointing to a bug in our work-in-progress.
+We can leverage pytest in conjunction with [an IDE](https://www.pythonlikeyoumeanit.com/Module1_GettingStartedWithPython/Getting_Started_With_IDEs_and_Notebooks.html) to run our tests in such incisive ways.
+
+
+### Utilizing pytest within an IDE
+
+Both [PyCharm and VSCode](https://www.pythonlikeyoumeanit.com/Module1_GettingStartedWithPython/Getting_Started_With_IDEs_and_Notebooks.html) can be configured to make keen use of pytest.
+The following images show a couple of the enhancements afforded to us by PyCharm; comparable features are available in VSCode.
+The IDEs will "discover" tests, and provide us with the ability to run individual tests.
+For example, in the following image, the green "play button" allows us to run `test_count_vowels_basic`.
+
+
+
+
+
+
+
+
+
+Furthermore, IDEs can provide a rich tree view of all the tests that are being run.
+This is especially useful as our test suite grows to contain a considerable number of tests.
+In the following image, we see that `test_version` is failing - we can click on the failing test in this tree-view, and our IDE will navigate us directly to the failing test.
+
+
+
+
+
+
+
+
+
+The first step for leveraging these features in your IDE is to enable the pytest framework in the IDE.
+The following links point to detailed instructions for configuring pytest with PyCharm and VSCode, respectively:
+
+- [Running tests in PyCharm](https://www.jetbrains.com/help/pycharm/pytest.html)
+- [Running tests in VSCode](https://code.visualstudio.com/docs/python/testing)
+
+These linked materials also include advanced details, like instructions for running tests in parallel, which are beyond the scope of this material but are useful nonetheless.
+
+
+## Enhanced Testing with pytest
+
+In addition to providing us with a simple means for organizing and running our test suite, pytest has powerful features that will both simplify and enhance our tests.
+We will now leverage these features in our test suite.
+
+
+### Enriched Assertions
+
+A failing "bare" assertion - an `assert` statement without an error message - can be a frustrating thing.
+Suppose, for instance, that one of our test-assertions about `count_vowels` fails:
+
+```python
+# a failing assertion without an error message is not informative
+
+assert count_vowels("aA bB yY", include_y=True) == 4
+---------------------------------------------------------------------------
+AssertionError Traceback (most recent call last)
+ in
+----> 1 assert count_vowels("aA bB yY", include_y=True) == 4
+
+AssertionError:
+```
+
+The problem with this bare assertion is that we don't know what `count_vowels("aA bB yY", include_y=True)` actually returned!
+We now have to go through the trouble of starting a python console, importing this function, and calling it with this specific input in order to see what our function was actually returning. An obvious remedy to this is for us to write our own error message, but this too is quite cumbersome when we consider the large number of assertions that we are destined to write.
+
+Fortunately, pytest comes to the rescue: it will "hijack" any failing bare assertion and will _insert a useful error message for us_.
+This is known as ["assertion introspection"](https://docs.pytest.org/en/latest/assert.html#assertion-introspection-details).
+For example, if the aforementioned assertion failed when being run by pytest, we would see the following output:
+
+```python
+# pytest will write informative error messages for us
+
+assert count_vowels("aA bB yY", include_y=True) == 4
+---------------------------------------------------------------------------
+AssertionError Traceback (most recent call last)
+~\Learning_Python\Python\Module6_Testing\Untitled1.ipynb in
+----> 1 assert count_vowels("aA bB yY", include_y=True) == 4
+
+AssertionError: assert 2 == 4
+ + where 2 = ('aA bB yY', include_y=True
+```
+
+See that the error message that pytest included for us indicates that `count_vowels("aA bB yY", include_y=True)` returned `2`, when we expected it to return `4`.
+From this we might suspect that `count_vowels` is not counting y's correctly.
+
+Here are some more examples of "enriched assertions", as provided by pytest.
+See that these error messages even provide useful "diffs", which specify specifically _how_ two similar objects differ, where possible.
+
+```python
+# comparing unequal lists
+assert [1, 2, 3] == [1, 2]
+E Left contains one more item: 3
+E Full diff:
+E - [1, 2, 3]
+E ? ---
+E + [1, 2]
+```
+
+```python
+# comparing unequal dictionaries
+assert {"a": 1, "b": 2} == {"a": 1, "b": 3}
+E AssertionError: assert {'a': 1, 'b': 2} == {'a': 1, 'b': 3}
+E Omitting 1 identical items, use -vv to show
+E Differing items:
+E {'b': 2} != {'b': 3}
+E Full diff:
+E - {'a': 1, 'b': 2}
+E ? ^
+E + {'a': 1, 'b': 3}...
+```
+
+```python
+# comparing unequal strings
+assert "moo" == "moon"
+E AssertionError: assert 'moo' == 'moon'
+E - moo
+E + moon
+E ? +
+```
+
+
+
+
+### Parameterized Tests
+
+Looking back to both `test_count_vowels_basic` and `test_merge_max_mappings`, we see that there is a lot of redundancy within the bodies of these test functions.
+The assertions that we make within a given test-function share identical forms - they differ only in the parameters that we feed into our functions and their expected output.
+Another shortcoming of this test-structure is that a failing assertion will block subsequent assertions from being evaluated.
+That is, if the second assertion in `test_count_vowels_basic` fails, the third and fourth assertions will not be evaluated in that run.
+This precludes us from potentially seeing useful patterns among the failing assertions.
+
+pytest provides a useful tool that will allow us to eliminate these structural shortcomings by transforming our test-functions into so-called _parameterized tests_. Let's parametrize the following test:
+
+```python
+# a simple test with redundant assertions
+
+def test_range_length_unparameterized():
+ assert len(range(0)) == 0
+ assert len(range(1)) == 1
+ assert len(range(2)) == 2
+ assert len(range(3)) == 3
+```
+
+This test is checking the property `len(range(n)) == n`, where `n` is any non-negative integer.
+Thus, the parameter to be varied here is the "size" of the range-object being created.
+Let's treat it as such by using pytest to write a parameterized test:
+
+```python
+# parameterizing a test
+import pytest
+
+# note that this test must be run by pytest to work properly
+@pytest.mark.parametrize("size", [0, 1, 2, 3])
+def test_range_length(size):
+ assert len(range(size)) == size
+```
+
+Make note that a pytest-parameterized test must be run using pytest; an error will raise if we manually call `test_range_length()`.
+When executed, pytest will treat this parameterized test as _four separate tests_ - one for each parameter value:
+
+```
+test_basic_functions.py::test_range_length[0] PASSED [ 25%]
+test_basic_functions.py::test_range_length[1] PASSED [ 50%]
+test_basic_functions.py::test_range_length[2] PASSED [ 75%]
+test_basic_functions.py::test_range_length[3] PASSED [100%]
+```
+
+See that we have successfully eliminated the redundancy from `test_range_length`;
+the body of the function now contains only a single assertion, making obvious the property that is being tested.
+Furthermore, the four assertions are now being run independently from one another and thus we can potentially see patterns across multiple fail cases in concert.
+
+
+
+#### Decorators
+
+The syntax used to parameterize this test may look alien to us, as we have yet to encounter this construct thus far.
+`pytest.mark.parameterize(...)` is a _decorator_ - an object that is used to "wrap" a function in order to transform its behavior.
+The `pytest.mark.parameterize(...)` decorator wraps our test function so that pytest can call it multiple times, once for each parameter value.
+The `@` character, in this context, denotes the application of a decorator:
+
+```python
+# general syntax for applying a decorator to a function
+
+@the_decorator
+def the_function_being_decorated():
+ pass
+```
+
+For an in-depth discussion of decorators, please refer to [Real Python's Primer on decorators](https://realpython.com/primer-on-python-decorators/#simple-decorators).
+
+
+
+#### Parameterization Syntax
+
+The general form for creating a parameterizing decorator with *a single parameter*, as we formed above, is:
+
+```python
+@pytest.mark.parametrize("", [, , ...])
+def test_function():
+ ...
+```
+
+We will often have tests that require multiple parameters.
+The general form for creating the parameterization decorator for $N$ parameters,
+each of which assume $J$ values, is:
+
+```python
+@pytest.mark.parametrize(", , [...], ",
+ [(, , [...], ),
+ (, , [...], ),
+ ...
+ (, , [...], ),
+ ])
+def test_function(, , [...], ):
+ ...
+```
+
+For example, let's take the following trivial test:
+
+```python
+def test_inequality_unparameterized():
+ assert 1 < 2 < 3
+ assert 4 < 5 < 6
+ assert 7 < 8 < 9
+ assert 10 < 11 < 12
+```
+
+and rewrite it in parameterized form.
+The decorator will have three distinct parameters, and each parameter, let's simply call them `a`, `b`, and `c`, will take on four values.
+
+```python
+# the parameterized form of `test_inequality_unparameterized`
+@pytest.mark.parametrize("a, b, c", [(1, 2, 3), (4, 5, 6), (7, 8, 9), (10, 11, 12)])
+def test_inequality(a, b, c):
+ assert a < b < c
+```
+
+
+
+
+**Note**
+
+The formatting for multi-parameter tests can quickly become unwieldy.
+It isn't always obvious where one should introduce line breaks and indentations to improve readability.
+This is a place where the ["black" auto-formatter](https://black.readthedocs.io/en/stable/) really shines!
+Black will make all of these formatting decisions for us - we can write our parameterized tests as haphazardly as we like and simply run black to format our code.
+
+
+
+
+
+
+**Reading Comprehension: Parameterizing Tests**
+
+Rewrite `test_count_vowels_basic` as a parameterized test with the parameters: `input_string`, `include_y`, and `expected_count`.
+
+Rewrite `test_merge_max_mappings` as a parameterized test with the parameters: `dict_a`, `dict_b`, and `expected_merged`.
+
+Before rerunning `test_basic_functions.py` predict how many distinct test cases will be reported by pytest.
+
+
+
+
+
+Finally, you can apply multiple parameterizing decorators to a test so that pytest will run _all combinations of the respective parameter values_.
+
+```python
+# testing all combinations of `x` and `y`
+@pytest.mark.parametrize("x", [0, 1, 2])
+@pytest.mark.parametrize("y", [10, 20])
+def test_all_combinations(x, y):
+ # will run:
+ # x=0 y=10
+ # x=0 y=20
+ # x=1 y=10
+ # x=1 y=20
+ # x=2 y=10
+ # x=2 y=20
+ pass
+```
+
+
+### Fixtures
+
+The final major pytest feature that we will discuss are "fixtures".
+A fixture, roughly speaking, is a means by which we can share information and functionality across our tests.
+Fixtures can be defined within our `conftest.py` file, and pytest will automatically "discover" them and make them available for use throughout our test suite in a convenient way.
+
+Exploring fixtures will quickly take us beyond our depths for the purposes of this introductory material, so we will only scratch the surface here.
+We can read about advanced details of fixtures [here](https://docs.pytest.org/en/latest/fixture.html#fixture).
+
+Below are examples of two useful fixtures.
+
+
+```python
+# contents of conftest.py
+
+import os
+import tempfile
+
+import pytest
+
+@pytest.fixture()
+def cleandir():
+ """ This fixture will use the stdlib `tempfile` module to
+ change the current working directory to a tmp-dir for the
+ duration of the test.
+
+ Afterwards, the test session returns to its previous working
+ directory, and the temporary directory and its contents
+ will be automatically deleted.
+
+ Yields
+ ------
+ str
+ The name of the temporary directory."""
+ with tempfile.TemporaryDirectory() as tmpdirname:
+ old_dir = os.getcwd() # get current working directory (cwd)
+ os.chdir(tmpdirname) # change cwd to the temp-directory
+ yield tmpdirname # yields control to the test to be run
+ os.chdir(old_dir) # restore the cwd to the original directory
+ # Leaving the context manager will prompt the deletion of the
+ # temporary directory and its contents. This cleanup will be
+ # triggered even if errors were raised during the test.
+
+
+@pytest.fixture()
+def dummy_email():
+ """ This fixture will simply have pytest pass the string:
+ 'dummy.email@plymi.com'
+ to any test-function that has the parameter name `dummy_email` in
+ its signature.
+ """
+ return "dummy.email@plymi.com"
+```
+
+
+
+The first one, `cleandir`, can be used in conjunction with tests that need to write files.
+We don't want our tests to leave behind files on our machines; the `cleandir` fixture will ensure that our tests will write files to a temporary directory that will be deleted once the test is complete.
+
+Second is a simple fixture called `dummy_email`.
+Suppose that our project needs to interact with a specific email address, suppose it's `dummy.email@plymi.com`, and that we have several tests that need access to this address.
+This fixture will pass this address to any test function that has the parameter name `dummy_email` in its signature.
+
+A reference implementation of `conftest.py` in our project can be found [here](https://github.com/rsokl/plymi_mod6/blob/fixtures/tests/conftest.py).
+Several reference tests that make use of these fixtures can be found [here](https://github.com/rsokl/plymi_mod6/blob/fixtures/tests/test_using_fixtures.py).
+
+Let's create a file `tests/test_using_fixtures.py`, and write some tests that put these fixtures to use:
+
+```python
+# contents of test_using_fixtures.py
+import pytest
+
+# When run, this test will be executed within a
+# temporary directory that will automatically be
+# deleted - along with all of its contents - once
+# the test ends.
+#
+# Thus we can have this test write a file, and we
+# need not worry about having it clean up after itself.
+@pytest.mark.usefixtures("cleandir")
+def test_writing_a_file():
+ with open("a_text_file.txt", mode="w") as f:
+ f.write("hello world")
+
+ with open("a_text_file.txt", mode="r") as f:
+ file_content = f.read()
+
+ assert file_content == "hello world"
+
+
+# We can use the `dummy_email` fixture to provide
+# the same email address to many tests. In this
+# way, if we need to change the email address, we
+# can simply update the fixture and all of the tests
+# will be affected by the update.
+#
+# Note that we don't need to use a decorator here.
+# pytest is smart, and will see that the parameter-name
+# `dummy_email` matches the name of our fixture. It will
+# thus call these tests using the value returned by our
+# fixture
+
+def test_email1(dummy_email):
+ assert "dummy" in dummy_email
+
+
+def test_email2(dummy_email):
+ assert "plymi" in dummy_email
+
+
+def test_email3(dummy_email):
+ assert ".com" in dummy_email
+```
+
+
+## Links to Official Documentation
+
+- [pytest](https://docs.pytest.org/en/latest/)
+- [pytest's system for test discovery](https://docs.pytest.org/en/latest/goodpractices.html#test-discovery)
+- [Testing in PyCharm](https://www.jetbrains.com/help/pycharm/pytest.html)
+- [Testing in VSCode](https://code.visualstudio.com/docs/python/testing)
+- [Assertion introspection](https://docs.pytest.org/en/latest/assert.html#assertion-introspection-details)
+- [Parameterizing tests](https://docs.pytest.org/en/latest/parametrize.html)
+- [Fixtures](https://docs.pytest.org/en/latest/fixture.html#fixture)
+
+
+## Reading Comprehension Solutions
+
+
+**Running a Test Suite: Solution**
+
+> Let's add the test function `test_broken_function` to our test suite.
+> We must include the word "test" in the function's name so that pytest will identify it as a test to run.
+> There are limitless ways in which we can make this test fail; we'll introduce a trivial false-assertion:
+
+```python
+def test_broken_function():
+ assert [1, 2, 3] == [1, 2]
+```
+
+> After introducing this broken test into `test_basic_functions.py` , running our tests should result in the following output:
+
+```
+$ pytest tests/
+============================= test session starts =============================
+platform win32 -- Python 3.7.5, pytest-5.3.2, py-1.8.0, pluggy-0.12.0
+rootdir: C:\Users\plymi_user\plymi_root_dir
+collected 4 items
+
+tests\test_basic_functions.py ..F [ 75%]
+tests\test_basic_numpy.py . [100%]
+
+================================== FAILURES ===================================
+____________________________ test_broken_function _____________________________
+
+ def test_broken_function():
+> assert [1, 2, 3] == [1, 2]
+E assert [1, 2, 3] == [1, 2]
+E Left contains one more item: 3
+E Use -v to get the full diff
+
+tests\test_basic_functions.py:40: AssertionError
+========================= 1 failed, 3 passed in 0.07s =========================
+```
+
+> Four tests were "discovered" and run by pytest. The pattern `..F` indicates that the first two tests in `test_basic_functions` passed and the third test failed.
+> It then indicates which test failed, and specifically that the assertion was false because a length-2 list cannot be equal to a length-3 list.
+
+
+
+**Parameterizing Tests: Solution**
+
+A reference implementation for this solution within the `plymi_mod6` project can be found [here](https://github.com/rsokl/plymi_mod6/blob/parameterized/tests/test_basic_functions.py).
+
+The contents of `test_basic_functions.py`, rewritten to use pytest-parameterized tests:
+
+```python
+import pytest
+from plymi_mod6.basic_functions import count_vowels, merge_max_mappings
+
+
+@pytest.mark.parametrize(
+ "input_string, include_y, expected_count",
+ [("aA bB yY", False, 2), ("aA bB yY", True, 4), ("", False, 0), ("", True, 0)],
+)
+def test_count_vowels_basic(input_string, include_y, expected_count):
+ assert count_vowels(input_string, include_y) == expected_count
+
+
+@pytest.mark.parametrize(
+ "dict_a, dict_b, expected_merged",
+ [
+ (dict(a=1, b=2), dict(b=20, c=-1), dict(a=1, b=20, c=-1)),
+ (dict(), dict(b=20, c=-1), dict(b=20, c=-1)),
+ (dict(a=1, b=2), dict(), dict(a=1, b=2)),
+ (dict(), dict(), dict()),
+ ],
+)
+def test_merge_max_mappings(dict_a, dict_b, expected_merged):
+ assert merge_max_mappings(dict_a, dict_b) == expected_merged
+```
+
+Running these tests via pytest should produce eight distinct test-case: four for `test_count_vowels_basic` and four for `test_merge_max_mappings`.
+
+```
+============================= test session starts =============================
+platform win32 -- Python 3.7.5, pytest-5.3.2, py-1.8.0, pluggy-0.12.0
+cachedir: .pytest_cache
+rootdir: C:\Users\plymi_user\Learning_Python\plymi_mod6_src
+collecting ... collected 8 items
+
+test_basic_functions.py::test_count_vowels_basic[aA bB yY-False-2] PASSED [ 12%]
+test_basic_functions.py::test_count_vowels_basic[aA bB yY-True-4] PASSED [ 25%]
+test_basic_functions.py::test_count_vowels_basic[-False-0] PASSED [ 37%]
+test_basic_functions.py::test_count_vowels_basic[-True-0] PASSED [ 50%]
+test_basic_functions.py::test_merge_max_mappings[dict_a0-dict_b0-expected_merged0] PASSED [ 62%]
+test_basic_functions.py::test_merge_max_mappings[dict_a1-dict_b1-expected_merged1] PASSED [ 75%]
+test_basic_functions.py::test_merge_max_mappings[dict_a2-dict_b2-expected_merged2] PASSED [ 87%]
+test_basic_functions.py::test_merge_max_mappings[dict_a3-dict_b3-expected_merged3] PASSED [100%]
+
+============================== 8 passed in 0.07s ==============================
+```
+
+
diff --git a/Python/_build/_images/individual_test.png b/Python/_build/_images/individual_test.png
new file mode 100644
index 00000000..381fba2d
Binary files /dev/null and b/Python/_build/_images/individual_test.png differ
diff --git a/Python/_build/_images/test_tree_view.png b/Python/_build/_images/test_tree_view.png
new file mode 100644
index 00000000..5b352aa5
Binary files /dev/null and b/Python/_build/_images/test_tree_view.png differ
diff --git a/Python/index.rst b/Python/index.rst
index f8650de9..3274b75e 100644
--- a/Python/index.rst
+++ b/Python/index.rst
@@ -64,6 +64,7 @@ I started learning to use Python in graduate school for my physics research, and
module_3_problems.rst
module_4.rst
module_5.rst
+ module_6.rst
changes.rst
Indices and tables
diff --git a/Python/module_5.rst b/Python/module_5.rst
index 0c0bf09f..63c8036d 100644
--- a/Python/module_5.rst
+++ b/Python/module_5.rst
@@ -1,5 +1,5 @@
Module 5: Odds and Ends
-=====================================
+=======================
This module contains materials that are extraneous to the essentials of Python as a language and of NumPy, but are nonetheless critical to doing day-to-day work using these tools.
The first section introduces some general guidelines for writing "good code". Specifically, it points you, the reader, to a style guide that many people in the Python community abide by. It also introduces a relatively new and increasingly-popular feature of Python, called type-hinting, which permits us to enhance our code with type-documentation annotations. The reader will also be introduced to NumPy's and Google's respective specifications for writing good docstrings.
diff --git a/Python/module_6.rst b/Python/module_6.rst
new file mode 100644
index 00000000..2b113105
--- /dev/null
+++ b/Python/module_6.rst
@@ -0,0 +1,21 @@
+Module 6: Testing Our Code
+==========================
+This module will introduce us to the critically-important and often-overlooked process of testing code.
+We will begin by considering some general motivations for writing tests.
+Next, we will study the basic anatomy of a test-function, including the :code:`assert` statement, which serves as the nucleus of our test functions.
+Armed with the ability to write a rudimentary test, we will welcome, with open arms, the powerful testing framework `pytest `_.
+This will inform how we structure our tests alongside our Python project; with pytest, we can incisively run our tests with the press of a single button.
+Furthermore, it will allow us to greatly streamline and even begin to automate some of our tests.
+Finally, we will take a step back to consider some strategies for writing effective tests.
+Among these is a methodology that is near and dear to my heart: property-based testing.
+This will take us down a bit of a rabbit hole, where we will find the powerful property-based testing library `Hypothesis `_ waiting to greet us (adorned with the mad Hatter's cap and all).
+
+.. toctree::
+ :maxdepth: 2
+ :caption: Contents:
+
+ Module6_Testing/Intro_to_Testing.md
+ Module6_Testing/Pytest.md
+ Module6_Testing/Hypothesis.md
+
+
diff --git a/docs/.doctrees/Module2_EssentialsOfPython/Functions.doctree b/docs/.doctrees/Module2_EssentialsOfPython/Functions.doctree
index 4ff5d28d..3ed93a2c 100644
Binary files a/docs/.doctrees/Module2_EssentialsOfPython/Functions.doctree and b/docs/.doctrees/Module2_EssentialsOfPython/Functions.doctree differ
diff --git a/docs/.doctrees/Module3_IntroducingNumpy/Broadcasting.doctree b/docs/.doctrees/Module3_IntroducingNumpy/Broadcasting.doctree
index 0375e784..dd124c77 100644
Binary files a/docs/.doctrees/Module3_IntroducingNumpy/Broadcasting.doctree and b/docs/.doctrees/Module3_IntroducingNumpy/Broadcasting.doctree differ
diff --git a/docs/.doctrees/Module5_OddsAndEnds/Modules_and_Packages.doctree b/docs/.doctrees/Module5_OddsAndEnds/Modules_and_Packages.doctree
index df4f5a44..45dd3576 100644
Binary files a/docs/.doctrees/Module5_OddsAndEnds/Modules_and_Packages.doctree and b/docs/.doctrees/Module5_OddsAndEnds/Modules_and_Packages.doctree differ
diff --git a/docs/.doctrees/Module5_OddsAndEnds/Testing_Your_Code.doctree b/docs/.doctrees/Module5_OddsAndEnds/Testing_Your_Code.doctree
deleted file mode 100644
index 2833f151..00000000
Binary files a/docs/.doctrees/Module5_OddsAndEnds/Testing_Your_Code.doctree and /dev/null differ
diff --git a/docs/.doctrees/Module5_OddsAndEnds/Writing_Good_Code.doctree b/docs/.doctrees/Module5_OddsAndEnds/Writing_Good_Code.doctree
index f513f21d..9e56eca8 100644
Binary files a/docs/.doctrees/Module5_OddsAndEnds/Writing_Good_Code.doctree and b/docs/.doctrees/Module5_OddsAndEnds/Writing_Good_Code.doctree differ
diff --git a/docs/.doctrees/Module6_Testing/Hypothesis.doctree b/docs/.doctrees/Module6_Testing/Hypothesis.doctree
new file mode 100644
index 00000000..915c93b0
Binary files /dev/null and b/docs/.doctrees/Module6_Testing/Hypothesis.doctree differ
diff --git a/docs/.doctrees/Module6_Testing/Intro_to_Testing.doctree b/docs/.doctrees/Module6_Testing/Intro_to_Testing.doctree
new file mode 100644
index 00000000..e05ac85b
Binary files /dev/null and b/docs/.doctrees/Module6_Testing/Intro_to_Testing.doctree differ
diff --git a/docs/.doctrees/Module6_Testing/Pytest.doctree b/docs/.doctrees/Module6_Testing/Pytest.doctree
new file mode 100644
index 00000000..2e73d66e
Binary files /dev/null and b/docs/.doctrees/Module6_Testing/Pytest.doctree differ
diff --git a/docs/.doctrees/environment.pickle b/docs/.doctrees/environment.pickle
index 6adf0b07..b3b0b327 100644
Binary files a/docs/.doctrees/environment.pickle and b/docs/.doctrees/environment.pickle differ
diff --git a/docs/.doctrees/index.doctree b/docs/.doctrees/index.doctree
index 124b0996..b83baaab 100644
Binary files a/docs/.doctrees/index.doctree and b/docs/.doctrees/index.doctree differ
diff --git a/docs/.doctrees/module_6.doctree b/docs/.doctrees/module_6.doctree
new file mode 100644
index 00000000..a783bac5
Binary files /dev/null and b/docs/.doctrees/module_6.doctree differ
diff --git a/docs/Module1_GettingStartedWithPython/Exercises/Informal_Intro_Python.html b/docs/Module1_GettingStartedWithPython/Exercises/Informal_Intro_Python.html
index 0093cf28..0f8e9a58 100644
--- a/docs/Module1_GettingStartedWithPython/Exercises/Informal_Intro_Python.html
+++ b/docs/Module1_GettingStartedWithPython/Exercises/Informal_Intro_Python.html
@@ -97,6 +97,7 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+Module 6: Testing Our Code
Changelog
diff --git a/docs/Module1_GettingStartedWithPython/GettingStartedWithPython.html b/docs/Module1_GettingStartedWithPython/GettingStartedWithPython.html
index d6e0e87c..25adba2a 100644
--- a/docs/Module1_GettingStartedWithPython/GettingStartedWithPython.html
+++ b/docs/Module1_GettingStartedWithPython/GettingStartedWithPython.html
@@ -116,6 +116,7 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+Module 6: Testing Our Code
Changelog
diff --git a/docs/Module1_GettingStartedWithPython/Getting_Started_With_IDEs_and_Notebooks.html b/docs/Module1_GettingStartedWithPython/Getting_Started_With_IDEs_and_Notebooks.html
index 462d4d3a..e881b7e5 100644
--- a/docs/Module1_GettingStartedWithPython/Getting_Started_With_IDEs_and_Notebooks.html
+++ b/docs/Module1_GettingStartedWithPython/Getting_Started_With_IDEs_and_Notebooks.html
@@ -116,6 +116,7 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+Module 6: Testing Our Code
Changelog
diff --git a/docs/Module1_GettingStartedWithPython/Informal_Intro_Python.html b/docs/Module1_GettingStartedWithPython/Informal_Intro_Python.html
index 124649a1..d168f12b 100644
--- a/docs/Module1_GettingStartedWithPython/Informal_Intro_Python.html
+++ b/docs/Module1_GettingStartedWithPython/Informal_Intro_Python.html
@@ -114,6 +114,7 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+Module 6: Testing Our Code
Changelog
diff --git a/docs/Module1_GettingStartedWithPython/Installing_Python.html b/docs/Module1_GettingStartedWithPython/Installing_Python.html
index 75e705e7..2e3c24d9 100644
--- a/docs/Module1_GettingStartedWithPython/Installing_Python.html
+++ b/docs/Module1_GettingStartedWithPython/Installing_Python.html
@@ -114,6 +114,7 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+Module 6: Testing Our Code
Changelog
diff --git a/docs/Module1_GettingStartedWithPython/Jupyter_Notebooks.html b/docs/Module1_GettingStartedWithPython/Jupyter_Notebooks.html
index a19840ae..3ce18f47 100644
--- a/docs/Module1_GettingStartedWithPython/Jupyter_Notebooks.html
+++ b/docs/Module1_GettingStartedWithPython/Jupyter_Notebooks.html
@@ -121,6 +121,7 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+Module 6: Testing Our Code
Changelog
diff --git a/docs/Module1_GettingStartedWithPython/Numerical_Work_In_Python.html b/docs/Module1_GettingStartedWithPython/Numerical_Work_In_Python.html
index cd00735a..e5311491 100644
--- a/docs/Module1_GettingStartedWithPython/Numerical_Work_In_Python.html
+++ b/docs/Module1_GettingStartedWithPython/Numerical_Work_In_Python.html
@@ -97,6 +97,7 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+Module 6: Testing Our Code
Changelog
diff --git a/docs/Module1_GettingStartedWithPython/SiteFormatting.html b/docs/Module1_GettingStartedWithPython/SiteFormatting.html
index e881b672..64fbbcb1 100644
--- a/docs/Module1_GettingStartedWithPython/SiteFormatting.html
+++ b/docs/Module1_GettingStartedWithPython/SiteFormatting.html
@@ -109,6 +109,7 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+Module 6: Testing Our Code
Changelog
diff --git a/docs/Module2_EssentialsOfPython/Basic_Objects.html b/docs/Module2_EssentialsOfPython/Basic_Objects.html
index 7da982d7..20e13803 100644
--- a/docs/Module2_EssentialsOfPython/Basic_Objects.html
+++ b/docs/Module2_EssentialsOfPython/Basic_Objects.html
@@ -150,6 +150,7 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+Module 6: Testing Our Code
Changelog
diff --git a/docs/Module2_EssentialsOfPython/ConditionalStatements.html b/docs/Module2_EssentialsOfPython/ConditionalStatements.html
index 70f5d637..781a1ad1 100644
--- a/docs/Module2_EssentialsOfPython/ConditionalStatements.html
+++ b/docs/Module2_EssentialsOfPython/ConditionalStatements.html
@@ -128,6 +128,7 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+Module 6: Testing Our Code
Changelog
diff --git a/docs/Module2_EssentialsOfPython/DataStructures.html b/docs/Module2_EssentialsOfPython/DataStructures.html
index 58596593..976f489c 100644
--- a/docs/Module2_EssentialsOfPython/DataStructures.html
+++ b/docs/Module2_EssentialsOfPython/DataStructures.html
@@ -125,6 +125,7 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+Module 6: Testing Our Code
Changelog
diff --git a/docs/Module2_EssentialsOfPython/DataStructures_III_Sets_and_More.html b/docs/Module2_EssentialsOfPython/DataStructures_III_Sets_and_More.html
index bcfbb213..227ae5c5 100644
--- a/docs/Module2_EssentialsOfPython/DataStructures_III_Sets_and_More.html
+++ b/docs/Module2_EssentialsOfPython/DataStructures_III_Sets_and_More.html
@@ -133,6 +133,7 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+Module 6: Testing Our Code
Changelog
diff --git a/docs/Module2_EssentialsOfPython/DataStructures_II_Dictionaries.html b/docs/Module2_EssentialsOfPython/DataStructures_II_Dictionaries.html
index 15f374d6..7e3a5e41 100644
--- a/docs/Module2_EssentialsOfPython/DataStructures_II_Dictionaries.html
+++ b/docs/Module2_EssentialsOfPython/DataStructures_II_Dictionaries.html
@@ -134,6 +134,7 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+Module 6: Testing Our Code
Changelog
diff --git a/docs/Module2_EssentialsOfPython/ForLoops.html b/docs/Module2_EssentialsOfPython/ForLoops.html
index 427598fe..f4eeb998 100644
--- a/docs/Module2_EssentialsOfPython/ForLoops.html
+++ b/docs/Module2_EssentialsOfPython/ForLoops.html
@@ -127,6 +127,7 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+Module 6: Testing Our Code
Changelog
diff --git a/docs/Module2_EssentialsOfPython/Functions.html b/docs/Module2_EssentialsOfPython/Functions.html
index af180b17..dc1e1c9b 100644
--- a/docs/Module2_EssentialsOfPython/Functions.html
+++ b/docs/Module2_EssentialsOfPython/Functions.html
@@ -136,6 +136,7 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+Module 6: Testing Our Code
Changelog
@@ -322,7 +323,7 @@ The def<
The return
Statement
-
In general, any Python object can follow a function’s return
statement. Furthermore, an empty return
statement can be specified, or the return statement of a function can be omitted altogether. In both of these cases, the function will return the ``None`` object.
+
In general, any Python object can follow a function’s return
statement. Furthermore, an empty return
statement can be specified, or the return statement of a function can be omitted altogether. In both of these cases, the function will return the None
object.
# this function returns `None`
# an "empty" return statement
def f():
diff --git a/docs/Module2_EssentialsOfPython/Generators_and_Comprehensions.html b/docs/Module2_EssentialsOfPython/Generators_and_Comprehensions.html
index 5a1c11b9..8df08021 100644
--- a/docs/Module2_EssentialsOfPython/Generators_and_Comprehensions.html
+++ b/docs/Module2_EssentialsOfPython/Generators_and_Comprehensions.html
@@ -140,6 +140,7 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Our Code
Changelog
diff --git a/docs/Module2_EssentialsOfPython/Introduction.html b/docs/Module2_EssentialsOfPython/Introduction.html
index 50774bbd..22813573 100644
--- a/docs/Module2_EssentialsOfPython/Introduction.html
+++ b/docs/Module2_EssentialsOfPython/Introduction.html
@@ -120,6 +120,7 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Our Code
Changelog
diff --git a/docs/Module2_EssentialsOfPython/Iterables.html b/docs/Module2_EssentialsOfPython/Iterables.html
index 3f9e5f4c..e2b0856a 100644
--- a/docs/Module2_EssentialsOfPython/Iterables.html
+++ b/docs/Module2_EssentialsOfPython/Iterables.html
@@ -127,6 +127,7 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Our Code
Changelog
diff --git a/docs/Module2_EssentialsOfPython/Itertools.html b/docs/Module2_EssentialsOfPython/Itertools.html
index 8d46448a..77170ab2 100644
--- a/docs/Module2_EssentialsOfPython/Itertools.html
+++ b/docs/Module2_EssentialsOfPython/Itertools.html
@@ -121,6 +121,7 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Our Code
Changelog
diff --git a/docs/Module2_EssentialsOfPython/Problems/DifferenceFanout.html b/docs/Module2_EssentialsOfPython/Problems/DifferenceFanout.html
index aba13461..4d012f2f 100644
--- a/docs/Module2_EssentialsOfPython/Problems/DifferenceFanout.html
+++ b/docs/Module2_EssentialsOfPython/Problems/DifferenceFanout.html
@@ -113,6 +113,7 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Our Code
Changelog
diff --git a/docs/Module2_EssentialsOfPython/Problems/EncodeAsString.html b/docs/Module2_EssentialsOfPython/Problems/EncodeAsString.html
index f93b0e70..0ececc7a 100644
--- a/docs/Module2_EssentialsOfPython/Problems/EncodeAsString.html
+++ b/docs/Module2_EssentialsOfPython/Problems/EncodeAsString.html
@@ -111,6 +111,7 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Our Code
Changelog
diff --git a/docs/Module2_EssentialsOfPython/Problems/MarginPercentage.html b/docs/Module2_EssentialsOfPython/Problems/MarginPercentage.html
index cbb0dd6c..936c4001 100644
--- a/docs/Module2_EssentialsOfPython/Problems/MarginPercentage.html
+++ b/docs/Module2_EssentialsOfPython/Problems/MarginPercentage.html
@@ -111,6 +111,7 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Our Code
Changelog
diff --git a/docs/Module2_EssentialsOfPython/Problems/MergeMaxDicts.html b/docs/Module2_EssentialsOfPython/Problems/MergeMaxDicts.html
index 506ef56e..1c9ef3fc 100644
--- a/docs/Module2_EssentialsOfPython/Problems/MergeMaxDicts.html
+++ b/docs/Module2_EssentialsOfPython/Problems/MergeMaxDicts.html
@@ -120,6 +120,7 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Our Code
Changelog
diff --git a/docs/Module2_EssentialsOfPython/Problems/Palindrome.html b/docs/Module2_EssentialsOfPython/Problems/Palindrome.html
index d8cf34b8..fc385d90 100644
--- a/docs/Module2_EssentialsOfPython/Problems/Palindrome.html
+++ b/docs/Module2_EssentialsOfPython/Problems/Palindrome.html
@@ -112,6 +112,7 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Our Code
Changelog
diff --git a/docs/Module2_EssentialsOfPython/Scope.html b/docs/Module2_EssentialsOfPython/Scope.html
index c8166c0c..9e71fc5c 100644
--- a/docs/Module2_EssentialsOfPython/Scope.html
+++ b/docs/Module2_EssentialsOfPython/Scope.html
@@ -121,6 +121,7 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Our Code
Changelog
diff --git a/docs/Module2_EssentialsOfPython/SequenceTypes.html b/docs/Module2_EssentialsOfPython/SequenceTypes.html
index 2ca34486..eaee9f65 100644
--- a/docs/Module2_EssentialsOfPython/SequenceTypes.html
+++ b/docs/Module2_EssentialsOfPython/SequenceTypes.html
@@ -128,6 +128,7 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Our Code
Changelog
diff --git a/docs/Module2_EssentialsOfPython/Variables_and_Assignment.html b/docs/Module2_EssentialsOfPython/Variables_and_Assignment.html
index 214a9b86..609d582d 100644
--- a/docs/Module2_EssentialsOfPython/Variables_and_Assignment.html
+++ b/docs/Module2_EssentialsOfPython/Variables_and_Assignment.html
@@ -126,6 +126,7 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Our Code
Changelog
diff --git a/docs/Module3_IntroducingNumpy/AccessingDataAlongMultipleDimensions.html b/docs/Module3_IntroducingNumpy/AccessingDataAlongMultipleDimensions.html
index 9fd45a6a..e3e9f094 100644
--- a/docs/Module3_IntroducingNumpy/AccessingDataAlongMultipleDimensions.html
+++ b/docs/Module3_IntroducingNumpy/AccessingDataAlongMultipleDimensions.html
@@ -130,6 +130,7 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Our Code
Changelog
diff --git a/docs/Module3_IntroducingNumpy/AdvancedIndexing.html b/docs/Module3_IntroducingNumpy/AdvancedIndexing.html
index 8ae0efff..32cfd621 100644
--- a/docs/Module3_IntroducingNumpy/AdvancedIndexing.html
+++ b/docs/Module3_IntroducingNumpy/AdvancedIndexing.html
@@ -124,6 +124,7 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Our Code
Changelog
diff --git a/docs/Module3_IntroducingNumpy/ArrayTraversal.html b/docs/Module3_IntroducingNumpy/ArrayTraversal.html
index 8760df27..bc800fae 100644
--- a/docs/Module3_IntroducingNumpy/ArrayTraversal.html
+++ b/docs/Module3_IntroducingNumpy/ArrayTraversal.html
@@ -116,6 +116,7 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Our Code
Changelog
diff --git a/docs/Module3_IntroducingNumpy/BasicArrayAttributes.html b/docs/Module3_IntroducingNumpy/BasicArrayAttributes.html
index b4b322dc..750c926f 100644
--- a/docs/Module3_IntroducingNumpy/BasicArrayAttributes.html
+++ b/docs/Module3_IntroducingNumpy/BasicArrayAttributes.html
@@ -115,6 +115,7 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Our Code
Changelog
diff --git a/docs/Module3_IntroducingNumpy/BasicIndexing.html b/docs/Module3_IntroducingNumpy/BasicIndexing.html
index 18fc5b5d..80af5e7e 100644
--- a/docs/Module3_IntroducingNumpy/BasicIndexing.html
+++ b/docs/Module3_IntroducingNumpy/BasicIndexing.html
@@ -130,6 +130,7 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Our Code
Changelog
diff --git a/docs/Module3_IntroducingNumpy/Broadcasting.html b/docs/Module3_IntroducingNumpy/Broadcasting.html
index 1731ff3f..24653361 100644
--- a/docs/Module3_IntroducingNumpy/Broadcasting.html
+++ b/docs/Module3_IntroducingNumpy/Broadcasting.html
@@ -129,6 +129,7 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Our Code
Changelog
@@ -603,16 +604,16 @@
Pairwise Distances Using For-Loops
diff --git a/docs_backup/Module1_GettingStartedWithPython/Numerical_Work_In_Python.html b/docs_backup/Module1_GettingStartedWithPython/Numerical_Work_In_Python.html
index 05084617..fdaf624b 100644
--- a/docs_backup/Module1_GettingStartedWithPython/Numerical_Work_In_Python.html
+++ b/docs_backup/Module1_GettingStartedWithPython/Numerical_Work_In_Python.html
@@ -97,6 +97,8 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Your Code
+
Changelog
diff --git a/docs_backup/Module1_GettingStartedWithPython/SiteFormatting.html b/docs_backup/Module1_GettingStartedWithPython/SiteFormatting.html
index f0de554a..bc19030c 100644
--- a/docs_backup/Module1_GettingStartedWithPython/SiteFormatting.html
+++ b/docs_backup/Module1_GettingStartedWithPython/SiteFormatting.html
@@ -109,6 +109,8 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Your Code
+
Changelog
diff --git a/docs_backup/Module2_EssentialsOfPython/Basic_Objects.html b/docs_backup/Module2_EssentialsOfPython/Basic_Objects.html
index 557c12fb..edf36355 100644
--- a/docs_backup/Module2_EssentialsOfPython/Basic_Objects.html
+++ b/docs_backup/Module2_EssentialsOfPython/Basic_Objects.html
@@ -150,6 +150,8 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Your Code
+
Changelog
diff --git a/docs_backup/Module2_EssentialsOfPython/ConditionalStatements.html b/docs_backup/Module2_EssentialsOfPython/ConditionalStatements.html
index 888baa74..1444116b 100644
--- a/docs_backup/Module2_EssentialsOfPython/ConditionalStatements.html
+++ b/docs_backup/Module2_EssentialsOfPython/ConditionalStatements.html
@@ -128,6 +128,8 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Your Code
+
Changelog
diff --git a/docs_backup/Module2_EssentialsOfPython/DataStructures.html b/docs_backup/Module2_EssentialsOfPython/DataStructures.html
index ac16d222..7253a483 100644
--- a/docs_backup/Module2_EssentialsOfPython/DataStructures.html
+++ b/docs_backup/Module2_EssentialsOfPython/DataStructures.html
@@ -125,6 +125,8 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Your Code
+
Changelog
diff --git a/docs_backup/Module2_EssentialsOfPython/DataStructures_III_Sets_and_More.html b/docs_backup/Module2_EssentialsOfPython/DataStructures_III_Sets_and_More.html
index 48fdc84c..df11f991 100644
--- a/docs_backup/Module2_EssentialsOfPython/DataStructures_III_Sets_and_More.html
+++ b/docs_backup/Module2_EssentialsOfPython/DataStructures_III_Sets_and_More.html
@@ -133,6 +133,8 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Your Code
+
Changelog
diff --git a/docs_backup/Module2_EssentialsOfPython/DataStructures_II_Dictionaries.html b/docs_backup/Module2_EssentialsOfPython/DataStructures_II_Dictionaries.html
index ce76be2b..4235cdc3 100644
--- a/docs_backup/Module2_EssentialsOfPython/DataStructures_II_Dictionaries.html
+++ b/docs_backup/Module2_EssentialsOfPython/DataStructures_II_Dictionaries.html
@@ -134,6 +134,8 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Your Code
+
Changelog
diff --git a/docs_backup/Module2_EssentialsOfPython/ForLoops.html b/docs_backup/Module2_EssentialsOfPython/ForLoops.html
index 9947d1ed..9db6cfaf 100644
--- a/docs_backup/Module2_EssentialsOfPython/ForLoops.html
+++ b/docs_backup/Module2_EssentialsOfPython/ForLoops.html
@@ -127,6 +127,8 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Your Code
+
Changelog
diff --git a/docs_backup/Module2_EssentialsOfPython/Functions.html b/docs_backup/Module2_EssentialsOfPython/Functions.html
index af03f7d9..a4c9dd7a 100644
--- a/docs_backup/Module2_EssentialsOfPython/Functions.html
+++ b/docs_backup/Module2_EssentialsOfPython/Functions.html
@@ -136,6 +136,8 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Your Code
+
Changelog
@@ -321,7 +323,7 @@
The def<
The return
Statement
-
In general, any Python object can follow a function’s return
statement. Furthermore, an empty return
statement can be specified, or the return statement of a function can be omitted altogether. In both of these cases, the function will return the ``None`` object.
+
In general, any Python object can follow a function’s return
statement. Furthermore, an empty return
statement can be specified, or the return statement of a function can be omitted altogether. In both of these cases, the function will return the None
object.
# this function returns `None`
# an "empty" return statement
def f():
diff --git a/docs_backup/Module2_EssentialsOfPython/Generators_and_Comprehensions.html b/docs_backup/Module2_EssentialsOfPython/Generators_and_Comprehensions.html
index 38fe390f..f0bd538e 100644
--- a/docs_backup/Module2_EssentialsOfPython/Generators_and_Comprehensions.html
+++ b/docs_backup/Module2_EssentialsOfPython/Generators_and_Comprehensions.html
@@ -140,6 +140,8 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Your Code
+
Changelog
diff --git a/docs_backup/Module2_EssentialsOfPython/Introduction.html b/docs_backup/Module2_EssentialsOfPython/Introduction.html
index 6ddc84dc..1a13e140 100644
--- a/docs_backup/Module2_EssentialsOfPython/Introduction.html
+++ b/docs_backup/Module2_EssentialsOfPython/Introduction.html
@@ -120,6 +120,8 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Your Code
+
Changelog
diff --git a/docs_backup/Module2_EssentialsOfPython/Iterables.html b/docs_backup/Module2_EssentialsOfPython/Iterables.html
index 84fa80eb..c2579c7f 100644
--- a/docs_backup/Module2_EssentialsOfPython/Iterables.html
+++ b/docs_backup/Module2_EssentialsOfPython/Iterables.html
@@ -127,6 +127,8 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Your Code
+
Changelog
diff --git a/docs_backup/Module2_EssentialsOfPython/Itertools.html b/docs_backup/Module2_EssentialsOfPython/Itertools.html
index aa436310..181f2269 100644
--- a/docs_backup/Module2_EssentialsOfPython/Itertools.html
+++ b/docs_backup/Module2_EssentialsOfPython/Itertools.html
@@ -121,6 +121,8 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Your Code
+
Changelog
diff --git a/docs_backup/Module2_EssentialsOfPython/Problems/DifferenceFanout.html b/docs_backup/Module2_EssentialsOfPython/Problems/DifferenceFanout.html
index 43240d2e..bec385da 100644
--- a/docs_backup/Module2_EssentialsOfPython/Problems/DifferenceFanout.html
+++ b/docs_backup/Module2_EssentialsOfPython/Problems/DifferenceFanout.html
@@ -113,6 +113,8 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Your Code
+
Changelog
diff --git a/docs_backup/Module2_EssentialsOfPython/Problems/EncodeAsString.html b/docs_backup/Module2_EssentialsOfPython/Problems/EncodeAsString.html
index bdbe1e80..d8ece4dd 100644
--- a/docs_backup/Module2_EssentialsOfPython/Problems/EncodeAsString.html
+++ b/docs_backup/Module2_EssentialsOfPython/Problems/EncodeAsString.html
@@ -111,6 +111,8 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Your Code
+
Changelog
diff --git a/docs_backup/Module2_EssentialsOfPython/Problems/MarginPercentage.html b/docs_backup/Module2_EssentialsOfPython/Problems/MarginPercentage.html
index 6d1f28a9..1da966be 100644
--- a/docs_backup/Module2_EssentialsOfPython/Problems/MarginPercentage.html
+++ b/docs_backup/Module2_EssentialsOfPython/Problems/MarginPercentage.html
@@ -111,6 +111,8 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Your Code
+
Changelog
diff --git a/docs_backup/Module2_EssentialsOfPython/Problems/MergeMaxDicts.html b/docs_backup/Module2_EssentialsOfPython/Problems/MergeMaxDicts.html
index 11a9aca3..6d2e1bdf 100644
--- a/docs_backup/Module2_EssentialsOfPython/Problems/MergeMaxDicts.html
+++ b/docs_backup/Module2_EssentialsOfPython/Problems/MergeMaxDicts.html
@@ -120,6 +120,8 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Your Code
+
Changelog
diff --git a/docs_backup/Module2_EssentialsOfPython/Problems/Palindrome.html b/docs_backup/Module2_EssentialsOfPython/Problems/Palindrome.html
index fddcfe82..660f4043 100644
--- a/docs_backup/Module2_EssentialsOfPython/Problems/Palindrome.html
+++ b/docs_backup/Module2_EssentialsOfPython/Problems/Palindrome.html
@@ -112,6 +112,8 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Your Code
+
Changelog
diff --git a/docs_backup/Module2_EssentialsOfPython/Scope.html b/docs_backup/Module2_EssentialsOfPython/Scope.html
index 7f2b21aa..4684dd87 100644
--- a/docs_backup/Module2_EssentialsOfPython/Scope.html
+++ b/docs_backup/Module2_EssentialsOfPython/Scope.html
@@ -121,6 +121,8 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Your Code
+
Changelog
diff --git a/docs_backup/Module2_EssentialsOfPython/SequenceTypes.html b/docs_backup/Module2_EssentialsOfPython/SequenceTypes.html
index 19324d74..70259204 100644
--- a/docs_backup/Module2_EssentialsOfPython/SequenceTypes.html
+++ b/docs_backup/Module2_EssentialsOfPython/SequenceTypes.html
@@ -128,6 +128,8 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Your Code
+
Changelog
diff --git a/docs_backup/Module2_EssentialsOfPython/Variables_and_Assignment.html b/docs_backup/Module2_EssentialsOfPython/Variables_and_Assignment.html
index 3a7be27a..19e16262 100644
--- a/docs_backup/Module2_EssentialsOfPython/Variables_and_Assignment.html
+++ b/docs_backup/Module2_EssentialsOfPython/Variables_and_Assignment.html
@@ -126,6 +126,8 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Your Code
+
Changelog
diff --git a/docs_backup/Module3_IntroducingNumpy/AccessingDataAlongMultipleDimensions.html b/docs_backup/Module3_IntroducingNumpy/AccessingDataAlongMultipleDimensions.html
index 860c0bed..3e8d92f9 100644
--- a/docs_backup/Module3_IntroducingNumpy/AccessingDataAlongMultipleDimensions.html
+++ b/docs_backup/Module3_IntroducingNumpy/AccessingDataAlongMultipleDimensions.html
@@ -130,6 +130,8 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Your Code
+
Changelog
diff --git a/docs_backup/Module3_IntroducingNumpy/AdvancedIndexing.html b/docs_backup/Module3_IntroducingNumpy/AdvancedIndexing.html
index 29ccefe0..a2b0bbea 100644
--- a/docs_backup/Module3_IntroducingNumpy/AdvancedIndexing.html
+++ b/docs_backup/Module3_IntroducingNumpy/AdvancedIndexing.html
@@ -124,6 +124,8 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Your Code
+
Changelog
diff --git a/docs_backup/Module3_IntroducingNumpy/ArrayTraversal.html b/docs_backup/Module3_IntroducingNumpy/ArrayTraversal.html
index c5ba0f18..282dd400 100644
--- a/docs_backup/Module3_IntroducingNumpy/ArrayTraversal.html
+++ b/docs_backup/Module3_IntroducingNumpy/ArrayTraversal.html
@@ -116,6 +116,8 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Your Code
+
Changelog
diff --git a/docs_backup/Module3_IntroducingNumpy/BasicArrayAttributes.html b/docs_backup/Module3_IntroducingNumpy/BasicArrayAttributes.html
index 11bf076a..917d749a 100644
--- a/docs_backup/Module3_IntroducingNumpy/BasicArrayAttributes.html
+++ b/docs_backup/Module3_IntroducingNumpy/BasicArrayAttributes.html
@@ -115,6 +115,8 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Your Code
+
Changelog
diff --git a/docs_backup/Module3_IntroducingNumpy/BasicIndexing.html b/docs_backup/Module3_IntroducingNumpy/BasicIndexing.html
index 8f506440..ea2dca95 100644
--- a/docs_backup/Module3_IntroducingNumpy/BasicIndexing.html
+++ b/docs_backup/Module3_IntroducingNumpy/BasicIndexing.html
@@ -130,6 +130,8 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Your Code
+
Changelog
diff --git a/docs_backup/Module3_IntroducingNumpy/Broadcasting.html b/docs_backup/Module3_IntroducingNumpy/Broadcasting.html
index 242168fe..fca97027 100644
--- a/docs_backup/Module3_IntroducingNumpy/Broadcasting.html
+++ b/docs_backup/Module3_IntroducingNumpy/Broadcasting.html
@@ -129,6 +129,8 @@
Module 3: Problems
Module 4: Object Oriented Programming
Module 5: Odds and Ends
+
Module 6: Testing Your Code
+
Changelog
@@ -602,16 +604,16 @@
Pairwise Distances Using For-Loops