测试过程中,会有需要将Test Report以各种样式呈现的需求。
本例使用ReactJS + babel + Bulma CSS呈现带菜单和分类的静态测试报告页面。
这种方式可以使数据和报告分离。
使用不同的data.js文件,可以展示不同的测试报告。
程序输出的样例报告效果如下,
程序代码如下,
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Bifang工程测试报告</title>
<script src="./src/lib/react-development.js"></script>
<script src="./src/lib/react-dom.development.js"></script>
<script src="./src/lib/babel.min.js"></script>
<link rel="stylesheet" href="./css/bulma/bulma.min.css">
</head>
<body>
<div id="main"></div>
<script src="./data.js" type="text/javascript"></script>
<script type="text/babel">
class ChannelInfo extends React.Component {
constructor(props) {
super(props)
}
render() {
return (<div>
<table className="table" border={0} style={ {width: "100%"} }>
<tbody>
<tr>
<th> topic name </th>
<td>{this.props.channel["topic_name"]}</td>
</tr>
<tr>
<th> Messages </th>
<td>
<ul>
{this.props.channel["messages"].map(
(msg, i) =>
<li key={i}>
<span className="subtitle is-6">
{JSON.stringify(msg)}
</span>
</li>
)}
</ul>
</td>
</tr>
</tbody>
</table>
</div>)
}
}
class PageContent extends React.Component {
constructor(props) {
super(props)
this.state = {
display: false
}
}
setDisplay() {
this.setState({display: !this.state.display})
}
render() {
return (<div className="block">
<div onClick={() => this.setDisplay()}>
<table class="table">
<tr>
<td><abbr title="Position">{this.props.content["descriptions"]["case_id"]}.</abbr></td>
<td>{this.props.content["descriptions"]["case_name"]}</td>
</tr>
</table>
</div>
<h2 className="is-5" style={this.state.display? {}: {display: "none"}}>
<table className="table">
<thead>
<tr>
<th><abbr title="Title">Title</abbr></th>
<th>Content</th>
</tr>
</thead>
<tbody>
<tr>
<th>Description</th>
<td>
<li> Test Case ID : { this.props.content["descriptions"]["case_id"] } </li>
<li> Case Name : { this.props.content["descriptions"]["case_name"]} </li>
<li> Validation Type: { this.props.content["validate_type"] } </li>
</td>
</tr>
<tr>
<th>Passed</th>
{this.props.content["passed"]?
<td className="is-success"> Yes </td>:
<td className="is-danger"> NO </td>
}
</tr>
<tr>
<th> Input Messages </th>
{
this.props.content["input"].map(
(channel, i) =>
<tr>
<td key={i}><ChannelInfo channel={channel} /></td>
</tr>
)
}
</tr>
<tr>
<th> Output Messages </th>
{
this.props.content["actual"].map(
(channel, i) =>
<tr>
<td key={i}><ChannelInfo channel={channel} /></td>
</tr>
)
}
</tr>
<tr>
<th> Expect Messages </th>
{
this.props.content["expectation"].map(
(channel, i) =>
<tr>
<td key={i}><ChannelInfo channel={channel} /></td>
</tr>
)
}
</tr>
<tr>
<th>
Test Result
</th>
<td>
<span className="subtitle is-6" dangerouslySetInnerHTML={{__html: this.props.content["result_info"].replace("\n", "<p>")}} />
</td>
</tr>
</tbody>
</table>
</h2>
</div>)
}
}
class MainPage extends React.Component {
constructor(props) {
super(props)
console.log("DATA==")
console.log(this.props.data)
this.state = {
case_st: "FAIL",
page: "UIUE"
}
}
setPage = (page_) => {
this.setState({page: page_})
}
setCaseStatus= (status_) => {
console.log("Set case Status!")
this.setState({case_st: status_})
}
renderHeader = () => {
if(this.state.page && this.props.data.hasOwnProperty(this.state.page)) {
return (
<div className="tile is-ancestor columns">
<div className="tile is-parent column is-4">
<article className={this.state.case_st === "FAIL"? "tile is-child notification is-danger": "tile is-child notification" } onClick={() => this.setCaseStatus("FAIL")}>
<p className="subtitle">FAILED</p>
<p className="subtitle">{this.props.data[this.state.page]["failed"]}</p>
</article>
</div>
<div className="tile is-parent column is-4">
<article className={this.state.case_st === "PASS"? "tile is-child notification is-danger": "tile is-child notification" } onClick={()=>this.setCaseStatus("PASS")}>
<p className="subtitle">Passed</p>
<p className="subtitle">{this.props.data[this.state.page]["passed"]}</p>
</article>
</div>
<div className="tile is-parent column is-4">
<article className="tile is-child notification">
<p className="subtitle">Pass Rate</p>
<p className="subtitle">{this.props.data[this.state.page]["pass_rate"]}</p>
</article>
</div>
</div>
)
}
}
renderBody = (bodyData) => {
return (<div className="block">
{bodyData.map((test_case, i) =>
<PageContent key={i} content={test_case} />
)}
</div>
)
}
render() {
return (
<div className="columns">
<div className="column is-2">
<div className="block">
<aside className="menu">
<p className="menu-label">测试报告</p>
<ul className="menu-list">
<li> <a onClick={() => this.setPage("UIUE")} className={this.state.page === "UIUE" ? "is-active": ""}>UIUE模块</a></li>
<li> <a onClick={() => this.setPage("ParkingFus")} className={this.state.page === "ParkingFus" ? "is-active": ""}>ParkingFus模块</a></li>
</ul>
</aside>
</div>
</div>
<div className="block column is-9">
{this.renderHeader()}
{
this.state.case_st === "FAIL" ? this.renderBody(this.props.data[this.state.page]["failed_cases"]):
this.renderBody(this.props.data[this.state.page]["passed_cases"])
}
</div>
</div>
)
}
}
class App extends React.Component {
constructor(props) {
super(props)
}
render() {
return (<div className="container" >
<MainPage data={this.props.data}/>
</div>)
}
}
ReactDOM.render(
<App data={js_obj}/>,
document.getElementById('main')
);
</script>
</body>
</html>
data.js文件如下,
let js_obj = {
"ParkingFus": {
"passed_cases": [
{
"passed": true,
"descriptions": {
"case_id": 1,
"case_name": "ParkingFus全景开启,软按键开启"
},
"input": [
{
"topic_name": "Send_Topic_name",
"messages": [
{
"struct": "SendCSC_APA_Req_Fusion_Struct",
"Get_Something_uint8": 1
},
{
"struct": "SendVDL_APA_Req_Fusion_Struct",
"Set_Something_uint8": 2
}
]
},
{
"topic_name": "Send_Topic_name_2",
"messages": [
{
"struct": "SendCSC_APA_Req_Fusion_Struct",
"Get_Something_uint8": 3
},
{
"struct": "SendVDL_APA_Req_Fusion_Struct",
"Set_Something_uint8": 4
}
]
}
],
"validate_type": "log|msg",
"expectation": [
{
"topic_name": "Rec_Topic_name",
"messages": [
{
"struct": "RecvCSC_APA_Req_Fusion_Struct",
"Get_Something_uint8": 1
},
{
"struct": "RecvVDL_APA_Req_Fusion_Struct",
"Set_Something_uint8": 2
}
]
},
{
"topic_name": "Rec_Topic_name_2",
"messages": [
{
"struct": "RecvCSC_APA_Req_Fusion_Struct",
"Get_Something_uint8": 3
},
{
"struct": "RecvVDL_APA_Req_Fusion_Struct",
"Set_Something_uint8": 4
}
]
}
],
"actual": [
{
"topic_name": "Rec_Topic_name",
"messages": [
{
"struct": "RecvCSC_APA_Req_Fusion_Struct",
"Get_Something_uint8": 1
},
{
"struct": "RecvVDL_APA_Req_Fusion_Struct",
"Set_Something_uint8": 2
}
]
},
{
"topic_name": "Rec_Topic_name_2",
"messages": [
{
"struct": "RecvCSC_APA_Req_Fusion_Struct",
"Get_Something_uint8": 3
},
{
"struct": "RecvVDL_APA_Req_Fusion_Struct",
"Set_Something_uint8": 4
}
]
}
],
"result_info": "Msg equal passed\nLog equal passed\n"
}
],
"failed_cases": [
{
"passed": false,
"descriptions": {
"case_id": 2,
"case_name": "ParkingFus全景开启,软按键开启"
},
"input": [
{
"topic_name": "Send_Topic_name",
"messages": [
{
"struct": "SendCSC_APA_Req_Fusion_Struct",
"Get_Something_uint8": 1
},
{
"struct": "SendVDL_APA_Req_Fusion_Struct",
"Set_Something_uint8": 2
}
]
},
{
"topic_name": "Send_Topic_name_2",
"messages": [
{
"struct": "SendCSC_APA_Req_Fusion_Struct",
"Get_Something_uint8": 3
},
{
"struct": "SendVDL_APA_Req_Fusion_Struct",
"Set_Something_uint8": 4
}
]
}
],
"validate_type": "log|msg",
"expectation": [
{
"topic_name": "Rec_Topic_name",
"messages": [
{
"struct": "RecvCSC_APA_Req_Fusion_Struct",
"Get_Something_uint8": 3
},
{
"struct": "RecvVDL_APA_Req_Fusion_Struct",
"Set_Something_uint8": 2
}
]
},
{
"topic_name": "Rec_Topic_name_2",
"messages": [
{
"struct": "RecvCSC_APA_Req_Fusion_Struct",
"Get_Something_uint8": 3
},
{
"struct": "RecvVDL_APA_Req_Fusion_Struct",
"Set_Something_uint8": 4
}
]
}
],
"actual": [
{
"topic_name": "Rec_Topic_name",
"messages": [
{
"struct": "RecvCSC_APA_Req_Fusion_Struct",
"Get_Something_uint8": 1
},
{
"struct": "RecvVDL_APA_Req_Fusion_Struct",
"Set_Something_uint8": 2
}
]
},
{
"topic_name": "Rec_Topic_name_2",
"messages": [
{
"struct": "RecvCSC_APA_Req_Fusion_Struct",
"Get_Something_uint8": 3
},
{
"struct": "RecvVDL_APA_Req_Fusion_Struct",
"Set_Something_uint8": 4
}
]
}
],
"result_info": "Msg equal passed\nLog equal passed\n"
}
],
"passed": 1,
"failed": 1,
"pass_rate": "50.0%"
},
"UIUE": {
"passed_cases": [
{
"passed": true,
"descriptions": {
"case_id": 1,
"case_name": "UIUE全景开启,软按键开启"
},
"input": [
{
"topic_name": "Send_Topic_name",
"messages": [
{
"struct": "SendCSC_APA_Req_Fusion_Struct",
"Get_Something_uint8": 1
},
{
"struct": "SendVDL_APA_Req_Fusion_Struct",
"Set_Something_uint8": 2
}
]
},
{
"topic_name": "Send_Topic_name_2",
"messages": [
{
"struct": "SendCSC_APA_Req_Fusion_Struct",
"Get_Something_uint8": 3
},
{
"struct": "SendVDL_APA_Req_Fusion_Struct",
"Set_Something_uint8": 4
}
]
}
],
"validate_type": "log|msg",
"expectation": [
{
"topic_name": "Rec_Topic_name",
"messages": [
{
"struct": "RecvCSC_APA_Req_Fusion_Struct",
"Get_Something_uint8": 1
},
{
"struct": "RecvVDL_APA_Req_Fusion_Struct",
"Set_Something_uint8": 2
}
]
},
{
"topic_name": "Rec_Topic_name_2",
"messages": [
{
"struct": "RecvCSC_APA_Req_Fusion_Struct",
"Get_Something_uint8": 3
},
{
"struct": "RecvVDL_APA_Req_Fusion_Struct",
"Set_Something_uint8": 4
}
]
}
],
"actual": [
{
"topic_name": "Rec_Topic_name",
"messages": [
{
"struct": "RecvCSC_APA_Req_Fusion_Struct",
"Get_Something_uint8": 1
},
{
"struct": "RecvVDL_APA_Req_Fusion_Struct",
"Set_Something_uint8": 2
}
]
},
{
"topic_name": "Rec_Topic_name_2",
"messages": [
{
"struct": "RecvCSC_APA_Req_Fusion_Struct",
"Get_Something_uint8": 3
},
{
"struct": "RecvVDL_APA_Req_Fusion_Struct",
"Set_Something_uint8": 4
}
]
}
],
"result_info": "Msg equal passed\nLog equal passed\n"
}
],
"failed_cases": [
{
"passed": false,
"descriptions": {
"case_id": 2,
"case_name": "UIUE全景开启,软按键开启"
},
"input": [
{
"topic_name": "Send_Topic_name",
"messages": [
{
"struct": "SendCSC_APA_Req_Fusion_Struct",
"Get_Something_uint8": 1
},
{
"struct": "SendVDL_APA_Req_Fusion_Struct",
"Set_Something_uint8": 2
}
]
},
{
"topic_name": "Send_Topic_name_2",
"messages": [
{
"struct": "SendCSC_APA_Req_Fusion_Struct",
"Get_Something_uint8": 3
},
{
"struct": "SendVDL_APA_Req_Fusion_Struct",
"Set_Something_uint8": 4
}
]
}
],
"validate_type": "log|msg",
"expectation": [
{
"topic_name": "Rec_Topic_name",
"messages": [
{
"struct": "RecvCSC_APA_Req_Fusion_Struct",
"Get_Something_uint8": 3
},
{
"struct": "RecvVDL_APA_Req_Fusion_Struct",
"Set_Something_uint8": 2
}
]
},
{
"topic_name": "Rec_Topic_name_2",
"messages": [
{
"struct": "RecvCSC_APA_Req_Fusion_Struct",
"Get_Something_uint8": 3
},
{
"struct": "RecvVDL_APA_Req_Fusion_Struct",
"Set_Something_uint8": 4
}
]
}
],
"actual": [
{
"topic_name": "Rec_Topic_name",
"messages": [
{
"struct": "RecvCSC_APA_Req_Fusion_Struct",
"Get_Something_uint8": 1
},
{
"struct": "RecvVDL_APA_Req_Fusion_Struct",
"Set_Something_uint8": 2
}
]
},
{
"topic_name": "Rec_Topic_name_2",
"messages": [
{
"struct": "RecvCSC_APA_Req_Fusion_Struct",
"Get_Something_uint8": 3
},
{
"struct": "RecvVDL_APA_Req_Fusion_Struct",
"Set_Something_uint8": 4
}
]
}
],
"result_info": "Msg equal passed\nLog equal passed\n"
}
],
"passed": 1,
"failed": 1,
"pass_rate": "50.0%"
}
}
其中使用的react-development.js,react-dom.development.js, babel.min.js和bulma.min.css可以到网上进行下载。是通用的css和js文件。