# Topic 41 - 42
###### tags: `The Pragmatic Programmer`
## Topic 41 Test to Code
> 🧠 Testing Is Not About Finding Bugs
We believe that the major benefits of testing happen when you ==think about and write the tests==, not when you run them.
---
### THINKING ABOUT TESTS
> How do you know that what you’re about to do is a good thing 🤔❓
The answer is that you can’t know that. No one can. ==But thinking about tests can make it more likely.==
<br/>
**TASK**: Query the database to return a list of people who watch more than 10 videos a week on your “world’s funniest dishwashing videos”site.
```=pyhton
def return_avid_viewers do
# ... hmmm ...
end
```
Start by imagining that you’d finished writing the function and now had to test it. How would you do that?
🧠 **Thinking process**
* test data ?
* some frameworks can help? or in our case need to query database?
* passing the database instance into our function rather than using some global one.
⬇️⬇️⬇️
```=pyhton
def return_avid_users(db) do
```
* how we’d populate that test data?
* we look at the database schema for fields (`opened_video` and `completed_video`.)
* But we don’t know what the requirement means, and our business contact is out.
* pass in the name of the field (which will allow us to test what we have, and potentially change it later):
⬇️⬇️⬇️
```=pyhton
def return_avid_users(db, qualifying_field_name) do
```
<br/>
---
### TESTS DRIVE CODING
Thinking about writing a test for our method made us look at it from the outside, as if we were a client of the code, and not its author.
> ⭐️ ==A Test Is the First User of Your Code==
the biggest benefit: testing is vital feedback that guides your coding.
Before you can test something, you have to understand it. (🌚 sounds silly 🌚)
* Starting to code without a clear understanding leads to longer and complex code with inadequate boundary conditions and error handling. Testing helps simplify the logic and identify patterns, allowing for better structuring of the function. Think about testing boundary and error conditions beforehand to simplify the code.
#### Test-Driven Development(test-driven development or TDD or test-first development.)
1. Decide on a small piece of functionality
2. Write a passing test(確保測試程式可執行)
3. Run all tests, verifying that the new test fails
4. Write minimal code to pass the test
5. Refactor the code and re-run the tests to ensure they pass.
👍🏻 TDD is beneficial for beginners because it guarantees tests for your code, leading to a continuous focus on testing.
👎 However, we’ve also seen people become slaves to TDD.
- They spend inordinate amounts of time ensuring that they always have 100% test coverage.
- They have lots of redundant tests.
- Their designs tend to start at the bottom and work their way up.
> ⭐️ ==Don’t forget to stop every now and then and look at the big picture.==
It is easy to become seduced by the green "tests passed" message, writing lots of code that doesn’t actually get you closer to a solution.
---
### TDD: YOU NEED TO KNOW WHERE YOU’RE GOING

