CS50P 5_Unit Test

Why Test?

A function needs to ensure it is well-designed.

Function Test by print()

def main():
    # test combine_str()
    combine_str("東華","大學")

def combine_str(str1, str2):
    """
    合併兩個字串:
    Args: str1 (str): 字串 1
          str2 (str): 字串 2
    print str: str1 + str2
    """
    print(str1 + str2)
    
main()
$ python strTool_1.py 
東華大學

strTool_1.py

Function Test Based on Return Value

def main():
    # test combine_str()
    print(combine_str("東華","大學"))

def combine_str(str1, str2):
    """
    合併兩個字串:
    Args: str1 (str): 字串 1
          str2 (str): 字串 2
    Returns: str: str1 + str2
    """
    return(str1 + str2)
    
main()
$ python strTool_2.py 
東華大學

strTool_2.py

Function to be Used

def main():
    # test combine_str()
    print(combine_str("東華","大學"))

def combine_str(str1, str2):
    return(str1 + str2)

if __name__ == "__main__":
    main()
$ python strTool.py 
東華大學

strTool.py

Using the strTool function with import package

import strTool
print(strTool.combine_str("測試 ","Combine_str"))
$ python strTool_use_1.py 
測試 Combine_str

strTool_use_1.py

import strTool as st
print(st.combine_str("測試 ","Combine_str"))
$ strTool_use_2.py
測試 Combine_str

strTool_use_2.py

Using the strTool function with a package alias

strTool_use_3.py
from strTool import combine_str
print(combine_str("測試 ","Combine_str"))
$ strTool_use_3.py
測試 Combine_str

Using the strTool function with
import package method

Python Test Framework

Many Functions, Many Tests?
Pytest is a fully-featured, mature Python testing tool that helps you write better programs.

MathTool Square Function

def main():
    print(f"square of 3 is {square(3)}")
    
def square(num):
    """回傳平方值
    Args:
        num (int): 數值
    Returns:
        int: 平方後的數值
    """
    return num**2

if __name__ == "__main__":
    main()
$ python mathTool.py
square of 3 is 9

mathTool.py

MathTool:  Test the Square Function

def main():
    test_square()
    
def test_square():
    if square(2) == 4:
        print(f"square of 2 is 4")
    if square(3) == 9:
        print(f"square of 3 is 9")
    
def square(num):
    return num**2

if __name__ == "__main__":
    main()
$ python mathTool_test.py
square of 2 is 4
square of 3 is 9

mathTool_test.py

Function Test Problem

  • How many numbers need to be tested?
  • How many tests pass?
  • How many tests fail?

Assert

If your code returns True, nothing happens.

If your code returns False, an AssertionError is raised.

assert condition, error_message

Example of Assertion

x = "hello"
print(f"x = {x}")
print("======================")

#if condition returns True, then nothing happens
print('assert x == "hello"') #test True
assert x == "hello", "x should be 'hello'"
print("======================")

#if condition returns False, AssertionError is raised
print('assert x == "goodbye"') #test Fail
assert x == "goodbye", "x should be 'hello'"
$ python assert.py 
x = hello
======================
assert x == "hello"
======================
assert x == "goodbye"
Traceback (most recent call last):
  File "/workspaces/87635444/test/unit_test/assert.py", line 12, in <module>
    assert x == "goodbye", "x should be 'hello'"
           ^^^^^^^^^^^^^^

assert.py

Square Function Test Using Assert

from mathTool import square

def main():
    test_square()
    
def test_square():
    assert square(3) == 9
    assert square(1) == 4, "一的平方是一"

if __name__ == "__main__":
    main()
test/unit_test/ $ python mathTool_assert.py 
Traceback (most recent call last):
  File "/workspaces/87635444/test/unit_test/mathTool_assert.py", line 11, in <module>
    main()
  File "/workspaces/87635444/test/unit_test/mathTool_assert.py", line 4, in main
    test_square()
  File "/workspaces/87635444/test/unit_test/mathTool_assert.py", line 8, in test_square
    assert square(1) == 4, "一的平方是一"
           ^^^^^^^^^^^^^^
AssertionError: 一的平方是一

mathTool_assert.py

pytest

File Name : testxxxx.py

Function   : testxxx

Pytest Example: Pass

from mathTool import square

def main():
    test_square()

def test_square():
    assert square(1) == 1
    assert square(3) == 9

if __name__ == "__main__":
    main()

test_math_pass.py

$ pytest
============================================= test session starts ==============================================
platform linux -- Python 3.10.7, pytest-7.1.3, pluggy-1.0.0
rootdir: /workspaces/87635444/test/unit_test
collected 1 item                                                                                               

test_math_pass.py .                                                                                           [100%]

============================================== 1 passed in 0.01s ===============================================

Pytest Example: Failed

from mathTool import square

def main():
    test_square()

def test_square():
    assert square(1) == 2 # assert error
    assert square(3) == 9

if __name__ == "__main__":
    main()

test_math_fail.py

Pytest Fail Result

$ pytest test_math_fail.py
============================================= test session starts ==============================================
platform linux -- Python 3.10.7, pytest-7.1.3, pluggy-1.0.0
rootdir: /workspaces/87635444/test/unit_test
collected 1 item                                                                                               

test_math_fail.py F                                                                                           [100%]

=================================================== FAILURES ===================================================
_________________________________________________ test_square __________________________________________________

    def test_square():
>       assert square(1) == 2
E       assert 1 == 2
E        +  where 1 = square(1)

test_math_fail.py:7: AssertionError
=========================================== short test summary info ============================================
FAILED test_math_fail.py::test_square - assert 1 == 2
============================================== 1 failed in 0.09s ===============================================

Catch AssertionError

from mathTool import square

def main():
    test_square()

