1.需求分析
隨著產(chǎn)品規(guī)模不斷完善,功能模塊逐漸增加摘完,傳統(tǒng)的人工測(cè)試逐步變得低效沼头。為了提高測(cè)試效率,可以將簡(jiǎn)單的功能測(cè)試點(diǎn)交由自動(dòng)化執(zhí)行解藻,讓測(cè)試人員可以空出更多的時(shí)間去關(guān)注業(yè)務(wù)老充,流程。而傳統(tǒng)的自動(dòng)化測(cè)試腳本由于維護(hù)性差螟左,成本高啡浊,收益低等因素導(dǎo)致難以有效的推行。而測(cè)試平臺(tái)化規(guī)劃好的話能將業(yè)務(wù)與代碼的耦合度降低胶背,有利于后期維護(hù)與可持續(xù)集成巷嚣。
2.方案設(shè)想
所有數(shù)據(jù)均保存在數(shù)據(jù)庫(kù)中,提供Web頁(yè)面進(jìn)行操作奄妨,其中
2.自定義方法:平臺(tái)提供瀏覽器基礎(chǔ)操作并提供功能用以保存測(cè)試人員自定義的方法砸抛,例如:
平臺(tái)提供基礎(chǔ)的點(diǎn)擊元素以及向元素輸入信息基礎(chǔ)功能评雌。結(jié)合頁(yè)面數(shù)據(jù)保存登錄頁(yè)的自定義方法向用戶名輸入框中輸入用戶名,向密碼輸入框中輸入密碼直焙,向驗(yàn)證碼輸入框中輸入驗(yàn)證碼景东,點(diǎn)擊登錄按鈕等。后續(xù)通過(guò)關(guān)鍵字驅(qū)動(dòng)進(jìn)行調(diào)用奔誓。
3.自定義類方法:自定義方法并不能解決所有的需求斤吐,有些復(fù)雜的邏輯時(shí),上述的方式并不適用厨喂,所以需要通過(guò)類中實(shí)現(xiàn)邏輯方法并提供給外界調(diào)用和措。例如需求:在一個(gè)table中需要通過(guò)判斷某元素的內(nèi)容,符合預(yù)期后再進(jìn)行操作
以上三種結(jié)合起來(lái)生成測(cè)試用例蜕煌。并與測(cè)試數(shù)據(jù)形成測(cè)試用例集(關(guān)鍵字驅(qū)動(dòng)以及數(shù)據(jù)驅(qū)動(dòng)相結(jié)合的混合驅(qū)動(dòng)模式)再通過(guò)所選用配置最終形成自動(dòng)化執(zhí)行的測(cè)試腳本派阱。(配置:解決測(cè)試用例運(yùn)行環(huán)境,瀏覽器等相關(guān)問(wèn)題)然后交給執(zhí)行引擎執(zhí)行并且輸出測(cè)試報(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);
}
/**
* 通過(guò)反射獲取類自定義方法
*
* @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.測(cè)試用例以及測(cè)試數(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í)例化何種瀏覽器與打開(kāi)什么環(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;
}
}
代碼僅作為可行性分析使用,并非最終版本盒刚。
測(cè)試代碼
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);
}
//通過(guò)用戶自定義保存方法運(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);
}
//通過(guò)自定義類方法運(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í)行效果再通過(guò)一個(gè)配置文件或者數(shù)據(jù)庫(kù)存儲(chǔ)PageObject類的路徑腺劣,即可通過(guò)純數(shù)據(jù)進(jìn)行驅(qū)動(dòng)測(cè)試運(yùn)行,后續(xù)再持續(xù)完善用戶因块,任務(wù)橘原,結(jié)果等相關(guān)模塊即可打造較為完善的自動(dòng)化測(cè)試平臺(tái),如此一來(lái),測(cè)試人員僅需在平臺(tái)中編輯測(cè)試用例與測(cè)試數(shù)據(jù)并新建相關(guān)測(cè)試任務(wù)即可靠柑。測(cè)試用例以及測(cè)試數(shù)據(jù)的格式為:
Page | Method | value |
---|---|---|
后臺(tái)登錄頁(yè) | 輸入用戶名 | ${userName} |
后臺(tái)登錄頁(yè) | 輸入密碼 | ${password} |
后臺(tái)登錄頁(yè) | 輸入驗(yàn)證碼 | ${captcha} |
后臺(tái)登錄頁(yè) | 點(diǎn)擊登陸 | - |
userName | password | captcha |
---|---|---|
userName1 | password1 | captcha1 |
userName2 | password2 | captcha2 |
userName3 | password3 | captcha3 |