django的單元測試框架unittest、覆蓋率


django的單元測試

指定測試范圍:

  • 指定運行某些測試文件./manage.py test --pattern="tests_*.py" -v 2
  • 運行所有測試文件./manage.py test -v 2
  • # Run all the tests in the animals.tests module
    $ ./manage.py test animals.tests
    
  • # Run all the tests found within the 'animals' package
    $ ./manage.py test animals
    
  • # Run just one test case
    $ ./manage.py test animals.tests.AnimalTestCase
    
  • # Run just one test method
    $ ./manage.py test animals.tests.AnimalTestCase.test_animals_can_speak

提高測試速度

Running tests in parallel

As long as your tests are properly isolated, you can run them in parallel to gain a speed up on multi-core hardware. See test --parallel.

臨時數據庫:

運行時會臨時創建數據庫,運行結束后刪除,數據庫名字:test_用到的數據庫。猜測是使用migragtion文件創建的。

使用選項--keepdb可以保留測試數據庫

The test --keepdb option preserves the test database between test runs. It skips the create and destroy actions which can greatly decrease the time to run tests.

舉例:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
    @Author      : 
    @Date        : 2019-01-02 22:48
    @Description : 本文件的作用描述
    @File        : test_demo.py 
"""

from unittest.mock import patch
from django.test import TestCase
import os


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


class Calculator(object):
    def add(self, a, b):
        return a+b

    def is_error(self):
        try:
            os.mkdir("11")
            return False
        except Exception as e:
            return True

    def multiple(self, a, b):
        return a * b


# Mock一個函數。
class TestOneFunc(TestCase):
    # 指定函數的寫法:要先寫當前文件名,即module name
    @patch('test_demo.multiple')
    def test_multiple(self, mock_multiple):
        mock_multiple.return_value = 3
        self.assertEqual(multiple(8, 14), 3)


class TestProducer(TestCase):
    def setUp(self):
        self.calculator = Calculator()

    # Mock的函數每次被調用返回不同的值
    @patch.object(Calculator, 'add')
    def test_effect(self, mock_add):
        # 注意list的順序要和后面的順序一一對應
        mock_add.side_effect = [1, 2, 3]
        self.assertEqual(self.calculator.add(8, 14), 1)
        self.assertEqual(self.calculator.add(8, 14), 2)
        self.assertEqual(self.calculator.add(8, 14), 3)

    # Mock一個對象里面的方法
    @patch.object(Calculator, 'add')
    def test_add1(self, mock_add1):
        # 模擬add函數的返回值為3,所以不管真實結果是否是3,都沒有影響
        mock_add1.return_value = 3
        self.assertEqual(self.calculator.add(8, 14), 3)

    # 讓Mock的函數拋出exception
    @patch('os.mkdir')
    def test_exception(self, mock_mkdir):
        mock_mkdir.side_effect = Exception
        self.assertEqual(self.calculator.is_error(), True)

    # Mock多個函數,主要是注意順序
    @patch.object(Calculator, 'add')
    @patch.object(Calculator, 'multiple')
    def test_both(self, mock_multiple, mock_add):
        mock_add.return_value = 1
        mock_multiple.return_value = 2
        self.assertEqual(self.calculator.add(8, 14), 1)
        self.assertEqual(self.calculator.multiple(8, 14), 2)


class ClassTemplate(object):
    def __init__(self, name):
        # __name c是私有變量,只有類本身可以訪問,子類也不可以訪問
        # _name 是受保護變量,類本身和子類可以訪問,from module import *無法導入
        # decorator/迭代器/yield/
        self.__name = name

    def run(self):
        print("my name is %s" % self.__name)

    @staticmethod
    def staticmethod(value):
        print("my value is {}".format(value))


if __name__ == '__main__':
    ClassTemplate('a_name').run()
    ClassTemplate.staticmethod('a_value')

 

from unittest.mock import patch, Mock
from django.test import TestCase, Client, RequestFactory
import os
# import requests
import client


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


class Calculator(object):
    def add(self, a, b):
        return a+b

    def is_error(self):
        try:
            os.mkdir("11")
            return False
        except Exception as e:
            return True

    def multiple(self, a, b):
        return a * b


# Mock一個函數。
class TestOneFunc(TestCase):
    def setUp(self):
        self.client = Client()
        self.request_factory = RequestFactory()
        # self.calculator = Calculator()

    # 指定函數的寫法:要先寫當前文件名,即module name
    @patch('test_demo.multiple')
    def test_multiple(self, mock_multiple):
        # https://docs.djangoproject.com/zh-hans/2.1/topics/testing/advanced/
        # 主要用於構造request對象,與client對比,沒有發生url接口的調用。
        request = self.request_factory.post('/vpc/list', data={'start': 0, 'length': 10})

        # 主要用於模仿url請求,調用並返回請求結果。
        response = self.client.get('/vpc/list')

        mock_multiple.return_value = 3
        self.assertEqual(multiple(8, 14), 3)


class TestProducer(TestCase):
    def setUp(self):
        self.calculator = Calculator()

    # Mock的函數每次被調用返回不同的值
    @patch.object(Calculator, 'add')
    def test_effect(self, mock_add):
        # 注意list的順序要和后面的順序一一對應
        mock_add.side_effect = [1, 2, 3]
        self.assertEqual(self.calculator.add(8, 14), 1)
        self.assertEqual(self.calculator.add(8, 14), 2)
        self.assertEqual(self.calculator.add(8, 14), 3)

    # Mock一個對象里面的方法
    @patch.object(Calculator, 'add')
    def test_add1(self, mock_add1):
        # 模擬add函數的返回值為3,所以不管真實結果是否是3,都沒有影響
        mock_add1.return_value = 3
        self.assertEqual(self.calculator.add(8, 14), 3)

    # 讓Mock的函數拋出exception
    @patch('os.mkdir')
    def test_exception(self, mock_mkdir):
        mock_mkdir.side_effect = Exception
        self.assertEqual(self.calculator.is_error(), True)

    # Mock多個函數,主要是注意順序
    @patch.object(Calculator, 'add')
    @patch.object(Calculator, 'multiple')
    def test_both(self, mock_multiple, mock_add):
        mock_add.return_value = 1
        mock_multiple.return_value = 2
        self.assertEqual(self.calculator.add(8, 14), 1)
        self.assertEqual(self.calculator.multiple(8, 14), 2)


class TestClient(TestCase):

    def test_success_request(self):
        success_send = Mock(return_value='200')
        client.send_request = success_send
        self.assertEqual(client.visit_ustack(), '200')

    def test_fail_request(self):
        fail_send = Mock(return_value='404')
        client.send_request = fail_send
        self.assertEqual(client.visit_ustack(), '404')


class TestClient1(TestCase):

    def test_call_send_request_with_right_arguments(self):
        client.send_request = Mock()
        client.visit_ustack()
        # Mock對象的called屬性表示該mock對象是否被調用過。
        self.assertEqual(client.send_request.called, True)
        # Mock對象的call_args表示該mock對象被調用的tuple,tuple的每個成員都是一個mock.call對象。
        # mock.call這個對象代表了一次對mock對象的調用,其內容是一個tuple,含有兩個元素,
        # 第一個元素是調用mock對象時的位置參數(*args),第二個元素是調用mock對象時的關鍵字參數( ** kwargs)
        call_args = client.send_request.call_args
        self.assertIsInstance(call_args[0][0], str)


# 使用patch或者patch.object的目的是為了控制mock的范圍,意思就是在一個函數范圍內,
# 或者一個類的范圍內,或者with語句的范圍內mock掉一個對象
class TestClient2(TestCase):

    def test_success_request(self):
        status_code = '200'
        success_send = Mock(return_value=status_code)
        with patch('client.send_request', success_send):
            from client import visit_ustack
            self.assertEqual(visit_ustack(), status_code)

    # patch用法
    def test_fail_request(self):
        status_code = '404'
        fail_send = Mock(return_value=status_code)
        with patch('client.send_request', fail_send):
            from client import visit_ustack
            self.assertEqual(visit_ustack(), status_code)

    # patch.object用法
    def test_fail_request2(self):
        status_code = '404'
        fail_send = Mock(return_value=status_code)
        with patch.object(client, 'send_request', fail_send):
            from client import visit_ustack
            self.assertEqual(visit_ustack(), status_code)


class ClassTemplate(object):
    def __init__(self, name):
        # __name c是私有變量,只有類本身可以訪問,子類也不可以訪問
        # _name 是受保護變量,類本身和子類可以訪問,from module import *無法導入
        # decorator/迭代器/yield/
        self.__name = name

    @classmethod
    def class_method(cls):
        cls.staticmethod('test')

    def run(self):
        print("my name is %s" % self.__name)

    @staticmethod
    def staticmethod(value):
        print("my value is {}".format(value))


if __name__ == '__main__':
    ClassTemplate('a_name').run()
    ClassTemplate.staticmethod('a_value')
    ClassTemplate.class_method()

 

覆蓋率

  • 安裝coverage包:
pip install coverage
  • 執行用例
coverage run --source '.' manage.py test -v 2
  • 查看覆蓋率:
macbook:portal zhaoxueyong$ coverage report

Mock

Mock對象的一般用法是這樣的:

  1. 找到你要替換的對象,這個對象可以是一個類,或者是一個函數,或者是一個類實例。

  2. 然后實例化Mock類得到一個mock對象,並且設置這個mock對象的行為,比如被調用的時候返回什么值,被訪問成員的時候返回什么值等。

  3. 使用這個mock對象替換掉我們想替換的對象,也就是步驟1中確定的對象。

  4. 之后就可以開始寫測試代碼,這個時候我們可以保證我們替換掉的對象在測試用例執行的過程中行為和我們預設的一樣。

  5. 可以替換自己寫的模塊的對象,其實也可以替換標准庫和第三方模塊的對象,方法是一樣的:先import進來,然后替換掉指定的對象就可以了

Client vs RequestFactory

from django.test import TestCase, Client, RequestFactory

RequestFactory and Client have some very different use-cases. To put it in a single sentence: RequestFactory returns a request, while Client returns a response.

The RequestFactory does what it says - it's a factory to create request objects. Nothing more, nothing less.

The Client is used to fake a complete request-response cycle. It will create a request object, which it then passes through a WSGI handler. This handler resolves the url, calls the appropriate middleware, and runs the view. It then returns the response object. It has the added benefit that it gathers a lot of extra data on the response object that is extremely useful for testing.

The RequestFactory doesn't actually touch any of your code, but the request object can be used to test parts of your code that require a valid request. The Client runs your views, so in order to test your views, you need to use the Client and inspect the response. Be sure to check out the documentation on the Client.

 

測試用例執行前后的工作:

class TestMathFunc(unittest.TestCase):
    """Test mathfuc.py"""
  # 所有測試用例執行前執行一次
    @classmethod
    def setUpClass(cls):
        print "This setUpClass() method only called once."
  # 所有測試用例執行后執行一次
    @classmethod
    def tearDownClass(cls):
        print "This tearDownClass() method only called once too."

  # 每個用例前執行一次
  def setUp(self): print "do something before test.Prepare environment."   # 每個用例后執行一次 def tearDown(self): print "do something after test.Clean up."

   @unittest.skip("I don't want to run this case.") def test_divide(self): """Test method divide(a, b)""" print "divide" self.assertEqual(2, divide(6, 3)) self.assertEqual(2.5, divide(5, 2))
 
 

 

測試套以及html輸出報告:

 

import unittest
from test_mathfunc import TestMathFunc
from HTMLTestRunner import HTMLTestRunner

if __name__ == '__main__':
    suite = unittest.TestSuite()
    suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestMathFunc))

    with open('HTMLReport.html', 'w') as f:
        runner = HTMLTestRunner(stream=f,
                                title='MathFunc Test Report',
                                description='generated by HTMLTestRunner.',
                                verbosity=2
                                )
        runner.run(suite)

 

test --pattern="test_plugin_check.py" -v 3 --debug-mode --debug-sql --parallel 2 

 

參考:

1、https://www.jianshu.com/p/b87d7b4a222a

2、https://segmentfault.com/a/1190000002965620

3、https://docs.python.org/3.7/library/unittest.mock.html

4、https://docs.djangoproject.com/zh-hans/2.1/topics/testing/overview/

5、https://docs.djangoproject.com/zh-hans/2.1/topics/testing/overview/

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM