posted: 2017-8-30 11:56 Gu Xiang Source: 51Testing Software Test Network original
http://www.51testing.com/html/69/n-3720769-2.html
1. test the PyramidFigure 1 Software Test Pyramid Figure 1 is the main Cohn proposed
Software TestingPyramid, he thought that as a test engineer should put a lot of
workSpent in
Unit TestAnd
Interface Test, while the rest is sent in UI testing as well as exploratory testing. Even though, the advantages of unit testing is very prominent, it is close to the code itself, running fast, development can write the product code while writing unit test code, once the unit test found defects, you can immediately find the corresponding product code to modify. However, the disadvantage of unit testing is also obvious, that is, how many product code you have, the corresponding unit test code should correspond to it, the result is that unit test code is equal to or even more than the number of product code, which is why unit testing in general small and medium-sized enterprises are difficult to fully promote the reasons. For UI-based testing because of changes in requirements, page adjustments are frequent, so in many enterprises, UI-based
Automated TestingAutomation of core functions that require no change is often a smoke-
Test Cases。 And based on the interface Test between the two (Interface test), based on the number of code is not many, the change is less than the advantages of more and more large enterprises to support.
2, UnitTestSince this article is about Django, Django is based on the
PythonLanguage, so here's what we're going to introduce here. I mainly introduce software interface testing based on Python requests. First let's look at the Python-based unittest,unittest formerly known as Pytest, which belongs to the Xunit framework. Let's take a look at a product code first. calculator.py
#!/usr/bin/env python#coding:utf-8class calculator:def __init__ (self, A, b): Self.a=int (a) Self.b=int (b) def myadd (self ): Return self.a+self.bdef mysubs (self): return self.a-self.bdef mymultiply (self): return self.a*self.bdef mydivide ( Self): Try:return self.a/self.bexcept zerodivisionerror:print ("Divisor cannot be zero") return 9999999999999999 |
It is obvious that this code implements the addition, subtraction, multiplication, and arithmetic functions. Class Calculator has two member variables, SELF.A and Self.b,myadd, Mysubs, mymultiply, mydivide respectively implement SELF.A+SELF.B, SELF.A-SELF.B, self.a*self.b , self.a/self.b four functions, in Mydivide, if the divisor is self.b to 0, we will do the corresponding processing, print "divisor cannot be zero" warning, and then return a very large number: 9999999999999999. Now let's take a look at the test code for the UnitTest framework that corresponds to this piece of code.
calculatortest.py#!/usr/bin/env python#coding:utf-8import unittestfrom Cal CuLator Import Calculatorclass calculatortest (unittest. TestCase):d EF setUp (self):p rint ("Test start!") def test_base (self): J=calculator (4,2) self.assertequal (J.myadd (), 6) self.assertequal (J.mysubs (), 2) Self.assertequal (J.mymultiply (), 8) self.assertequal (J.mydivide (), 2) def test_divide (self): J=calculator (4,0) Self.assertequal (J.mydivide (), 9999999999999999) def tearDown (self):p rint ("Test end!") If __name__== ' __main__ ': #构造测试集suite =unittest. TestSuite () suite.addtest (Calculatortest ("Test_base")) Suite.addtest (Calculatortest ("Test_divide")) #运行测试集合runner =unittest. Texttestrunner () Runner.run (suite) |
First we use the UnitTest test framework must first Importunittest class, UnitTest class is Python's own test class, as long as you install Python, this class is automatically installed. Then we introduce the class to be tested: Fromcalculator import calculator. The test class parameter for UnitTest must be unittest. TestCase. As with other Xunit test frameworks, UnitTest also has an initialization function and a purge function, defined as DEF setup (self): and Def TearDown (self): because there is no practical operation here, we are only in Def setup ( Self): Prints a "Test start!" in the function A string that prints a "testend!" in the Def TearDown (self): function String. UnitTest the function name of the specific test function must begin with Test_, which is somewhat similar to Junit3,j=calculator (4,2) first defines a class variable J for self.a = 4 and self.b = 2, and then asserts Self.assertequal () function to verify that the result of the calculation is consistent with the expected result. In the Deftest_divide (self): function We have specifically tested the case where the divisor is 0. The main function of UnitTest is the same as other main functions for if__name__== ' __main__ ': first through suite=unittest. TestSuite () to construct the test set, and then through Suite.addtest (Calculatortest ("Test_base")), Suite.addtest (Calculatortest ("Test_divide")) Add two test functions and then pass the runner=unittest. Texttestrunner (), Runner.run (suite) to perform the testing work.
when many test files need to be run in batches, we can do the following: 1, defines the file name of these test files as a pattern that can be matched with a regular function, such as a. py file that starts or ends with test. 2, Set up a batch-processing py file, such as runtest.py. runtest.py #!/usr/bin/env python #coding: utf-8 import unittest test_dir= './' Discover=unittest.defaulttestloader.disc Over (test_dir,pattern= "*test.py") if __name__== ' __main__ ': runner=unittest. Texttestrunner () Runner.run (Discover) Test_dir: Defines the path to the test file, which is the current path. Discover=unittest.defaulttestloader.discover (test_dir,pattern= "*test.py") is the. py file (pattern= "* test.py ") and then in the main function by calling Runner=unittest. Texttestrunner (), Runner.run (Discover) Two lines of code to implement the execution of test cases in all files that match. Since the introduction to the unittest of the bulk operation, here I need to introduce how to use unittest to generate a good test report. We first go to the website http://tungwaiyip.info/software/HTMLTestRunner.html download the htmltestrunner.py file into the%python_home%\lib\ directory. If you are using python2.x you do not need to make changes, otherwise please make the following changes:
94 Line Import Stringio change to import io539 line Self.outputbuffer = Stringio.stringio () to Self.outputbuffer = Io. Stringio () 631 lines print >>sys.stderr, ' \ntime Elapsed:%s '% (self.stoptime-self.starttime) changed to print (Sys.stderr, ' \ Ntime Elapsed:%s '% (self.stoptime-self.starttime)) 642 line if not Rmap.has_key (CLS): Instead CLS in rmap:766 row UO = O.decode ( ' Latin-1 ') instead of UO = o772 line UE = E.decode (' latin-1 ') to UE = E |
So we add Fp=open ("result.html", "WB") in front of the runtest.py header to Fromhtmltestrunner import Htmltestrunner,runner.run (Discover), Runner=htmltestrunner (stream=fp,title= ' Test report ', description= ' test Case Execution Report '), followed by Fp.close (), After running the test case, you can generate an aesthetically pleasing HTML-based test report, and the final runtest.py code is as follows.
Runtest.py#!/usr/bin/env python#coding:utf-8import unittestfrom htmltestrunner import HTMLTestRunnertest_dir= './' Discover=unittest.defaulttestloader.discover (test_dir,pattern= "*test.py") if __name__== ' __main__ ': runner= UnitTest. Texttestrunner () #以下用于生成测试报告fp =open ("result.html", "WB") Runner =htmltestrunner (stream=fp,title= ' test report ', description = ' Test Case Execution Report ') Runner.run (Discover) fp.close () |
Figure 2 Test report, of course, the test case here just described more. Figure 2 UnitTest test Report
3, Resuests object Introduction and useIf we use request first to download requests, we can use the old way, through the pip command to download >PIP install requests
Let me begin by introducing the use of the requests object.
1) Send a GET request via requests. The response = Requests.get (url,params=payload) URL is the sent address, payload is the requested parameter, the format is the dictionary type, and the preceding variable is named Params,response as the return variable. For example: URL =http://www.a.com/user.jsp payload={"id": "1", "name": "Tom"} data = Requests.get (url,params=payload)
2) Send the POST request via requests. The response = requests.post (url,data=payload) URL is the sent address, payload is the requested parameter, the format is the dictionary type, and the preceding variable is named Data,response as the return variable. For example: URL =http://www.b.com/login.jsp payload={"username": "Tom", "Password": "123456"} data = Requests.post (url,data= Payload
3) Return value of requestsHere, let's discuss the return value of the next requests. See table 1. Table 1:requests The content information of the request URL of the return value in this article describes the status of the request page (status code), which is often used as a verification point in HTTP protocol-based interface testing. 1XX: Indicates that the message is less than 2XX: Indicates that success is often used: 200: Correct #3XX represents redirection. Frequently used is: 304: No change 4XX indicates that client error is often used: 404: URL does not exist 5XX,6XX indicates a server error. Frequently used: 500: Server Internal Error 4) with this knowledge, let's take a look at how to implement the interface test by request, and here we set up the test case with the login module described earlier as the test object. The test case is shown in table 2. Table 2: Login Module test Cases entered the post-login page, "View shopping cart" assumes that our correct user name is Jerry, the correct password is 000000, so we design the test code
Testlogin.pyimport requests# Correct user name, wrong password url= "http://127.0.0.1:8000/login_action/payload={{" username ":" Jerry "," Password ":" 000000 "}}data = requests.post (url,data=payload) if (str (data.status_code) = = ' 200 ') and ("Username or password error" in str (data.text)) print ("Pass") Else:print ("Fail") #错误的用户名, the correct password url= "Http://127.0.0.1:8000/login_ action/payload={{"username": "Tom", "Password": "123456"}}data = requests.post (url,data=payload) if (STR ( Data.status_code) = = ' $ ') and ("Username or password error" in str (data.text)) print ("Pass") Else:print ("Fail") #错误的用户名, the wrong password url= "http ://127.0.0.1:8000/login_action/payload={{"username": "Tom", "Password": "000000"}}data = requests.post (URL, Data=payload) if (str (data.status_code) = = ' + ') and ("Username or password error" in str (data.text)) print ("Pass") Else:print (" Fail ") #正确的用户名, the correct password url=" http://127.0.0.1:8000/login_action/payload={{"username": "Jerry", "Password": "123456"}} data = requests.post (url,data=payload) if (str (data.status_code) = = ' + ') and ("View shopping cart" in str (data.Text) print ("Pass") Else:print ("Fail") |
Such code can be tested, but there is no test framework to limit, the code is not conducive to maintenance, more detrimental to the implementation of the batch, we have just introduced the UnitTest framework to transform.
Testlogin.pyimport unittest,requestsclass mylogin (unittest. TestCase):d EF setUp (self):p rint ("--------Test start--------") def test_login_1:url= "http://127.0.0.1:8000/login_action/ payload={{"username": "Tom", "Password": "000000"}}data = requests.post (url,data=payload) self.assertequal (' $ ', str (data.status_code)) Self.assertin ("Username or password error", str (data.text)) def test_login_2:url= "http/ 127.0.0.1:8000/login_action/payload={{"username": "Jerry", "Password": "123456"}}data = requests.post (URL, data=payload) self.assertequal (' $ ', str (data.status_code)) Self.assertin ("Username or password error", str (data.text)) def Test_ Login_3:url= "http://127.0.0.1:8000/login_action/payload={{" username ":" Tom "," Password ":" 000000 "}}data = Requests.post (url,data=payload) self.assertequal (' $ ', str (data.status_code)) Self.assertin ("Username or password error", str ( Data.text) def test_login_4:url= "http://127.0.0.1:8000/login_action/payload={{" username ":" Jerry "," Password ":" 000000 "}}data = requests.post (url,data=payload) Self.asserteqUAL (' $ ', str (data.status_code)) Self.assertin (("View shopping cart", str (data.text)) def tearDown (self):p rint ("-------- Test End--------") if __name__== ' __main__ ': #构造测试集suite =unittest. TestSuite () suite.addtest (MyLogin (" test_login_1")) Suite.addtest (MyLogin (" test_login_2")) Suite.addtest (MyLogin (" test_login_3")) Suite.addtest (MyLogin (" test_login_4")) #运行测试集合runner =unittest . Texttestrunner () Runner.run (suite) |
The program passes self.assertequal (' + ', str (data.status_code)) to determine whether the return code is the same as expected, via Self.assertin ("Username or password error", str (data.text)) To determine whether the specified string is included in the returned text. Test Cases test_login_1, test_login_2, and test_login_3 are used to test for error conditions, and a "user name or password error" Prompt will appear in the return page, test_login_4 for the correct test case, login to meet the requirements, The page jumps to the Login Item List page and displays a connection to the "View cart", so we determine if the test was successful by returning to the page with "View Cart".
4. Data-driven automated interface testingData-driven automated testing is presented by HP in its well-known product QTP and is a standard for industry automation testing, known as data-driven to parameterize test data. Since Python's technology for reading XML is quite mature, we can put the test data in XML to design data-driven automated interface testing. First look at how I designed the XML file.
Loginconfig.xml<node><case><testid>testcase001</testid><title> User Login </Title> <Method>post</Method><Desc> correct user name, error password </desc><url>http://127.0.0.1:8000/login_ action/</url><inptarg>{"username": "Jerry", "Password": "12345"}</inptarg><result>200< /result><checkword> User name or password error </checkword></case><case><testid>testcase002</ testid><title> User Login </Title><Method>post</Method><Desc> wrong username, correct password </Desc> <url>http://127.0.0.1:8000/login_action/</url><inptarg>{"username": "Smith", "Password": " knyzh158 "}</inptarg><result>200</result><checkword> user name or password error </checkword></ Case><case><testid>testcase003</testid><title> User Login </title><method>post </Method><Desc> wrong user name, error password </Desc><Url>http://127.0.0.1:8000/login_action/</Url> <inptarg>{"username":" Smith "," Password ":" 12345 "}</inptarg><result>200</result><checkword> username or password error </ Checkword></case><case><testid>testcase004</testid><title> User Login </Title> <Method>post</Method><Desc> correct user name, correct password </desc><url>http://127.0.0.1:8000/login_ action/</url><inptarg>{"username": "Jerry", "Password": "knyzh158"}</inptarg><result>200 </Result><CheckWord> View Shopping Cart </CheckWord></case></node> |
Here <node></node> is the root identity,<case>...</case> represents a test case, which has four <case>...</case> pairs, Each of the above represents four test cases. In the <case>...</case> pair, some data is for us to read more convenient, some of the data is used in the program, the following to be described separately. <Desc>...</Desc>: Test Case Description <Url></Url>: Test URL Address (program used) <InptArg>...</InptArg>: request parameter, with {}, for values that conform to the Python dictionary format (used by the program) <Result>...</Result>: Return code (used by the program) <CheckWord>...</CheckWord> : The validation string (used by the program) in the PY file we introduce the Minidom class by invoking the From Xml.dom import minidom; dom = minidom.parse (' Loginconfig.xml ') To get the XML file that needs to be read, Root = dom.documentelement to start getting the contents of the node in the file, and then use the statement AAA = Root.getelementsbytagname (' aaa ') to get all the leaf nodes in the file <AAA>...</AAA> to the data in the file because there are multiple <AAA>...</AAA> pairs, so return the parameter AAA to an object list pair, and then pass the for keyin Aaa:aaavalue = Key.firstChild.data print (Aaavalue) to get the parameters in each <AAA>...</AAA> pair. However, because the XML file is often more than one label, and to appear, really like our files so loginconfig.xml in the <TestId>...<TestId>, <title;
<Title>, <Method>...</Method> ... so we can get it this way. AAA = Root.getelementsbytagname (' aaa ') bbb = root.getelementsbytagname (' bbb ') CCC = root.getelementsbytagname (' CCC ') i = 0for Keyin aaa:aaavalue = Aaa[i].firstchild.databbbvalue = Bbb[i].firstchild.datacccvalue = Ccc[i]. Firstchild.dataprint (aaavalue) print (bbbvalue) print (cccvalue) I =i+1 |
Let's take a look at the test code.
Loginconfig.xml#!/usr/bin/env python#coding:utf-8import unittest,requestsfrom xml.dom Import Minidomclass MyLogin (unittest. TestCase):d EF setUp (self):p rint ("--------Test End--------") #从XML中读取数据dom = minidom.parse (' loginconfig.xml ') root = Dom.documentelementtestids = Root.getelementsbytagname (' TestID ') Titles = root.getelementsbytagname (' Title ') Methods = root.getelementsbytagname (' Method ') Descs = root.getelementsbytagname (' Desc ') Urls = Root.getelementsbytagname (' Url ') Inptargs = root.getelementsbytagname (' inptarg ') Results = Root.getelementsbytagname (' Result ') checkwords =root.getelementsbytagname (' Checkword ') i = 0mylists=[]for TestID in testids:mydicts={} #获取每一个数据, forming a dictionary mydicts["TestID"] = testids[i].firstchild.datamydicts["Title"] = Titles[i]. firstchild.datamydicts["Method"] = methods[i].firstchild.datamydicts["Desc"] = Descs[i]. firstchild.datamydicts["URL"] = urls[i].firstchild.datamydicts["Inptarg"] = Inptargs[i].firstchild.datamydicts["Result"] = results[i].firstchild.datamydicts["Checkword"] =checkwords[i].firstchild.datamylists.append ( mydicts) i = i+1self.mylists = Mylistsdef Test_login (self): for mylist in self.mylists:payload = eval (mylist[" Inptarg "]) url=mylist[" url "] #发送请求try: If mylist[" Method "] = =" POST ":d ata = requests.post (url,data=payload ) elif mylist["method"] = = "Get":d ata = requests.get (url,params=payload) else:print ("Method parameter get Error") Except Exception as e:self.assertequal (mylist["result"], "404") else:self.assertEqual (mylist["Result"],str ( Data.status_code)) Self.assertin (mylist["Checkword"],str (data.text)) def tearDown (self):p rint ("-------- Test End--------") if __name__== ' __main__ ': #构造测试集suite =unittest. TestSuite () suite.addtest (MyLogin ("Test_login")) #运行测试集合runner =unittest. Texttestrunner () Runner.run (suite) |
SetUp (self) takes all the leaf node data in XML and puts it in a list variable named mylists. and return to the self.mylists variable, each item in the list is a dictionary type of data, key is the XML of all leaf node tags, key corresponding to the value of the XML tag content. Finally self. Mylists is passed to each of the test functions. Now let's take a look at the function test_login (self). For Mylistin self.mylists: Take out each of the self.mylists that you just defined in the initialization. Payload =eval (mylist["Inptarg"]): To get the data labeled Inptarg, because in the XML format definition, this item in {}, inside is a value parameter pair, because mylist["Inptarg" return is a {} String that is enclosed in a dictionary format, so we have to transfer the function eval () to a dictionary variable to assign to payload. url=mylist["URL"] is the address for sending HTTP. Then by judging whether mylist["Method" is equal to "post" or "get", choose to use data = Requests.post (url,data=payload) or data =requests.get (URL, Params=payload) to send a message, and receive information in the variable data. Finally, self.assertequal (mylist["Result"],str (Data.status_code)) is adopted to determine if the return code conforms to the desired result, and Self.assertin (mylist["Checkword" ],str (Data.text)) expects the code mylist["Checkword"] to determine whether the test was successful in the returned content str (data.text). It is noted here that in the program except Exception as E is determined by self.assertequal (mylist["result"), "404") to determine whether the expected result does not exist. In this project we also add a similar runtest.py to run all of the test cases. The format is the same as before and is not repeated again. Figure 3 is a test report that adds a registered interface test code. Figure 3 HTTP Interface test report based on Python requests
5. Further optimization The attentive classmate may find that the Setup function in the above program we can do some encapsulation optimization, we create a separate py file getxml.py, the content is as follows:
Getxml.py#!/usr/bin/env python#coding:utf-8from xml.dom Import minidomclass GetXML ():d EF Getxmldata (xmlfile): #从XML中读取数据dom = minidom.parse (xmlfile) root = Dom.documentelementtestids = Root.getelementsbytagname (' TestID ') Titles = root.getelementsbytagname (' Title ') Methods = Root.getelementsbytagname (' Method ') Descs = root.getelementsbytagname (' Desc ') Urls = Root.getelementsbytagname (' Url ') Inptargs = root.getelementsbytagname (' inptarg ') Results = root.getelementsbytagname (' Result ') CheckWords =root.getelementsbytagname (' Checkword ') i = 0mylists=[]for TestID in testids:mydicts={} #获取每一个数据, form dictionary mydicts[" TestID "] = testids[i].firstchild.datamydicts[" Title "] = titles[i].firstchild.datamydicts[" Method "] = methods[i].firstchild.datamydicts["Desc"] = descs[i].firstchild.datamydicts["URL"] = Urls[i]. firstchild.datamydicts["Inptarg"] = inptargs[i].firstchild.datamydicts["Result"] = Results[i].firstChild.datamydicts["Checkword"] =checkwords[i].firstchild.datamylists.append (mydicts) i = I+1return mylists |
This changes the logintest.py to the setup function only by changing:
Loginconfig.xml...from GetXML Import GetXML #引入刚才建立的类 ... class MyLogin (unittest. TestCase):d EF setUp (self):p rint ("--------Test start--------") self.mylists = Getxml.getxmldata ("Loginconfig.xml") # Calling a function in a class ... |
Copyright NOTICE: 51Testing Software Testing Network original production, reproduced when you must be in the form of hyperlinks to indicate the original source, author information and this statement, or will be held liable
Data-driven HTTP interface testing based on Python requests