人脸情绪识别 测试心得

这也是我第一次系统对软件系统进行测试,从中了解到了很多之前我从来没有注意到的一些情况,也让我对自己的编码以及软件设计等方面重新进行了深刻的反思,意识到了之前自己的思维上的很多的不足。

个人认为,对软件或程序进行测试,很大一部分就是对当初对软件或者程序的设计过程中的思考是否具有完备性的一次自我检查。

下面列举了很多我在测试过程得到的一些感悟和思考:

你很难预测用户会采取怎样的行为,因此要做“最坏”的打算。

  • 用户采取错误的操作
  1. 本系统中,有让用户输入提取帧速率的模块,这个帧速率必须进行严格限定,要让用户在输入错误数据时(例如负数,乃至字母汉字等),保证程序停止继续运行,并给出相应提示框。
  2. 注册中,一定严格限制用户填写的用户名和密码规范性。在最初开始测试前,用户甚至可以通过连续空格或者汉字来完成注册,这显然是不合理的。
  3. 本系统中,有一个功能是实现通过摄像头摄取用户面部,然后即时地对用户的人脸情绪进行曲线图反馈的功能。但若用户在已经打开摄像头的情况下,还对上面所提到的提取帧速率进行修改的话,会导致程序内部多处紊乱,因此应该专门对该情况进行判定,并弹出警告对话框。
  • 用户采取恶意的操作

    登陆过程,一定要防止用户使用SQL注入。以本系统中的代码为例,一定要采用数据库连接类中对应的API完成字符串拼接,而不是直接进行拼接。

        sql = "SELECT * FROM " + DB_TABLE_NAME + " WHERE username=%s AND password=%s"
        self._cursor.execute(sql, (username, password))

    而不是

        sql = "SELECT * FROM " + DB_TABLE_NAME + " WHERE username=%s AND password=%s" %  (username, password)
        self._cursor.execute(sql)

  

  该部分总结:因此在测试过程中应该用“最坏的恶意”去揣测用户,尤其是在给予用户极大自主权的部分(例如自行输入的文本框,或者自行选择文件的文本对话框等),一定要对用户的行为进行严格的判断并给出对应合理的解决措施。

很多情况下,程序都会具有在开发时很难注意到的、较为极端的,但却能又能让程序直接崩溃的特例。

  • 本系统中,可以对给定视频进行逐帧的情绪分析,并绘制出对应的情绪变化曲线图。但当截取到帧只要一张或零张时(因为视频过短,或者设置的提取帧间隔过长),是无法作图的,对这种情况应该单列并给予用户警告,否则程序直接崩溃:
        self._ax.axis([0, (length - 1) * interval, 0, 100])
        self.draw()    

    因为该部分横坐标最大长度是通过截取帧的数目减去1乘上提取帧间隔得到的。当length的值为0或者1,length - 1的值为-1或0,自然出现异常。

    当然这部分的确不应该在测试过程中才发现,在当时进行编码的过程中就应该注意到这里length - 1是可能是负数或零的,所以一定要进行异常分析。

  • 本系统中,可以打开摄像头进行实时的情绪识别。然而在此过程中,由于可能出现找不到脸的情况,采取的解决方案是用上一帧的情绪值代替此处。但是这里自然会出现一个问题,那就是若第一帧就是找不到脸的怎么办?因为第一帧是没有上一帧的,这里直接导致程序崩溃(因为出现了负数的数组索引访问)。

    因此这部分需要严格设定情绪的初始值,而不是寄望于不停地使用上一帧的情绪值。

  

  该部分总结:编码过程中,一定要养成良好的习惯!

        例如,出现对数组的索引访问,一定要考虑一下,这个索引会不会在某种特别特别极端的情况下变成负的或者出现溢出?

        对任何对象的成员函数进行调用时,一定要考虑一下,这个对象是否正确的初始化?是否可能为None从而出现空指针错误?

        ...

建立有效的异常处理与测试机制并打印控制台日志能够极大地为测试过程提供方便。

  所幸我在最开始编码上就极力要求自己在代码中加入非常严格的异常处理以及测试部分,这让我们在最后测试的过程中少走了很多的弯路。

  • 任何的类都有主函数对类进行测试

    例如,在用户管理的数据库帮助类下面,就有这样的主函数帮助测试:

