框架化你的代码

2021-04-23 11:14:05 浏览数 (1)

前面讲了如何不写代码可以实现自动化。 但是录制的代码很杂乱且冗余,可读性和维护性太差。 下面来介绍如何搭建自动化框架,将录制的代码整理,让其变得简洁。 python市面见得多的是以下两种框架。 1.unittest

(1)测试类必须继承unittest.TestCase

(2)测试函数必须以”test_”开头

(3)测试类必须有unittest.main()方法

2.pytest

(1)测试文件的文件名必须以”test_”开头,或者以”_test”结尾

(2)测试类命名必须以”Test”开头

(3)测试函数名必须以”test”开头

(4)测试类里面不能使用”init”方法

总结:pytest是基于unittest衍生出来的新的测试框架,使用起来相对于unittest来说更简单、效率来说更高,pytest兼容unittest测试用例,但是反过来unittest不兼容pytest,所以说pytest的容错性更好一些!在使用交互逻辑上面pytest比unittest更全一些!

unittest是python自带的测试库,自我感觉的话,如果对python只是了解基础知识,学unittest框架相对于来说要好理解些,unittest框架也完全可以实现市场上大部分的业务测试!建议可以先了解下unittest框架,然后在学pytest框架,可以更好的去感受一下两个框架的优缺点。

unittest是python内置的用于测试代码的模块,无需安装, 使用简单方便 unittest工作原理 写好一个完整的TestCase 多个TestCase 由TestLoder被加载到TestSuite里面, TestSuite也可以嵌套TestSuite 由TextTestRunner来执行TestSuite,测试的结果保存在TextTestResult中 TestFixture指的是环境准备和恢复

unittest中最核心的部分是:TestFixture、TestCase、TestSuite、TestRunner

Test Fixture

用于测试环境的准备和恢复还原, 一般用到下面几个函数。

  • setUp():准备环境,执行每个测试用例的前置条件
  • tearDown():环境还原,执行每个测试用例的后置条件
  • setUpClass():必须使用@classmethod装饰器,所有case执行的前置条件,只运行一次
  • tearDownClass():必须使用@classmethod装饰器,所有case运行完后只运行一次
Test Case
  • 参数verbosity可以控制错误报告的详细程度:默认为1。0,表示不输出每一个用例的执行结果;2表示详细的执行报告结果。
  • Verbosity=1情况下成功是 .,失败是 F,出错是 E,跳过是 S
  • 测试的执行跟方法的顺序没有关系, 默认按字母顺序
  • 每个测试方法均以 test 开头
  • Verbosity=2情况下会打印测试的注释

被测代码,demo.py文件

代码语言:javascript复制
#!/usr/bin/python
# -*- coding: utf-8 -*-

def add(a, b):
    return a b

def minus(a, b):
    return a-b

测试case, test_demo_class.py文件

代码语言:javascript复制
#!/usr/bin/python
# -*- coding: utf-8 -*-

import unittest
from demo import add, minus

class TestDemo(unittest.TestCase):
    """Test mathfuc.py"""

    @classmethod
    def setUpClass(cls):
        print ("this setupclass() method only called once.n")

    @classmethod
    def tearDownClass(cls):
        print ("this teardownclass() method only called once too.n")

    def setUp(self):
        print ("do something before test : prepare environment.n")

    def tearDown(self):
        print ("do something after test : clean up.n")

    def test_add(self):
        """Test method add(a, b)"""
        self.assertEqual(3, add(1, 2))
        self.assertNotEqual(3, add(2, 2))

    def test_minus(self):
        """Test method minus(a, b)"""
        self.assertEqual(1, minus(3, 2))
        self.assertNotEqual(1, minus(3, 2))

    @unittest.skip("do't run as not ready")
    def test_minus_with_skip(self):
        """Test method minus(a, b)"""
        self.assertEqual(1, minus(3, 2))
        self.assertNotEqual(1, minus(3, 2))

if __name__ == '__main__':
    # verbosity=*:默认是1;设为0,则不输出每一个用例的执行结果;2-输出详细的执行结果
    unittest.main(verbosity=1)

Test Suite
  • 一般通过addTest()或者addTests()向suite中添加。case的执行顺序与添加到Suite中的顺序是一致的
  • @unittest.skip()装饰器跳过某个case (1)skip():无条件跳过 @unittest.skip("i don't want to run this case. ") (2)skipIf(condition,reason):如果condition为true,则 skip @unittest.skipIf(condition,reason) (3)skipUnless(condition,reason):如果condition为False,则skip @unittest.skipUnless(condition,reason)
