实验五 单元测试

实验五  单元测试

一、实验目的

1)掌握单元测试的方法

2) 学习XUnit测试原理及框架;

3)掌握使用测试框架进行单元测试的方法和过程。

二、实验内容与要求

1、了解单元测试的原理与框架

 1.1 单元测试原理

  单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如C语言中单元指一个函数,Java里单元指一个类,图形化的软件中可以指一个窗口或一个菜单等。总的来说,单元就是人为规定的最小的被测功能模块。单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。单元测试是由程序员自己来完成,最终受益的也是程序员自己。可以这么说,程序员有责任编写功能代码,同时也就有责任为自己的代码编写单元测试。执行单元测试,就是为了证明这段代码的行为和我们期望的一致。

  单元测试的内容包括模块接口测试、局部数据结构测试、路径测试、错误处理测试、边界测试

(1)模块接口测试

  模块接口测试是单元测试的基础。只有在数据能正确流入、流出模块的前提下,其他测试才有意义。模块接口测试也是集成测试的重点,这里进行的测试主要是为后面打好基础。测试接口正确与否应该考虑下列因素: 

  -输入的实际参数与形式参数的个数是否相同 

  -输入的实际参数与形式参数的属性是否匹配 

  -输入的实际参数与形式参数的量纲是否一致 

  -调用其他模块时所给实际参数的个数是否与被调模块的形参个数相同; 

  -调用其他模块时所给实际参数的属性是否与被调模块的形参属性匹配; 

  -调用其他模块时所给实际参数的量纲是否与被调模块的形参量纲一致; 

  -调用预定义函数时所用参数的个数、属性和次序是否正确; 

  -是否存在与当前入口点无关的参数引用; 

  -是否修改了只读型参数; 

  -对全程变量的定义各模块是否一致; 

  -是否把某些约束作为参数传递。

  如果模块功能包括外部输入输出,还应该考虑下列因素: 

  -文件属性是否正确; 

  -OPEN/CLOSE语句是否正确; 

  -格式说明与输入输出语句是否匹配; 

  -缓冲区大小与记录长度是否匹配; 

  -文件使用前是否已经打开; 

  -是否处理了文件尾; 

  -是否处理了输入/输出错误; 

  -输出信息中是否有文字性错误。 

  -局部数据结构测试; 

  -边界条件测试; 

  -模块中所有独立执行通路测试;

(2)局部数据结构测试

  检查局部数据结构是为了保证临时存储在模块内的数据在程序执行过程中完整、正确,局部功能是整个功能运行的基础。重点是一些函数是否正确执行,内部是否运行正确。局部数据结构往往是错误的根源,应仔细设计测试用例,力求发现下面几类错误: 

  -不合适或不相容的类型说明; 

  -变量无初值; 

  -变量初始化或省缺值有错; 

  -不正确的变量名(拼错或不正确地截断); 

  -出现上溢、下溢和地址异常。

(3)边界条件测试

  边界条件测试是单元测试中最重要的一项任务。众所周知,软件经常在边界上失效,采用边界值分析技术,针对边界值及其左、右设计测试用例,很有可能发现新的错误。边界条件测试是一项基础测试,也是后面系统测试中的功能测试的重点,边界测试执行的较好,可以大大提高程序健壮性。

(4)独立路径测试

  在模块中应对每一条独立执行路径进行测试,单元测试的基本任务是保证模块中每条语句至少执行一次。测试目的主要是为了发现因错误计算、不正确的比较和不适当的控制流造成的错误。具体做法就是程序员逐条调试语句。常见的错误包括: 

  -误解或用错了算符优先级; 

  -混合类型运算; 

  -变量初值错; 

  -精度不够; 

  -表达式符号错。

