当前位置: 首页 > news >正文

软件测试自动化Java篇【Selenium+Junit 5】

文章目录

  • Selenium
    • 环境部署
    • 自动化测试例子
    • 常见的元素操作
    • 窗口
    • 等待
    • 浏览器的操作
    • 弹窗
    • 选择器
    • 执行脚本
    • 文件上传
    • 浏览器参数
  • Junit 5
    • 导入依赖
    • Junit 4 和 Junit5 注解对比
    • 断言
    • 测试顺序
    • 参数化
      • 单参数
      • 多参数
      • 动态参数
    • 测试套件
      • 指定类来运行测试用例
      • 指定包名来运行包下测试用例

Selenium

为什么选择selenium作为我们的web自动化测试工具?

  1. 开源免费
  2. 支持多浏览器
  3. 支持多系统
  4. 支持多语言【Java,Python,C#,Rubby,JavaScript,Kolin】
  5. selenium包提供了很多可供测试使用的API

环境部署

Chrome浏览器
在这里插入图片描述
Chrome驱动【驱动器版本要和浏览器版本对应越详细越好】
在这里插入图片描述
然后把驱动包放在安装jdk的bin目录下
在这里插入图片描述

selenium工具包
在这里插入图片描述

自动化测试例子

分为接口自动化测试和UI自动化测试

UI自动化测试包含界面测试

我们都知道用户的设备很多,有手机,平板和电脑。这些设备运行的项目在发布之初都需要经过测试,这些测试又分为web自动化测试 和 移动端自动化测试。这里以 web自动化测试 为主。
自动化测试的工具有很多,比如 QTP、Selenium,Jmeter、Loadrunner。本期就以 Selenium 为主进行介绍最简单的自动化测试

测试点击百度

import org.openqa.selenium.By;
import org.openqa.selenium.chrome.ChromeDriver;private class AutoTest {// 模拟百度搜索关键词public static void baiduSearchKey() {String url = "https://www.baidu.com";String keyWords = "新闻";ChromeDriver chromeDriver=new ChromeDriver();try {Thread.sleep(5000);// 输入框输入文字chromeDriver.findElement(new By.ByXPath("//*[@id=\"kw\"]")).sendKeys(keyWords);Thread.sleep(5000);// 点击搜索按钮chromeDriver.findElement(new By.ByXPath("//*[@id=\"su\"]")).click();Thread.sleep(5000);} catch (InterruptedException e) {throw new RuntimeException(e);}chromeDriver.quit();}public static void main(String[] args) {baiduSearchKey();}
}

获取元素方法有很多,但经常用的是 ByXPath和ByCssSelector
在这里插入图片描述
ByXpath
/:相当于子级一层一层的完整xpath路径
//:相当于越级,直接越过之前的html标签,根据id来查询标签

后文中只有之前的几个操作时获取部分xpath,以后的获取都是完整的获取xpath路径【看//数量即可知道是否根据带通配符或者id查询来区别】

常见的元素操作

  1. 输入文本 sendKeys(str)
    对元素操作的前提是元素能够被找到,如果查询的元素不是可编辑的文本标签,那么前端不受影响,后端也不报错。按照程序的设定正常退出
  2. 点击操作 click()&提交操作 submit()
private static void TestBlogLogin() {// 输入邮箱String url = "http://localhost:8080/index.html";String email = "1969612859@qq.com";String password = "admin";ChromeDriver chromeDriver = new ChromeDriver();chromeDriver.get(url);try {Thread.sleep(3000);// 输入邮箱chromeDriver.findElement(new By.ByXPath("//*[@id=\"login\"]/div[1]/input")).sendKeys(email);Thread.sleep(3000);// 输入密码chromeDriver.findElement(new By.ByXPath("//*[@id=\"login\"]/div[2]/input")).sendKeys(password);Thread.sleep(3000);// 点击登录【也可以回车登录】chromeDriver.findElement(new By.ByXPath("/html/body/div/div/div/form[1]/button")).click();//chromeDriver.findElement(new By.ByXPath("/html/body/div/div/div/form[1]/button")).submit();Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}chromeDriver.quit();
}

提交操作 submit 仅适用于form表单之类的submit操作,一般用的少
Selenium更推荐使用 click ,功能比 submit 更丰富

  1. 清除操作 clear()
private static void TestBlogLogin() {// 输入正确的邮箱和密码String url = "http://localhost:8080/index.html";String email = "1969612859@qq.com";String password = "admin";ChromeDriver chromeDriver = new ChromeDriver();chromeDriver.get(url);try {Thread.sleep(3000);chromeDriver.findElement(new By.ByXPath("//*[@id=\"login\"]/div[1]/input")).sendKeys("手滑输入错了");Thread.sleep(3000);// 清除文本chromeDriver.findElement(new By.ByXPath("//*[@id=\"login\"]/div[1]/input")).clear();Thread.sleep(3000);chromeDriver.findElement(new By.ByXPath("//*[@id=\"login\"]/div[1]/input")).sendKeys(email);Thread.sleep(3000);chromeDriver.findElement(new By.ByXPath("//*[@id=\"login\"]/div[2]/input")).sendKeys(password);Thread.sleep(3000);chromeDriver.findElement(new By.ByXPath("/html/body/div/div/div/form[1]/button")).click();Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}chromeDriver.quit();
}
  1. 获取文本值 getText()
private static void TestBlogLogin() {// 输入正确的邮箱和密码String url = "http://localhost:8080/index.html";String email = "1969612859@qq.com";String password = "admin";ChromeDriver chromeDriver = new ChromeDriver();chromeDriver.get(url);try {// 获取第一道题目的标题String text = chromeDriver.findElement(new By.ByXPath("/html/body/div/div/div[2]/div/div/div/div/div/div/table/tbody/tr[1]/td[2]")).getText();System.out.println("getText()获取到的文本:" + text);Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}chromeDriver.quit();
}

在这里插入图片描述

在这里插入图片描述

发现即使 URL 跳转到了别的网页,由于线程休眠3s的缘故,并不会很快就去获取页面元素而是给了页面一个加载的时间,因此可以完全获取到跳转到其它页面的标签

  1. 获取属性 getAttribute(attr)
    在这里插入图片描述