Test Loder
  • TestLoadder用来加载TestCase到TestSuite中。
  • loadTestsFrom*()方法从各个地方寻找testcase,创建实例,然后addTestSuite,再返回一个TestSuite实例
  • defaultTestLoader() 与 TestLoader()功能差不多,复用原有实例

unittest.TestLoader().loadTestsFromTestCase(testCaseClass) unittest.TestLoader().loadTestsFromModule(module) unittest.TestLoader().loadTestsFromName(name,module=None) unittest.TestLoader().loadTestsFromNames(names,module=None) unittest.TestLoader().discover()

实例如下, test_demo_module.py文件

代码语言:javascript复制
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
import HTMLReport
import unittest
import test_demo_class
from test_demo_class import TestDemo

if __name__ == '__main__':
    paras = sys.argv[1:]
    args = paras[0]
    report = paras[1]

    suite = unittest.TestSuite()
    if args == 'test':
        tests = [TestDemo("test_minus"), TestDemo("test_add"), TestDemo("test_minus_with_skip")]
        suite.addTests(tests)
    elif args == 'tests':
        suite.addTest(TestDemo("test_minus"))
        suite.addTest(TestDemo("test_add"))
        suite.addTest(TestDemo("test_minus_with_skip"))
    elif args == 'class':
        suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestDemo))
    elif args == 'module':
        suite.addTests(unittest.TestLoader().loadTestsFromModule(test_demo_class))
    elif args == 'mix':
        suite.addTests(unittest.TestLoader().loadTestsFromName('test_demo_class.TestDemo.test_minus'))
    elif args == 'mixs':
        suite.addTests(unittest.TestLoader().loadTestsFromNames(['test_demo_class.TestDemo.test_minus', 'test_demo_class.TestDemo', 'test_demo_class']))
    elif args == 'discover':
        suite.addTests(unittest.TestLoader().discover('.', 'test_*.py', top_level_dir=None))

    if report == 'terminal':
        runner = unittest.TextTestRunner(verbosity=1)
        runner.run(suite)
    elif report == 'txt':
        with open('ut_log.txt', 'a') as fp:
            runner = unittest.TextTestRunner(stream=fp, verbosity=1)
            runner.run(suite)
    elif report == 'html':
        runner = HTMLReport.TestRunner(report_file_name='test',
                               output_path='report',
                               title='测试报告',
                               description='测试描述',
                               sequential_execution=True
                               )
        runner.run(suite)


Testing Report
  • 终端报告:如上terminal 分支
  • TXT报告:如上txt 分支,当前目录会生成ut_log.txt文件
  • HTML 报告:如上html 分支,终端上打印运行信息同时会在当前目录生成report文件夹, 文件夹下有test.htmltest.log文件

例子如下: 创建一个函数集mathfunc.py

代码语言:javascript复制
def add(a,b):
    return a b
 
def minus(a, b):
    return a-b
 
def multi(a, b):
    return a*b
 
def divide(a, b):
   return a/b

接下来是为这些方法写的一个测试: test_mathfunc.py

代码语言:javascript复制
import unittest
from mathfunc import *
 
class TestMathFunc(unittest.TestCase):
 
    def setUp(self):
        print "do something befor test.prepare environment"
 
    def tearDown(self):
        print "do something after test.Clean up"
 
    def test_add(self):
        self.assertEqual(3,add(1,2))
        self.assertNotEqual(3,add(2,2))
 
    @unittest.skip("i don't want to run this case")
    def test_minus(self):
        self.assertEqual(1,minus(3,2))
 
    def test_multi(self):
        self.assertEqual(6,multi(2,3))
 
    def test_divide(self):
        self.assertEqual(2,divide(6,3))
        self.assertEqual(2.5,divide(5,2))
 
if __name__ == '__main__':
    unittest.main()

组织TestSuite

上面的代码演示了如何编写一个简单的测试,下面说一下怎么控制用例执行的顺序。我们就要用到TestCase,添加到TestCaseDE中的case是会按照添加的顺序执行的。

来个例子:

在文件夹中再新建一个文件。test_suite.py

代码语言:javascript复制
# -*- coding: utf-8 -*-
 
import unittest
from test_mathfunc import TestMathFunc
 