(5)错误处理测试

  检查模块的错误处理功能是否包含有错误或缺陷。例如,是否拒绝不合理的输入;出错的描述是否难以理解、是否对错误定位有误、是否出错原因报告有误、是否对错误条件的处理不正确;在对错误处理之前错误条件是否已经引起系统的干预等。通常单元测试在编码阶段进行。在源程序代码编制完成,经过评审和验证,确认没有语法错误之后,就开始进行单元测试的测试用例设计。利用设计文档,设计可以验证程序功能、找出程序错误的多个测试用例。对于每一组输入,应有预期的正确结果。

1.2 测试框架

  xUnit是各种代码驱动测试框架的统称,这些框架可以测试 软件的不同内容(单元),比如函数和类。xUnit框架的主要优点是,它提供了一个自动化测试的解决方案。可以避免多次编写重复的测试代码。

底层是xUnit的framwork,xUnit的类库,提供了对外的功能方法、工具类、api等,TestCase(具体的测试用例)去使用framwork,TestCase执行后会有TestResult,使用TestSuite控制TestCase的组合,TestRunner执行器,负责执行case,TestListener过程监听,监听case成功失败以及数据结果,输出到结果报告中。

Unit测试框架包括四个要素:

(1)测试目标(对象)

  一组认定被测对象或被测程序单元测试成功的预定条件或预期结果的设定。Fixture就是被测试的目标,可以是一个函数、一组对象或一个对象。  测试人员在测试前应了解被测试的对象的功能或行为。

(2)测试集

  测试集是一组测试用例,这些测试用例要求有相同的测试Fixture,以保证这些测试不会出现管理上的混乱。

(3)测试执行

  单个单元测试的执行可以按下面的方式进行:

  第一步 编写 setUp() 函数,目的是:建立针对被测试单元的独立测试环境;举个例子,这可能包含创建临时或代理的数据库、目录,再或者启动一个服务器进程。

  第二步 编写所有测试用例的测试体或者测试程序;

  第三步 编写tearDown()函数,目的是:无论测试成功还是失败,都将环境进行清理,以免影响后续的测试;

(4)断言  

  断言实际上就是验证被测程序在测试中的行为或状态的一个函数或者宏。断言的失败会引发异常,终止测试的执行。

1.3   面向特定语言的,基于xUnit框架的自动化测试框架

  Junit  : 主要测试用Java语言编写的代码

  CPPunit:主要测试用C++语言编写的代码

  unittest , PyUnit:主要测试用python语言编写的代码

  MiniUnit:   主要用于测试C语言编写的代码

三、实验过程

  我们的项目是生命游戏,总共分为五个模块,主函数,输入检测模块,初始化地图模块,地图变化模块,打印地图模块,本实验着测试初始化地图模块、输入检测模块和地图变化模块。我们的开发环境是pycharm,所以我们使用自带的unittest来测试我们的代码。不需要重新安装测试工具。

1、unittest核心工作原理

unittest中最核心的四个概念是:test case, test suite, test runner, test fixture。

下面我们分别来解释这四个概念的意思,先来看一张unittest的静态类图(下面的类图以及解释均来源于网络,原文链接):

unittest类图

  • 一个TestCase的实例就是一个测试用例。什么是测试用例呢?就是一个完整的测试流程,包括测试前准备环境的搭建(setUp),执行测试代码(run),以及测试后环境的还原(tearDown)。元测试(unit test)的本质也就在这里,一个测试用例是一个完整的测试单元,通过运行这个测试单元,可以对某一个问题进行验证。

  • 而多个测试用例集合在一起,就是TestSuite,而且TestSuite也可以嵌套TestSuite。

  • TestLoader是用来加载TestCase到TestSuite中的,其中有几个loadTestsFrom__()方法,就是从各个地方寻找TestCase,创建它们的实例,然后add到TestSuite中,再返回一个TestSuite实例。

  • TextTestRunner是来执行测试用例的,其中的run(test)会执行TestSuite/TestCase中的run(result)方法。 
    测试的结果会保存到TextTestResult实例中,包括运行了多少测试用例,成功了多少,失败了多少等信息。

  • 而对一个测试用例环境的搭建和销毁,是一个fixture。

2、测试源代码

init.py

"""
init
"""
import random


def init_map(rows, cols):
    """
    :param rows: int
    :param cols: int
    :return: list
    """
    if rows <= 0 or rows > 100 or cols <= 0 or cols > 100:
        return 0
    # 初始化指定长宽的地图, rows(int), cols(int), 返回第一张随机细胞状态图
    initial_map = [[0 for i in range(cols)]for i in range(rows)]
    for i in range(0, rows):
        for j in range(0, cols):
            initial_map[i][j] = random.randint(0, 1)
    return initial_map

check.py

"""
check
"""
import sys


def check_int(date):
    """
    :param date:str
    :return: int
    """
    try:
        int(date)
    except ValueError:
        print('error')
        sys.exit(0)
    date = int(date)
    if date < 1:
        print('error')
        sys.exit(0)
    else:
        return date

 new_map.py

"""
cell_laws
"""


def cell_laws(mov_map):
    """
    :param mov_map: list
    :return: list
    """
    # 入口参数为一个二维列表,根据生命法则推演出下一张地图并返回
    diction = {
        "left": (0, -1),
        "write": (0, 1),
        "up": (-1, 0),
        "down": (1, 0),
        "up_left": (-1, -1),
        "up_write": (-1, 1),
        "down_left": (1, -1),
        "down_write": (1, 1)
    }
    row = len(mov_map)
    col = len(mov_map[0])
    live_cell_num = 0
    new_map = [[0 for i in range(len(mov_map[0]))]for i in range(len(mov_map))]
    for i in range(0, row):
        for j in range(0, col):
            for dire in diction:
                t_row = (i + diction[dire][0]) % len(mov_map)
                t_col = (j + diction[dire][1]) % len(mov_map[0])
                if mov_map[t_row][t_col] == 1:
                    live_cell_num += 1
            if live_cell_num == 3:
                # 周围有三个活细胞,下一状态为活
                new_map[i][j] = 1
            elif live_cell_num == 2:
                # 周围有两个活细胞,保持原状态
                new_map[i][j] = mov_map[i][j]
            else:
                # 其他情况下一状态为死
                new_map[i][j] = 0
            live_cell_num = 0
    return new_map

out_map.py

"""
print
"""
import pygame


def out_map(every_map):
    """
    :param every_map:list
    :return: list
    """
    # 入口参数:arr[list][list], 返回演变出的下一张细胞状态图
    row = len(every_map)
    col = len(every_map[0])
    pygame.init()
    pygame.display.set_caption('life_game')
    screen = pygame.display.set_mode([len(every_map[0]) * 6, len(every_map) * 6])
    screen.fill([255, 255, 255])
    for i in range(0, row):
        for j in range(0, col):
            if every_map[i][j] == 1:
                pygame.draw.rect(screen, [0, 0, 255], [j * 6, 6 * i, 5, 5], 0)
            else:
                pygame.draw.rect(screen, [255, 255, 255], [j * 6, i * 6, 5, 5], 0)
    pygame.display.flip()
    pygame.time.delay(5)
    # 程序延迟一段时间
    return 1

 life_game.py

# -*- coding:utf-8 -*-
# author by : 2382852105@qq.com and 2817393972@qq.com
"""
main
"""
import sys
import pygame
import out_map
import check
import new_map
import init

map_rows = input('input height:')
map_rows = check.check_int(map_rows)
map_cols = input('input width:')
map_cols = check.check_int(map_cols)
game_map = init.init_map(map_rows, map_cols)
while 1:
    game_map = new_map.cell_laws(game_map)
    out_map.out_map(game_map)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()

3、测试用例设计

合法性检测模块:check.py测试用例

测试数据 期望结果
合理数据:‘50’ 50
错误数据:‘s’ -1
错误数据:‘-1’ -1

初始化模块:init.py测试用例

测试数据 期望结果
合理数据:row = 10,col = 10 1
边界数据:row = 0,col = 0 -1
错误数据:row = -1,col = -1 -1

 地图变化模块:new_map.py测试用例

测试数据 期望结果
old_map = [
[0, 1, 0],
[0, 1, 1],
[0, 1, 1],
]
new_map = [
[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
]

4、测试代码设计

合法性检测模块:check.py测试代码

"""test check.py"""
import unittest
from check import *


class TestCheck(unittest.TestCase):
    """test check.py"""
    def test_check1(self):
        """test check method"""
        self.assertEqual(50, check_int('50'))

    def test_check2(self):
        """test check method"""
        self.assertEqual(-1, check_int('s'))

    def test_check3(self):
        """test check method"""
        self.assertEqual(-1, check_int('-1'))


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

 初始化模块:init.py测试代码

"""
test init.py
"""
import unittest
from init import *


class TestInit(unittest.TestCase):
    """test init.py"""

    def test_init_map1(self):
        """test init_map method"""
        if init_map(10, 10) == -1:
            sign = init_map(10, 10)
        else:
            sign = 1
        self.assertEqual(1, sign)

    def test_init_map2(self):
        """test init_map method"""
        if init_map(0, 0) == -1:
            sign = init_map(0, 0)
        else:
            sign = 1
        self.assertEqual(-1, sign)

    def test_init_map3(self):
        """test init_map method"""
        if init_map(0, 0) == -1:
            sign = init_map(0, 0)
        else:
            sign = 1
        self.assertEqual(-1, sign)


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

 地图变化模块:new_map.py测试代码

"""test new_map.py"""
import unittest
from new_map import *


class TestNewMap(unittest.TestCase):
    """test new_map.py"""
    def test_cell_laws(self):
        """test cell_laws method"""
        old_map = [
            [0, 1, 0],
            [0, 1, 1],
            [0, 1, 1],
        ]
        new_map = [
            [0, 0, 0],
            [0, 0, 0],
            [0, 0, 0],
        ]
        self.assertEqual(new_map, cell_laws(old_map))


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

使用test_suite.py来组织测试

# -*- coding: utf-8 -*-

import unittest
from test_init import TestInit
from test_check import TestCheck
from test_new_map import TestNewMap

if __name__ == '__main__':
    suite = unittest.TestSuite()

    tests = [TestCheck("test_check1"), TestCheck("test_check2"), TestCheck("test_check3"),
             TestInit("test_init_map1"), TestInit("test_init_map2"), TestInit("test_init_map3"),
             TestNewMap("test_cell_laws")]

    suite.addTests(tests)
   with open('UnittestTextReport.txt', 'w') as f:
runner = unittest.TextTestRunner(stream=f, verbosity=2)
runner.run(suite)

5、测试结果与分析

测试结果如下图:

   从测试结果可以看出我们的测试代码均已通过了测试。

6、上传测试报告和测试代码

 

四、思考题

  比较以下二个工匠的做法,你认为哪种好?结合编码和单元测试,谈谈你的认识。

  我觉得第一个工匠的做法好,在编码的过程当中,如果不按照某一条特定的主线、提前设计好的方向去编码,而是走一步看一步的编码方式,即使最后完成了相应的需求,那他也一定是不科学的,而之后再进行测试整理,无疑会耗费大量的时间精力,那我们何不在编码之前,就将整个设计思路、大致方向、以及细致的模块划分做好,有条有理的去一步步完成,这样最后编码结束的时候,也会给后续的测试,优化以及扩展提供很大的帮助,节省人力物力财力。

五、实验小结

  由于思考不够全面可能还有些情况没有考虑到,在写测试代码的时候对测试的期望数据以及是否通过测试的意义产生了迷茫,经过一段时间的思考,觉得应该测试代码是否具有通过初始数据得到预期数据的能力,然后将结果表现在测试结果上,通过测试表示代码具备在各种数据情况下正确得到预期结果的能力,我们的代码是经过了一些修改之后才通过了测试,说明在写代码的时候没有考虑到一些极端情况下的数据处理,以及捕获异常的能力。

原文地址:https://www.cnblogs.com/zuiaixinxin/p/13020497.html