-
Notifications
You must be signed in to change notification settings - Fork 1
Added lessons #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
e8d9814
dff91f3
06550df
4996877
1ae37bb
aed9fd0
c7ca8ed
7b0fdb0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| --- | ||
| title: Introduction | ||
| teaching: 5 | ||
| exercises: 0 | ||
| questions: | ||
| - "Whats is Pytest?" | ||
| - "Why test?" | ||
| objectives: | ||
| - "Understand the place of testing in a scientific workflow." | ||
| - "Understand that testing has many forms." | ||
| keypoints: | ||
| - "Tests check whether the observed result, from running the code, is what was expected ahead of time." | ||
| - "Tests should ideally be written before the code they are testing is written, however some tests must be written after the code is written." | ||
| - "Assertions and exceptions are like alarm systems embedded in the software, guarding against exceptional bahavior." | ||
| - "Unit tests try to test the smallest pieces of code possible, usually functions and methods." | ||
| - "Integration tests make sure that code units work together properly." | ||
| - "Regression tests ensure that everything works the same today as it did yesterday." | ||
| --- | ||
|
|
||
| # What is Pytest? | ||
|
|
||
| <center><img src="https://realpython.com/cdn-cgi/image/width=1920,format=auto/https://files.realpython.com/media/Intermediate-Advanced-PyTest-Features_Watermarked.43fb169e7121.jpg" alt="Pytest" style="width:576px;height:324px;"><p><a href="https://realpython.com/pytest-python-testing/">Via Real Python</a></p></center> | ||
|
|
||
| Testing the code is a crucial task that confirms that the code is working correctly and meets the quality standards for customers and, in this case, for analysis. Automated tests are important to confirm that code is working correctly. This is called **defensive programming** and the most common way to do it is to add alarms and tests into our code so that it checks itself. | ||
|
|
||
| At its core, Pytest follows the "convention over configuration" principle, which means it provides sensible defaults and encourages a standardized structure for tests. This allows you to focus on writing test logic rather than dealing with complex setup or boilerplate code. | ||
|
|
||
| # Why Test? | ||
|
|
||
| “Trying to improve the quality of software by doing more testing is like trying to lose weight by weighting yourself more often.” - Steve McConnell | ||
|
|
||
| - Testing won’t correct a buggy code | ||
| - Testing will tell you were the bugs are… | ||
| - … if (and only if) the test cases cover the scenarios that cause the bugs or occur. | ||
|
|
||
| Also, automated tests only test a narrow interpretation of quality software development. They do not help test that your software is useful and help solves a users’ problem. | ||
|
|
||
| There are many ways to test software, such as: | ||
|
|
||
| - Assertions | ||
| - Exceptions | ||
| - Unit Tests | ||
| - Regression Tests | ||
| - Integration Tests | ||
|
|
||
| *Exceptions and Assertions*: While writing code, `exceptions` and `assertions` | ||
| can be added to sound an alarm as runtime problems come up. These kinds of | ||
| tests, are embedded in the software iteself and handle, as their name implies, | ||
| exceptional cases rather than the norm. | ||
|
|
||
| *Unit Tests*: Unit tests investigate the behavior of units of code (such as | ||
| functions, classes, or data structures). By validating each software unit | ||
| across the valid range of its input and output parameters, tracking down | ||
| unexpected behavior that may appear when the units are combined is made vastly | ||
| simpler. | ||
|
|
||
| *Regression Tests*: Regression tests defend against new bugs, or regressions, | ||
| which might appear due to new software and updates. | ||
|
|
||
| *Integration Tests*: Integration tests check that various pieces of the | ||
| software work together as expected. | ||
|
|
||
| ## 'pytest' vs 'unittest'? | ||
|
|
||
| Python has a built in framework for testing called ``unittest``. It is based on the Java x-unit style framework. Unittest requires developers to create classes derived from the TestCase module and then define the test cases as methods in the class. This means that there is a lot of boilerware code required to execute the tests, in contrast to Pytest, which is more condensed and easier to write. | ||
|
YarelisAcevedo marked this conversation as resolved.
|
||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,220 @@ | ||
| --- | ||
| title: Assertions and Exceptions | ||
| teaching: 10 | ||
| exercises: 0 | ||
| questions: | ||
| - "How can we compare observed and expected values?" | ||
| - "How do I handle unusual behavior while the code runs?" | ||
| objectives: | ||
| - "Assertions can halt execution if something unexpected happens." | ||
| - "Assertions are the building blocks of tests." | ||
| - "Learn when to use exceptions and what exceptions are available" | ||
| keypoints: | ||
| - "The `assert` keyword is used to set an assertion." | ||
| - "Assertions halt execution if the argument is false." | ||
| - "Assertions do nothing if the argument is true." | ||
| - "Exceptions are effectively specialized runtime tests" | ||
| - "Exceptions can be caught and handled with a try-except block" | ||
| --- | ||
|
|
||
| Pytest provides a rich set of built-in assertion statements that you can use to check conditions in your tests. These assertions make it easy to express and verify the expected outcomes of your code. The assert keyword in python has the | ||
| following behavior: | ||
|
|
||
| ```python | ||
| >>> assert True == False | ||
|
YarelisAcevedo marked this conversation as resolved.
|
||
| ``` | ||
|
|
||
| ~~~ | ||
| Traceback (most recent call last): | ||
| File "<stdin>", line 1, in <module> | ||
| AssertionError | ||
| ~~~ | ||
| {: .output} | ||
|
|
||
| ```python | ||
| >>> assert True == True | ||
| ``` | ||
|
|
||
| ~~~ | ||
| ~~~ | ||
| {: .output} | ||
|
|
||
| (Nothing happened because it "passed" but mind we are not testing something here really, we are just asserting that True is in fact == True) | ||
|
|
||
| Assertions halt code execution instantly if the comparison is false. | ||
| It does nothing at all if the comparison is true. These are therefore a very | ||
| good tool for guarding the function against foolish (e.g. human) input. | ||
|
|
||
| The advantage of assertions is their ease of use. They are rarely more than one | ||
| line of code. The disadvantage is that assertions halt execution | ||
| indiscriminately and the helpfulness of the resulting error message is usually | ||
| quite limited. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you can add custom error messages to them: |
||
|
|
||
| Another simple example of assertions: | ||
|
|
||
| ```python | ||
| >>> # file name: test_sample.py | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wouldn't add the |
||
| >>> import cmath | ||
| >>> | ||
| >>> def invariant_mass(energy, momentum): | ||
| >>> return cmath.sqrt(energy**2 - momentum**2) | ||
| >>> | ||
| >>> def test_answer(): | ||
| >>> assert invariant_mass(3, 7) == 1 | ||
| ``` | ||
|
|
||
| ```bash | ||
| pytest | ||
| ``` | ||
|
|
||
| ~~~ | ||
| collected 1 item | ||
|
|
||
| test_sample.py F [100%] | ||
|
|
||
| =================================== FAILURES =================================== | ||
| _________________________________ test_answer __________________________________ | ||
|
|
||
| def test_answer(): | ||
| > assert invariant_mass(3, 7) == 1 | ||
| E assert 6.324555320336759j == 1 | ||
| E + where 6.324555320336759j = invariant_mass(3, 7) | ||
|
|
||
| test_sample.py:7: AssertionError | ||
| =========================== short test summary info ============================ | ||
| FAILED test_sample.py::test_answer - assert 6.324555320336759j == 1 | ||
| ============================== 1 failed in 0.13s =============================== | ||
| ~~~ | ||
| {: .output} | ||
|
|
||
|
|
||
| Also, input checking may require decending a rabbit hole of exceptional cases. | ||
| What happens when the input provided to the invariant_mass function is a string, rather | ||
| than a list of numbers? Or even, if it is a NaN result? | ||
|
|
||
| 1. Open the terminal | ||
| 2. Create the following function and name the file ``invariant_mass.py``: | ||
|
|
||
| ```python | ||
| import cmath | ||
|
|
||
| def invariant_mass(energy, momentum): | ||
| return cmath.sqrt(energy**2 - momentum**2) | ||
| ``` | ||
|
|
||
| 3. In the function, insert an assertion that checks whether the input is actually a list. | ||
|
YarelisAcevedo marked this conversation as resolved.
|
||
|
|
||
| > ## Hint | ||
| > | ||
| > Hint: Use the [isinstance function](https://docs.python.org/2/library/functions.html#isinstance). | ||
| {: .callout} | ||
|
|
||
| > ## Input as NaN using numpy | ||
| > | ||
| > What happens if we use numpy instead of cmath in the code? Why we get a `NaN` as an answer? | ||
| > | ||
| > > ## Solution | ||
| > > | ||
| > > In `numpy`, NaN is a float and assigning a NaN sets the imaginary part to zero. From the programmer's point | ||
| > > of view, it is a logical behavior but not mathematically (definition of not a number). This can be a | ||
| > > used as a marker for invalid data, but in this case it doesn't help. | ||
| > {: .solution} | ||
| {: .challenge} | ||
|
|
||
| > ## Testing Near Equality | ||
| > | ||
| > Assertions are also helpful for catching abnormal behaviors, such as those | ||
| > that arise with floating point arithmetic. Using the assert keyword, how could | ||
| > you test whether some value is almost the same as another value? | ||
| > | ||
| > - Use the `assert` keyword to check whether the number a is greater than 2. | ||
| > - Use the `assert` keyword to check that a is equal to 2 within an error of 0.003. | ||
| {: .callout} | ||
|
|
||
| ```python | ||
| from mynum import a | ||
| # greater than 2 assertion here | ||
| # 0.003 assertion here | ||
| ``` | ||
|
|
||
| ## Comparing Numbers within a Tolerance | ||
|
|
||
| Pytest as well as numpy has a built-in class for floating-point comparisons called ``pytest.approx``. | ||
|
|
||
| ```python | ||
| >>> from pytest import approx | ||
| >>> 0.003 + 2 == approx(2.003) | ||
| True | ||
| ``` | ||
|
|
||
| # Exceptions | ||
|
|
||
| Exceptions are more sophisticated than assertions. They are the standard error | ||
|
YarelisAcevedo marked this conversation as resolved.
|
||
| messaging system (unforseen circumstance) in most modern programming languages. Fundamentally, when an | ||
| error is encountered, an informative exception is 'thrown' or 'raised'. Mind that a failed assert, | ||
| also raises an exception `AssertionError`, so be very careful. | ||
|
|
||
| For example, instead of the assertion in the case before, an exception can be | ||
| used. | ||
|
|
||
| ```python | ||
| import cmath | ||
|
|
||
| def invariant_mass(energy, momentum): | ||
| if (energy**2 < momentum**2).any(): | ||
| raise Exception("Energy has to be greater than momentum") | ||
|
|
||
| return cmath.sqrt(energy**2 - momentum**2) | ||
| ``` | ||
|
|
||
| Once an exception is raised, it will be passed upward in the program scope. | ||
| An exception be used to trigger additional error messages or an alternative | ||
| behavior rather than immediately halting code | ||
| execution, the exception can be 'caught' upstream with a try-except block. | ||
| When wrapped in a try-except block, the exception can be intercepted before it reaches | ||
| global scope and halts execution. | ||
|
|
||
| To add information or replace the message before it is passed upstream, the try-catch | ||
| block can be used to catch-and-reraise the exception: | ||
|
|
||
| ```python | ||
| import cmath | ||
|
|
||
| def invariant_mass(energy, momentum): | ||
| try: | ||
| if (energy**2 < momentum**2).any(): | ||
| raise ValueError("Energy has to be greater than momentum") | ||
|
|
||
| except TypeError: | ||
| raise TypeError("Please insert a number or list") | ||
| ``` | ||
|
|
||
| If an alternative behavior is preferred, the exception can be disregarded and a | ||
| responsive behavior can be implemented like so: | ||
|
|
||
| ```python | ||
| import cmath | ||
|
|
||
| def invariant_mass(energy, momentum): | ||
| try: | ||
| if (energy**2 < momentum**2).any(): | ||
| raise ValueError("Energy has to be greater than momentum") | ||
|
|
||
| elif (energy < 0).any() or (momentum < 0).any(): | ||
| raise ValueError("Energy and momentum must be positive") | ||
|
|
||
| else: | ||
| return cmath.sqrt(energy**2 - momentum**2) | ||
|
|
||
| except TypeError: | ||
| raise TypeError("Please insert a number or list") | ||
| ``` | ||
|
|
||
| > ## What else could go wrong? | ||
| > | ||
| > 1. Think of some other type of exception that could be raised by the try | ||
| > block. | ||
| > 2. Guard against it by adding an except clause. | ||
| > 3. Use the mean function in three different ways, so that you cause each | ||
| > exceptional case. | ||
| {: .callout} | ||
Uh oh!
There was an error while loading. Please reload this page.