硒测试的干净架构

在这篇博客文章中,我想介绍一种具有最佳设计模式的Selenium测试的简洁架构:页面对象,页面元素(通常称为HTML包装器)和自行开发的非常小巧的框架。 该体系结构不限于示例中使用的Java,也可以以任何其他语言应用于Selenium测试。

定义和关系。

页面对象 。 页面对象封装了网页的行为。 每个网页都有一个页面对象,该对象将页面的逻辑抽象到外部。 这意味着,与网页的交互被封装在page对象中。 Selenium的By定位符可以在页面上查找元素,也没有对外披露。 页面对象的调用者不应忙于By定位符,例如By.idBy.tageNameBy.cssSelector。Selenium测试类对页面对象进行操作。 从网上商店举个例子:页面对象类可以称为例如ProductPageShoppingCartPagePaymentPage等。这些始终是带有自己的URL的整个网页的类。

页面元素 (又名HTML包装器 )。 页面元素是网页的另一个细分。 它表示一个HTML元素,并封装了与此元素进行交互的逻辑。 我将页面元素称为HTML包装器。 HTML包装器是可重用的,因为多个页面可以包含相同的元素。 例如,DatepickerHTML包装器可以提供以下方法(API):“在输入字段中设置日期”,“打开日历弹出窗口”,“在日历弹出窗口中选择给定的日期”等。其他HTML包装可以例如,“自动完成”,“面包屑”,“复选框”,“单选按钮”,“多选”,“消息”等。HTML包装器可以是复合的。 这意味着它可以包含多个小元素。 例如,产品目录由产品组成,购物车由项目组成,等等。内部元素的Selenium的“ 按”定位符封装在复合页面元素中。

Martin Fowler描述了页面对象和HTML包装器作为设计模式。

Selenium测试类的骨骼结构。

测试类的结构良好。 它以单个过程步骤的形式定义测试顺序。 我建议采用以下结构:

public class MyTestIT extends AbstractSeleniumTest {@FlowOnPage(step = 1, desc = "Description for this method")void flowSomePage(SomePage somePage) {...}@FlowOnPage(step = 2, desc = "Description for this method")void flowAnotherPage(AnotherPage anotherPage) {...}@FlowOnPage(step = 3, desc = "Description for this method")void flowYetAnotherPage(YetAnotherPage yetAnotherPage) {...}...
}

MyTestIT类是用于集成测试的JUnit测试类。 @FlowOnPage是网页上测试逻辑的方法注释。 step参数定义测试序列中的序列号。 计数从1开始。这意味着,将在步骤= 2的方法之前处理步骤= 1的带注释的方法。第二个参数desc表示描述该方法的作用。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FlowOnPage {int step() default 1;String desc();
}

带页面对象作为方法参数调用带注释的方法。 单击按钮或链接通常会切换到下一页。 开发的框架应确保在调用带有下一步的带注释的方法之前,已完全加载下一页。 下图说明了测试类,页面对象和HTML包装器之间的关系。

硒

别这样 用@Test注释的JUnit方法在哪里,解析@FlowOnPage注释的逻辑在哪里 ? 该代码隐藏在超类AbstractSeleniumTest中

public abstract class AbstractSeleniumTest {// configurable base URLprivate final String baseUrl = System.getProperty("selenium.baseUrl", "http://localhost:8080/contextRoot/");private final WebDriver driver;public AbstractSeleniumTest() {// create desired WebDriverdriver = new ChromeDriver();// you can also set here desired capabilities and so on...}/*** The single entry point to prepare and run test flow.*/@Testpublic void testIt() throws Exception {LoadablePage lastPageInFlow = null;List <Method> methods = new ArrayList<>();// Seach methods annotated with FlowOnPage in this and all super classesClass c = this.getClass();while (c != null) {for (Method method: c.getDeclaredMethods()) {if (method.isAnnotationPresent(FlowOnPage.class)) {FlowOnPage flowOnPage = method.getAnnotation(FlowOnPage.class);// add the method at the right positionmethods.add(flowOnPage.step() - 1, method);}}c = c.getSuperclass();}for (Method m: methods) {Class<?>[] pTypes = m.getParameterTypes();LoadablePage loadablePage = null;if (pTypes != null && pTypes.length > 0) {loadablePage = (LoadablePage) pTypes[0].newInstance();}if (loadablePage == null) {throw new IllegalArgumentException("No Page Object as parameter has been found for the method " +m.getName() + ", in the class " + this.getClass().getName());}// initialize Page Objects Page-Objekte and set parent-child relationshiploadablePage.init(this, m, lastPageInFlow);lastPageInFlow = loadablePage;}if (lastPageInFlow == null) {throw new IllegalStateException("Page Object to start the test was not found");}// start testlastPageInFlow.get();}/*** Executes the test flow logic on a given page.** @throws AssertionError can be thrown by JUnit assertions*/public void executeFlowOnPage(LoadablePage page) {Method m = page.getMethod();if (m != null) {// execute the method annotated with FlowOnPagetry {m.setAccessible(true);m.invoke(this, page);} catch (Exception e) {throw new AssertionError("Method invocation " + m.getName() +", in the class " + page.getClass().getName() + ", failed", e);}}}@Afterpublic void tearDown() {// close browserdriver.quit();}/*** This method is invoked by LoadablePage.*/public String getUrlToGo(String path) {return baseUrl + path;}public WebDriver getDriver() {return driver;}
}

