Compare commits
6 Commits
3e3cb00013
...
540fe46f87
Author | SHA1 | Date |
---|---|---|
Jason Zhu | 540fe46f87 | |
Jason Zhu | b1d13ee557 | |
Jason Zhu | 7bd7283b07 | |
Jason Zhu | 0e43a516aa | |
Jason Zhu | 80c26c12ed | |
Jason Zhu | 81069c3f88 |
|
@ -0,0 +1,82 @@
|
||||||
|
# Chapter 6. Improving Functional Tests: Ensuring Isolation and Removing Voodoo Sleeps
|
||||||
|
|
||||||
|
## 6.1 Ensuring Test Isolation in Functional Tests
|
||||||
|
|
||||||
|
* Problem left on scratchpad: Clean up after FT runs
|
||||||
|
* Methods:
|
||||||
|
* 1. In `functional_tests.py` add in codes to delete database
|
||||||
|
* 2. Use class `LiveServerTestCase` (Django 1.4). It will create a test db and start up a dev server for functional tests
|
||||||
|
|
||||||
|
How to use `LiveServerTestCase`:
|
||||||
|
* It's derived from Django's `TestCase` class, so can be run by Django test runner
|
||||||
|
* Django test runner search for all scripts start with `test`
|
||||||
|
|
||||||
|
What to do?:
|
||||||
|
1. Create a `functional_test` directory, i.e. create a *functional_test* app
|
||||||
|
2. rename `functional_test.py` as `tests.py` and move it into `functional_test` directory
|
||||||
|
3. Modify `tests.py` to let it use `LiveServerTestCase` as shown below, and instead of hardcoding visit to localhost port 8000, `LiveserverTestCase` provide attribute `live_server_url`.
|
||||||
|
|
||||||
|
```python
|
||||||
|
class NewVisitorTest(LiveServerTestCase):
|
||||||
|
...
|
||||||
|
|
||||||
|
def test_can_start_a_list_and_retrieve_it_later(self):
|
||||||
|
# Edith has heard about a cool new online to-do app. She goes
|
||||||
|
# to check out its homepage
|
||||||
|
self.browser.get(self.live_server_url)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.1.1 Running Just the Unit Tests
|
||||||
|
|
||||||
|
We can specify what we want to run by
|
||||||
|
|
||||||
|
```
|
||||||
|
python manage.py test functional_tests
|
||||||
|
```
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
```
|
||||||
|
python manage.py test lists
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6.2 Aside: Upgrading Selenium and Geckodriver
|
||||||
|
|
||||||
|
Sometimes, mismatch between firefox and (Selenium + Geckodriver) may result failure in test, as firefox may auto upgrade itself at night. Upgrade Selenium + Geckodriver as follow:
|
||||||
|
|
||||||
|
1. `pip install --upgrade selenium`
|
||||||
|
2. Download new geckodriver
|
||||||
|
|
||||||
|
## 6.3 On Implicit and Explicit Waits, and Voodoo time.sleeps
|
||||||
|
|
||||||
|
Implicit Waits & Explicit Waits:
|
||||||
|
* Implicit Waits: Selenium tries to wait "automatically" for you when it thinks the page is loading. But it's unstable, DON'T USE IT.
|
||||||
|
* Explicit Waits: `time.sleep(...)` in FT. It's hard to set a fixed second (Voodoo Sleep). A while loop with try/except will be more better
|
||||||
|
|
||||||
|
```python
|
||||||
|
from selenium.common.exceptions import WebDriverException
|
||||||
|
|
||||||
|
MAX_WAIT = 10 # Maximum amount of time we're prepared to wait
|
||||||
|
[...]
|
||||||
|
|
||||||
|
def wait_for_row_in_list_table(self, row_text):
|
||||||
|
start_time = time.time()
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
table = self.browser.find_element_by_id('id_list_table')
|
||||||
|
rows = table.find_elements_by_tag_name('tr')
|
||||||
|
self.assertIn(row_text, [row.text for row in rows])
|
||||||
|
return
|
||||||
|
except (AssertionError, WebDriverException) as e:
|
||||||
|
if time.time() - start_time > MAX_WAIT:
|
||||||
|
raise e
|
||||||
|
time.sleep(0.5)
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace all `check_for_row_in_list_table` with `wait_for_row_in_list_table`
|
||||||
|
|
||||||
|
### "BEST PRACTICES" Applied in this Chapter
|
||||||
|
|
||||||
|
* Ensuring test isolation and managing global state
|
||||||
|
* Avoid "voodoo" sleeps (i.e. `time.sleep`)
|
||||||
|
* Don't rely on Selenium's implicit waits
|
|
@ -0,0 +1,43 @@
|
||||||
|
# Chapter 7. Working Incrementally
|
||||||
|
|
||||||
|
Target: our design only allows one global list. Each user should have a separate list.
|
||||||
|
|
||||||
|
TDD technique to learn: adapt existing code using an incremental, step-by-step process which takes you from working state to working state (avoid crashing).
|
||||||
|
|
||||||
|
## 7.1 Small Design When Necessary
|
||||||
|
|
||||||
|
### Not Big Design Up Front
|
||||||
|
|
||||||
|
* Traditional software dev: *Big Design Up Front*
|
||||||
|
* New software dev cycle: Agile, put MVP (Minimum Viable Product) out early, and let design evolve gradually
|
||||||
|
|
||||||
|
Planning for MVP:
|
||||||
|
* Each user can store their own list
|
||||||
|
* A list is made up of several items, with a description as attribute
|
||||||
|
* Saved List should be permanent
|
||||||
|
|
||||||
|
### YAGNI!
|
||||||
|
|
||||||
|
Agile gospel: YAGNI (You Aren't Gonna Need It). Don't overthink about design (e.g. username? list title?) No matter how cool the idea was, you won't end up using it.
|
||||||
|
|
||||||
|
### REST(ish)
|
||||||
|
|
||||||
|
* QUES: Our data structure sit in model of Model-View-Controller (MVC). How should View & Controller work?
|
||||||
|
* ANS: **REST (Representational State Transfer)** is the usual approach to design web-based APIs. But currently we don't stick strickly to it. (Visit Appendix F for full REST API implementation)
|
||||||
|
|
||||||
|
Design of our to-do list app:
|
||||||
|
* REST suggest that URL structure should match our data structure. So each list have its own URL `/list/<list identifier>`
|
||||||
|
* View a list: use GET request. (It's a normal browser visit to the page)
|
||||||
|
* Create a brand new list: a special URL will accept POST requests `lists/new`
|
||||||
|
* Add new item to list: have a separate URL to send POST requests `/lists/<list identifier>/add_item`
|
||||||
|
|
||||||
|
## 7.2 Implementing the New Design Incrementally Using TDD
|
||||||
|
|
||||||
|
![Implementing New Design using TDD](img/7-1.jpg)
|
||||||
|
|
||||||
|
* On top-level, adding new feature by:
|
||||||
|
* 1. new FT
|
||||||
|
* 2. New app code
|
||||||
|
* 3. Refactor code
|
||||||
|
* On UT level:
|
||||||
|
* Add/Modify tests to verify changes we make
|
Binary file not shown.
After Width: | Height: | Size: 195 KiB |
Loading…
Reference in New Issue