private static void TestBlogLogin() {// 输入正确的邮箱和密码String url = "http://localhost:8080/index.html";String email = "1969612859@qq.com";String password = "admin";ChromeDriver chromeDriver = new ChromeDriver();chromeDriver.get(url);try {// 获取属性String role = chromeDriver.findElement(new By.ByXPath("/html/body/div/div/div[2]/div/div/div/div/div/div/table/tbody/tr[1]/td[5]/div/div")).getAttribute("role");Thread.sleep(3000);String aria_valuenow = chromeDriver.findElement(new By.ByXPath("/html/body/div/div/div[2]/div/div/div/div/div/div/table/tbody/tr[1]/td[5]/div/div")).getAttribute("aria-valuenow");Thread.sleep(3000);String aria_valuemin = chromeDriver.findElement(new By.ByXPath("/html/body/div/div/div[2]/div/div/div/div/div/div/table/tbody/tr[1]/td[5]/div/div")).getAttribute("aria-valuemin");Thread.sleep(3000);String aClass = chromeDriver.findElement(new By.ByXPath("/html/body/div/div/div[2]/div/div/div/div/div/div/table/tbody/tr[1]/td[5]/div/div")).getAttribute("class");Thread.sleep(3000);String style = chromeDriver.findElement(new By.ByXPath("/html/body/div/div/div[2]/div/div/div/div/div/div/table/tbody/tr[1]/td[5]/div/div")).getAttribute("style");Thread.sleep(3000);System.out.printf("role=%s\naria_valuenow=%s\naria_valuemin=%s\naClass=%s\nstyle=%s\n", role, aria_valuenow, aria_valuemin, aClass, style);} catch (InterruptedException e) {throw new RuntimeException(e);}chromeDriver.quit();
}

在这里插入图片描述
6. 获取标题getTitle()和urlgetCurrenturl()

