处理测试结果

对于一个测试用例,其测试结果包括这个测试用例的执行通过与否,和对应的日志信息。

测试通过与不通过

对于QTA,判断一个测试用例是否通过的原则:

  • 测试用例类定义有问题,比如缺少DocString或必要属性,缺少run_test接口的实现。
  • 如果测试用例类定义正确,则当所有测试步骤都通过时,测试用例测试通过,否则测试用例不通过”。

而判断一个测试步骤是否通过,主要看是否出现以下任意一个情况:

  • 测试断言失败,即调用assert或wait_for系列的接口检查不通过
  • 测试代码问题,Python代码执行异常
  • 测试执行过程中,QTA内置的logger有错误级别的日志

第一种情况在前面《设计测试用例》章节已经有介绍,对于后面的两种情况,我们看下面的例子:

class ExceptTest(TestCase):
    '''异常测试
    '''
    owner = "foo"
    status = TestCase.EnumStatus.Ready
    priority = TestCase.EnumPriority.Normal
    timeout = 1

    def run_test(self):
        #---------------------------
        self.start_step("异常测试")
        #---------------------------
        raise RuntimeError("抛异常")

以上的用例有问题,执行比如有会有异常抛出,因此测试结果是不通过的:

============================================================
测试用例:ExceptTest 所有者:foo 优先级:Normal 超时:1分钟
============================================================
----------------------------------------
步骤1: 异常测试
CRITICAL: run_test执行失败
Traceback (most recent call last):
  File "D:\workspace\qtaf5\testbase\testcase.py", line 550, in _thread_run
    getattr(self._testcase, it)()
  File "D:\workspace\qtaf5\test\hellotest.py", line 86, in run_test
    raise RuntimeError("抛异常")
RuntimeError: 抛异常

============================================================
测试用例开始时间: 2016-02-02 15:12:03
测试用例结束时间: 2016-02-02 15:12:03
测试用例执行时间: 00:00:0.02
测试用例步骤结果:  1:失败
测试用例最终结果: 失败
============================================================

再看看一个测试不通过的用例的例子:

class LogErrorTest(TestCase):
    '''异常测试
    '''
    owner = "foo"
    status = TestCase.EnumStatus.Ready
    priority = TestCase.EnumPriority.Normal
    timeout = 1

    def run_test(self):
        #---------------------------
        self.start_step("异常测试")
        #---------------------------
        self.fail("异常发生")

if __name__ == '__main__':
    ExceptTest().debug_run()

上面的用例是调用日志的接口,记录一个错误的日志,因此测试也不通过:

============================================================
测试用例:LogErrorTest 所有者:foo 优先级:Normal 超时:1分钟
============================================================
----------------------------------------
步骤1: 异常测试
ERROR: 异常发生
============================================================
测试用例开始时间: 2016-02-02 15:14:19
测试用例结束时间: 2016-02-02 15:14:19
测试用例执行时间: 00:00:0.01
测试用例步骤结果:  1:失败
测试用例最终结果: 失败
============================================================

测试日志

从前面测试用例的例子可以看到,测试结果主要包括几类信息:

  • 测试用例基本信息,如名称、负责、优先级等
  • 测试用例执行的基本信息,比如开始时间、结束时间
  • 测试用例执行结果,通过或不通过
  • 各个测试步骤的日志信息,包括测试步骤的名称、测试步骤通过与否,和测试步骤执行过程中的日志、断言失败信息等

前面三点的信息都是固定的,第四点的信息是基于测试用例的代码而变化的,像一些特殊的日志信息,比如断言失败的日志,会由用户的assert或wait_for接口产生。但是一般来说,用户可以通过下面两个接口记录日志:

def log_info(self, info ):
   '''Log一条信息

   :type info: string
   :param info: 要Log的信息
   '''

def fail(self, message):
   '''测试用例失败

   :type message: string
   :param message: 要Log的信息
   '''

以上两个接口在《设计测试用例》章节已经有介绍,从使用上,这两个接口只能在测试用例类的方法中使用,如果需要在测试用例之外的代码,比如lib层,则可以使用QTA内置的logger:

from testbase import logger
logger.info("hello")
logger.error("error")

上面的代码等价于在测试用例中使用log_info和fail:

self.log_info("hello")
self.fail("error")

QTA内置的logger的接口和Python标准库的logging的logger是完全兼容的。

测试结果对象

对于一个测试用例对象,在执行过程中都会有一个test_result属性表示此测试用例对应的测试结果,我们也可以通过这个测试结果对象的接口去记录日志信息:

self.test_result.info("hello")
self.test_result.error("error")

上面的代码等价于在测试用例中使用log_info和fail:

self.log_info("hello")
self.fail("error")

test_result属性返回的类型为“testbase.testresult.TestResultBase”,更多接口可以参考接口文档。