def test_square():
    try:
        assert square(1) == 2
    except AssertionError:
        print("1的平方不是2")
    try:
        assert square(3) == 9
    except AssertionError:
        print("3的平方不是9")

if __name__ == "__main__":
    main()
python catch_assert_error.py
1的平方不是2

catch_assert_error.py

Pytest Pass

While the AssertionError was caught, pytrst passes.
pytest catch_assert_error.py
============================================= test session starts ==============================================
platform linux -- Python 3.10.7, pytest-7.1.3, pluggy-1.0.0
rootdir: /workspaces/87635444/test/unit_test
collected 1 item                                                                                               

catch_assert_error.py .                                                                                           [100%]

============================================== 1 passed in 0.01s ===============================================

Multiple Assert

from mathTool import square

def main():
    test_square()

def test_square():
    assert square(2) == 4
    assert square(3) == 9
    assert square(-2) == 4
    assert square(-3) == 9
    assert square(0) == 0

if __name__ == "__main__":
    main()

multiple_assert.py

Multiple Assert Pytest Pass

pytest multiple_assert.py
============================================= test session starts ==============================================
platform linux -- Python 3.10.7, pytest-7.1.3, pluggy-1.0.0
rootdir: /workspaces/87635444/test/unit_test
collected 1 item                                                                                               

multiple_assert.py .                                                                                           [100%]

============================================== 1 passed in 0.01s ===============================================

Different Test Method

from mathTool import square

def main():
    test_square()

def test_positive():
    assert square(1) == 2  # erroe 1 != 2
    assert square(2) == 4
    assert square(3) == 9

def test_negative():
    assert square(-1) == 1
    assert square(-2) == 4
    assert square(-3) == 9

def test_zero():
    assert square(0) == 0

if __name__ == "__main__":
    main()

different_test.py

Different Test Method Fail.

$ pytest different_test.py
============================================= test session starts ==============================================
platform linux -- Python 3.10.7, pytest-7.1.3, pluggy-1.0.0
rootdir: /workspaces/87635444/test/unit_test
collected 3 items                                                                                              

different_test.py F..                                                                                         [100%]

=================================================== FAILURES ===================================================
________________________________________________ test_positive _________________________________________________

    def test_positive():
>       assert square(1) == 2
E       assert 1 == 2
E        +  where 1 = square(1)

different_test.py:7: AssertionError
=========================================== short test summary info ============================================
FAILED different_test.py::test_positive - assert 1 == 2
========================================= 1 failed, 2 passed in 0.08s ==========================================
test/unit_test/ $ 

Function has an error

def main():
    square("aaa")

def square(num):
    return num**2

if __name__ == "__main__":
    main()
python math_tool.py 
Traceback (most recent call last):
  File "/workspaces/87635444/test/unit_test/math_tool.py", line 8, in <module>
    main()
  File "/workspaces/87635444/test/unit_test/math_tool.py", line 2, in main
    square("aaa")
  File "/workspaces/87635444/test/unit_test/math_tool.py", line 5, in square
    return num**2
TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'int'

math_tool.py

Error in assert method - getting error

from mathTool import square
import pytest

def main():
    test_square()

def test_positive():
    assert square(1) == 1
    assert square(2) == 4
    assert square(3) == 9

def test_str():
    assert  square("cat") # will raise TypeError

if __name__ == "__main__":
    main()

math_tool_error.py

Pytest: Troubleshooting Errors and Failures

pytest math_tool_error.py
============================================= test session starts ==============================================
platform linux -- Python 3.10.7, pytest-7.1.3, pluggy-1.0.0
rootdir: /workspaces/87635444/test/unit_test
collected 2 items                                                                                              

math_tool_error.py .F                                                                                          [100%]

=================================================== FAILURES ===================================================
___________________________________________________ test_str ___________________________________________________

    def test_str():
>       assert  square("cat")

math_tool_error.py:13: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

num = 'cat'

    def square(num):
>       return num**2
E       TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'int'

math_tool_error.py:5: TypeError
=========================================== short test summary info ============================================
FAILED math_tool_error.py::test_str - TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'int'
========================================= 1 failed, 1 passed in 0.14s ==========================================

Pytest: Catching Expected Custom Error

from mathTool import square
import pytest

def main():
    test_square()

def test_positive():
    assert square(1) == 1
    assert square(2) == 4
    assert square(3) == 9

def test_str():
    with pytest.raises(TypeError):
        assert  square("cat") # will raise TypeError

if __name__ == "__main__":
    main()

catch_error.py

Pass: pytest catches expected custom error

$ pytest catch_error.py
============================================= test session starts ==============================================
platform linux -- Python 3.10.7, pytest-7.1.3, pluggy-1.0.0
rootdir: /workspaces/87635444/test/unit_test
collected 2 items                                                                                              

catch_error.py ..                                                                                          [100%]

============================================== 2 passed in 0.01s ===============================================

Function with Default Value

def main():
    print(hello())
    print(hello("Ted"))

def hello(to="world"):
    return f"hello, {to}"

if __name__ == "__main__":
    main()
$ python hello.py
hello, world
hello, Ted

hello.py

Test Hello with Default Value and List

from hello import hello

def test_default():
    assert hello() == "hello, world"

def test_argument():
    assert hello("David") == "hello, David"

def test_arguments():
    for name in ["Hermione", "Harry", "Ron"]:
        assert hello(name) == f"hello, {name}"

test_hello.py

Pytest test_hello.py: PASS

$ pytest test_hello.py
============================================= test session starts ==============================================
platform linux -- Python 3.10.7, pytest-7.1.3, pluggy-1.0.0
rootdir: /workspaces/87635444/test/unit_test
collected 3 items                                                                                              

test_hello.py ...                                                                                        [100%]

============================================== 3 passed in 0.01s ===============================================