RT-Thread上使用utest+jenkins实现持续集成和自动化测试

122 阅读3分钟

前情提要: 随着模块越来越多,测试维护成本越来越高,实现自动化便提上日程,网上关于嵌入式软件的持续集成和自动化测试的资料较少,utest是RTThread自带的测试框架,也没有接入jenkins,也没有测试报告,所以很多地方需要自己再做处理。本文记录了笔者搭建测试框架中详细的实现过程、踩过的坑和解决方法以及一些思考。

环境:RT-Thread、SCons、qemu、jenkins、utest

1. 使用jenkins实现持续集成

持续集成( Continuous integration ,简称 CI)指的是,频繁地将代码集成到主干。

持续集成的目的,就是让产品可以快速迭代,同时还能保持高质量。它的核心措施是,代码集成到主干之前必须通过自动化测试。只要有一个测试用例失败,就不能集成。

先新建一个jenkins的任务,再一个个解决问题

新建任务

构建一个自由风格的软件项目(或者可以拷贝一个scm的项目)

general和源码管理可以根据实际情况配置

构建触发器可选择定时构建或者gitlab/gerrit来触发,或者多选也可以

上图示意为周一到周五22:00定时构建

接下来build step和构建后操作就是重头戏了

build step

选择执行shell脚本

脚本内容分为几步:

  1. 获取开发代码
  2. 获取测试代码,以及mock服务
  3. 编译运行
  4. 生成html格式的测试结果报告

构建后操作

  1. 发布HTML reports
  2. 发送邮件通知

2. 问题解决

问题一:怎样执行用例

我们平时都是在整个系统编译运行之后,进入msh命令行界面进行交互,输入“utest_list","utest_run"分别查看测试用例和运行测试用例

最终采用的解决方法:在/board/vexpress/arm-a9/applications/main.c中加上

#include <msh.h>
#include <finsh.h>

//main函数中加入:
msh_exec("utest_run",11);

问题二:怎样结束进程

utest执行完,结束qemu是用的ctrl+A,然后再输入X,这里怎么结束进程呢

一直没有搜到怎样退出,后来想明白了,自己怎么能结束自己呢,必须得要从外部来实现,然后碰巧看到一篇帖子求助怎么退出qemu的,发现不知道Ctrl+A+X的小伙伴一直是杀进程的,灵光一闪,那这不就是我自动化需要的方案吗!

接着就是代码实现过程:总体逻辑就是,1.启动三个进程,A进程运行,B进程等待,C进程判断log内容,2. A进程运行并将log重定向为log.txt,3.C进程判断log出现utest执行结束,便唤醒B线程来杀qemu进程

核心代码如下

import threading
import time
import logging
import subprocess
import os

logging.basicConfig(format="%(asctime)s %(threadName)s %(thread)s %(message)s",level=logging.INFO)
event = threading.Event()

is_executing = False

def logrecorder():
    logging.info("log: logrecorder start")
    with open('utest_log.txt', 'r') as log_file:
        while True:
            flag = log_file.tell()
            line = log_file.readline()
            if not line:
                time.sleep(1)
                log_file.seek(flag)
            else:
                #print(line)
                if line.find('[ utest    ] finished') != -1:
                    logging.info("find finished")
                    event.set()
                    break
def test():
    time.sleep(3)
    event.set()

def run():
    logging.info("log: run start")
    os.system('scons --target=board/vexpress/arm-a9/qemu/test-memblock-unit.conf --run >> utest_log.txt')
    logging.info("log: run finish")

def kill():
    logging.info(threading.current_thread().name)
    #logging.info("log: kill thread start")
    event.wait()
    #logging.info("log: kill really start")
    os.system('ps -aux | grep qemu | grep scmbuild | grep test-memblock-unit |  awk \'{print $2}\' | xargs kill -9')
    is_executing = False
    logging.info("log: kill finish")
    is_executing = False

if __name__=='__main__':
    
    a = threading.Thread(name='run',target=run)
    b = threading.Thread(name='kill',target=kill)
    c = threading.Thread(name='logrecorder',target=logrecorder)
    d = threading.Thread(name='test',target=test)
    b.start()
    a.start()
    time.sleep(5)
    c.start()

问题三:生成测试报告

怎样生成测试报告,一目了然

因为utest没有直接生成报告的功能,所以只能自己根据log来产生结果,而且utest有一个很大问题,就是其中某个测试套件中的某条case failed了,后面的case都会跳过,所以我们目前将统计的颗粒度就设为测试套件。通过python读取log,获取需要的数值,再传到html中显示,再把html文件作为jenkins的报告输出

部分代码:

from jinja2 import Environment, FileSystemLoader
import os
import time


def txt_read():

    return totalcase, runcase, passcase, failcase, set(faillist)

def generate_html(body, current_time, result):
    env = Environment(loader=FileSystemLoader('./'))
    template = env.get_template('template.html')
    with open("result.html", 'w+', encoding="UTF-8") as fout:
        html_content = template.render(body=body,
                                       current_time = current_time,
                                       result=result)
        fout.write(html_content)


def send_result():
    totalcase, runcase, passcase, failcase,failname = txt_read()
    if (runcase - passcase != failcase):
        print("Maybe something is wrong")
    body = []
    if (failname):
        result = {'ID': 1, 'total': totalcase, 'run': runcase, 'pass': passcase,
              'fail': failcase, 'more' : failname}
    else:
        result = {'ID': 1, 'total': totalcase, 'run': runcase, 'pass': passcase,
                   'fail': failcase, 'more': "无"}
    body.append(result)
    current_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))
    if(failcase == 0):
        result = "pass"
    else:
        result = "fail"
    generate_html(body, current_time, result)


if __name__ == '__main__':
    txt_read()
    send_result()
 

再自己写一个templete.html,每次调用python脚本即可根据templete.html生成对应的result.html,示例:

<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<html align = 'left'>
<h1>测试报告</h1>
<body>
 <div id="table0" >
        <table border= '0px ' width = "70%" cellspacing='1' cellpadding='3' align='left' bgcolor="black" >
            <tr style="color:white; font-weight: bold;text-align:left; background-color:#777;">
                <th>轮数</th>
                <th>总用例数</th>
                <th>执行</th>
                <th>通过数</th>
                <th>失败数</th>
            </tr>
            {% for item in body %}
            <tr align='left' bgcolor="white">
                <td>{{ item.ID }}</td>
                <td>{{ item.total }}</td>
                <td>{{ item.run }}</td>
                <td>{{ item.pass }}</td>
                <td>{{ item.fail }}</td>
            </tr>
            {% endfor %}
        </table>
    </div>
    </body>

     <body>
    <div id="table1" class="hidden">
        <table  border="0" width = "70%" cellspacing='1' cellpadding='3' align='left' bgcolor="black"   >
            <tr style="color:white; font-weight: bold;text-align:left; background-color:#777;">
                <th>测试用例</th>
                <th>结果</th>
                <th>备注</th>
            </tr>

            {% for name in failname %}
            <tr align='left' bgcolor="white" >
                <td>{{ name }}</td>
                <td style = "color:FF0000"> Failed </td>
                <td></td>
            </tr>
            {% endfor %}
        </table>
    </div>
    </body>

问题四:邮件通知

在构建后的步骤中添加邮件报告,内容设置为html

在最下方点开

可以看到各种环境变量,能够直接用到我们这里的设置中

Project Recipient List:这里可以添加邮件地址,cc:地址可以抄送

因为我已经自己生成了测试报告,为了能把报告结合起来,采用了一下方式:

<!DOCTYPE html>  
<html>  
<head>  
<meta charset="UTF-8">  
<title>${ENV, var="JOB_NAME"}-第${BUILD_NUMBER}次构建日志</title>  
</head>  
  
<body leftmargin="8" marginwidth="0" topmargin="8" marginheight="4"  
    offset="0">  
    <h3>以下是Jenkins自动发送的邮件,请勿回复!</h3>
    <div>
    <table width="95%" cellpadding="0" cellspacing="0" 
            style="font-size: 11pt; font-family: Tahoma, Arial, Helvetica, sans-serif"> 
        <tr>    
                <th><br />
            <h2>构建信息</h2> 
        </th>
            </tr> 
        <tr>  
            <td>  
                <ul>  
                    <li>项目名称 : ${PROJECT_NAME}</li><br />  
                    <li>触发原因: ${CAUSE}</li><br />                    
                    <li>项目  Url : <a href="${PROJECT_URL}">${PROJECT_URL}</a></li><br />
                    <li>工作目录 : <a href="${PROJECT_URL}ws">${PROJECT_URL}ws</a></li><br />
                    <li>测试报告 : <a href="${PROJECT_URL}HTML_20Report">${PROJECT_URL}HTML_20Report</a></li>
                </ul>  
            </td> 
        </tr>  
    </table> 
    </div>


    <div>
    <table width="95%" cellpadding="0" cellspacing="0" 
            style="font-size: 11pt; font-family: Tahoma, Arial, Helvetica, sans-serif"> 
            <tr>  
                <th><br />
            <h2>测试报告</h2>
                </th>  
            </tr>

        <tr>
            <td>
                <div>${FILE ,path="/workspace/fw_autotest/resultBrief.html"}</div>
            </td>
        </tr>
 
    </table> 
    </div>
     
  </body>  
</html>

问题五:报告优化

由于每个模块的开发和测试的需求不同,为了尽量能既一目了然又详细,所以在邮件里只是会展示结果,想要了解详细情况可以点击测试报告链接,在里面能够展示所有用例和log

点击测试报告的链接就会跳转到jenkins的html报告

image.png

其余问题:从安全性考虑,jenkins禁止了css和js的加载,所以格式会乱

解决方案:

解决jenkins显示html样式问题-CSDN博客