---
# System prepended metadata

title: Python Test

---

# 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)
```