if __name__ == '__main__':
    suite = unittest.TestSuite()
 
    tests = [TestMathFunc("test_add"), TestMathFunc("test_minus"), TestMathFunc("test_divide")]
    suite.addTests(tests)
 
    runner = unittest.TextTestRunner(verbosity=2)
如何控制用例执行顺序

在unittest中,用例是以test开头的方法定义的,默认执行顺序是根据用例名称升序进行,而不是用例定义的先后顺序。 在unittest中解决用例执行顺序的问题是使用TestSuite来定义顺序

如何让多个用例共用setup、teardown

unittest的setup、teardown会在每个用例执行前后执行一次,如上面测试用例类中有3个测试用例, 那么每个用例执行前会执行setup,执行后会执行teardown,即setup、teardown总共会调用三次, 但考虑实际自动化测试场景,多个用例只需执行一次setup,全部用例执行完成后,执行一次teardown, 针对该种场景,unittest的处理方法是使用setupclass、teardownclass

如何跳过用例

在自动化测试中,经常会遇到挑选用例的情况,在unittest中的解决方法是使用skip装饰器, 其中skip装饰器主要有3种:unittest.skip(reason)、unittest.skipIf(condition,reason)、 unittest.skipUnless(condition,reason),即在满足condition条件下跳过该用例, reason用于描述跳过的原因

如何生成html格式的测试报告

Unittest中默认生成的报告格式为txt,如果想生成html格式的报告,可以使用HtmlTestRunner模块, 安装后导入该模块,使用HTMLTestRunner代替默认的TextTestRunner()执行测试用例即可。

代码语言:javascript复制
import unittest
from test_mathfunc import TestMathFunc
from HTMLTestRunner import HTMLTestRunner
 
if __name__ == '__main__':
    suite = unittest.TestSuite()
 
    #使用这种方法可以对测试用例排序
    #tests = [TestMathFunc("test_add"), TestMathFunc("test_minus"), TestMathFunc("test_divide")]
    #suite.addTests(tests)
    
    #使用TestLoader的方法传入TestCase
    suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestMathFunc))
 
    #在同目录下生成txt格式的测试报告
    #with open('UnittestTextReport.txt', 'a') as f:
        #runner = unittest.TextTestRunner(stream=f, verbosity=2)
        #runner.run(suite)
 
    with open('HTMLReport.html','w') as f:
        runner = HTMLTestRunner(stream = f,
                                title = u'测试报告',
                                description = u'测试用例的执行情况',
                                verbosity = 2
                                )
        runner.run(suite)

如果我们想执行某个路径下的case:

代码语言:javascript复制
  for filename in os.listdir(self.cases):
            if filename == "report":
                break
        else:
            os.mkdir(self.cases '/report')
 
        now = time.strftime("%Y-%m-%d_%H_%M_%S")
        fp = open("./report/"  now  "result.html", 'wb')
        tests = unittest.defaultTestLoader.discover("./test_case",pattern='*sta.py',top_level_dir=None)
        runner = HTMLTestRunner(stream=fp, title=self.title, description=self.des)
        runner.run(tests)
        fp.close()

队于htmtestrunner这个文件,我们可以去下载:https://www.cnblogs.com/pandaly/p/13212376.html 也可以去https://github.com/huilansame/HTMLTestRunner_PY3

小结:

1、unittest是python自带的单元测试框架,可以用来作为我们自动化测试框架的用例组织执行框架

2、unittest流程:写好TestCase,然后由TestLoader加载TestCase到TestSuite,然后由TextTestRunner来运行TestSuite,运行的结果保存在TextTestResult中,我们通过命令行或者unittest.main()执行时,main会调用TextTestRunner中的run来执行,或者我们可以直接通过TextTestRunner来执行用例。

3、一个class继承unittest.TestCase即是一个TestCase,其中以 test 开头的方法在load时被加载为一个真正的TestCase。

4、verbosity参数可以控制执行结果的输出,0 是简单报告、1 是一般报告、2 是详细报告。

5、可以通过addTest和addTests向suite中添加case或suite,可以用TestLoader的loadTestsFrom__()方法。

6、用 setUp()、tearDown()、setUpClass()以及 tearDownClass()可以在用例执行前布置环境,以及在用例执行后清理环境

7、我们可以通过skip,skipIf,skipUnless装饰器跳过某个case,或者用TestCase.skipTest方法。

8、参数中加stream,可以将报告输出到文件:可以用TextTestRunner输出txt报告,以及可以用HTMLTestRunner输出html报告.

0 人点赞