> “How do you eat an elephant?”
“One bite at a time.”
When you can’t comprehend the whole problem, take small steps, one test at a time. However, this approach can mislead you, encouraging you to focus on and endlessly polish the easy problems while ignoring the real reason you’re coding.
🥸👎🏻 TDD approach: [In 2006, Ron Jeffries began a series of blog posts about his test-driven approach to coding a Sudoku solver.](https://ronjeffries.com/categories/sudoku/)
An alternative approach: [Peter Norvig offers an alternative approach, rather than being driven by tests, he starts with a basic understanding of how these kinds of problems are traditionally solved.](http://norvig.com/sudoku.html)
> ⭐️ Tests can definitely help drive development. But, as with every drive, unless you have a destination in mind, you can end up going in circles.
---
### BACK TO CODE
Component-based development has long been a lofty goal of software development.
#### UNIT TESTING
Hardware chip-level testing and software unit testing are similar in that they both test modules in isolation to verify behavior. Thorough testing under controlled conditions helps gain confidence in how a module will perform.
#### TESTING AGAINST CONTRACT
We want to write test cases that ensure that a given unit honors its contract.
- whether the code meets the contract
- whether the contract means what we think it means.
**What does this mean in practice?**
**TASK:** A square root routine.
```=pyhton
pre-conditions:
argument >= 0;
post-conditions:
((result * result) - argument).abs <= epsilon*argument;
```
- Reject negative input.
- Accept input of zero (boundary value).
- Verify that the calculated square root of values between zero and the maximum expressible argument is within a small fraction of the original value (epsilon).
We can write a basic test script to exercise the square root function using its contract and assuming that it does its own pre- and postcondition checking.
```
assertWithinEpsilon(my_sqrt(0), 0)
assertWithinEpsilon(my_sqrt(2.0), 1.4142135624)
assertWithinEpsilon(my_sqrt(64.0), 8.0)
assertWithinEpsilon(my_sqrt(1.0e7), 3162.2776602)
assertRaisesException fn => my_sqrt(-4.0) end
```
In the real world, any nontrivial module is likely to be dependent on a number of other modules.
**EXMAPLE**: We have a module A that uses a DataFeed and a LinearRegression.
1. DataFeed’s contract, in full
2. LinearRegression’s contract, in full
3. A’s contract, which relies on the other contracts but does not directly expose them.
==subcomponents first -> module itself==
This technique is a great way to reduce debugging effort: we can quickly concentrate on the likely source of the problem within module A, and not waste time reexamining its subcomponents.
#### AD HOC TESTING(臨時測試)
Not to be confused with “odd hack”!Ad-hoc testing is when we run poke at our code manually.
At the end of the debugging session, you need to formalize this ad hoc test.
#### BUILD A TEST WINDOW
Despite the best sets of tests, bugs can still surface in production environments.
- Log files
- a diagnostic control window. ”hot-key” sequence or magic URL.
- a feature switch
#### A CULTURE OF TESTING
You really only have a few choices:
- Test First 👍(This is probably the best choice in most circumstances, as it ensures that testing happens.)
- Test During
- Test Never
==A culture of testing means all the tests pass all the time.== Ignoring consistently failing tests leads to ignoring all tests, starting a vicious spiral.
> ⭐️ Test Your Software, or Your Users Will
## Topic 42 Property-Based Testing
If you write the original code and you write the tests, is it possible that an incorrect assumption could be expressed in both ❓
The code passes the tests, because it does what it is supposed to based on your understanding.
- Have different people write tests and the code under test. 👎🏻
- Computer does some testing for you 👍
---
### CONTRACTS(合約), INVARIANTS(不變數), AND PROPERTIES(屬性)
> ⭐️ Use Property-Based Tests to Validate Your Assumptions
- CONTRACTS (you feed it input, and it will make certain guarantees about the outputs it produces.)
- INVARIANTS(state remain true)
- PROPERTIES: (CONTRACTS and INVARIANTS)
**TASK**: tests for sorted list.
- PROPERTIES:
1. the sorted list is the same size as the original.
2. no element in the result can be greater than the one that follows it.
```=pyhton
from hypothesis import given
import hypothesis.strategies as some
@given(some.lists(some.integers()))
def test_list_size_is_invariant_across_sorting(a_list):
original_length = len(a_list)
a_list.sort()
assert len(a_list) == original_length
@given(some.lists(some.text()))
def test_sorted_result_is_ordered(a_list):
a_list.sort()
for i in range(len(a_list) - 1):
assert a_list[i] <= a_list[i + 1]
```
RESULT:
```
$ pytest sort.py
======================= test session starts
========================
...
plugins: hypothesis-4.14.0
sort.py .. [100%]
===================== 2 passed
```
Not much drama there.
:::spoiler hypothesis?
Hypothesis is an advanced testing library for Python. It lets you write tests which are parametrized by a source of examples, and then generates simple and comprehensible examples that make your tests fail. This lets you find more bugs in your code with less work.
:::
---
### TEST DATA GENERATION 測試資料的生成
We can specify the test data we want to generate.
```
@given(some.lists(some.integers()))
```
⬇️⬇️⬇️
```
@given(some.integers(min_value=5, max_value=10).map(lambda x: x *
2))
```
---
### FINDING BAD ASSUMPTIONS
**TASK**: A simple order processing and stock control system. Warehouse. We can query a warehouse to see if something is in stock, remove things from stock, and get the current stock levels.
```=python
class Warehouse:
def __init__(self, stock):
self.stock = stock
<!-- 是否有庫存 -->
def in_stock(self, item_name):
return (item_name in self.stock) and (self.stock[item_name] > 0)
<!-- 從庫存中移除 -->
def take_from_stock(self, item_name, quantity):
if quantity <= self.stock[item_name]:
self.stock[item_name] -= quantity
else:
raise Exception("Oversold {}".format(item_name))
<!-- 從庫存數量-->
def stock_count(self, item_name):
return self.stock[item_name]
```
TEST: ✅ PASS
```=pyhton
def test_warehouse():
wh = Warehouse({"shoes": 10, "hats": 2, "umbrellas": 0})
assert wh.in_stock("shoes")
assert wh.in_stock("hats")
assert not wh.in_stock("umbrellas")
wh.take_from_stock("shoes", 2)
assert wh.in_stock("shoes")
wh.take_from_stock("hats", 2)
assert not wh.in_stock("hats")
```
TASK: A function that processes a request to order
items from the warehouse.
```=pyhton
def order(warehouse, item, quantity):
if warehouse.in_stock(item):
warehouse.take_from_stock(item, quantity)
return ( "ok", item, quantity )
else:
return ( "not available", item, quantity )
```
TEST: ✅ PASS
```=pyhton
def test_order_in_stock():
wh = Warehouse({"shoes": 10, "hats": 2, "umbrellas": 0})
status, item, quantity = order(wh, "hats", 1)
assert status == "ok"
assert item == "hats"
assert quantity == 1
assert wh.stock_count("hats") == 1
def test_order_not_in_stock():
wh = Warehouse({"shoes": 10, "hats": 2, "umbrellas": 0})
status, item, quantity = order(wh, "umbrellas", 1)
assert status == "not available"
assert item == "umbrellas"
assert quantity == 1
assert wh.stock_count("umbrellas") == 0
def test_order_unknown_item():
wh = Warehouse({"shoes": 10, "hats": 2, "umbrellas": 0})
status, item, quantity = order(wh, "bagel", 1)
assert status == "not available"
assert item == "bagel"
assert quantity == 1
```
#### everything looks fine. let’s add some property tests.
1. stock = taken items + remaining items;
In the following test, we run our test with the item parameter chosen randomly from "hat" or "shoe" and the quantity chosen from 1 to 4:
```=python
@given(item = some.sampled_from(["shoes", "hats"]),
quantity = some.integers(min_value=1, max_value=4))
def test_stock_level_plus_quantity_equals_original_stock_level(item,
quantity):
wh = Warehouse({"shoes": 10, "hats": 2, "umbrellas": 0})
initial_stock_level = wh.stock_count(item)
(status, item, quantity) = order(wh, item, quantity)
if status == "ok":
assert wh.stock_count(item) + quantity == initial_stock_level
```
TEST:
:::danger
🚫 Fail: we tried to remove three hats from the warehouse, but it only has two in stock.
:::
==Our property testing found a faulty assumption==: our in_stock function only checks that there’s at least one of the given item in stock. Instead we need to make sure we have enough to fill the order👍 :
```=python
def in_stock(self, item_name, quantity):
» return (item_name in self.stock) and (self.stock[item_name] >=
quantity)
```
```=python
def order(warehouse, item, quantity):
» if warehouse.in_stock(item, quantity):
warehouse.take_from_stock(item, quantity)
return ( "ok", item, quantity )
else:
return ( "not available", item, quantity )
```
:::spoiler Detail
```=
$ pytest stock.py
. . .
stock.py:72:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
stock.py:76: in test_stock_level_plus_quantity_equals_original_stock_level
(status, item, quantity) = order(wh, item, quantity)
stock.py:40: in order
warehouse.take_from_stock(item, quantity)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <stock.Warehouse object at 0x10cf97cf8>, item_name = 'hats'
quantity = 3
def take_from_stock(self, item_name, quantity):
if quantity <= self.stock[item_name]:
self.stock[item_name] -= quantity
else:
> raise Exception("Oversold {}".format(item_name))
E Exception: Oversold hats
stock.py:16: Exception
---------------------------- Hypothesis ----------------------------
Falsifying example:
test_stock_level_plus_quantity_equals_original_stock_level(
item='hats', quantity=3)
```
:::
---
### PROPERTY-BASED TESTS OFTEN SURPRISE YOU
It’s powerful because you set up some rules for generating inputs, set up some assertions for validating output, and then just let it rip.
---
### PROPERTY-BASED TESTS ALSO HELP YOUR DESIGN
:::success
⭐️ A unit test is the first client of your API.
⭐️ Property-based tests make you think about your code in terms of invariants and contracts.
:::
Try using both.
---
### EXERCISES
1. Look back at the warehouse example. Are there any other properties that you can test?
:::spoiler ANSWER
One property we can test is that an order succeeds if the warehouse has enough items on hand. We can generate orders for random quantities of items, and verify that an "OK" tuple is returned if the warehouse had stock.
:::
2. Your company ships machinery. Each machine comes in a crate, and each crate is rectangular. The crates vary in size. Your job is to write some code to pack as many crates as possible in a single layer that fits in the delivery truck. The output of your code is a list of all the crates. For each crate, the list gives the location in the truck, along with the width and height. What properties of the output could be tested?
:::spoiler ANSWER
- Do any two crates overlap?
- Does any part of any crate exceed the width or length of the truck?
- Is the packing density (area used by crates divided by the area of the truck bed) less than or equal to 1?
- If it’s part of the requirement, does the packing density exceed the minimum acceptable density?
:::