test_result的日志接口,无论info、error等,其实都是调用log_record实现,比如info的接口:

def info(self, msg,  record=None, attachments=None):
    '''处理一个INFO日志
    '''
    self.log_record(EnumLogLevel.INFO, msg, record, attachments)

可以看到这里其实有两个另外的参数:record和attachments。record主要是给用户传递自定义的参数给自定义的测试结果对象,这块会在《执行测试》中讨论。而atachments参数表示的是测试用例的附加文件信息,比如截图、Dump文件或日志文件等。

下面是使用attachments参数的例子:

self.test_result.info("这个是一个截图", attachments={"PC截图":"desktop.png"})

调试执行的结果:

INFO: 这个是一个截图
PC截图:desktop.png

attachments参数是一个字典,因此也支持多个附件:

self.test_result.info("这个是全部截图", attachments={"PC截图":"desktop.png", "手机截图":"mobile.png"})

在调试执行是,附件的日志信息意义其实不大,但是对于其他执行方式,如果采用不同的测试结果格式(比如xml、网页报告),测试附件会直接附加在对应的测试结果中,方便用户分析测试用例问题。这块会在《执行测试》中讨论,这里也不展开讨论。

测试日志的级别

test_result的log_record接口第一个参数就是日志级别,比如对于info接口,其对应的日志的级别就是INFO。以下是test_result目前支持的全部日志级别信息:

class EnumLogLevel(object):
   '''日志级别
   '''
   DEBUG = 10
   INFO = 20
   Environment = 21  #测试环境相关信息, device/devices表示使用的设备、machine表示执行的机器
   ENVIRONMENT = Environment

   WARNING = 30
   ERROR = 40
   ASSERT = 41 #断言失败,actual/expect/code_location
   CRITICAL = 60
   APPCRASH = 61 #测试目标Crash
   TESTTIMEOUT = 62 #测试执行超时
   RESNOTREADY = 69 #当前资源不能满足测试执行的要求

其中,INFO/WANRING/ERROR/CRITICAL的类型都是和Python的logging模块的日志级别对应的,是一般的日志级别。除此之外,ASSERT是在断言失败的时候使用,也就是wait_for_和assert_系列结果中使用,用户不用直接使用。TESTTIMEOUT和RESNOTREADY也是内置的类型,由测试框架调用,用户一般都不用使用。用户可以使用的剩下的两个特殊的日志级:ENVIRONMENT和APPCRASH。

ENVIRONMENT用于日志环境信息,比如测试用例使用PC、手机等信息,比如:

self.test_result.log_record(EnumLogLevel.ENVIRONMENT, "测试用例执行机名称", {"machine":socket.gethostname()})
self.test_result.log_record(EnumLogLevel.ENVIRONMENT, "使用移动设备", {"device":"01342300111222"})
self.test_result.log_record(EnumLogLevel.ENVIRONMENT, "使用移动设备", {"devices":["93284972333", "21903948324923"]})

APPCRASH用于记录被测对象的Crash,比如:

self.test_result.log_record(EnumLogLevel.APPCRASH, "QQ Crash", attachments={"QQ日志": "QQ.tlg", "QQ Dump": "QQ34ef450a.dmp"})

注解

ENVIRONMENT和APPCRASH约定的record参数类型并不是强制的,但是如果希望日志被内置的测试结果类型更好的处理,需要按照其约定来调用。

异常时日志

测试用例执行过程中有两种可能的异常情况,用例执行超时或者用例测试代码异常。在这种情况下,QTA一般会记录当时的堆栈信息,但是如果需要在这种情况增加更多的信息,比如当时的截图、环境信息等,则可以使用测试用例类的get_extra_fail_record接口。示例代码如下:

class EnvLogOnExceptTest(TestCase):
    '''异常时记录IP和时间
    '''
    owner = "foo"
    status = TestCase.EnumStatus.Ready
    priority = TestCase.EnumPriority.Normal
    timeout = 1

    def run_test(self):
        raise RuntimeError("异常")

    def get_extra_fail_record(self):
        record, attachments = super(EnvLogOnExceptTest, self).get_extra_fail_record()
        record['当前IP'] = socket.gethostbyname(socket.gethostname())
        attachments['当前代码文件'] = __file__
        return record, attachments

get_extra_fail_record主要是提供一个hook点,可以在日志异常信息时,让测试用例去修改record和attachments参数。上面的例子就是在record和attachments增加了两项内容。

get_extra_fail_record是在日志级别为ERROR或者以上时被执行,也就是包括:

  • self.fail、logger.error和test_result.error产生ERROR级别的日志时
  • self.assert_和self.wait_for_系列接口断言失败时
  • 测试用例执行超时时
  • 测试用例执行异常时