1.需求分析
隨著產(chǎn)品規(guī)模不斷完善,功能模塊逐漸增加,傳統(tǒng)的人工測試逐步變得低效。為了提高測試效率,可以將簡單的功能測試點(diǎn)交由自動化執(zhí)行,讓測試人員可以空出更多的時間去關(guān)注業(yè)務(wù),流程。而傳統(tǒng)的自動化測試腳本由于維護(hù)性差,成本高,收益低等因素導(dǎo)致難以有效的推行。而測試平臺化規(guī)劃好的話能將業(yè)務(wù)與代碼的耦合度降低,有利于后期維護(hù)與可持續(xù)集成。
2.方案設(shè)想

所有數(shù)據(jù)均保存在數(shù)據(jù)庫中,提供Web頁面進(jìn)行操作,其中

2.自定義方法:平臺提供瀏覽器基礎(chǔ)操作并提供功能用以保存測試人員自定義的方法,例如:
平臺提供基礎(chǔ)的點(diǎn)擊元素以及向元素輸入信息基礎(chǔ)功能。結(jié)合頁面數(shù)據(jù)保存登錄頁的自定義方法向用戶名輸入框中輸入用戶名,向密碼輸入框中輸入密碼,向驗(yàn)證碼輸入框中輸入驗(yàn)證碼,點(diǎn)擊登錄按鈕等。后續(xù)通過關(guān)鍵字驅(qū)動進(jìn)行調(diào)用。
3.自定義類方法:自定義方法并不能解決所有的需求,有些復(fù)雜的邏輯時,上述的方式并不適用,所以需要通過類中實(shí)現(xiàn)邏輯方法并提供給外界調(diào)用。例如需求:在一個table中需要通過判斷某元素的內(nèi)容,符合預(yù)期后再進(jìn)行操作
以上三種結(jié)合起來生成測試用例。并與測試數(shù)據(jù)形成測試用例集(關(guān)鍵字驅(qū)動以及數(shù)據(jù)驅(qū)動相結(jié)合的混合驅(qū)動模式)再通過所選用配置最終形成自動化執(zhí)行的測試腳本。(配置:解決測試用例運(yùn)行環(huán)境,瀏覽器等相關(guān)問題)然后交給執(zhí)行引擎執(zhí)行并且輸出測試報(bào)告
3.核心代碼
1.基礎(chǔ)父類
/**
* 基礎(chǔ)父類,所有PageObject類均繼承該類
*
* @author miduo
*
*/
public class Page {
private WebDriver driver;
public Page(WebDriver driver) {
super();
this.driver = driver;
if (this.driver != null) {
this.driver.manage().window().maximize();
}
}
public WebDriver getDriver() {
return driver;
}
@Auto(key = "click")
public void click(String locator) {
findElement(locator).click();
}
@Auto(key = "input")
public void input(String locator, String value) {
findElement(locator).sendKeys(value);
}
@Auto(key = "open")
public void open(String url) {
driver.get(url);
}
/**
* 通過反射獲取類自定義方法
*
* @param key
* @return
*/
protected Method getCustomMethodByKey(String key) {
Method[] methods = getClass().getMethods();
for (Method method : methods) {
Auto autoKey = method.getAnnotation(Auto.class);
if (autoKey != null && autoKey.key().equals(key)) {
return method;
}
}
return null;
}
protected final WebElement findElement(String locator) {
int index = locator.indexOf(".");
String type = locator.substring(0, index).toLowerCase();
String value = locator.substring(index + 1);
switch (type) {
case "id":
return findElement(By.id(value));
case "name":
return findElement(By.name(value));
case "css":
return findElement(By.cssSelector(value));
case "xpath":
return findElement(By.xpath(value));
case "class":
return findElement(By.className(value));
case "tag":
return findElement(By.tagName(value));
case "link":
return findElement(By.partialLinkText(value));
default:
return null;
}
}
private final WebElement findElement(By by) {
return new WebDriverWait(driver, 10).until(new ExpectedCondition<WebElement>() {
@Override
public WebElement apply(WebDriver d) {
return d.findElement(by);
}
});
}
}
2.PageObject具體實(shí)現(xiàn)類
public class LoginPage extends Page {
public LoginPage(WebDriver driver) {
super(driver);
}
/**
* PageObject自定義類方法:登錄
*
* @param userName
* @param password
* @param captcha
*/
@Auto(key = "login")
public void login(String userName, String password, String captcha) {
input("id.Account", userName);
input("id.Password", password);
input("id.Captcha", captcha);
click("xpath.//input[@type='submit']");
}
}
3.自定義方法
public class Step {
private Class<? extends Page> clazz;
private String methodKey;
private Object value;
public Step() {
super();
}
public Step(Class<? extends Page> clazz, String methodKey, Object value) {
super();
this.clazz = clazz == null ? Page.class : clazz;
this.methodKey = methodKey;
this.value = value;
}
public Class<? extends Page> getClazz() {
return clazz;
}
public void setClazz(Class<? extends Page> clazz) {
this.clazz = clazz;
}
public String getMethodKey() {
return methodKey;
}
public void setMethodKey(String methodKey) {
this.methodKey = methodKey;
}
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
4.測試用例以及測試數(shù)據(jù)
public class TestCase {
private ArrayList<Step> steps;
public TestCase() {
super();
this.steps = new ArrayList<>();
}
public ArrayList<Step> getSteps() {
return steps;
}
public void setSteps(ArrayList<Step> steps) {
this.steps = steps;
}
public void addStep(Step step) {
this.steps.add(step);
}
public Result perform(WebDriver driver, Parameter parameter) {
Result result = new Result();
for (Step step : steps) {
Object[] value = step.getValue();
Object[] coValue = new Object[value.length];
Boolean change= false;
if (parameter != null) {
for (int i = 0; i < value.length; i++) {
String key = value[i].toString();
coValue[i] = key;
if (key.startsWith("${") && key.endsWith("}")) {
change= true;
Object v = parameter.get(key.substring(2, key.length() - 1));
value[i] = v == null ? value[i] : v;
}
}
}
if (!step.perform(driver, value)) {
result.setResult(false);
return result;
}
if (change) {
step.setValue(coValue);
}
}
result.setResult(true);
return result;
}
}
public class Parameter {
private Map<String, Object> parameter;
public Parameter() {
super();
this.parameter = new HashMap<>();
}
public void add(String key, Object value) {
this.parameter.put(key, value);
}
public void remove(String key) {
this.parameter.remove(key);
}
public Object get(String key) {
return this.parameter.get(key);
}
}
5.執(zhí)行引擎
public class Runner {
public static List<Result> perform(TestCase testCase, List<Parameter> testData) {
List<Result> list = new ArrayList<>();
System.out.println(testData.size());
if (testData != null && testData.size() > 0) {
for (Parameter parameter : testData) {
// 實(shí)例化何種瀏覽器與打開什么環(huán)境由配置決定,在此不實(shí)現(xiàn)
System.setProperty("webdriver.chrome.driver", System.getProperty("user.dir") + "http://chromedriver.exe");
WebDriver driver = new ChromeDriver();
driver.get("http://*********保密********");
list.add(testCase.perform(driver, parameter));
}
} else {
System.setProperty("webdriver.chrome.driver", System.getProperty("user.dir") + "http://chromedriver.exe");
WebDriver driver = new ChromeDriver();
driver.get("http://*********保密********");
list.add(testCase.perform(driver, null));
}
return list;
}
}
代碼僅作為可行性分析使用,并非最終版本。
測試代碼
public class Test {
private static List<Parameter> parameters;
static {
Parameter parameter = new Parameter();
parameter.add("userName", "userName_01");
parameter.add("password", "password_01");
parameter.add("captcha", "captcha_01");
Parameter paramete1r = new Parameter();
paramete1r.add("userName", "userName_02");
paramete1r.add("password", "password_02");
paramete1r.add("captcha", "captcha_02");
parameters = new ArrayList<>();
parameters.add(parameter);
parameters.add(paramete1r);
}
//通過用戶自定義保存方法運(yùn)行
@org.testng.annotations.Test
public void test1() {
TestCase tc = new TestCase();
tc.addStep(new Step(null, "input", "id.Account", "${userName}"));
tc.addStep(new Step(null, "input", "id.Password", "${password}"));
tc.addStep(new Step(null, "input", "id.Captcha", "${captcha}"));
tc.addStep(new Step(null, "click", "xpath.//input[@type='submit']"));
Runner.perform(tc, parameters);
}
//通過自定義類方法運(yùn)行
@org.testng.annotations.Test
public void test2() {
TestCase tc = new TestCase();
tc.addStep(new Step(LoginPage.class, "login", "${userName}", "${password}", "${captcha}"));
Runner.perform(tc, parameters);
}
}
代碼執(zhí)行效果
再通過一個配置文件或者數(shù)據(jù)庫存儲PageObject類的路徑,即可通過純數(shù)據(jù)進(jìn)行驅(qū)動測試運(yùn)行,后續(xù)再持續(xù)完善用戶,任務(wù),結(jié)果等相關(guān)模塊即可打造較為完善的自動化測試平臺,如此一來,測試人員僅需在平臺中編輯測試用例與測試數(shù)據(jù)并新建相關(guān)測試任務(wù)即可。測試用例以及測試數(shù)據(jù)的格式為:
| Page | Method | value |
|---|---|---|
| 后臺登錄頁 | 輸入用戶名 | ${userName} |
| 后臺登錄頁 | 輸入密碼 | ${password} |
| 后臺登錄頁 | 輸入驗(yàn)證碼 | ${captcha} |
| 后臺登錄頁 | 點(diǎn)擊登陸 | - |
| userName | password | captcha |
|---|---|---|
| userName1 | password1 | captcha1 |
| userName2 | password2 | captcha2 |
| userName3 | password3 | captcha3 |