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 ===============================================

CS50P 5_Unit Test

By wschen

CS50P 5_Unit Test

CS50P 5_Unit Test presentation covers the importance of testing in programming, different testing methods, and the use of Python test frameworks like pytest. It also showcases examples of successful and failed tests, along with handling errors and assertions.

  • 135