+1

Python Unit Testing

Testing là một phần cũng khá là quan trọng trong quá trình phát triển phần mềm, việc kết hợp song song viết unittes và code giúp cho chúng ta có thể hạn chế được rất nhiều các bug tiềm ẩn có thể xảy ra. Trong bài viết này sẽ cung cấp cho bạn đọc một số kinh nghiệm, cũng như là vài ví dụ nhỏ khi mà viết unittest bằng ngôn ngữ Python.

Ở trong các ví dụ demo cho việc test này mình có sử dụng unittest, đây là một Unit testing framework khá là mạnh và nổi bật của ngôn ngữ python, bên cạnh việc hỗ trợ các phương pháp test truyền thống unittest còn hỗ trợ cả việc test automation, sharing setup và shutdown code cho việc thực hiện test, gom nhóm các test thành các bộ (collections),...

Ngoài ra để hiệu quả hơn khi làm việc với unittest thì unittest còn cung cấp thêm cho chúng ta một vài các khái niệm test hướng đối tượng sau:

  • Test fixture

Một test fixture sẽ là việc setup những cần thiết trước khi bắt đầu thực thi test và cleanup sau khi quá trình test được hoàn tất

  • Test case

Một test case là một individual unit testing, individual unit testing này chị trách nhiệm
kiểm trả kết so sánh kết quả expect và kết quả thực tế khi thực thi mã lệnh test, unittest lib cũng cung cấp cho chúng ta một base class TestCase, base class này được sử dụng để khởi tạo những test case khác nhau

  • Test suite

Một test suite là một collection các test case, test suite được sử dụng để gom nhóm các test nên được thực thi cùng nhau

  • Test runner

Một test runner là một component làm nhiệm vụ didueef phối (orchestrates) thực thi các test và cuối cùng là gửi kết quả outcome đến cho user Test runner có thể sử dụng UI, textual interface hoặc thậm chí là return một vài giá trị đặc biệt khi thực thi các mã lệnh test

Một vài rule cơ bản giúp cho unittest sẽ tốt hơn :

  • Một testing unit nên chỉ tập trung vào một phần nhỏ của 1 chức năng
  • Mỗi một unittest nên độc lập hoàn toàn với các unittest còn lại, sau mỗi lần chạy test data mock, function mock nên được clearly
  • Trước khi chạy test total, nên chạy độc lập từng module test một và đảm bảo sẽ pass một cách thành công
  • Có thể sử dụng tên dài cho function test

Bắt đầu test bằng unittest....

1. Test Setup và Teardown với unittest.TestCase

Phương thức setUp sẽ được chạy mỗi trước mỗi lần mà chúng ta excute test unit, tức là trước khi chạy vào unittest thì setUp sẽ được call và chạy. Trái với method setUp, phương thức tearDown sẽ được call và chạy mỗi khi chúng ta thực thì xong test. Hai methods kể trên là những optional hay dùng khi triển khai một unittest, thông thường khi thực hiện viết test chúng ta hay kế thừa lại class TestCase, trong class này thì cũng có sẵn các 2 method setUp và tearDown tuy nhiên thì chúng là những phương thức rỗng bạn có thể implement lại 2 method này để tránh việc phát sinh các exceptions ko mong muốn.


   import unittest

    class TestExportData(unittest.TestCase):

        def setUp(self):
          super(TestExportData, self).setUp()
          self.mock_data = {}

        def test(self):
          self.assertEqual(len(self.mock_data), 5)

        def tearDown(self):
          super(TestExportData, self).tearDown()
          self.mock_data = []

    if __name__ == '__main__':
        unittest.main()

2. Asserting on Exceptions

Bạn có thể kiểm tra xem một hàm có throws ra một ngoại lệ hay không với built-in unittest thông qua hai phương pháp sau:

Sử dụng context manager:


import unittest

def division_function(dividend, divisor):
  return dividend / divisor


class MyTestCase(unittest.TestCase):

  def test_using_context_manager(self):
      with self.assertRaises(ZeroDivisionError) as exc:
      x = division_function(1, 0)

    self.assertEqual(exc.exception.message, 'integer division by zero')

Providing a callable function

import unittest

def division_function(dividend, divisor):
  return dividend / divisor

class MyTestCase(unittest.TestCase):

  def test_passing_function(self):
    self.assertRaises(ZeroDivisionError, division_function, 1, 0)

Ở ví dụ trên exception cần check là tham số đầu tiên và một function gọi được chuyển dưới dạng tham số thứ hai

3. Testing Exceptions

Thông thường khi viết code chúng ta sẽ không tránh khỏi được việc truyền sai params vào function, điều này có thể gây ra lỗi trong quá trình chạy code và gây crashhh app. Để giảm thiểu được tối đa vấn đề này chúng ta có thể tạo một class đảm nhận việc handle các exceptions khi chúng xảy ra


class HandleInvalidInputException(Exception):
     pass

class exception sẽ được raised khi mà params chúng ta truyền vào bị invalid, tiếp đó sẽ khởi tạo một function như bên dưới


class HandleInvalidInputException(Exception):
  pass

def convert2number(random_input):
  try:
    input = int(random_input)
  except ValueError:
    raise HandleInvalidInputException("Expected an integer!")
  return input

Để kiểm tra xem khi nào exception được raised chúng ta có thể sử dựng assertRaises để check, assertRaises được sử dụng theo cách như sau:


import unittest

class HandleInvalidInputException(Exception):
  pass

def convert2number(random_input):
  try:
    input = int(random_input)
  except ValueError:
    raise HandleInvalidInputException("Expected an integer!")
  return input



class ExceptionTestCase(unittest.TestCase):

  def test_wrong_input_string(self):
    self.assertRaises(HandleInvalidInputException, convert2number, "not a number")

  def test_correct_input(self):
    try:
      result = convert2number("56")
      self.assertIsInstance(result, int)
    except HandleInvalidInputException:
      self.fail()

**3. Mocking functions **

Để có thể mock một function trong python chúng ta có thể sử dụng create_autospec, hàm này sẽ mock object theo specs của chúng.


def multiply(a, b):
    return a * b


from custom_math import multiply

def multiples_of(integer, *args, num_multiples=0, **kwargs):
 multiples = []

 for x in range(1, num_multiples + 1):
     multiple = multiply(integer,x, *args, **kwargs)
     multiples.append(multiple)

 return multiples

Nhiệm vụ của chúng ta bây giờ là test multiples_of bằng cách mocking multiply, ví dụ phía bên dưới sẽ sử dụng thư viện unittest để làm nhiệm vụ này :


import unittest
import custom_math

from unittest.mock import create_autospec
from process_math import multiples_of

custom_math.multiply = create_autospec(custom_math.multiply)

class TestCustomMath(unittest.TestCase):

     def test_multiples_of(self):
         multiples = multiples_of(3, num_multiples=1)
         custom_math.multiply.assert_called_with(3, 1)

    def test_multiples_of_with_bad_inputs(self):
         with self.assertRaises(TypeError) as e:
         multiples_of(1, "extra arg", num_multiples=1) # raise a TypeError

Bạn đọc tham khảo thêm tại :

[1] https://docs.python.org/3/library/unittest.html

[2] https://docs.python-guide.org/writing/tests/


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí