# Python Test
- 隨時隨地都要能正確執行,關注本身的邏輯,而非外部的關聯
- 標題需要具備好的可讀性、明確、標題與測試的內容精確吻合
- 一個測試只應該關注一件事情,如果受測目標有多種狀況,應該分成好幾個測試去涵蓋所有邏輯
- Arrange = 準備受測物件、參數、預期結果
Act = 執行受測方法
Assert = 驗證執行結果與預測結果是否一致
## 特定的值
關注的值就可以直接給值,不用去假造他。
```python
def call_my_name(name):
return 'Hello ' + name
def test_call_my_name():
assert main.call_my_name('PE') == 'Hello PE'
```
## ANY_STH
測試著重於測試流程,不著重於資料到底是什麼,只要讓他該有的都有,不讓程式環節出錯(某環節可能使用這個值來做一些事),就可以用 `ANY_STH` 。
但如果測試的結果與這個資料有關的話,就不該使用 `ANY_STH` ,因為通常測試結果就是一個特定的值。
下面的比較沒有絕對的對錯,只是通常以值直接顯示時,會比較覺得他是一個重點而關注他的值,以 `ANY_STH` 就不用關注他的值。
```python
def call_my_name(account):
accout = account.strip()
name = find_name(account)
greet(name)
ANY_ACCOUNT = 'PE'
@patch.object(main, 'greet')
@patch.object(main, 'find_name')
def test_call_my_name_1(find_name, greet):
main.call_my_name(ANY_ACCOUNT)
find_name.assert_called_once_with(ANY_ACCOUNT)
greet.assert_called_once_with(find_name.return_value)
@patch.object(main, 'greet')
@patch.object(main, 'find_name')
def test_call_my_name_2(find_name, greet):
main.call_my_name('PE')
find_name.assert_called_once_with('PE')
greet.assert_called_once_with(find_name.return_value)
```
## `mock.ANY`
> Sometimes you may need to make assertions about some of the arguments in a call to mock, but either not care about some of the arguments or want to pull them individually out of `call_args` and make more complex assertions on them.
>
> [ANY - unittest.mock](https://docs.python.org/3/library/unittest.mock.html#unittest.mock.ANY)
在做 assertion 的時候,如果想要忽略某些 argument,就可以使用 `ANY` 。(只有在做 assertion 的時候用)
```python
def call_my_name(name):
time = datetime.now()
greet(name, time=time)
@patch.object(main, 'greet')
def test_call_my_name(greet):
main.call_my_name(ANY_NAME)
greet.assert_called_once_with(ANY_NAME, time=ANY)
```
## `mock.sentinel`
> The sentinel object provides a convenient way of providing **unique objects** for your tests.
>
> Sometimes when testing you need to test that a specific object is passed as an argument to another method, or returned.
>
> [sentinel ⎯ unittest.mock](https://docs.python.org/3/library/unittest.mock.html#unittest.mock.sentinel)
是一個獨一無二的值,常搭配 `assert_called_with` 使用,用來判斷該值是否作為參數傳遞給某個方法。
用 `sentinel` 的好處是可以不用去真的建出 object,字串、數字都是給資料時很容易給的,但如果想要建出 File Object、Workbook 等 object 就比較不容易,容易讓測試冗長且失焦。
但如果測試某環節可能使用這個值來做一些事,就不該使用 `sentinel` ,而該使用 `ANY_STH` 或是直接給特定的值。
如果該值是回傳值的一部分(回傳多個值、 `side_effect` 、透過 Builder 建起),就很需要用到 `sentinel` 的功能。
回傳多個值:
```python
def call_my_name(account):
name, _ = find_profile(account)
greet(name)
@patch.object(main, 'greet')
@patch.object(main, 'find_profile')
def test_call_my_name(find_profile, greet):
find_profile.return_value = sentinel.name, sentinel.id
main.call_my_name(ANY_ACCOUNT)
greet.assert_called_once_with(sentinel.name)
```
`side_effect` :
```python
def call_my_name(accounts):
for account in accounts:
name = find_name(name)
greet(name)
@patch.object(main, 'greet')
@patch.object(main, 'find_profile')
def test_call_my_name(find_profile, greet):
find_profile.side_effect = [sentinel.name1, sentinel.name2]
main.call_my_name(ANY_ACCOUNT)
assert greet.mock_calls == [sentinel.name1, sentinel.name2]
```
透過 Builder 建起: 有疑慮~~~
```python
def call_my_name(account):
profile = find_profile(account)
greet(profile.name)
@patch.object(main, 'greet')
@patch.object(main, 'find_profile')
def test_call_my_name(find_profile, greet):
find_profile.return_value = Builder() \
.name(sentinel.name) \
.id(sentinel.id) \
.build()
main.call_my_name(ANY_ACCOUNT)
greet.assert_called_once_with(sentinel.name)
```
如果不是特指回傳值的一部分,可以直接取用 `return_value`
```python
def call_my_name(account):
name = find_name(account)
greet(name)
@patch.object(main, 'greet')
@patch.object(main, 'find_name')
def test_call_my_name(find_name, greet):
main.call_my_name(ANY_ACCOUNT)
greet.assert_called_once_with(find_name.return_value)
```