if __name__ == '__main__':
    usersHelper = UsersHelper()
    username = '456'
    password = '456'
    # print(usersHelper.register(username, password))
    print(usersHelper.confirm(username, password))
  • 在控制台上进行较为系统的日志打印

    例如,对视频进行情绪曲线作图时,控制台日志如下,里面的一些警告信息(例如找到了多对眼睛等)都一目了然。

  

  同样的,在通过摄像头进行识别时,也能清楚地看到中间的过程,包括缓存的存储信息,找不到人脸的错误信息,找不到人眼的警告信息等。

  

  • 建立严谨的异常处理

    例如该系统中对人脸的截取部分。本系统不仅对人脸进行截取,还能够对人脸进行摆正操作,然而这些过程都可能出现很多异常的东西,如找不到人脸,找到多个人脸,找不到眼睛(因为人脸摆正依赖对眼睛的识别)的异常处理必须严谨慎重。代码如下:

    def get_face(self, image, rotate=False):
        """
        :param image: 需要截取的人脸矩阵
        :param rotate: 是否进行摆正处理,默认为False
        :return: 返回截取的脸,与一个flag值,为True表示有找到多个脸
        """
        if rotate:
            # 试图旋转
            try:
                angle = self.get_angle(image)
            except:
                # 捕捉眼睛过程出现异常,此时放弃旋转
                pass
            else:
                # 未出现异常,正常旋转
                image = self.rotate_about_center(image, angle)
        faces = self._face_classifier.detectMultiScale(image)
        if len(faces) == 0:
            # 找不到人脸
            print('ERROR: No Face Found'.center(80, '-'))
            raise Exception('No Face Found')
            # 抛出异常
        elif len(faces) > 1:
            # 找到多个人脸时,只在控制台上打印警告信息,不抛出异常,但令flag值为True
            print('WARNING: Multiple Faces Found'.center(80, '-'))
            return image[faces[0][1]:faces[0][1] + faces[0][3], faces[0][0]:faces[0][0] + faces[0][2]], True
        return image[faces[0][1]:faces[0][1] + faces[0][3], faces[0][0]:faces[0][0] + faces[0][2]], False

    在设计类的过程中抛出的异常,在用到对象的时候自然就要用到,例如

        try:
            image, flag = self._faceIdentifier.get_face(image, rotate=True)
            # 尝试获取脸部
        except Exception as e:
            # 若找不到人脸,直接返回错误信息,函数结束
            return '', [], str(e)
        if flag:
            # 若找到多个人脸,暂时不结束程序,但记录错误信息
            exception = 'Multiple Faces Found'

    此外,对很多容易产生异常的函数,一定要对其传入参数进行严格判断,并抛出对应异常

    

 def get_data(self, X, Y, n):
        """
        从CK+数据集获取数据存到给定矩阵中
        :param X: numpy(n, IMAGE_SIZE, IMAGE_SIZE, CHANNEL_NUM), 输入矩阵
        :param Y: numpy(n), 结果矩阵
        :param n: int, 需要获取的数据数量
        :return:
        """
        if len(X) != n or len(Y) != n:
            # 输入矩阵或输出矩阵的维度不符合要求
            raise Exception('Illegal Length of Lists')
        if n > self._size:
            # 试图获取的数据数量超出SQL查询结果
            raise Exception('CK+ Samples Out of Range')

  该部分总结:磨刀不误砍柴工,写代码时写好注释、测试函数、异常处理等等,在测试的时候则会真的省去很多事。到底是什么地方出现问题一目了然。

总结

  软件测试是一个相当具有技巧,并相当必要的过程。由于我们下学期才上系统的软件测试的课程,现在我们能做的也是必须要做的就是严谨自己的思维,增加自己考虑问题的全面性,并对用户的任何可能行为作出全面的预测分析,在编码中多做工夫,从而就能在软件测试过程中少做工夫。

猜你喜欢

转载自www.cnblogs.com/cadenza/p/10222166.html