時候,我們希望在 JavaScript 中將 2 個已經存在的數組串拼接成 1 個數組。
簡單來說就是將數組進行合并。
這個時候,我們可以使用 JavaScript 的 concat 函數。
考察下面的代碼:
const array1 = ['a', 'b', 'c'];
const array2 = ['d', 'e', 'f'];
const array3 = array1.concat(array2);
console.log(array3);
// expected output: Array ["a", "b", "c", "d", "e", "f"]
上面的代碼將 2 個數組 array1 和 array2 合并成了一個新的數組為 array3, 在這個新的數組中的元素就是
array2 在 array1 后面添加得到的。
如果你需要合并的數組不只有 2 個,你還有多個的話,你可以同樣使用上面的方法,但是在參數中傳遞進多個數組就行。
concat()
concat(value0)
concat(value0, value1)
concat(value0, value1, ... , valueN)
如上面的代碼,你并不需要將 concat 多次運行來進行合并,concat 這個方法允許傳遞多個需要合并數組為參數。
網盜概念^-^相同的測試腳本使用不同的測試數據來執行,測試數據和測試行為完全分離, 這樣的測試腳本設計模式稱為數據驅動。(網盜結束)當我們測試某個網站的登錄功能時,我們往往會使用不同的用戶名和密碼來驗證登錄模塊對系統的影響,那么如果我們每一條數據都編寫一條測試用例,這無疑是增加了代碼量,代碼重復,且顯得那么臃腫(誰不喜歡身材好的呢?你懂的),這時候我們可以使用不同數據驅動代碼執行相同的用例測試不同的場景。
我們再來說說實施數據驅動測試的步驟:
1.創建/準備測試數據
2.封裝讀取數據的方法,保留測試腳本調用的接口/屬性(我們需要傳遞給腳本什么參數)
3.編寫自動化測試腳本
4.腳本中調用封裝好的處理數據文件的模塊并引入測試數據
5.執行測試腳本并分析測試結果
1.安裝python3.x開發環境(能看到此文章的應該都有這個環境,沒有的自行百度吧)
2.安裝數據驅動模塊ddt
安裝方式1:cmd下執行命令 pip install ddt
安裝方式2:https://pypi.org/simple/ddt/ 下載 并解壓任意目錄,cmd 運行命令python setup.py install
3.驗證安裝 pycharm 新建python文件并輸入 import ddt 運行無報錯信息既表示安裝成功或者cmd 命令依次輸入python回車 import ddt回車 無保存信息表示安裝成功
4.unittest框架和ddt進行數據驅動
1.訪問地址:https://mail.sohu.com/fe/#/login
2.輸入用戶名和密碼
3.點擊登錄按鈕
4.判斷是否登錄成功
數據存在當前腳本中
數據準備
我們要實現的是用戶登錄的操作,所以用戶名和密碼是必須有的,期望結果可以有也可以沒有。數據類型看源代碼!
1 from selenium import webdriver
2 from ddt import ddt, data, unpack
3 import unittest
4 import time
5 from selenium.common.exceptions import NoSuchWindowException
6 '''
7 簡單數據驅動測試
8 '''
9 @ddt
10 class ddtTest(unittest.TestCase):
11 # 數據 可以是元祖, 列表, 字典(可迭代對象)
12 value = [['12691569846@sohu.com', 'xiacha11520','https://mail.sohu.com/fe/#/homepage'],
13 ['12691569844@sohu.com', 'xiacha11520','https://mail.sohu.com/fe/#/homepage']]
14 # value = [{'uname':'******@sohu.com', 'password':'xiacha11520','expected':'https://mail.sohu.com/fe/#/homepage'},
15 # {'uname':'******@sohu.com', 'password':'xiacha11520','expected':'https://mail.sohu.com/fe/#/homepage'}]
16 def setUp(self):
17 self.testUrl = 'https://mail.sohu.com/fe/#/login'
18 self.driver = webdriver.Firefox()
19 self.driver.get(self.testUrl)
20
21 @data(*value) # * 解析數據
22 @unpack# 用來解包, 將每組數據的第一個數據傳遞給uname依次類推, 當數據為字典時,形參需和字段的key值相同
23 def test_case1(self, uname, password, expected):
24 try:
25 username = self.driver.find_element_by_xpath("//input[@placeholder='請輸入您的郵箱']")
26 username.send_keys(uname)
27 time.sleep(1)
28 userpassword = self.driver.find_element_by_xpath("//input[@placeholder='請輸入您的密碼']")
29 userpassword.send_keys(password)
30 self.driver.find_element_by_xpath("//input[@type='submit']").click()
31 time.sleep(2)
32 currenturl = self.driver.current_url
33 self.assertEqual(expected, currenturl,'登錄失敗')
34 except NoSuchWindowException as e:
35 print(e)
36 raise
37 except AssertionError:
38 print('期望值是{}, 實際值是{}'.format(expected,currenturl))
39 raise
40 except Exception:
41 raise
42 def tearDown(self):
43 self.driver.quit()
44 # pass
45 if __name__ == '__main__':
46 unittest.main()
1.@ddt來裝飾測試類(ddt數據驅動的規范寫法,記住就ok)
2.@data(*value)裝飾測試用例(也是一種規范,這邊又涉及到裝飾器,不懂的可以百度,這邊不再贅述,一句話兩句話也說不清楚)記住:*value作用是打散數據,比如上面代碼是用一個大列表存儲兩個小列表存放數據的,那么*value會得到兩個小列表,每個小列表是一組測試數據
3.@unpack 解析*value數據,會把兩個小列表里面的每一個數據取出來分別傳遞給我們測試用例的形參
方式1缺點
存儲大量數據時,需查看源代碼,不利于腳本的維護
數據準備
新建一個json文件(也可以是txt文件),將我們需要的兩組測試數據以列表的形式寫到json文件中,每組數據的每一項參數用相同的符號分割開(方便腳本讀取數據)
{
"value1":"******@sohu.com||xiacha11520||https://mail.sohu.com/fe/#/homepage",
"value2":"******@sohu.com||xiacha11520||https://mail.sohu.com/fe/#/homepage"
}
1 from selenium import webdriver
2 from ddt import ddt, file_data
3 import unittest, time
4 from selenium.common.exceptions import NoSuchWindowException
5 import HTMLTestRunner
6 '''
7 從文件中讀測試數據
8 '''
9
10 @ddt # ddt裝飾測試類
11 class Testdata(unittest.TestCase):
12
13 def setUp(self):
14 self.driver = webdriver.Firefox()
15 self.driver.get('https://mail.sohu.com/fe/#/login')
16
17 @file_data('test_data.json') # 讀取文件的 文件中數據可以是一個列表,也可以是一個字典
18 def test_data(self,value):
19 uname, password, expected = tuple(value.strip().split('||')) # value是一個字符串
20 # print(type(value),value)
21 try:
22 username = self.driver.find_element_by_xpath("//input[@placeholder='請輸入您的郵箱']")
23 username.send_keys(uname)
24 time.sleep(1)
25 userpassword = self.driver.find_element_by_xpath("//input[@placeholder='請輸入您的密碼']")
26 userpassword.send_keys(password)
27 self.driver.find_element_by_xpath("//input[@type='submit']").click()
28 time.sleep(2)
29 currenturl = self.driver.current_url
30 self.assertEqual(expected, currenturl,'登錄失敗')
31 except NoSuchWindowException as e:
32 raise e
33 except AssertionError:
34 print('期望值是{}, 實際值是{}'.format(expected,currenturl))
35 raise
36 except Exception:
37 raise
38
39 def tearDown(self):
40 self.driver.quit()
41 if __name__ == '__main__':
42 unittest.main()
43 # import os
44 # from datetime import date
45 # currentPath = os.path.dirname(os.path.abspath(__file__))# 獲取當前文件目錄
46 # reportPath = os.path.join(currentPath,'report') # 創建一個report目錄
47 # if not os.path.exists(reportPath):
48 # os.mkdir(reportPath) # 判斷目錄是否存在, 不存在就創建
49 # reportName = os.path.join(reportPath, str(date.today())+'report.html') # 拼接html報告
50 # with open(reportName,'wb') as f:
51 # suite = unittest.TestLoader().loadTestsFromTestCase(Testdata)
52 # runner = HTMLTestRunner.HTMLTestRunner(stream=f,verbosity=1, title='數據驅動測試報告', description='數據驅動')
53 # runner.run(suite)
源碼分析
1.相對上個實例,這里使用了@file_data(文件路徑), 參數必須是一個文件,這里是一個json文件, 數據可以是一個列表,也可以是一個字典
# 列表形式
[
"*******@sohu.com||xiacha11520||https://mail.sohu.com/fe/#/homepage",
"*******@sohu.com||xiacha11520||https://mail.sohu.com/fe/#/homepage"
]
2.測試用例接收的是一個字符串,需要對字符串進行處理,把用戶名,密碼,期望值解析出來
方式2優缺點
測試數據存在文件中,方便管理修改,添加數據,易于維護,缺點呢?emmm個人認為這種方式最好!
數據準備
新建一個xml格式的文件,按照xml格式的語法需求,填寫數據(xml文檔我也不是很懂,簡單的顯示個文字啥的還可以^-^!)
<?xml version="1.0"?>
<bookList type="technolog">
<book>
<uname>******@sohu.com</uname>
<password>xiaochao11520</password>
<expected>https://mail.sohu.com/fe/#/homepage</expected>
</book>
<book>
<uname>******@sohu.com</uname>
<password>xiaochao11520</password>
<expected>https://mail.sohu.com/fe/#/homepage</expected>
</book>
</bookList>
實例代碼
1 from xml.etree import ElementTree
2
3 class ParseXml(object):
4 def __init__(self, xmlpath):
5 self.xmlpath = xmlpath
6
7 # 獲取根節點
8 def getRoot(self):
9 tree = ElementTree.parse(self.xmlpath)
10 root = tree.getroot()
11 return root
12
13 # 根據根節點查找子節點
14 def findNodeByName(self, parentNode, nodeName):
15 nodes = parentNode.findall(nodeName)
16 return nodes
17
18 def getNodeOfChildText(self, node):
19 # 獲取節點node下所有子節點的節點名作為key
20 # 本節點作為value組成的字典對象
21 childrenTextDict = {}
22 for i in list(node.iter())[1:]: # node 節點下的所有節點組成的列表
23 childrenTextDict[i.tag] = i.text
24 # print(list(node.iter())[1:])
25 return childrenTextDict
26
27 # 獲取節點node下面的節點的所有數據
28 def getDataFromXml(self, node):
29 root = self.getRoot()
30 books = self.findNodeByName(root, node)
31 dataList=[]
32 for book in books:
33 childrentext = self.getNodeOfChildText(book)
34 dataList.append(childrentext)
35 return dataList
36 if __name__=='__main__':
37 xml = ParseXml('./xmlData.xml')
38 root = xml.getRoot()
39 print(root.tag)
40 books = xml.findNodeByName(root, 'book') # 查找所有的book節點
41 for book in books:
42 # print(book[0].tag, book[0].text)
43 print(xml.getNodeOfChildText(book))
44 print(xml.getDataFromXml('book'))
1 from dataDdt.doXML import ParseXml
2 from selenium import webdriver
3 from selenium.common.exceptions import NoSuchWindowException, TimeoutException
4 import unittest
5 from ddt import ddt, data,unpack
6 import time
7 from selenium.webdriver.support import expected_conditions as EC
8 from selenium.webdriver.support.ui import WebDriverWait
9 from selenium.webdriver.common.by import By
10 values = ParseXml('./xmlData.xml')
11 @ddt
12 class xmltest(unittest.TestCase):
13
14 def setUp(self):
15 self.driver = webdriver.Firefox()
16 self.driver.get('https://mail.sohu.com/fe/#/login')
17 @data(*values.getDataFromXml('book'))
18 @unpack
19 def test_xml(self,uname, password, expected):
20 try:
21 wait = WebDriverWait(self.driver,5)
22 wait.until(EC.element_to_be_clickable((By.XPATH, "//input[@type='submit']")))
23 username = self.driver.find_element_by_xpath("//input[@placeholder='請輸入您的郵箱']")
24 username.send_keys(uname)
25 time.sleep(1)
26 userpassword = self.driver.find_element_by_xpath("//input[@placeholder='請輸入您的密碼']")
27 userpassword.send_keys(password)
28 self.driver.find_element_by_xpath("//input[@type='submit']").click()
29 time.sleep(2)
30 currenturl = self.driver.current_url
31 self.assertEqual(expected, currenturl, '登錄失敗')
32 except TimeoutException as e:
33 raise e
34 except NoSuchWindowException as e:
35 raise e
36 except AssertionError as e:
37 print('期望值是{}, 實際值是{}'.format(expected, currenturl))
38 raise e
39 except Exception:
40 raise
41 def tearDown(self):
42 self.driver.quit()
43
44 if __name__=='__main__':
45 unittest.main()
源碼分析
1.xml文檔編寫(深入了解需百度)有點像html,但又有不同,xml中的節點可以是任意名稱,每個節點同樣是成雙出現
2.增加了doXML.py文檔,用來解析xml文件,方便腳本獲取數據(注釋寫的很詳細,不懂的化可以慢慢調試,哪里不懂print哪里)
3.測試腳本和上面的實例大致相同
方式3優缺點
優點是做到了數據與測試的分離,方便數據維護,缺點也比較明顯,需要對xml文檔有一定的了解
上面的數據驅動測試步驟是我自己總結的,看了上面的實例對于步驟應該還算合理,下面是我在網上找到的數據驅動測試步驟(感覺比較官方!大家可以參考)
1.編寫測試腳本, 腳本需要支持從程序對象, 文件,或者數據庫讀入數據。(個人觀點:如果腳本先編寫完,測試數據還未準備,后期還要做修改)
2.將測試腳本使用的測試數據存入程序對象,文件,或者數據庫等外部介質中。(個人觀點:這個階段實為準備數據的階段,也就是我們數據要存在哪里,理應放在第一步)
3.運行腳本過程中,循環調用存儲在外部介質中的測試數據。(個人觀點:這里要考慮我們如何讀取,使用數據)
4.驗證所有的測試結果是否符合預期結果
最后今天的文章就到這里了,喜歡的可以點贊收藏加關注。
閱讀本文大概需要 6 分鐘
日常開發軟件可能會遇到這類小眾需求,導出數據到 Word、Excel 以及 PDF文件,如果你使用 C++ 編程語言,那么可以選擇的方案不是很多,恰好最近剛好有這部分需求,整理下這段時間踩過的坑,方便后人
日常開發的軟件使用最多的應該是導出數據到 Word 文檔中,目前可以用的方案有這幾種
沒有十全十美的方案,任何方案都存在優點和缺點,下面來詳細看下這幾種方案的優缺點以及適用場景
“
原理:事先編輯好一份 Word 模板,需要替換內容的 地方預留好位置,然后使用特殊字段進行標記,后面使用代碼進行全量替換即可完成
我們先編輯一份 Word 模板文檔,內容大概如下所示:
QFile file("XML-Template.xml");
if (!file.open(QIODevice::ReadOnly))
{
qDebug() << "open xxml file fail. " << file.errorString();
return 0;
}
QByteArray baContent = file.readAll();
file.close();
QString strAllContent = QString::fromLocal8Bit(baContent);
strAllContent.replace("$VALUE0", "1");
strAllContent.replace("$VALUE1", QString::fromLocal8Bit("法外狂徒張三"));
strAllContent.replace("$VALUE2", QString::fromLocal8Bit("考試不合格"));
strAllContent.replace("$VALUE3", "2");
strAllContent.replace("$VALUE4", QString::fromLocal8Bit("李四"));
strAllContent.replace("$VALUE5", QString::fromLocal8Bit("合格"));
QFile newFile("export.doc");
if (!newFile.open(QIODevice::WriteOnly))
{
qDebug() << "file open fail." << newFile.errorString();;
return 0;
}
newFile.write(strAllContent.toLocal8Bit());
newFile.close();
可以看出來這種方式比較繁瑣,重點是編輯固定的模板格式,而且編輯好后保存成XML格式后還需要繼續調整,這種 XML 格式標簽很多,不小心就修改錯了,導致導出的文檔打不開
這種方式適合模板內容不太復雜,內容較少的情況下使用
“
原理:采用 Micro Soft公開的接口進行通訊,進行讀寫時會打開一個 `Word進程來交互
COM 技術概述
Qt 為我們提供了專門進行交互的類和接口,使用 Qt ActiveX框架就可以很好的完成交互工作
使用時需要引入對應的模塊,在 pro 文件引入模塊
QT *= axcontainer
打開文檔寫入內容
QAxObject *pWordWidget = new(std::nothrow) QAxObject;
bool bResult = pWordWidget->setControl("word.Application");
if (!bResult)
{
return false;
}
// 設置是否顯示
pWordWidget->setProperty("Visible", false);
QAxObject *pAllDocuments = pWordWidget->querySubObject("Documents");
if(nullptr == pAllDocuments)
{
return false;
}
// 新建一個空白文檔
pAllDocuments->dynamicCall("Add (void)");
// 獲取激活的文檔并使用
QAxObject *pActiveDocument = pAllDocuments->querySubObject("ActiveDocument");
if(nullptr == pActiveDocument)
{
return false;
}
// 插入字符串
QAxObject *pSelectObj = pWordWidget->querySubObject("Selection");
if (nullptr != pSelectObj)
{
pSelectObj->dynamicCall("TypeText(const QString&)", "公眾號:devstone");
}
……
可以看出來使用起來不難,對于新手友好一點,很多寫入操作方法比較繁瑣,需要自己重新封裝一套接口
這種方式同樣適用于寫入 Excel 文件,后面再說
“
原理:這種方式得益于 Word支持 HTML格式導出渲染顯示,那么反向也可以支持,需要我們拼接 HTML格式內容,然后寫入文件保存成 .doc格式
QString HTML2Word::getHtmlContent()
{
QString strHtml = "";
strHtml += "<html>";
strHtml += "<head>";
strHtml += "<title>測試生成word文檔</title>";
strHtml += "<head>";
strHtml += "<body style=\"bgcolor:yellow\">";
strHtml += "<h1 style=\"background-color:red\">測試qt實現生成word文檔</h1>";
strHtml += "<hr>";
strHtml += "<p>這里是插入圖片<img src=\"D:\\title.jpg" alt=\"picture\" width=\"100\" height=\"100\"></p>";
strHtml += "</hr>";
strHtml += "</body>";
strHtml += "</html>";
return strHtml;
}
// 保存寫入文件
QFile file("D:/htmp2Word.doc");
if (!file.open(QIODevice::WriteOnly))
{
return false;
}
QTextStream out(&file);
out << getHtmlContent();
file.close();
這種方式難點在于 HTML格式拼接,任何缺失字段都會導致導出失敗,適合小眾需求下導出
圖片問題其實可以手動進行轉化,文檔導出成功后手動拷貝內容到新的文檔,這樣圖片就真正插入到文檔中,文檔發送給別人也不會丟失圖片了
還有一個坑就是:如果你使用 WPS 打開導出的文檔,默認顯示的是 web視圖,需要手動進行調整
某些電腦分辨率變化也會導致生成的文檔中字體等產生變化
可以使用的第三方庫幾乎沒有,網絡上找到的有這么幾個
DuckX庫 docx庫
在讀寫 Word這部分,C++ 基本沒有可以使用的第三方庫,不像其他語言Java、C#、Python有很多可以選擇,這個痛苦也只有 C++ 程序員能夠理解了吧
所以怎么選擇還是看自己項目需求吧,沒有十全十美的方案
上面說了這么多,都是導出生成 Wrod,那么下面來看看有那些方式可以讀取顯示 Word內容
這種需求應該不會很多,而且顯示難度更大一些
使用 COM組件方式,即采用 QAxWidget框架顯示 office 文檔內容,本質上就是在我們編寫的 Qt 界面上嵌入 office 的軟件,這種方式其實和直接打開 Word查看沒有啥區別,效果、性能上不如直接打開更好一些
目前一般都會采用折中方案,把 Word 轉為 PDF 進行預覽加載顯示,我們知道 PDF 渲染庫比較多,生態相對來說要好一些,在選擇上就更廣泛些,如何使用后面部分有專門介紹 PDF章節
目前有一個支持比較好的第三方庫可以使用,整體使用基本可以滿足日常使用
QXlsx
這款開源庫支持跨平臺,Linux、Windows、Mac、IOS、Android,使用方式支持動態庫調用和源碼直接集成,非常方便
編譯支持 qmake和cmake,可以根據你自己的項目直接集成編譯,讀寫速度非常快
QXlsx::Document xlsx;
// 設置一些樣式
QXlsx::Format titleFormat;
titleFormat.setBorderStyle(QXlsx::Format::BorderThin); // 邊框樣式
titleFormat.setRowHeight(1,1,30); // 設置行高
titleFormat.setHorizontalAlignment(QXlsx::Format::AlignHCenter); // 設置對齊方式
// 插入文本
xlsx.write(1,1, "微信公眾號:devstone", titleFormat);
// 合并單元格
xlsx.mergeCells(QXlsx::CellRange(2,1,4,4), titleFormat);
// 導出保存
xlsx.saveAs("D:/xlsx_export.xlsx");
// 添加工作表
xlsx.addSheet("devstone");
可以看到上手非常容易、各個函數命名也貼近 Qt Api,是一款非常良心的開源軟件
“
PS:注意該軟件使用 MIT 許可協議,這樣對于很多個人或者公司來說非常良心,意味著你可以無償使用、修改該項目,但是必須在你項目中也添加同樣的 MIP許可
上面也提到了,還可以使用 COM 組件的方式讀寫 Excel,不過有了這款開源庫基本就可以告別 COM組件方式了
PDF相關開源庫挺多的,給了 C++ 程序員莫大的幫助,目前可用的主要有這些
其中 mupdf和 poppler 屬于功能強大但是很難編譯的那種,需要有扎實的三方庫編譯能力,否則面對 n 個依賴庫會無從下手
不過可喜的是 Github 上有兩個開源庫可以供選擇
這個庫其實封裝了 pdf.js庫,使用 WebEngine來執行 JavaScript進而加載文件
項目地址
這種方式對環境有特殊要求了,如果你的項目使用的 Qt 版本不支持 WebEngine,那么就無法使用
這個庫是 Qt 官方親自操刀對第三方庫進行了封裝,暴露的 API 和 Qt 類似,使用起來非常舒服
Qt 官方
代碼結構以及使用 Demo
關于如何使用,官方已經給了我們非常詳細的步驟了,直接跟著下面幾步就 OK 了
官方教程
git clone git://code.qt.io/qt-labs/qtpdf
cd qtpdf
git submodule update --init --recursive
qmake
make
cd examples/pdf/pdfviewer
qmake
make
./pdfviewer /path/to/my/file.pdf
可以看到使用了谷歌開源的 pdfium 三方庫,編譯時需要單獨更新下載這個庫,因為某些原因可能你無法下載,不過好在有人在 GitHub上同步了這個倉庫的鏡像,有條件還是建議直接下載最新穩定版的
可正常訪問的倉庫地址:https://github.com/PDFium/PDFium
相關類可以看這個文檔:https://developers.foxit.com/resources/pdf-sdk/c_api_reference_pdfium/modules.html
“
最后還要注意項目開源協議:pdfium引擎開始來自于福昕,一個中國本土的軟件公司,Google與其合作最終進行了開源,目前采用的是 BSD 3-Clause 協議,這種協議允許開發者自由使用、修改源代碼,也可以修改后重新發布,允許閉源進行商業行為,不過需要你在發布的產品中包含原作者代碼中的 BSD 協議
以上就是項目中常用的文檔處理方法總結,當然了肯定也還有其它方案可以實現,畢竟條條大路通羅馬,如果你還要不錯的方案和建議歡迎留言
PS: 以上方案和對應的源碼編譯、使用例子會統一上傳到 GitHub對應的倉庫,方便后人使用
取之互聯網、回報互聯網
原創不易,如果覺得對你有幫助,歡迎點贊、在看、轉發
推薦閱讀
*請認真填寫需求信息,我們會在24小時內與您取得聯系。