如您所见,只有一种测试方法testIt可以解析批注,创建具有关系的页面对象并启动测试流程。

页面对象的结构。

每个页面对象类均从LoadablePage类继承,而该类又从Selenium的LoadableComponent类继承。 这篇写得很好的文章对L oadableComponent进行了很好的解释: LoadableComponent的简单和高级用法 。 LoadablePage是我们自己的类,实现如下:

import org.openqa.selenium.support.ui.WebDriverWait;
import org.junit.Assert;
import org.openqa.selenium.By;
import org.openqa.selenium.Keys;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.support.PageFactory;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.LoadableComponent;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Method;
import java.util.List;public abstract class LoadablePage<T extends LoadableComponent<T>> extends LoadableComponent<T> {private final static Logger LOGGER = LoggerFactory.getLogger(LoadablePage.class);private AbstractSeleniumTest seleniumTest;private String pageUrl;private Method method;private LoadablePage parent;/*** Init method (invoked by the framework).** @param seleniumTest instance of type AbstractSeleniumTest* @param method to be invoked method annotated with @FlowOnPage* @param parent parent page of type LoadablePage*/void init(AbstractSeleniumTest seleniumTest, Method method, LoadablePage parent) {this.seleniumTest = seleniumTest;this.pageUrl = seleniumTest.getUrlToGo(getUrlPath());this.method = method;this.parent = parent;PageFactory.initElements(getDriver(), this);}/*** Path of the URL without the context root for this page.** @return String path of the URL*/protected abstract String getUrlPath();/**** Specific check which has to be implemented by every page object.* A rudimentary check on the basis of URL is undertaken by this class.* This method is doing an extra check if the page has been proper loaded.** @throws Error thrown when the check fails*/protected abstract void isPageLoaded() throws Error;@Overrideprotected void isLoaded() throws Error {// min. check against the page URLString url = getDriver().getCurrentUrl();Assert.assertTrue("You are not on the right page.", url.equals(pageUrl));// call specific check which has to be implemented on every pageisPageLoaded();}@Overrideprotected void load() {if (parent != null) {// call the logic in the parent pageparent.get();// parent page has navigated to this page (via click on button or link).// wait until this page has been loaded.WebDriverWait wait = new WebDriverWait(getDriver(), 20, 250);wait.until(new ExpectedCondition<Boolean> () {@Overridepublic Boolean apply(WebDriver d) {try {isLoaded();return true;} catch (AssertionError e) {return false;}}});} else {// Is there no parent page, the page should be navigated directlyLOGGER.info("Browser: {}, GET {}", getDriver(), getPageUrl());getDriver().get(getPageUrl());}}/*** Ensure that this page has been loaded and execute the test code on the this page.** @return T LoadablePage*/public T get() {T loadablePage = super.get();// execute flow logicseleniumTest.executeFlowOnPage(this);return loadablePage;}/*** See {@link WebDriver#findElement(By)}*/public WebElement findElement(By by) {return getDriver().findElement(by);}/*** See {@link WebDriver#findElements(By)}*/public List<WebElement> findElements(By by) {return getDriver().findElements(by);}public WebDriver getDriver() {return seleniumTest.getDriver();}protected String getPageUrl() {return pageUrl;}Method getMethod() {return method;}
}

如您所见,每个页面对象类都需要实现两个抽象方法:

/*** Path of the URL without the context root for this page.** @return String path of the URL*/
protected abstract String getUrlPath();/**** Specific check which has to be implemented by every page object.* A rudimentary check on the basis of URL is undertaken by the super class.* This method is doing an extra check if the page has been proper loaded.** @throws Error thrown when the check fails*/
protected abstract void isPageLoaded() throws Error;

现在,我想显示一个具体页面对象的代码和一个测试SBB Ticket Shop的测试类,以便读者可以对页面对象进行测试。 页面对象TimetablePage包含基本元素HTML包装器。

public class TimetablePage extends LoadablePage<TimetablePage> {@FindBy(id = "...")private Autocomplete from;@FindBy(id = "...")private Autocomplete to;@FindBy(id = "...")private Datepicker date;@FindBy(id = "...")private TimeInput time;@FindBy(id = "...")private Button search;@Overrideprotected String getUrlPath() {return "pages/fahrplan/fahrplan.xhtml";}@Overrideprotected void isPageLoaded() throws Error {try {assertTrue(findElement(By.id("shopForm_searchfields")).isDisplayed());} catch (NoSuchElementException ex) {throw new AssertionError();}}public TimetablePage typeFrom(String text) {from.setValue(text);return this;}public TimetablePage typeTo(String text) {to.setValue(text);return this;}public TimetablePage typeTime(Date date) {time.setValue(date);return this;}public TimetablePage typeDate(Date date) {date.setValue(date);return this;}public TimetablePage search() {search.clickAndWaitUntil().ajaxCompleted().elementVisible(By.cssSelector("..."));return this;}public TimetableTable getTimetableTable() {List<WebElement> element = findElements(By.id("..."));if (element.size() == 1) {return TimetableTable.create(element.get(0));}return null;}
}

在页面对象中,可以通过@FindBy@FindBys@FindAll批注或按需动态创建HTML包装器(简单或复合),例如,如TimetableTable.create(element) ,其中element是基础WebElement 。 通常,注释不适用于自定义元素。 默认情况下,它们仅与Selenium的WebElement一起使用。 但是让他们也使用自定义元素并不难。 您必须实现扩展DefaultFieldDecorator的自定义FieldDecorator 。 自定义FieldDecorator允许对自定义HTML包装程序使用@FindBy@FindBys@FindAll批注。 这里提供一个示例项目,其中提供了实现细节和自定义元素的示例。 您还可以赶上硒的臭名昭著的StaleElementReferenceException在您的自定义FieldDecorator并重新创建由最初定位的基础WebElement。 框架用户看不到StaleElementReferenceException,并且即使在此期间更新了引用的DOM元素(从DOM中删除并再次添加新内容)时,也可以在WebElement上调用方法。 这个想法与代码段是可以在这里找到 。

好吧,让我展示测试课程。 在测试班中,我们想测试当16岁以下的儿童没有父母旅行时购物车中是否出现提示。 首先,我们必须输入“从”到“到”的车站,在时间表中单击所需的连接,然后在下一页添加一个子项,该子项显示所选连接的旅行报价。

public class HintTravelerIT extends AbstractSeleniumTest {@FlowOnPage(step = 1, desc = "Seach a connection from Bern to Zürich and click on the first 'Buy' button")void flowTimetable(TimetablePage timetablePage) {// Type from, to, date and timetimetablePage.typeFrom("Bern").typeTo("Zürich");Date date = DateUtils.addDays(new Date(), 2);timetablePage.typeDate(date);timetablePage.typeTime(date);// search for connectionstimetablePage.search();// click on the first 'Buy' buttonTimetableTable table = timetablePage.getTimetableTable();table.clickFirstBuyButton();}@FlowOnPage(step = 2, desc = "Add a child as traveler and test the hint in the shopping cart")void flowOffers(OffersPage offersPage) {// Add a childDateFormat df = new SimpleDateFormat("dd.MM.yyyy", Locale.GERMAN);String birthDay = df.format(DateUtils.addYears(new Date(), -10));offersPage.addTraveler(0, "Max", "Mustermann", birthDay);offersPage.saveTraveler();// Get hintsList<String> hints = offersPage.getShoppingCart().getHints();assertNotNull(hints);assertTrue(hints.size() == 1);assertEquals("A child can only travel under adult supervision", hints.get(0));}
}

HTML包装器的结构。

我建议为所有HTML包装器创建一个抽象基类。 我们称之为HtmlWrapper 。 此类可以提供一些常用方法,例如clickclickAndWaitUntilfindElement(s)getParentElementgetAttributeisDisplayed ……对于可编辑元素,您可以创建一个继承自HtmlWrapper的类EditableWrapper 。 此类可以为可编辑元素提供一些常用方法,例如: clear (清除输入), enter (按下Enter键), isEnabled (检查元素是否已启用),…。所有可编辑元素都应继承自EditableWrapper 。 此外,您可以分别为单值和多值元素提供两个接口EditableSingleValueEditableMultipleValue 。 下图展示了这个想法。 它显示了三个基本HTML包装的类层次结构:

  • 日期选择器 。 它继承自EditableWrapper并实现EditableSingleValue接口。
  • MultiSelect 。 它继承自EditableWrapper并实现EditableMultiValue接口。
  • 留言 。 它直接扩展HtmlWrapper,因为消息不可编辑。

包装纸

您是否需要更多有关HTML包装程序的实现细节? jQuery Datepicker的详细信息可以在这篇出色的文章中找到 。 MultiSelect是著名的Select2小部件的包装。 我已经通过以下方式在项目中实现了包装器:

public class MultiSelect extends EditableWrapper implements EditableMultiValue<String> {protected MultiSelect(WebElement element) {super(element);}public static MultiSelect create(WebElement element) {assertNotNull(element);return new MultiSelect(element);}@Overridepublic void clear() {JavascriptExecutor js = (JavascriptExecutor) getDriver();js.executeScript("jQuery(arguments[0]).val(null).trigger('change')", element);}public void removeValue(String...value) {if (value == null || value.length == 0) {return;}JavascriptExecutor js = (JavascriptExecutor) getDriver();Object selectedValues = js.executeScript("return jQuery(arguments[0]).val()", element);String[] curValue = convertValues(selectedValues);String[] newValue = ArrayUtils.removeElements(curValue, value);if (newValue == null || newValue.length == 0) {clear();} else {changeValue(newValue);}}public void addValue(String...value) {if (value == null || value.length == 0) {return;}JavascriptExecutor js = (JavascriptExecutor) getDriver();Object selectedValues = js.executeScript("return jQuery(arguments[0]).val()", element);String[] curValue = convertValues(selectedValues);String[] newValue = ArrayUtils.addAll(curValue, value);changeValue(newValue);}@Overridepublic void setValue(String...value) {clear();if (value == null || value.length == 0) {return;}changeValue(value);}@Overridepublic String[] getValue() {JavascriptExecutor js = (JavascriptExecutor) getDriver();Object values = js.executeScript("return jQuery(arguments[0]).val()", element);return convertValues(values);}private void changeValue(String...value) {Gson gson = new Gson();String jsonArray = gson.toJson(value);String jsCode = String.format("jQuery(arguments[0]).val(%s).trigger('change')", jsonArray);JavascriptExecutor js = (JavascriptExecutor) getDriver();js.executeScript(jsCode, element);}@SuppressWarnings("unchecked")private String[] convertValues(Object values) {if (values == null) {return null;}if (values.getClass().isArray()) {return (String[]) values;} else if (values instanceof List) {List<String> list = (List<String> ) values;return list.toArray(new String[list.size()]);} else {throw new WebDriverException("Unsupported value for MultiSelect: " + values.getClass());}}
}

为了完整起见,还有一个Message实现的示例:

public class Message extends HtmlWrapper {public enum Severity {INFO("info"),WARNING("warn"),ERROR("error");Severity(String severity) {this.severity = severity;}private final String severity;public String getSeverity() {return severity;}}protected Message(WebElement element) {super(element);}public static Message create(WebElement element) {assertNotNull(element);return new Message(element);}public boolean isAnyMessageExist(Severity severity) {List<WebElement> messages = findElements(By.cssSelector(".ui-messages .ui-messages-" + severity.getSeverity()));return messages.size() > 0;}public boolean isAnyMessageExist() {for (Severity severity: Severity.values()) {List<WebElement> messages = findElements(By.cssSelector(".ui-messages .ui-messages-" + severity.getSeverity()));if (messages.size() > 0) {return true;}}return false;}public List<String> getMessages(Severity severity) {List<WebElement> messages = findElements(By.cssSelector(".ui-messages .ui-messages-" + severity.getSeverity() + "-summary"));if (messages.isEmpty()) {return null;}List<String> text = new ArrayList<> ();for (WebElement element: messages) {text.add(element.getText());}return text;}
}

消息将消息组件包装在PrimeFaces中 。

结论

完成页面对象和HTML包装器的编写后,您可以安心并专注于舒适地编写Selenium测试。 随时分享您的想法。

翻译自: https://www.javacodegeeks.com/2016/04/clean-architecture-selenium-tests.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/354892.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ThinkPHP5 封装邮件发送服务(可发附件)

1、Composer 安装 phpmailer 1composer require phpmailer/phpmailer2、ThinkPHP 中封装邮件服务类 我把它封装在扩展目录 extend/Mail.php 文件里&#xff0c;内容如下&#xff1a; 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748…

php中如何定义常量和变量的区别,php define常量定义与变量区别

常量在使用前必须要定义&#xff0c;否则程序执行会出错。在php中使用define()函数来定义常量。1、语法格式&#xff1a;define("常量名称","常量的值");例如&#xff1a;define("php360","完美的php");下面还是来一个范例吧&#xff…

PHP小语种网站开发,当阳小语种建站

当阳小语种建站&#xff0c;六脉(LIUMAI)&#xff0c;专注于帮助中小企业开拓海外市场获取外贸订单&#xff0c;提供外贸整体解决方案。当阳小语种建站&#xff0c; 拥有强大的搜索引擎优化功能&#xff0c;通过操作全球贸易通后台可以轻松实现对英语及小语种网站的搜索优化&am…

密码学笔记——eval(function(p,a,c,k,e,d) 加密破解

密码学笔记——eval(function(p,a,c,k,e,d) 的加密破解 例题&#xff1a; 小明某天在看js的时候&#xff0c;突然看到了这么一段代码&#xff0c;发现怎么也理不出代码逻辑&#xff0c;你能帮帮他吗&#xff1f; 格式&#xff1a;SimCTF{} eval(function(p,a,c,k,e,d){efunctio…

Spring Boot Cassandra的第一步

如果您想通过Spring Boot开始使用Cassandra NoSQL数据库&#xff0c;最好的资源可能是此处提供的Cassandra示例和Spring数据Cassandra文档 。 在这里&#xff0c;我将采取一些绕过的方式&#xff0c;实际是在本地安装Cassandra并对其进行基本测试&#xff0c;我的目标是在下一…

php提交表单显示错误,php – 在提交注册表单时使用jQuery显示错误

你需要修好几件事情。>首先&#xff0c;处理注册过程的文件不应该是与表单相同的文件。>它纯粹用于处理数据&#xff0c;因此不能使用头(“Location&#xff1a;login.php”)直接重定向浏览器。这部分应该由你的JavaScript代码来处理。>您还需要告诉浏览器&#xff0c…

Selenium-键盘操作

在webdriver的Keys类中提供了键盘所有的按键操作,当然也包括一些常见的组合操作如CtrlA全选),CtrlC(复制),CtrlV(粘贴).更多参考官方文档对应的编码http://selenium-python.readthedocs.org/api.html from selenium.webdriver.common.keys import keys send_kyes(Keys.ENTER) …

Python学习笔记----try...except...else

Python 中的异常处理&#xff1a; 一、try...except...else 程序运行过程中会出现类似以下错误&#xff1a; 1 a10 2 b0 3 ca/b 4 print(c) 运行结果为&#xff1a; Traceback (most recent call last): File "D:/Study/s14/day4/临时.py", line 13, in <module&g…

matlab_ga(),matlab遗传算法ga函数

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼function optimization4()A[];b[];Aeq[];beq[];LB[0.1;0.03;0.03;0.1;0.03;0.03];UB[0.4;0.06;0.06;0.4;0.06;0.06];nvars6;optionsgaoptimset(TimeLimit,inf,PlotFcns,{gaplotbestf},PopulationSize,10,Generations,15,PopInitRan…

Windows负载机JVM 远程监控Linux服务器下tomcat

基本是跟着网上的操作进行的&#xff0c;除了遇到一个Local host name unknown的问题&#xff1a; 一、Linux服务器操作部分 服务器地址&#xff1a;10.64.111.68 首先配置JMX&#xff1a; 1.找到jdk目录 [rootC68 demo]# echo $JAVA_HOME /root/demo/jdk1.8.0_60 2. cd 到/roo…

idea struts插件_使用Struts 2的查询网格(无插件)

idea struts插件当将jQuery与struts 2结合使用时&#xff0c;开发人员被说服使用struts2-jQuery插件 。 因为大多数论坛和其他Internet资源都支持jQuery struts2 jQuery插件。我有这种经验。 我想使用带有struts 2的jQuery网格插件&#xff0c;但不使用struts2 jQuery插件。 对…

matlab 值法确定各指标权重,Matlab学习系列19. 熵值法确定权重

19. 熵值法确定权重一、基本原理在信息论中&#xff0c;熵是对不确定性的一种度量。信息量越大&#xff0c;不确定性就越小&#xff0c;熵也就越小&#xff1b;信息量越小&#xff0c;不确定性越大&#xff0c;熵也越大。 根据熵的特性&#xff0c;可以通过计算熵值来判断一个事…

在Sqoop中管理密码的关键提示

Sqoop是用于Hadoop的流行数据传输工具。 Sqoop允许从结构化数据存储&#xff08;如关系数据库&#xff0c;企业数据仓库和NoSQL数据存储&#xff09;轻松导入和导出数据。 Sqoop还与Hive&#xff0c;HBase和Oozie等基于Hadoop的系统集成。 在此博客文章中&#xff0c;我将介绍…

php 商城套餐搭配功能,速卖通商品搭配套餐功能已上线!设置速卖通搭配套餐仅需三步...

据雨果网获悉&#xff0c;速卖通商品搭配套餐功能已于 10 月 19 日上线。商品搭配套餐的主要功能及作用&#xff0c;主要是帮助速卖通的卖家&#xff0c;通过自行选择商品&#xff0c;设置不同商品间搭配优惠促销价格&#xff0c;提高商品推广内容的丰富性及专业性&#xff0c;…

二维码支付原理分析及安全性的探究

“二维码支付”安全么&#xff1f; 1 引言 随时支付宝和微信的线下不断推广&#xff0c;目前使用手机进行二维码支付已经逐渐成为一种时尚了。 但是大家有没有思考过&#xff1a;这种便捷的支付方式到底安不安全呢&#xff1f;今天我们就针对这个话题来进行一些探讨吧。 2 …

python创建一个包,如何从python包创建一个osx应用程序/ dmg?

我不知道正确的方法&#xff0c;但是这种手动方法是我用于简单脚本的方法&#xff0c;似乎已经适当地执行了。我会假设我所在的任何目录&#xff0c;我的程序的Python文件都在相对的src /目录中&#xff0c;我要执行的文件(具有正确的shebang和执行权限)被命名为main.py。$ mkd…

自定义类加载器

转载自&#xff1a;http://www.cnblogs.com/xrq730/p/4847337.html 为什么要自定义类加载器转载于:https://www.cnblogs.com/IvySue/p/7490656.html

guice spring_Spring vs Guice:重要的一个关键区别

guice spring根据弹簧对象的名称识别它们 不管使用XML还是Java配置都没有关系&#xff0c;Spring范围大致类似于Map <String&#xff0c;Object>结构。 这意味着您不能有两个名称相同的对象 。 为什么这是一件坏事&#xff1f; 如果您的大型应用程序具有许多Configuratio…

query row php,php – 如何在Codeigniter上使用$query- row获取类对象

我目前正在使用Codeigniter框架.在下面的代码中,我想获得一个Animal_model对象,而不是stdClass对象.class Animal_model extends CI_Model{var $idanimal;var $name;public static $table animals;function __construct() {parent::__construct();}function getone(self $anim…

vue2.0版本指令v-if与v-show的区别

v-if&#xff1a; 判断是否加载&#xff0c;可以减轻服务器的压力&#xff0c;在需要时加载。 v-show&#xff1a;调整css dispaly属性&#xff0c;可以使客户端操作更加流畅。 v-if示例&#xff1a; <!DOCTYPE html> <html><head><meta charset"UTF…