快速提升单元覆盖率

最近到新公司,接手了几十个老项目。由于项目特殊需要,需要快速将一个模块的单元测试覆盖率提升到80%以上。

怀着忐忑的心情看了一下,该模块居然还有一个单元测试,整体覆盖率为0,欲哭无泪啊。

手工写是来不及了,那就想办法自动生成吧。找了一下,最终决定采用EvoSuite。

EvoSuite有多种方式可以配置,包括命令行模式、Maven插件模式以、Eclipse插件模式、IDEA插件模式等。

一、maven模式
1、修改POM文件,在对应位置添加相关内容

<properties>
	<evosuiteVersion>1.0.6</evosuiteVersion>
</properties>

<dependencies>
	<dependency>
		<groupId>junit</groupId>
		<artifactId>junit</artifactId>
		<version>4.12</version>
		<scope>test</scope>
	</dependency>
	<dependency>
		<groupId>org.evosuite</groupId>
		<artifactId>evosuite-standalone-runtime</artifactId>
		<version>${evosuiteVersion}</version>
		<scope>test</scope>
	</dependency>
</dependencies>

<build>
	<pluginManagement>
		<plugins>
			<plugin>
				<groupId>org.eclipse.m2e</groupId>
				<artifactId>lifecycle-mapping</artifactId>
				<version>1.0.0</version>
				<configuration>
					<lifecycleMappingMetadata>
						<pluginExecutions>
							<pluginExecution>
								<pluginExecutionFilter>
									<groupId>org.apache.maven.plugins</groupId>
									<artifactId>maven-compiler-plugin</artifactId>
									<versionRange>[2.5,)</versionRange>
									<goals>
										<goal>prepare</goal>
									</goals>
								</pluginExecutionFilter>
								<action>
									<ignore />
								</action>
							</pluginExecution>
						</pluginExecutions>
					</lifecycleMappingMetadata>
				</configuration>
			</plugin>
		</plugins>
	</pluginManagement>
	<plugins>
		<plugin>
			<groupId>org.evosuite.plugins</groupId>
			<artifactId>evosuite-maven-plugin</artifactId>
			<version>${evosuiteVersion}</version>
			<executions>
				<execution>
					<goals>
						<goal>prepare</goal>
					</goals>
					<phase>process-test-classes</phase>
				</execution>
			</executions>
		</plugin>

		<plugin>
			<groupId>org.apache.maven.plugins</groupId>
			<artifactId>maven-surefire-plugin</artifactId>
			<version>2.17</version>
			<configuration>
				<properties>
					<property>
						<name>listener</name>
						<value>org.evosuite.runtime.InitializingListener</value>
					</property>
				</properties>
			</configuration>
		</plugin>

		<!--plugin>
			<groupId>org.codehaus.mojo</groupId>
			<artifactId>build-helper-maven-plugin</artifactId>
			<version>1.8</version>
			<executions>
				<execution>
					<id>add-test-source</id>
					<phase>generate-test-sources</phase>
					<goals>
						<goal>add-test-source</goal>
					</goals>
					<configuration>
						<sources>
							<source>.evosuite/evosuite-tests</source>
						</sources>
					</configuration>
				</execution>
			</executions>
		</plugin-->

		<plugin>
			<artifactId>maven-compiler-plugin</artifactId>
			<version>3.8.1</version>
			<configuration>
				<source>1.8</source>
				<target>1.8</target>
			</configuration>
		</plugin>
	</plugins>
</build>

2、生成单元测试

mvn -DmemoryInMB=4000 -Dcores=4 evosuite:generate test

#生成的单元测试在
#.evosuite/best-tests
#拷贝到正确的路径就可以了

二、命令行模式
1、下载evosuite-1.0.6.jar包

Downloads

2、收集项目依赖,把evosuite-1.0.6.jar也放入target/dependency文件夹

mvn dependency:copy-dependencies

3、生成单元测试

cd target/dependency
java -jar evosuite-1.0.6.jar -help
java -Duse_separate_classloader=false -jar evosuite-1.0.6.jar -projectCP YOUR_CLASS_PATH -generateSuite -target ..\classes

#生成的单元测试在
#target/dependency/evosuite-tests
#拷贝到正确的路径就可以了

三、Eclipse插件模式
在eclipse中安装evosuite插件,需要额外的插件地址:
http://www.evosuite.org/update

四、单元覆盖率
1、插件安装
在eclipse中搜索并安装EclEmma Java Code Coverage插件,直接搜索即可

2、修改class loader配置

#默认使用单独的class loader,覆盖率会为0
separateClassLoader = true
#全局替换为
separateClassLoader = false

3、然后在项目上,右键,Coverage as-》JUnit Test
就可以看到覆盖率了哦。
我试过两个项目,一个简单的项目,覆盖率为95以上。
一个复杂一些的Web项目,覆盖率仅为30%左右。

五、总结
生成的单元测试,实际上没有什么维护性,如何用于生产环境,待探索。

SonarQube集成代码覆盖率

Java项目

#清理,并构建
mvn clean install javadoc:aggregate -Dadditionalparam=-Xdoclint:none surefire-report:report -Daggregate=true

#sonar收集信息
mvn sonar:sonar -Dsonar.host.url=http://127.0.0.1:9000 -Dsonar.scm.disabled=True -Dsonar.junit.reportPaths=target/surefire

.Net项目

#准备收集信息
MSBuild.SonarQube.Runner.exe begin /k:"MyProject" /n:"MyProject" /v:"1.0" /d:sonar.host.url=http://127.0.0.1:9000 /d:sonar.scm.disabled=True /d:sonar.cs.vstest.reportsPaths="%CD%\*.trx" /d:sonar.cs.vscoveragexml.reportsPaths="%CD%\MyProject.coverage.xml"

#清理并构建项目
msbuild MyProject /t:Clean /t:Rebuild

#进行单元测试并生成测试报告
"%VSINSTALLDIR%\Team Tools\Dynamic Code Coverage Tools\CodeCoverage.exe" collect /output:"%CD%\MyProject.coverage" /verbose "%VSINSTALLDIR%\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe" /Logger:trx "MyProject\bin\Debug\MyProject.dll"

#报告转换为xml格式
"%VSINSTALLDIR%\Team Tools\Dynamic Code Coverage Tools\CodeCoverage.exe" analyze /output:"%CD%\MyProject.coverage.xml" "%CD%\MyProject.coverage"

#结束分析
MSBuild.SonarQube.Runner.exe end

Selenium入门04

接上一节,说一下Grid的用法

1、启动hub

set JAVA_HOME=C:\NeoLanguages\Java\JDK\jdk_x86_1.8.0_77
set PATH=%JAVA_HOME%\bin;%PATH%;C:\NeoTest\TestSelenium\trunk\bin\x86\;
set webdriver.gecko.driver=C:\NeoTest\TestSelenium\trunk\bin\x86\geckodriver.exe

java -jar ../lib/selenium-server-standalone-3.3.1.jar -role hub -hubConfig hubConfig.json -debug true

pause

hubConfig.json

{
  "port": 4444,
  "newSessionWaitTimeout": -1,
  "servlets" : [],
  "withoutServlets": [],
  "custom": {},
  "capabilityMatcher": "org.openqa.grid.internal.utils.DefaultCapabilityMatcher",
  "throwOnCapabilityNotPresent": true,
  "cleanUpCycle": 5000,
  "role": "hub",
  "debug": false,
  "browserTimeout": 0,
  "timeout": 1800
}

2、启动node

set JAVA_HOME=C:\NeoLanguages\Java\JDK\jdk_x86_1.8.0_77
set PATH=%JAVA_HOME%\bin;%PATH%;C:\NeoTest\TestSelenium\trunk\bin\x86\;
set webdriver.gecko.driver=C:\NeoTest\TestSelenium\trunk\bin\x86\geckodriver.exe

java -jar ../lib/selenium-server-standalone-3.3.1.jar -role node -nodeConfig node01Config.json -debug true

pause

node01Config.json

{
 "capabilities":
  [
    {
      "browserName": "firefox",
      "maxInstances": 5,
      "seleniumProtocol": "WebDriver"
    }
  ],
  "proxy": "org.openqa.grid.selenium.proxy.DefaultRemoteProxy",
  "maxSession": 5,
  "port": 5555,
  "register": true,
  "registerCycle": 5000,
  "hub": "http://192.168.130.178:4444",
  "nodeStatusCheckTimeout": 5000,
  "nodePolling": 5000,
  "role": "node",
  "unregisterIfStillDownAfter": 60000,
  "downPollingLimit": 2,
  "debug": false,
  "servlets" : [],
  "withoutServlets": [],
  "custom": {}
}

3、可以在hub中看到注册情况
http://localhost:4444/grid/console
这个地方一定要注意,每个node上的浏览器及操作系统信息,向hub发送指令时,必须符合该信息

4、运行脚本
TestHelloWorldHub.py

# !C:\Languages\Python\Python27\python.exe
# -*- coding: utf-8 -*-
'''
Created on 2016-10-22
@author: Hansen
HelloWorld sample for NeoSelenium
'''

from NeoSelenium import initEngine
from NeoSelenium import deInitEngine
from NeoSelenium import initEngineHub

#一个简单的查询测试
def neohope_search_test():
    try:
        #myEngine = initEngine('ie32')
        #myEngine = initEngine('ie64')
        #myEngine = initEngine('chrome')
        #myEngine = initEngine('ff32')
        #myEngine = initEngine('ff64')
        #myEngine = initEngineRemote()
	myEngine = initEngineHub()

        base_url = "https://www.neohope.com"
        myEngine.get(base_url + "/")
        myEngine.find_element_by_name("s").clear()
        myEngine.find_element_by_name("s").send_keys("Metabase")
        myEngine.find_element_by_css_selector("button.search-submit").click()
        myEngine.implicitly_wait(1000)
        #print(myEngine.find_elements_by_xpath("//div[@id='content']/article"))
        queryResultLenght = len(myEngine.find_elements_by_xpath("//div[@id='content']/article"))
        #print(queryResultLenght)
        #应该是1但现在是10
        assert queryResultLenght==1
    finally:
        #deInitEngine(myEngine)
        print("test end")

#start here
neohope_search_test()

NeoSelenium.py

#!C:\Languages\Python\Python27\python.exe
# -*- coding: utf-8 -*-
'''
Created on 2017-04-07
@author: Hansen
NeoSelenium
'''

import os
from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities

NPath = None
x86Path = "C:\NeoTest\TestSelenium\\trunk\\bin\\x86;"
x64Path = "C:\NeoTest\TestSelenium\\trunk\\bin\\x64;"

'''
selenium remote engine init
'''
def initEngineRemote():
    engine = webdriver.Remote(command_executor="http://192.168.130.178:4444/wd/hub", desired_capabilities=DesiredCapabilities.FIREFOX)
    return engine

'''
selenium remote engine init
'''
def initEngineHub():
    engine = webdriver.Remote(command_executor="http://192.168.130.178:5555/wd/hub", desired_capabilities={
        "browserName": "firefox",
        "platform": "VISTA"
    })
    return engine   

'''
selenium engine deInit
'''
def deInitEngine(engine):
    engine.close()
    engine.quit()


Selenium入门03

接上一节,说一下如何进行远程测试

1、下载selenium-server-standalone的jar包
http://www.seleniumhq.org/download/

2、运行selenium-server-standalone

set JAVA_HOME=C:\NeoLanguages\Java\JDK\jdk_x86_1.8.0_77
set PATH=%JAVA_HOME%\bin;%PATH%;C:\NeoTest\TestSelenium\trunk\bin\x86\;
set webdriver.gecko.driver=C:\NeoTest\TestSelenium\trunk\bin\x86\geckodriver.exe

java -jar ../lib/selenium-server-standalone-3.3.1.jar -role standalone

pause

3、远程测试脚本
TestHelloWorldRemote.py

# !C:\Languages\Python\Python27\python.exe
# -*- coding: utf-8 -*-
'''
Created on 2016-10-22
@author: Hansen
HelloWorld sample for NeoSelenium
'''

from NeoSelenium import deInitEngine
from NeoSelenium import initEngineRemote

#一个简单的查询测试
def neohope_search_test():
    try:
        #myEngine = initEngine('ie32')
        #myEngine = initEngine('ie64')
        #myEngine = initEngine('chrome')
        #myEngine = initEngine('ff32')
        #myEngine = initEngine('ff64')
	myEngine = initEngineRemote()

        base_url = "https://www.neohope.com"
        myEngine.get(base_url + "/")
        myEngine.find_element_by_name("s").clear()
        myEngine.find_element_by_name("s").send_keys("Metabase")
        myEngine.find_element_by_css_selector("button.search-submit").click()
	myEngine.implicitly_wait(1000)
	#print(myEngine.find_elements_by_xpath("//div[@id='content']/article"))
	queryResultLenght = len(myEngine.find_elements_by_xpath("//div[@id='content']/article"))
	#print(queryResultLenght)
	#应该是1但现在是10
        assert queryResultLenght==1
    finally:
        #deInitEngine(myEngine)
	print("test end")

