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