// 模拟百度搜索关键词
private static void baiduSearchKey() {String url = "https://www.baidu.com";String keyWords = "新闻";ChromeDriver chromeDriver = new ChromeDriver();chromeDriver.get(url);HashMap<String, List<String>> info = new HashMap<>();try {Thread.sleep(5000);ArrayList<String> before = new ArrayList<String>() {{add(chromeDriver.getTitle());add(chromeDriver.getCurrentUrl());}};// 输入框输入文字chromeDriver.findElement(new By.ByXPath("//*[@id=\"kw\"]")).sendKeys(keyWords);Thread.sleep(5000);// 点击搜索按钮chromeDriver.findElement(new By.ByXPath("//*[@id=\"su\"]")).click();Thread.sleep(5000);System.out.println("搜索之后");ArrayList<String> after = new ArrayList<String>() {{add(chromeDriver.getTitle());add(chromeDriver.getCurrentUrl());}};info.put("before", before);info.put("after", after);} catch (InterruptedException e) {throw new RuntimeException(e);}chromeDriver.quit();System.out.println(info);
}

在这里插入图片描述

窗口

  1. 窗口大小
    窗口大小的设置:最大/小化,全屏窗口,手动设置窗口大小
private static void windControl() {String url = "https://www.baidu.com";ChromeDriver chromeDriver = new ChromeDriver();chromeDriver.get(url);try {Thread.sleep(1500);// 窗口最大化chromeDriver.manage().window().maximize();Thread.sleep(1500);// 窗口最小化chromeDriver.manage().window().minimize();Thread.sleep(1500);// 全屏chromeDriver.manage().window().fullscreen();Thread.sleep(1500);// 手动设置窗口大小chromeDriver.manage().window().setSize(new Dimension(1024, 768));Thread.sleep(1500);} catch (InterruptedException e) {throw new RuntimeException(e);}chromeDriver.quit();
}
  1. 切换窗口
    一般的窗口切换可能会导致找不到元素的BUG
private static void windControl() {String url = "https://www.baidu.com";ChromeDriver chromeDriver = new ChromeDriver();chromeDriver.get(url);try {Thread.sleep(1500);// 点击准备跳转chromeDriver.findElement(new By.ByXPath("/html/body/div[1]/div[1]/div[3]/a[6]")).click();String value = chromeDriver.findElement(new By.ByXPath("/html/body/div[1]/div[1]/div[3]/div/div/div/div/div/div[2]/form/span[2]/input")).getAttribute("value");System.out.println(value);} catch (InterruptedException e) {throw new RuntimeException(e);}chromeDriver.quit();
}

在这里插入图片描述
由于无法获取跳转页面的元素,所以就会报错。因此需要一个页面切换功能

private static void windControl() {String url = "https://www.baidu.com";ChromeDriver chromeDriver = new ChromeDriver();chromeDriver.get(url);try {Thread.sleep(1500);// 获取当前页面句柄String currentHandle = chromeDriver.getWindowHandles().toString();System.out.println("当前首页句柄:"+currentHandle);Thread.sleep(1500);// 点击准备跳转chromeDriver.findElement(new By.ByXPath("/html/body/div[1]/div[1]/div[3]/a[6]")).click();// 获取跳转之后所有标签页的句柄Set<String> handles = chromeDriver.getWindowHandles();for (String handle : handles) {if (currentHandle != handle){chromeDriver.switchTo().window(handle);}}String value = chromeDriver.findElement(new By.ByXPath("/html/body/div[1]/div[1]/div[3]/div/div/div/div/div/div[2]/form/span[2]/input")).getAttribute("value");System.out.println(value);} catch (InterruptedException e) {throw new RuntimeException(e);}chromeDriver.quit();
}

运行的退出码已经恢复正常
在这里插入图片描述
3. 屏幕截图
需要导入一个常用的commons-io库

<dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.6</version>
</dependency>
private static void windControl() {String url = "https://www.baidu.com";ChromeDriver chromeDriver = new ChromeDriver();chromeDriver.get(url);try {Thread.sleep(1500);// 点击准备跳转chromeDriver.findElement(new By.ByXPath("/html/body/div[1]/div[1]/div[3]/a[6]")).click();File screenshotAs = chromeDriver.getScreenshotAs(OutputType.FILE);// 把屏幕截图好的文件放在指定的路径下String fileName = "my.png";try {FileUtils.copyFile(screenshotAs, new File(fileName));} catch (IOException e) {throw new RuntimeException(e);}String value = chromeDriver.findElement(new By.ByXPath("/html/body/div[1]/div[1]/div[3]/div/div/div/div/div/div[2]/form/span[2]/input")).getAttribute("value");System.out.println(value);} catch (InterruptedException e) {throw new RuntimeException(e);}chromeDriver.quit();
}

这里会发生报错,由于屏幕截图原因。所以会保存当时的获取状态。
在这里插入图片描述
会把当前的现场截图保存下来【由于没有切换页面,所以也就会报错,截图代码发生在报错之前的代码,所以也就保留了事故现场

等待

程序执行的速度比页面渲染速度快很多,因此之前的等待都是使用线程的强制等待,这并不合理。等待一共分为四种。
强制等待、隐式等待、显示等待、流畅等待

  1. 强制等待
    程序阻塞运行,Thread.sleep()。会用到,但是自动化里不能用的特别多,否则拖慢速度
  2. 隐式等待
    隐式等待会一直轮询判断元素是否存在,如果不存在就等待设置好的时间里不断地进行轮询知道元素能够被找到
private static void waitController(){String url = "https://www.baidu.com";ChromeDriver chromeDriver=new ChromeDriver();// 添加隐式等待:会作用于chromeDriver整个生命周期chromeDriver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3));chromeDriver.get(url);chromeDriver.findElement(new By.ByXPath("/html/body/div[1]/div[1]/div[5]/div/div/form/span[1]/input")).sendKeys("迪丽热巴");chromeDriver.findElement(new By.ByXPath("/html/body/div[1]/div[1]/div[5]/div/div/form/span[2]/input")).click();chromeDriver.findElement(new By.ByXPath("/html/body/div[2]/div[3]/div[1]/div[3]/div[1]/div[1]/div/div/div/div/div[2]/div/a/div/p/span/span"));chromeDriver.quit();
}
  1. 显示等待
    隐式等待针对的是 driver的整个生命周期,所以对全部元素有效,但有时候并非全部都需要等待,我们可以只针对其中一个元素进行等待操作,其余元素交给程序快速运行进而提高自动化测试的效率
private static void webDriverWait() {String url = "https://www.baidu.com";ChromeDriver chromeDriver = new ChromeDriver();chromeDriver.get(url);chromeDriver.findElement(new By.ByXPath("/html/body/div[1]/div[1]/div[5]/div/div/form/span[1]/input")).sendKeys("迪丽热巴");chromeDriver.findElement(new By.ByXPath("/html/body/div[1]/div[1]/div[5]/div/div/form/span[2]/input")).click();// 只针对某个元素显示等待new WebDriverWait(chromeDriver, Duration.ofSeconds(3)).until(lambda -> chromeDriver.findElement(new By.ByXPath("/html/body/div[2]/div[3]/div[1]/div[3]/div[1]/div[1]/div/div/div/div/div[2]/div/a/div/p/span/span")));chromeDriver.get(url);
}

这里使用了 lambda表达式 的语法,我们查看一下 until的源码
在这里插入图片描述
参数是一个泛型,所以可以传入任何泛型函数。然后就是执行的循环,获取页面元素操作。

  • 隐式等待 和 显式等待 并不能同时使用,可能会出现意想不到的结果
  • 隐式等待 和 显示等待 均不出现效果的时候可以使用 强制等待

浏览器的操作

浏览器的回退,前进和刷新操作

private static void navigateControl() {String url = "https://www.baidu.com";ChromeDriver chromeDriver = new ChromeDriver();// 简写
//        chromeDriver.get(url);// 非简写chromeDriver.navigate().to(url);chromeDriver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3));// 想要回退到访问百度网址之前的状态chromeDriver.navigate().back();// 前进,有进入到了百度首页chromeDriver.navigate().forward();// 刷新百度首页chromeDriver.navigate().refresh();chromeDriver.quit();
}

弹窗

弹窗一共有3大类型,如下所示分别为:alert、confirm和prompt
在这里插入图片描述
但是却发现,浏览器进入检查时候无法获取到弹窗的元素
处理弹窗的步骤

  1. 将driver对象作用到弹窗上(切换到弹窗)
  2. 选择确认/取消(提示弹窗输入文本)
    作用到弹窗上:driver.switchTo.alert()
    确认:driver.accept()
    取消:`driver.dismiss()
    输入:driver.sendKeys()

前端代码

<div id="alertVue"><button v-on:click="f()">{{value}}</button>
</div>
<div id="confirmVue"><button v-on:click="f()">{{value}}</button>
</div>
<div id="promptVue"><button v-on:click="f()">{{value}}</button>
</div><script type="text/javascript" src="vue.js"></script>
<script>const alertVue = new Vue({el: '#alertVue',data: {value: "Alert弹窗",},methods: {f() {window.alert("alert弹窗");}}});const confirmVue = new Vue({el: '#confirmVue',data: {value: "Confirm确认框",},methods: {f() {if (window.confirm("是否确认?")) {confirmVue.value = "true";} else {confirmVue.value = "false";}}}});const promptVue = new Vue({el: '#promptVue',data: {value: "输入框",},methods: {f() {promptVue.value = window.prompt("输入框");}}});
</script>

测试代码

private static void alertController(){String url = "file:///D:/Documents/Program/Java/SeleniumTest/Autotest/src/test/java/alertHTML.html";ChromeDriver chromeDriver=new ChromeDriver();chromeDriver.navigate().to(url);try {Thread.sleep(1500);// 点击 alertchromeDriver.findElement(new By.ByXPath("/html/body/div[1]/button")).click();Thread.sleep(1500);// 切换到弹窗Alert alert = chromeDriver.switchTo().alert();// 确认操作:accept/取消操作:dismissalert.dismiss();Thread.sleep(1500);// 点击 confirmchromeDriver.findElement(new By.ByXPath("/html/body/div[2]/button")).click();Thread.sleep(1500);// 切换到弹窗alert = chromeDriver.switchTo().alert();// 确认操作alert.accept();Thread.sleep(1500);// 点击 promptchromeDriver.findElement(new By.ByXPath("/html/body/div[3]/button")).click();Thread.sleep(1500);// 切换到弹窗alert = chromeDriver.switchTo().alert();Thread.sleep(1500);// 在页面上看不到输入的文本效果,但是其实已经输入了alert.sendKeys("阿斯顿马丁");// 接受文本alert.accept();Thread.sleep(1500);// 确认操作} catch (InterruptedException e) {throw new RuntimeException(e);}chromeDriver.quit();
}
  • prompt 弹窗进行 sendKeys(str) 的时候不会在页面出现效果,但是程序已经输入有文本信息
  • alert 警告弹窗虽然只有确认选项,但是也可以用 dismiss和accept 都可以进行取消操作
  • url 一定要复制浏览器中选择而不是从资源路径中选择【浏览器的url前边会多一个file:///

选择器

选择框的处理困难在于无法将下拉框的元素进行定位,每当点击检查的时候,下拉框就会消失
这个时候我们可以根据三个条件进行选择

  1. 文本
  2. 属性
  3. 下标

html代码

<div id="selectVue"><select><option v-for="(month, index) in monthes" v-bind:value="index+1">{{month}}</option></select>
</div><script>
const selectVue = new Vue({el: '#selectVue',data: {monthes: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December",],},
});
</script>

Java代码

private static void alertController() {String url = "file:///D:/Documents/Program/Java/SeleniumTest/Autotest/src/test/java/alertHTML.html";ChromeDriver chromeDriver = new ChromeDriver();chromeDriver.navigate().to(url);try {// option选择器WebElement element = chromeDriver.findElement(new By.ByXPath("/html/body/div[4]/select"));Thread.sleep(1500);// 先创建选择框对象Select select = new Select(element);Thread.sleep(1500);// 根据文本来选择【并不会像正常操作一样点击一下选择框而是直接选中下拉框】select.selectByVisibleText("September");Thread.sleep(1500);// 根据属性值选择select.selectByValue("1");Thread.sleep(1500);// 根据下标选择【0开始】,选取九月select.selectByIndex(8);Thread.sleep(1500);} catch (InterruptedException e) {throw new RuntimeException(e);}chromeDriver.quit();
}

XPath的选择器是从1开始;而下标选择从0开始

执行脚本

执行 js 代码,有时候会遇到输入文本不完全或者不生效的情况下,可以使用执行原生js脚本进行解决

private static void scriptControl(){String url = "https://image.baidu.com/";ChromeDriver chromeDriver = new ChromeDriver();chromeDriver.navigate().to(url);try {// 执行 js 代码让页面置底chromeDriver.executeScript("document.documentElement.scrollTop = 500");Thread.sleep(1500);// 执行 js 代码让页面置顶chromeDriver.executeScript("document.documentElement.scrollTop = 0");Thread.sleep(1500);// 百度输入框输入指定文本chromeDriver.navigate().to("https://www.baidu.com/");chromeDriver.executeScript("var ss=document.querySelector('#kw'); ss.value='英雄钢笔'");Thread.sleep(1500);} catch (InterruptedException e) {throw new RuntimeException(e);}chromeDriver.quit();
}

文件上传

文件上传的难点在于当点击上传文件时的弹窗是系统自己的弹窗,并不是浏览器的弹窗。对于Selenium而言并不能控制系统。
在这里插入图片描述

private static void fileUploadControl(){String url = "file:///D:/Documents/Program/Java/SeleniumTest/Autotest/src/test/java/alertHTML.html";ChromeDriver chromeDriver = new ChromeDriver();chromeDriver.navigate().to(url);try {Thread.sleep(1500);chromeDriver.findElement(new By.ByXPath("/html/body/div[5]/input")).sendKeys("D:\\Documents\\Program\\Java\\SeleniumTest\\Autotest\\src\\test\\java\\alertHTML.html");Thread.sleep(1500);} catch (InterruptedException e) {throw new RuntimeException(e);}chromeDriver.quit();
}

这里运行完毕之后并不是把文件上传到服务器而是要把上传的文件路径+文件以名称的方式放到这里

浏览器参数

实际工作中,测试人员将自动化部署在机器上自动的执行,测试人员不会每次都一直盯着自动化执行的过程,而是直接查看自动化执行的结果。
无头模式:类似于Linux的&模式运行命令挂载在后台进程,不在前台显示。浏览器而言的话就是没有画面的运行一些自动化测试脚本,这样可以节约一定的机器内存资源。

private static void paramControl() {// 百度搜索 ”测试开发工程师“// 先创建选项对象然后设置浏览器参数String url = "https://www.baidu.com/";ChromeOptions chromeOptions = new ChromeOptions();chromeOptions.addArguments("-headless");// 添加浏览器参数设置ChromeDriver chromeDriver = new ChromeDriver(chromeOptions);chromeDriver.navigate().to(url);chromeDriver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3));chromeDriver.findElement(new By.ByXPath("/html/body/div[1]/div[1]/div[5]/div/div/form/span[1]/input")).sendKeys("测试开发工程师");chromeDriver.findElement(new By.ByXPath("/html/body/div[1]/div[1]/div[5]/div/div/form/span[2]/input")).click();chromeDriver.quit();
}

Junit 5

自动化是Selenium脚本来实现的,Junit是java的单元测试工具.只不过我们在实现自动化的时候需要借用Junit库里面提供的一些方法
Junit 5支持最低支持jdk8
目前Java领域内最为流行的单元测试框架 ------ JUnit
Junit 5 = Junit Platform + Junit Jupiter + Junit Vintage
Junit Platform: Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入
Junit Jupiter: Junit Jupiter提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部 包含了一个测试引擎,用于在Junit Platform上运行
Junit Vintage: 由于JUnit已经发展多年,为了照顾老的项目,JUnit Vintage提供了兼容JUnit4.x,Junit3.x的测试引擎
在这里插入图片描述

导入依赖

<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter -->
<dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.9.2</version><scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.junit.platform/junit-platform-suite -->
<dependency><groupId>org.junit.platform</groupId><artifactId>junit-platform-suite</artifactId><version>1.9.2</version><scope>test</scope>
</dependency>

Junit 4 和 Junit5 注解对比

Junit 5Junit 4说明
@Test@Test被注解的是一个测试方法,和Junit 4相同
@BeforeAll@BeforeClass被注解的(静态)方法将在当前类中的所有@Test方法执行一次
@BeforeEach@Before被注解的方法将在当前类中的么欸个@Test方法前执行
@AffterAll@AfterClass被注解的(静态)方法将在当前类中的所有@Test方法执行一次
@AffterEach@After被注解的方法将在当前类中的么欸个@Test方法后执行
@DIsable@Ignore被注解的方法不会执行(将被跳过),但会报告为已执行

Junit 4 中@Testorg.junit.Test;
Junit 5 中@Testorg.junit.jupiter.api.Test;

@BeforeAll 注解必须用在 static 修饰的代码块上,否则会报错

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;public class JunitTest {// 当前方法要在所有测试用例之前执行一次@BeforeAllstatic void beforeAll() {System.out.println("@BeforeAll");}// 当前方法要在每个测试用例执行之前执行一次@BeforeEachvoid beforeEach() {System.out.println("@BeforeEach");}@Testvoid t1() {System.out.println("@Test1");}@Testvoid t2() {System.out.println("@Test2");}@Testvoid t3() {System.out.println("@Test3");}
}

在这里插入图片描述
After效果和Before效果相反

import org.junit.jupiter.api.*;public class JunitTest {// 当前方法要在所有测试用例之前执行一次@BeforeAllstatic void beforeAll() {System.out.println("@BeforeAll");}// 当前方法要在每个测试用例执行之前执行一次@BeforeEachvoid beforeEach() {System.out.println("@BeforeEach");}@Testvoid t1() {System.out.println("@Test1");}@Testvoid t2() {System.out.println("@Test2");}@Testvoid t3() {System.out.println("@Test3");}@AfterEachvoid afterEach() {System.out.println("@AfterEach");}@AfterAllstatic void afterAll() {System.out.println("@AfterAll");}
}

在这里插入图片描述

断言

断言方法说明
assertEquals(expected, actual)如果 expected 不等于 actual ,则断言失败
assertFalse(booleanExpression)如果 booleanExpression 不是 false ,则断言失败
assertNull(actual)如果 actual 不是 null ,则断言失败
assertNotNull(actual)如果 actual 是 null ,则断言失败
assertTrue(booleanExpression)如果 booleanExpression 不是 true ,则断言失败
@Test
void testAttributeValue() {String url = "https://www.baidu.com/";ChromeOptions chromeOptions = new ChromeOptions();chromeOptions.addArguments("-headless");ChromeDriver chromeDriver = new ChromeDriver(chromeOptions);chromeDriver.navigate().to(url);chromeDriver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3));String value = chromeDriver.findElement(By.cssSelector("#su")).getAttribute("value");// 假如这里获取到的属性值不是 百度一下 而是百度两下System.out.println("value:" + value);Assertions.assertEquals("百度两下", value);chromeDriver.quit();
}

断言失败
在这里插入图片描述
断言成功

@Test
void testAttributeValue() {String url = "https://www.baidu.com/";ChromeOptions chromeOptions = new ChromeOptions();chromeOptions.addArguments("-headless");ChromeDriver chromeDriver = new ChromeDriver(chromeOptions);chromeDriver.navigate().to(url);chromeDriver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3));String value = chromeDriver.findElement(By.cssSelector("#su")).getAttribute("value");// 假如这里获取到的属性值不是 百度一下 而是百度两下System.out.println("value:" + value);Assertions.assertNotEquals("百度两下", value);chromeDriver.quit();
}

在这里插入图片描述

不会提示错误地方

测试顺序

import org.junit.jupiter.api.*;public class JunitTest {// 当前方法要在所有测试用例之前执行一次@BeforeAllstatic void beforeAll() {System.out.println("@BeforeAll");}// 当前方法要在每个测试用例执行之前执行一次@BeforeEachvoid beforeEach() {System.out.println("@BeforeEach");}@Testvoid ae() {System.out.println("@Test1");}@Testvoid bc() {System.out.println("@Test2");}@Testvoid ab() {System.out.println("@Test3");}@AfterEachvoid afterEach() {System.out.println("@AfterEach");}@AfterAllstatic void afterAll() {System.out.println("@AfterAll");}
}@BeforeAll
@BeforeEach
@Test3
@AfterEach
@BeforeEach
@Test1
@AfterEach
@BeforeEach
@Test2
@AfterEach
@AfterAll

发现执行顺序按照方法名排序一样,这里的顺序并不是按照程序中代码的顺序执行。有时候我们需要按照顺序执行代码,比如测试系统的时候必须要用户先登陆才可以后续的一些操作的时候就需要按照顺序执行测试代码
添加一个 @TestMethodOrder 注解,然后添加参数 MethodOrderer.OrderAnnotation.class,最后再给每个执行的代码编辑顺序即可

import org.junit.jupiter.api.*;@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class JunitTest {// 当前方法要在所有测试用例之前执行一次@BeforeAllstatic void beforeAll() {System.out.println("@BeforeAll");}// 当前方法要在每个测试用例执行之前执行一次@BeforeEachvoid beforeEach() {System.out.println("@BeforeEach");}@Test@Order(1)void ae() {System.out.println("@Test1");}@Test@Order(2)void bc() {System.out.println("@Test2");}@Test@Order(3)void ab() {System.out.println("@Test3");}@AfterEachvoid afterEach() {System.out.println("@AfterEach");}@AfterAllstatic void afterAll() {System.out.println("@AfterAll");}
}
@BeforeAll
@BeforeEach
@Test1
@AfterEach
@BeforeEach
@Test2
@AfterEach
@BeforeEach
@Test3
@AfterEach
@AfterAll

参数化

  1. 尽可能地通过一个用例,多组参数来模拟用户的行为
  2. 使用了参数化注解之后就不能再使用 @Test 注解

单参数

@ParameterizedTest// 使用参数化注解之前需要先声明该方法为参数化方法
@ValueSource(strings = {"1969612859@qq.com", "1969612858@qq.com"})// 通过注解提供数据源
void SingleParamsTest(String email){System.out.println(email);
}

在这里插入图片描述
可以看到 @ValueSource(数据类型={参数1, 参数2, 参数3...}) 提供的数据源类型有很多
在这里插入图片描述

多参数

@ParameterizedTest
@CsvSource({"1969612859@qq.com, admin", "1969612858@qq.com, root"})
void muchParamsTest(String email, String password){System.out.printf("email:%s<==>password:%s\n", email, password);
}email:1969612859@qq.com<==>password:admin
email:1969612858@qq.com<==>password:root

当参数很多的时候,我们可以把参数放入文件中然后通过文件读取获取参数

@ParameterizedTest
@CsvFileSource(files = "D:\\Documents\\Program\\Java\\SeleniumTest\\Autotest\\src\\test\\resources\\userData.csv")
void csvFileParamsTest(String email, String password){System.out.printf("email:%s<==>password:%s\n", email, password);
}

在这里插入图片描述
发现 @CsvFileSource 的源码可以放入很多参数来设置文件,这里只放了 files 一个参数的值,文件默认编码已经是 UTF-8 所以就可以不用管编码问题
在这里插入图片描述

动态参数

这里导入一个随机工具类 common-util–使用方法

// 动态参数
static Stream<Arguments> methodParams(){// 构造动态参数String[] arr = new String[10];for (int i = 0; i < arr.length; i++) {arr[i] = RandomUtil.randomNumbers(10)+"@qq.com";}return Stream.of(Arguments.arguments(arr[0], RandomUtil.randomString(8)),Arguments.arguments(arr[1], RandomUtil.randomString(8)),Arguments.arguments(arr[2], RandomUtil.randomString(8)),Arguments.arguments(arr[3], RandomUtil.randomString(8)),Arguments.arguments(arr[4], RandomUtil.randomString(8)),Arguments.arguments(arr[5], RandomUtil.randomString(8)),Arguments.arguments(arr[6], RandomUtil.randomString(8)),Arguments.arguments(arr[7], RandomUtil.randomString(8)),Arguments.arguments(arr[8], RandomUtil.randomString(8)),Arguments.arguments(arr[9], RandomUtil.randomString(8)));
}
@ParameterizedTest
@MethodSource(value = "methodParams")
void dynamicMethodParams(String email, String password){System.out.printf("email:%s<==>password:%s\n", email, password);
}
email:0150592006@qq.com<==>password:5knqh9nd
email:6506175266@qq.com<==>password:jndm1vx6
email:4815105218@qq.com<==>password:9e6yaky2
email:5072613647@qq.com<==>password:56vjv9ff
email:1471676761@qq.com<==>password:0uq2mx9r
email:0637284991@qq.com<==>password:k5xcauzb
email:8646939279@qq.com<==>password:q9zltwfd
email:7903224405@qq.com<==>password:wrgn7fxr
email:2771169159@qq.com<==>password:f3l255bc
email:8080867273@qq.com<==>password:mnpveuxj

在这里插入图片描述

这里为了方便,就直接使用 Arguments 来替代

小技巧
如果数据源与测试方法同名,则无需执行数据源是来自哪里的

// 动态参数
static Stream<Arguments> dynamicMethodParams(){// 构造动态参数String[] arr = new String[10];for (int i = 0; i < arr.length; i++) {arr[i] = RandomUtil.randomNumbers(10)+"@qq.com";}return Stream.of(Arguments.arguments(arr[0], RandomUtil.randomString(8)),Arguments.arguments(arr[1], RandomUtil.randomString(8)),Arguments.arguments(arr[2], RandomUtil.randomString(8)),Arguments.arguments(arr[3], RandomUtil.randomString(8)),Arguments.arguments(arr[4], RandomUtil.randomString(8)),Arguments.arguments(arr[5], RandomUtil.randomString(8)),Arguments.arguments(arr[6], RandomUtil.randomString(8)),Arguments.arguments(arr[7], RandomUtil.randomString(8)),Arguments.arguments(arr[8], RandomUtil.randomString(8)),Arguments.arguments(arr[9], RandomUtil.randomString(8)));
}
@ParameterizedTest
@MethodSource// 自动寻找与方法名同名的数据源
void dynamicMethodParams(String email, String password){System.out.printf("email:%s<==>password:%s\n", email, password);
}
email:7264428849@qq.com<==>password:wewp75wh
email:4884099760@qq.com<==>password:q57n49kf
email:2779525807@qq.com<==>password:dkyfns0t
email:7260421546@qq.com<==>password:kfxow9gw
email:9750978471@qq.com<==>password:a5dwh5g4
email:7595861999@qq.com<==>password:g9gszroo
email:5860224630@qq.com<==>password:er521mby
email:7009711558@qq.com<==>password:qhftn0rh
email:9374899761@qq.com<==>password:zsjkkyns
email:6637232897@qq.com<==>password:fu8g79om

测试套件

指定类来运行测试用例

package TestSuite;import org.junit.platform.suite.api.SelectClasses;
import org.junit.platform.suite.api.Suite;// 测试套件:通过 @Suite 注解
@Suite
@SelectClasses(value = {aaa.class, bbb.class})
public class runSuite {
}

测试套件测试 aaa.java, bbb.java, ccc.java 的测试结果,类下想要运行的用例必须要被 @Test 注解修饰
在这里插入图片描述
@Suite 注解标识该类是测试套件类而不是测试类
@SelectClasses 部分源码告知我们需要填上 class 类名
在这里插入图片描述

指定包名来运行包下测试用例

package TestSuite;import org.junit.platform.suite.api.SelectClasses;
import org.junit.platform.suite.api.SelectPackages;
import org.junit.platform.suite.api.Suite;// 测试套件:通过 @Suite 注解
@Suite
//@SelectClasses(value = {aaa.class, bbb.class})
@SelectPackages("TestSuite")
public class runSuite {
}

发现连 ccc.java 的测试也打印输出了
在这里插入图片描述

最好是把要测试的文件以 xxxTest 结尾比较好,但是因为文件夹是 TestSuite的缘故,所以现在即使不是 xxxTest 结尾的包文件也能被扫描到并执行 @Test 注解的方法,否则就会执行报错。

报错问题如下所示
在这里插入图片描述
解决方案如下所示
在这里插入图片描述

相关文章:

软件测试自动化Java篇【Selenium+Junit 5】

文章目录Selenium环境部署自动化测试例子常见的元素操作窗口等待浏览器的操作弹窗选择器执行脚本文件上传浏览器参数Junit 5导入依赖Junit 4 和 Junit5 注解对比断言测试顺序参数化单参数多参数动态参数测试套件指定类来运行测试用例指定包名来运行包下测试用例Selenium 为什么…...

Clip:学习笔记

Clip 文章目录Clip前言一、原理1.1 摘要1.2 引言1.3 方法1.4 实验1.4.1 zero-shot Transfer1.4.2 PROMPT ENGINEERING AND ENSEMBLING1.5 局限性二、总结前言 阅读论文&#xff1a; Learning Transferable Visual Models From Natural Language Supervision CLIP 论文逐段精读…...

STM32CubexMX与FreeRTOS学习

目录 LED与EXTI配置 基本定时器使用 软件定时器 在HAL库中实现printf 重点--记得自己添加头文件 队列实现 二值信号量实现 计数信号量实现 DMA实现 ADC配置 RTC配置 看门狗 窗口看门狗 FreeRTOS结合MX软件开发&#xff0c;基础配置直接生成&#xff0c;我们只…...

Master Slave 主从同步错误 Slave_IO_Running:NO/Slave_SQL_Running: No

Master Slave 主从同步错误 Slave_IO_Running:NO Slave_SQL_Running:Yes #在Slave库上查看状态 mysql> show slave status\G Slave_IO_Running: No Slave_SQL_Running: Yes #重启master库&#xff1a;service mysqld restart mysql> show master status; ------------…...

JavaScript函数之prototype原型和原型链

文章目录1. 原型2. 显式和隐式原型3. 原型链3.1 访问顺序4. instanceof4.1 如何判断1. 原型 函数的prototype属性 每个函数都有一个prototype属性&#xff0c;它默认指向一个Object空对象&#xff08;即&#xff1a;原型对象&#xff09;。原型对象中有一个属性constructor&a…...

从上海分时电价机制调整看转供电用户电能计费

安科瑞 耿敏花2022年12月16日&#xff0c;上海市发改委发布《关于进一步完善我市分时电价机制有关事项的通知》(沪发改价管〔2022〕50号)。通知明确上海分时电价机制&#xff0c;一般工商业及其他两部制、大工业两部制用电夏季&#xff08;7、8、9月&#xff09;和冬季&#xf…...

TypeScript类型体操:获取数组中元素对象属性的值作为新类型

title: TypeScript类型体操&#xff1a;获取数组中元素对象属性的值作为新类型 date: 2023-03-03 20:58:24 categories: TypeScript类型体操 tags: TypeScript类型体操TypeScript 首先先说获取数组中元素对象属性的值作为新类型的解决方案 使用 as const 强调不可变数组使用 …...

npm,yarn和pnpm

npm扁平的node_modules结构比如项目依赖了A 和 C&#xff0c;而 A 和 C 依赖了不同版本的 B1.0 和 B2.0&#xff0c;D也依赖B1.0, node_modules 结构如下&#xff1a;node_modules ├── A1.0.0 ├── B1.0.0 └── C1.0.0└── node_modules└── B2.0.0C依赖的B2.0因为版…...

【算法】【数组与矩阵模块】在排好序的矩阵中找数,时间复杂度O(M+N)

目录前言问题介绍解决方案代码编写java语言版本c语言版本c语言版本思考感悟写在最后前言 当前所有算法都使用测试用例运行过&#xff0c;但是不保证100%的测试用例&#xff0c;如果存在问题务必联系批评指正~ 在此感谢左大神让我对算法有了新的感悟认识&#xff01; 问题介绍 …...

【Java|基础篇】计算机中数据的存储规则

文章目录前言:1.计算机中的数据2.二进制的介绍二进制的运算规则常见的进制3.字符的存储4.汉字的存储5.图片的存储6.音频的存储总结:前言: 本篇文章只是为了科普 计算机中数据的存储规则 1.计算机中的数据 计算机的数据大致分为三类:文本数据,图片和音频 注:视频是图片和音频…...

RestTemplate使用HttpClient连接池

文章目录RestTemplate使用HttpClient连接池ClientHttpRequestFactorySimpleClientHttpRequestFactorySimpleClientHttpRequestFactory 设置超时时间HttpURLConnection的缺点HttpComponentsClientHttpRequestFactoryPoolingHttpClientConnectionManager配置连接池HttpClient总结…...

Python 操作Redis

在 Python中我们使用 redis库来操作 Redis数据库。Redis数据库的使用命令这里就不介绍了。 需要安装 redis库。检查是否安装redis&#xff1a; pip redis 如果未安装&#xff0c;使用 pip命令安装 redis。 pip install redis #安装最新版本 一、Redis连接 Redis提供两个类 Re…...

CEC2020:鱼鹰优化算法(Osprey optimization algorithm,OOA)求解CEC2020(提供MATLAB代码

一、鱼鹰优化算法简介 鱼鹰优化算法&#xff08;Osprey optimization algorithm&#xff0c;OOA&#xff09;由Mohammad Dehghani 和 Pavel Trojovsk于2023年提出&#xff0c;其模拟鱼鹰的捕食行为。 鱼鹰是鹰形目、鹗科、鹗属的仅有的一种中型猛禽。雌雄相似。体长51-64厘米…...

词对齐 - MGIZA++

文章目录关于 MGIZAgiza-py安装 MGIZA命令说明mkclsd4normhmmnormplain2sntsnt2coocsnt2coocrmpsnt2plainsymalmgizageneral parameters:No. of iterations:parameter for various heuristics in GIZA for efficient training:parameters for describing the type and amount o…...

GUI 之 Tkinter编程

GUI 图形界面&#xff0c;Tkinter 是 Python 内置的 GUI 库&#xff0c;IDLE 就是 Tkinter 设计的。 1. Tkinter 之初体验 import tkinter as tkroot tk.Tk() # 创建一个窗口root.title(窗口标题)# 添加 label 组件 theLabel tk.Label(root, text文本内容) theLabel.p…...

【软件测试】性能测试面试题都问什么?面试官想要什么?回答惊险避坑......

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 1、你认为不同角色关…...

后端开发基础能力以及就Java的主流开发框架介绍

前言&#xff1a;java语言开发转后端&#xff0c;必须了解后端主流的一些东西&#xff0c;共勉。 后端开发需要具备以下基础能力&#xff1a; 1.编程语言&#xff1a;熟练掌握至少一门编程语言&#xff0c;如Java、Python、Ruby、PHP、C#等。 2.数据结构和算法&#xff1a;具…...

H2数据库连接时用户密码错误:Wrong user name or password [28000-214] 28000/28000 (Help)

H2数据库连接时用户密码错误: 2023-03-03 08:25:07 database: wrong user or password; user: "SA" org.h2.message.DbException: Wrong user name or password [28000-214]出现的问题配置信息原因解决办法org.h2.message.DbException: Wrong user name or password …...

青岛诺凯达机械盛装亮相2023济南生物发酵展,3月与您相约

BIO CHINA生物发酵展&#xff0c;作为生物发酵产业一年一度行业盛会&#xff0c;由中国生物发酵产业协会主办&#xff0c;上海信世展览服务有限公司承办&#xff0c;2023第10届国际生物发酵展&#xff08;济南&#xff09;于2023年3月30-4月1日在山东国际会展中心&#xff08;济…...

【JAVA程序设计】【C00111】基于SSM的网上图书商城管理系统——有文档

基于SSM的网上图书商城管理系统——有文档项目简介项目获取开发环境项目技术运行截图项目简介 基于ssm框架开发的网上在线图书售卖商城项目&#xff0c;本项目分为三种权限&#xff1a;系统管理员、卖家、买家 管理员角色包含以下功能&#xff1a; 用户信息管理、权限管理、订…...

基于卷积神经网络CNN的三相故障识别

目录 背影 卷积神经网络CNN的原理 卷积神经网络CNN的定义 卷积神经网络CNN的神经元 卷积神经网络CNN的激活函数 卷积神经网络CNN的传递函数 卷积神经网络CNN手写体识别 基本结构 主要参数 MATALB代码 结果图 展望 背影 现在生活&#xff0c;为节能减排&#xff0c;减少电能损…...

Java工厂设计模式详解,大厂的Java抽象工厂模式分享!

我是好程序员-小源&#xff01;本期文章主要给大家分享&#xff1a;Java工厂设计模式。文中使用通俗易懂的案例&#xff0c;使你快速学习和轻松上手&#xff01;一、什么是Java抽象工厂模式1. Java抽象工厂是23种设计模式中创建型模式的一种&#xff0c;Java抽象工厂是由多个工…...

Git 企业级分支提交流程

Git 企业级分支提交流程 首先在本地分支hfdev上进行开发&#xff0c;开发后要经过测试。 如果测试通过了&#xff0c;那么久可以合并到本地分支develop&#xff0c;合并之后hfdev和development应该完全一样。 git add 文件 git commit -m ‘注释’ git checkout develop //切换…...

C/C++每日一练(20230303)

目录 1. 字符串相乘 2. 单词拆分 II 3. 串联所有单词的子串 1. 字符串相乘 给定两个以字符串形式表示的非负整数 num1 和 num2&#xff0c;返回 num1 和 num2 的乘积&#xff0c;它们的乘积也表示为字符串形式。 示例 1: 输入: num1 "2", num2 "3"…...

Python3-条件控制

Python3 条件控制 Python 条件语句是通过一条或多条语句的执行结果&#xff08;True 或者 False&#xff09;来决定执行的代码块。 可以通过下图来简单了解条件语句的执行过程: 代码执行过程&#xff1a; if 语句 Python中if语句的一般形式如下所示&#xff1a; if condi…...

KDZD地埋电缆故障测试仪

一、产品特性 ★电缆故障测试仪&#xff08;闪测仪&#xff09; &#xff08;1&#xff09;使用范围广&#xff1a;用于测量各种不同截面、不同介质的各种电力电缆、高频同轴电缆&#xff0c;市话电缆及两根以上均匀铺设的地埋电线等电缆高低阻、短路、开路、断线以及高阻泄漏…...

爆款升级!新系列南卡Neo最强旗舰杀到,业内首款无线充骨传导耳机!

中国专业骨传导耳机品牌NANK南卡于近日发布了全新南卡Neo骨传导运动耳机&#xff0c;打造一款佩戴最舒适、音质体验最好的骨传导耳机。推出第2代声学響科技技术&#xff0c;提供更优质的开放式骨传导听音体验&#xff0c;透过不一样的音质体验&#xff0c;打造更好的骨传导耳机…...

基于Spring Boot+Thymeleaf的在线投票系统

文章目录 项目介绍主要功能截图:后台登录注册个人信息展示投票数据显示首页展示对战匹配分数排行榜部分代码展示设计总结项目获取方式🍅 作者主页:Java韩立 🍅 简介:Java领域优质创作者🏆、 简历模板、学习资料、面试题库【关注我,都给你】 🍅文末获取源码联系🍅…...

【每日一题Day135】LC1487保证文件名唯一 | 哈希表

保证文件名唯一【LC1487】 给你一个长度为 n 的字符串数组 names 。你将会在文件系统中创建 n 个文件夹&#xff1a;在第 i 分钟&#xff0c;新建名为 names[i] 的文件夹。 由于两个文件 不能 共享相同的文件名&#xff0c;因此如果新建文件夹使用的文件名已经被占用&#xff0…...

计算机系统的基本组成 第一节

一、计算机系统 计算机系统是指&#xff1a;电子数字通用、计算机系统 由硬件和软件两个子系统组成 硬件是保存和运行软件的物质基础 软件是指挥硬件完成预期功能的智力部分 重点&#xff1a; 计算机系统部件 五个 1、数据运算部件&#xff1a;完成对数据的运算处理功能…...

海南海口做网站/深圳网站设计实力乐云seo

记录一个小知识点&#xff1a; 我们会看到这样利用循环的代码&#xff1a; for _ in range(n): _ 在这里是什么意思&#xff1f; _ 是占位符&#xff1a;表示不在意变量的值&#xff0c;只是用于循环遍历n次&#xff0c;和使用i没什么区别&#xff0c;只不过使用_就表示我不在…...

开发工具宏怎么使用/无锡seo网络推广

文章目录一阶逻辑的语法和语义全称量词和存在量词等词equality运用一阶逻辑一阶逻辑的断言和查询亲属关系论域(the kinship domain)总结资源分享一阶逻辑的语法和语义 命题逻辑是我们最先学的逻辑&#xff0c;比较简单&#xff0c;也比较好理解。 因为命题逻辑表达能力不够&am…...

阿里云网站建设考试/百度网站

如果使用相同的工艺&#xff0c;将得到相同的面包。 ——白面包法则设计碎碎念记忆法的初衷源于一个朴素的想法&#xff1a;既然都有专门适合太监修炼的武功&#xff08;葵花宝典&#xff09;&#xff0c;为什么就没有专门适合…...

织梦dedecms资讯文章类网站模板/seo如何快速排名

Ser vlet 3十二月2009开发平台标准版6&#xff0c;6可插性&#xff0c;易于开发&#xff0c;异步ser vlet&#xff0c;安全&#xff0c;文件上传 Ser vlet 2.5九月2005开发平台标准版5&#xff0c;5需要平台标准版5&#xff0c;支持注释 Ser vlet 2.4十一月2003中1.4&#xff0…...

徐汇网站设计/企业查询免费

软件测试工程师&#xff0c;和开发工程师相比起来&#xff0c;虽然前期可能不会太深&#xff0c;但是涉及的面还是比较广的。前期面试实习生或者一年左右的岗位&#xff0c;问的也主要是一些基础性的问题比较多。涉及的知识主要有MySQL数据库的使用、Linux操作系统的使用、软件…...

有什么做动画的网站/网站注册账号

内容来源&#xff1a;2018年1月30日&#xff0c;VMware大中华区高级技术讲师姚泉在“VMware官方在线直播”进行《VMware云管平台运维管理》演讲分享。IT 大咖说作为独家视频合作方&#xff0c;经主办方和讲者审阅授权发布。阅读字数&#xff1a;4068 | 6分钟阅读嘉宾演讲视频地…...