#start here
neohope_search_test()

NeoSelenium.py

#!C:\Languages\Python\Python27\python.exe
# -*- coding: utf-8 -*-
'''
Created on 2017-04-07
@author: Hansen
NeoSelenium
'''

import os
from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities

NPath = None
x86Path = "C:\NeoTest\TestSelenium\\trunk\\bin\\x86;"
x64Path = "C:\NeoTest\TestSelenium\\trunk\\bin\\x64;"

'''
selenium remote engine init
'''
def initEngineRemote():
    engine = webdriver.Remote(command_executor="http://192.168.130.178:4444/wd/hub", desired_capabilities=DesiredCapabilities.FIREFOX)
    return engine

'''
selenium engine deInit
'''
def deInitEngine(engine):
    engine.close()
    engine.quit()

Selenium入门02

下面就说一下如何用Python进行自动化测试咯

1、安装语言包

pip install selenium

2、在selenium网站下载对应浏览器的driver
http://www.seleniumhq.org/download/

3、改一下我写的这个文件,将driver路径改成正确的路径
NeoSelenium.py

#!C:\Languages\Python\Python27\python.exe
# -*- coding: utf-8 -*-
'''
Created on 2017-04-07
@author: Hansen
NeoSelenium
'''

import os
from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities

NPath = None
x86Path = "C:\NeoTest\TestSelenium\\trunk\\bin\\x86;"
x64Path = "C:\NeoTest\TestSelenium\\trunk\\bin\\x64;"

def initIE32():
    global x86Path
    global NPath
    os.environ["PATH"] = x86Path+NPath
    #print(os.environ["PATH"])
    engine = webdriver.Ie()
    return engine

def initIE64():
    global x64Path
    global NPath
    os.environ["PATH"] = x64Path+NPath
    #print(os.environ["PATH"])
    engine = webdriver.Ie()
    return engine

def initChrome32():
    global x86Path
    global NPath
    os.environ["PATH"] = x86Path+NPath
    engine = webdriver.Chrome()
    return engine

def initFirfox32():
    global x86Path
    global NPath
    os.environ["PATH"] = x86Path+NPath
    #print(os.environ["PATH"])
    engine = webdriver.Firefox()
    return engine

def initFirfox64():
    global x64Path
    global NPath
    os.environ["PATH"] = x64Path+NPath
    #print(os.environ["PATH"])
    engine = webdriver.Firefox()
    return engine

'''
selenium engine init
'''
def initEngine(engineType):
    global NPath
    if(NPath is None):
        NPath = os.environ["PATH"]
    if(cmp('IE32',engineType.upper())==0):
        return initIE32()
    elif(cmp('IE64',engineType.upper())==0):
        return initIE64()
    elif(cmp('CHROME',engineType.upper())==0 or cmp('CHROME32',engineType.upper())==0):
        return initChrome32()
    elif(cmp('FIREFOX',engineType.upper()) or cmp('FIREFOX32',engineType.upper()) or cmp('FF',engineType.upper())==0 or cmp('FF32',engineType.upper())==0):
        return initFirfox32()
    elif(cmp('FIREFOX64',engineType.upper()) or cmp('FF64',engineType.upper())==0):
        return initFirfox64()
    else:
        print("engin type: "+engineType.upper()+" not supported!")
        return None

'''
selenium remote engine init
'''
def initEngineRemote():
    engine = webdriver.Remote(command_executor="http://localhost:4444/wd/hub", desired_capabilities=DesiredCapabilities.FIREFOX)
    return engine
    

'''
selenium engine deInit
'''
def deInitEngine(engine):
    engine.close()
    engine.quit()

4、第一个自动化测试脚本
TestHelloWorld.py

# !C:\Languages\Python\Python27\python.exe
# -*- coding: utf-8 -*-
'''
Created on 2016-10-22
@author: Hansen
HelloWorld sample for NeoSelenium
'''

from NeoSelenium import initEngine
from NeoSelenium import deInitEngine

#一个简单的查询测试
def neohope_search_test():
    try:
        #myEngine = initEngine('ie32')
        #myEngine = initEngine('ie64')
        #myEngine = initEngine('chrome')
        myEngine = initEngine('ff32')
        #myEngine = initEngine('ff64')

        base_url = "https://www.neohope.com"
        myEngine.get(base_url + "/")
        myEngine.find_element_by_name("s").clear()
        myEngine.find_element_by_name("s").send_keys("Metabase")
        myEngine.find_element_by_css_selector("button.search-submit").click()
	myEngine.implicitly_wait(1000)
	#print(myEngine.find_elements_by_xpath("//div[@id='content']/article"))
	queryResultLenght = len(myEngine.find_elements_by_xpath("//div[@id='content']/article"))
	#print(queryResultLenght)
	#应该是1但现在是10
        assert queryResultLenght==1
    finally:
        #deInitEngine(myEngine)
	print("test end")

#start here
neohope_search_test()

5、运行脚本

PS:
IE驱动抛出异常解决方案

"Unexpected error launching Internet Explorer. Protected Mode settings are not the same for all zones. Enable Protected Mode must be set to the same value (enabled or disabled) for all zones."

解决方法:

     
打开IE->Internet options->Security->Enable Protected Mode
这个框,在四个域下面要是一致的(要么都勾上,要么都不勾上)

Selenium入门01

1、常用模块介绍

Selenium IDE 是一个FF插件,用于录制测试用例并重新运行。可以保持为html脚本,也可以导出为各种语言的单元测试
Selenium Html Runner 可以直接运行Selenium IDE导出的html脚本
Selenium Standalone Server 有三种运行模式(standalone、hub、node),后两种用于grid
Selenium Remote Control Selenium RC, Selenium1采用代理网站及JS注入的方式,达到操控网页的目的,在Selenium3已经取消支持
Browser Drivers Selenium2为在不修改网页的情况下,达到操控网页的无敌,利用了各种浏览器的API,达到操控浏览器的目的
Remote Web Drivers 远程运行测试(与Selenium Standalone Server或Selenium Standalone Hub进行交互时使用)
Selenium GRID 用于同时对多个操作系统的多种浏览器进行自动化测试(包括一个HUB,和多个NODE)
Language Binding 各种开发语言包

2、Selenium IDE
2.1、FF下载插件并重启
https://addons.mozilla.org/en-US/firefox/addon/selenium-ide/
2.2、录制插件

用FF打开你要测试的网站
打开Selenium IDE
Tools->Selenium IDE
输入BaseURL,点击右侧小红点儿,开始录制
在页面上进行操作(最好不要切换页面)

2.3、保存TestSuit及TestCase

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <meta content="text/html; charset=UTF-8" http-equiv="content-type" />
  <title>Test Suite</title>
</head>
<body>
<table id="suiteTable" cellpadding="1" cellspacing="1" border="1" class="selenium"><tbody>
<tr><td><b>Test Suite</b></td></tr>
<tr><td><a href="neohope.case.search.html">neohope_search_testcase001</a></td></tr>
</tbody></table>
</body>
</html>

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head profile="http://selenium-ide.openqa.org/profiles/test-case">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="selenium.base" href="https://www.neohope.com/" />
<title>neohope_search_testcase001</title>
</head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<thead>
<tr><td rowspan="1" colspan="3">neohope_search_testcase001</td></tr>
</thead><tbody>
<tr>
	<td>open</td>
	<td>/</td>
	<td></td>
</tr>
<tr>
	<td>type</td>
	<td>name=s</td>
	<td>Metabase</td>
</tr>
<tr>
	<td>clickAndWait</td>
	<td>css=button.search-submit</td>
	<td></td>
</tr>
<tr>
	<td>assertXpathCount</td>
	<td>//article</td>
	<td>1</td>
</tr>

</tbody></table>
</body>
</html>

2.4、导出python脚本
Selenium IDE->File->Export Test Case As…->Python2/WebDriver

3、用selenium-html-runner运行
3.1、下载并配置jdk
3.2、下载selenium-html-runner-3.0.1.jar
3.3、在selenium网站下载对应浏览器的driver
http://www.seleniumhq.org/download/
3.4、运行

set JAVA_HOME=C:\NeoLanguages\Java\JDK\jdk_x86_1.8.0_77
set PATH=%JAVA_HOME%\bin;%PATH%;C:\NeoTest\TestSelenium\trunk\bin\x86\;
set webdriver.gecko.driver=C:\NeoTest\TestSelenium\trunk\bin\x86\geckodriver.exe

java -jar ../lib/selenium-html-runner-3.0.1.jar -htmlSuite *firefox "https://www.neohope.com/" "../testcases/neohope.suit" "../testcases/neohope.result"
PAUSE