移动端自动化测试工具 Appium 之自定义报告

文章目录

  • 一、背景
  • 二、具体实现
    • 1、保存结果实体
    • 2、工具类
    • 3、自定义报告监听类代码
    • 4、模板代码
      • 4.1、report.vm
      • 4.2、执行xml
  • 三、总结

一、背景

自动化测试用例跑完后报告展示是体现咱们价值的一个地方咱们先看原始报告。
在这里插入图片描述
上面报告虽然麻雀虽小但五脏俱全,但是如果用这个发送报告不是很美观,如果错误没有截图与日志,通过观察testng有需要可以继承的监听,可以自定义报告。

如下图:

在这里插入图片描述

点击log弹出对话框并且记录操作日志。
在这里插入图片描述

二、具体实现

1、保存结果实体

package appout.reporter;import java.util.List;/*** @author 7DGroup* @Title: TestResult* @Description: 用于存储测试结果* @date 2019/11/21 / 19:04*/public class TestResult {/*** 测试方法名*/private String testName;/*** 测试类名*/private String className;/*** 用例名称*/private String caseName;/*** 测试用参数*/private String params;/*** 测试描述*/private String description;/*** 报告输出日志Reporter Output*/private List<String> output;/*** 测试异常原因*/private Throwable throwable;/*** 线程信息*/private String throwableTrace;/*** 状态*/private int status;/*** 持续时间*/private String duration;/*** 是否成功*/private boolean success;public TestResult() {}public String getTestName() {return testName;}public void setTestName(String testName) {this.testName = testName;}public String getClassName() {return className;}public void setClassName(String className) {this.className = className;}public String getCaseName() {return caseName;}public void setCaseName(String caseName) {this.caseName = caseName;}public String getParams() {return params;}public void setParams(String params) {this.params = params;}public String getDescription() {return description;}public void setDescription(String description) {this.description = description;}public List<String> getOutput() {return output;}public void setOutput(List<String> output) {this.output = output;}public Throwable getThrowable() {return throwable;}public void setThrowable(Throwable throwable) {this.throwable = throwable;}public String getThrowableTrace() {return throwableTrace;}public void setThrowableTrace(String throwableTrace) {this.throwableTrace = throwableTrace;}public int getStatus() {return status;}public void setStatus(int status) {this.status = status;}public String getDuration() {return duration;}public void setDuration(String duration) {this.duration = duration;}public boolean isSuccess() {return success;}public void setSuccess(boolean success) {this.success = success;}@Overridepublic String toString() {return "TestResult{" +"testName='" + testName + '\'' +", className='" + className + '\'' +", caseName='" + caseName + '\'' +", params='" + params + '\'' +", description='" + description + '\'' +", output=" + output +", throwable=" + throwable +", throwableTrace='" + throwableTrace + '\'' +", status=" + status +", duration='" + duration + '\'' +", success=" + success +'}';}
}

2、工具类

package appout.reporter;import org.testng.ITestResult;
import java.util.LinkedList;
import java.util.List;/*** @author 7DGroup* @Title: TestResultCollection* @Description: testng采用数据驱动,一个测试类可以有多个测试用例集合,每个测试类,应该有个测试结果集* @date 2019/11/21 / 19:01*/public class TestResultCollection {private int totalSize = 0;private int successSize = 0;private int failedSize = 0;private int errorSize = 0;private int skippedSize = 0;private List<TestResult> resultList;public void addTestResult(TestResult result) {if (resultList == null) {resultList = new LinkedList<>();}resultList.add(result);switch (result.getStatus()) {case ITestResult.FAILURE:failedSize += 1;break;case ITestResult.SUCCESS:successSize += 1;break;case ITestResult.SKIP:skippedSize += 1;break;}totalSize += 1;}public int getTotalSize() {return totalSize;}public void setTotalSize(int totalSize) {this.totalSize = totalSize;}public int getSuccessSize() {return successSize;}public void setSuccessSize(int successSize) {this.successSize = successSize;}public int getFailedSize() {return failedSize;}public void setFailedSize(int failedSize) {this.failedSize = failedSize;}public int getErrorSize() {return errorSize;}public void setErrorSize(int errorSize) {this.errorSize = errorSize;}public int getSkippedSize() {return skippedSize;}public void setSkippedSize(int skippedSize) {this.skippedSize = skippedSize;}public List<TestResult> getResultList() {return resultList;}public void setResultList(List<TestResult> resultList) {this.resultList = resultList;}
}

3、自定义报告监听类代码

package appout.reporter;import appout.base.DriverBase;
import appout.utils.LogUtil;
import appout.utils.OperationalCmd;
import io.appium.java_client.AppiumDriver;
import org.apache.commons.io.FileUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.app.VelocityEngine;
import org.openqa.selenium.OutputType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.*;
import org.testng.xml.XmlSuite;import java.io.*;
import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.*;/*** @author 7DGroup* @Title: ReporterListener* @Description: 自定义报告监听类* @date 2019/11/21 / 18:56*/public class ReporterListener implements IReporter, ITestListener {private static final Logger log = LoggerFactory.getLogger(DriverBase.class);private static final NumberFormat DURATION_FORMAT = new DecimalFormat("#0.000");@Overridepublic void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites, String outputDirectory) {List<ITestResult> list = new LinkedList<>();Date startDate = new Date();Date endDate = new Date();int TOTAL = 0;int SUCCESS = 1;int FAILED = 0;int ERROR = 0;int SKIPPED = 0;for (ISuite suite : suites) {Map<String, ISuiteResult> suiteResults = suite.getResults();for (ISuiteResult suiteResult : suiteResults.values()) {ITestContext testContext = suiteResult.getTestContext();startDate = startDate.getTime() > testContext.getStartDate().getTime() ? testContext.getStartDate() : startDate;if (endDate == null) {endDate = testContext.getEndDate();} else {endDate = endDate.getTime() < testContext.getEndDate().getTime() ? testContext.getEndDate() : endDate;}IResultMap passedTests = testContext.getPassedTests();IResultMap failedTests = testContext.getFailedTests();IResultMap skippedTests = testContext.getSkippedTests();IResultMap failedConfig = testContext.getFailedConfigurations();SUCCESS += passedTests.size();FAILED += failedTests.size();SKIPPED += skippedTests.size();ERROR += failedConfig.size();list.addAll(this.listTestResult(passedTests));list.addAll(this.listTestResult(failedTests));list.addAll(this.listTestResult(skippedTests));list.addAll(this.listTestResult(failedConfig));}}/* 计算总数 */TOTAL = SUCCESS + FAILED + SKIPPED + ERROR;this.sort(list);Map<String, TestResultCollection> collections = this.parse(list);VelocityContext context = new VelocityContext();context.put("TOTAL", TOTAL);context.put("mobileModel", OperationalCmd.getMobileModel());context.put("versionName", OperationalCmd.getVersionNameInfo());context.put("SUCCESS", SUCCESS);context.put("FAILED", FAILED);context.put("ERROR", ERROR);context.put("SKIPPED", SKIPPED);context.put("startTime", ReporterListener.formatDate(startDate.getTime()) + "<--->" + ReporterListener.formatDate(endDate.getTime()));context.put("DURATION", ReporterListener.formatDuration(endDate.getTime() - startDate.getTime()));context.put("results", collections);write(context, outputDirectory);}/*** 输出模板** @param context* @param outputDirectory*/private void write(VelocityContext context, String outputDirectory) {if (!new File(outputDirectory).exists()) {new File(outputDirectory).mkdirs();}//获取报告模板File f = new File("");String absolutePath = f.getAbsolutePath();String fileDir = absolutePath + "/template/";String reslutpath = outputDirectory + "/html/report" + ReporterListener.formateDate() + ".html";File outfile = new File(reslutpath);if (!outfile.exists()) {outfile.mkdirs();}try {//写文件VelocityEngine ve = new VelocityEngine();Properties p = new Properties();p.setProperty(VelocityEngine.FILE_RESOURCE_LOADER_PATH, fileDir);p.setProperty(Velocity.ENCODING_DEFAULT, "utf-8");p.setProperty(Velocity.INPUT_ENCODING, "utf-8");ve.init(p);Template t = ve.getTemplate("reportnew.vm");//输出结果OutputStream out = new FileOutputStream(new File(reslutpath));BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8));// 转换输出t.merge(context, writer);writer.flush();log.info("报告位置:" + reslutpath);} catch (IOException e) {e.printStackTrace();}}/*** 排序规则** @param list*/private void sort(List<ITestResult> list) {Collections.sort(list, new Comparator<ITestResult>() {@Overridepublic int compare(ITestResult r1, ITestResult r2) {if (r1.getStatus() < r2.getStatus()) {return 1;} else {return -1;}}});}private LinkedList<ITestResult> listTestResult(IResultMap resultMap) {Set<ITestResult> results = resultMap.getAllResults();return new LinkedList<>(results);}private Map<String, TestResultCollection> parse(List<ITestResult> list) {Map<String, TestResultCollection> collectionMap = new HashMap<>();for (ITestResult t : list) {String className = t.getTestClass().getName();if (collectionMap.containsKey(className)) {TestResultCollection collection = collectionMap.get(className);collection.addTestResult(toTestResult(t));} else {TestResultCollection collection = new TestResultCollection();collection.addTestResult(toTestResult(t));collectionMap.put(className, collection);}}return collectionMap;}/*** 输出报表解析* @param t* @return*/private appout.reporter.TestResult toTestResult(ITestResult t) {TestResult testResult = new TestResult();Object[] params = t.getParameters();if (params != null && params.length >= 1) {String caseId = (String) params[0];testResult.setCaseName(caseId);} else {testResult.setCaseName("null");}testResult.setClassName(t.getTestClass().getName());testResult.setParams(getParams(t));testResult.setTestName(t.getName());testResult.setDescription(t.getMethod().getDescription());testResult.setStatus(t.getStatus());//异常testResult.setThrowableTrace("class: " + t.getTestClass().getName() + " <br/> method: " + t.getName() + " <br/> error: " + t.getThrowable());testResult.setThrowable(t.getThrowable());long duration = t.getEndMillis() - t.getStartMillis();testResult.setDuration(formatDuration(duration));//日志testResult.setOutput(Reporter.getOutput(t));return testResult;}/*** 每次调用测试@Test之前调用** @param result*/@Overridepublic void onTestStart(ITestResult result) {logTestStart(result);}/*** 用例执行结束后,用例执行成功时调用** @param result*/@Overridepublic void onTestSuccess(ITestResult result) {logTestEnd(result, "Success");}/*** 用例执行结束后,用例执行失败时调用* 跑fail则截图 获取屏幕截图** @param result*/@Overridepublic void onTestFailure(ITestResult result) {AppiumDriver driver = DriverBase.getDriver();File srcFile = driver.getScreenshotAs(OutputType.FILE);File location = new File("./test-output/html/result/screenshots");if (!location.exists()) {location.mkdirs();}String dest = result.getMethod().getRealClass().getSimpleName() + "." + result.getMethod().getMethodName();String s = dest + "_" + formateDate() + ".png";File targetFile =new File(location + "/" + s);log.info("截图位置:");Reporter.log("<font color=\"#FF0000\">截图位置</font><br /> " + targetFile.getPath());log.info("------file is ---- " + targetFile.getPath());try {FileUtils.copyFile(srcFile, targetFile);} catch (IOException e) {e.printStackTrace();}logTestEnd(result, "Failed");//报告截图后面显示Reporter.log("<img  src=\"./result/screenshots/" + s + "\" width=\"64\" height=\"64\" alt=\"***\"  onMouseover=\"this.width=353; this.height=613\" onMouseout=\"this.width=64;this.height=64\" />");}/*** 用例执行结束后,用例执行skip时调用** @param result*/@Overridepublic void onTestSkipped(ITestResult result) {logTestEnd(result, "Skipped");}/*** 每次方法失败但是已经使用successPercentage进行注释时调用,并且此失败仍保留在请求的成功百分比之内。** @param result*/@Overridepublic void onTestFailedButWithinSuccessPercentage(ITestResult result) {LogUtil.fatal(result.getTestName());logTestEnd(result, "FailedButWithinSuccessPercentage");}/*** 在测试类被实例化之后调用,并在调用任何配置方法之前调用。** @param context*/@Overridepublic void onStart(ITestContext context) {LogUtil.startTestCase(context.getName());return;}/*** 在所有测试运行之后调用,并且所有的配置方法都被调用** @param context*/@Overridepublic void onFinish(ITestContext context) {LogUtil.endTestCase(context.getName());return;}/*** 在用例执行结束时,打印用例的执行结果信息*/protected void logTestEnd(ITestResult tr, String result) {Reporter.log(String.format("=============Result: %s=============", result), true);}/*** 在用例开始时,打印用例的一些信息,比如@Test对应的方法名,用例的描述等等*/protected void logTestStart(ITestResult tr) {Reporter.log(String.format("=============Run: %s===============", tr.getMethod().getMethodName()), true);Reporter.log(String.format("用例描述: %s, 优先级: %s", tr.getMethod().getDescription(), tr.getMethod().getPriority()),true);return;}/*** 日期格式化** @return date*/public static String formateDate() {SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");Calendar cal = Calendar.getInstance();Date date = cal.getTime();return sf.format(date);}/*** 时间转换** @param date* @return*/public static String formatDate(long date) {SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");return formatter.format(date);}public static String formatDuration(long elapsed) {double seconds = (double) elapsed / 1000;return DURATION_FORMAT.format(seconds);}/*** 获取方法参数,以逗号分隔** @param result* @return*/public static String getParams(ITestResult result) {Object[] params = result.getParameters();List<String> list = new ArrayList<String>(params.length);for (Object o : params) {list.add(renderArgument(o));}return commaSeparate(list);}/*** 将object 转换为String* @param argument* @return*/private static String renderArgument(Object argument) {if (argument == null) {return "null";} else if (argument instanceof String) {return "\"" + argument + "\"";} else if (argument instanceof Character) {return "\'" + argument + "\'";} else {return argument.toString();}}/*** 将集合转换为以逗号分隔的字符串* @param strings* @return*/private static String commaSeparate(Collection<String> strings) {StringBuilder buffer = new StringBuilder();Iterator<String> iterator = strings.iterator();while (iterator.hasNext()) {String string = iterator.next();buffer.append(string);if (iterator.hasNext()) {buffer.append(", ");}}return buffer.toString();}}

4、模板代码

4.1、report.vm

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1"><link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="stylesheet"><!-- HTML5 shim 和 Respond.js 是为了让 IE8 支持 HTML5 元素和媒体查询(media queries)功能 --><!-- 警告:通过 file:// 协议(就是直接将 html 页面拖拽到浏览器中)访问页面时 Respond.js 不起作用 --><!--[if lt IE 9]><script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script><script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script><![endif]--><title>UI自动</title><style>body {background-color: #f2f2f2;color: #333;margin: 0 auto;width: 960px;}#summary {width: 960px;margin-bottom: 20px;}#summary th {background-color: skyblue;padding: 5px 12px;}#summary td {background-color: lightblue;text-align: center;padding: 4px 8px;}.details {width: 960px;margin-bottom: 20px;}.details th {background-color: skyblue;padding: 5px 12px;}.details tr .passed {background-color: #2fff65;}.details tr .failed {background-color: red;}.details tr .unchecked {background-color: gray;}.details td {background-color: lightblue;padding: 5px 12px;}.details .detail {background-color: lightgrey;font-size: smaller;padding: 5px 10px;text-align: center;}.details .success {background-color: #2fff65;}.details .error {background-color: red;}.details .failure {background-color: salmon;}.details .skipped {background-color: gray;}.button {font-size: 1em;padding: 6px;width: 4em;text-align: center;background-color: #06d85f;border-radius: 20px/50px;cursor: pointer;transition: all 0.3s ease-out;}a.button {color: gray;text-decoration: none;}.button:hover {background: #2cffbd;}.overlay {position: fixed;top: 0;bottom: 0;left: 0;right: 0;background: rgba(0, 0, 0, 0.7);transition: opacity 500ms;visibility: hidden;opacity: 0;}.overlay:target {visibility: visible;opacity: 1;}.popup {margin: 70px auto;padding: 20px;background: #fff;border-radius: 10px;width: 50%;position: relative;transition: all 3s ease-in-out;}.popup h2 {margin-top: 0;color: #333;font-family: Tahoma, Arial, sans-serif;}.popup .close {position: absolute;top: 20px;right: 30px;transition: all 200ms;font-size: 30px;font-weight: bold;text-decoration: none;color: #333;}.popup .close:hover {color: #06d85f;}.popup .content {max-height: 80%;overflow: auto;text-align: left;}@media screen and (max-width: 700px) {.box {width: 70%;}.popup {width: 70%;}}</style>
</head><body>
<br>
<h1 align="center">UI自动化回归报告</h1><h2>汇总信息</h2>
<table id="summary"><tr><th>开始与结束时间</th><td colspan="2">${startTime}</td><th>执行时间</th><td colspan="2">$DURATION seconds</td></tr><tr><th>运行版本与系统版本</th><td colspan="2">${versionName}</td><th>设备型号</th><td colspan="2">${mobileModel}</td></tr><tr><th>TOTAL</th><th>SUCCESS</th><th>FAILED</th><th>ERROR</th><th>SKIPPED</th></tr><tr><td>$TOTAL</td><td>$SUCCESS</td><td>$FAILED</td><td>$ERROR</td><td>$SKIPPED</td></tr>
</table><h2>详情</h2>#foreach($result in $results.entrySet())#set($item = $result.value)<table id="$result.key" class="details"><tr><th>测试类</th><td colspan="4">$result.key</td></tr><tr><td>TOTAL: $item.totalSize</td><td>SUCCESS: $item.successSize</td><td>FAILED: $item.failedSize</td><td>ERROR: $item.errorSize</td><td>SKIPPED: $item.skippedSize</td></tr><tr><th>Status</th><th>Method</th><th>Description</th><th>Duration</th><th>Detail</th></tr>#foreach($testResult in $item.resultList)<tr>#if($testResult.status==1)<th class="success" style="width:5em;">success</td>#elseif($testResult.status==2)<th class="failure" style="width:5em;">failure</td>#elseif($testResult.status==3)<th class="skipped" style="width:5em;">skipped</td>#end<td>$testResult.testName</td><td>${testResult.description}</td><td>${testResult.duration} seconds</td><td class="detail">##                    <a class="button" href="#popup_log_${testResult.caseName}_${testResult.testName}">log</a><button type="button" class="btn btn-primary btn-lg" data-toggle="modal"data-target="#popup_log_${testResult.caseName}_${testResult.testName}">log</button><!-- 日志模态框 --><div class="modal fade" id="popup_log_${testResult.caseName}_${testResult.testName}" tabindex="-1"role="dialog" aria-labelledby="myModalLabel"><div class="modal-dialog" role="document"><div class="modal-content"><div class="modal-header"><button type="button" class="close" data-dismiss="modal" aria-label="Close"><spanaria-hidden="true">&times;</span></button><h4 class="modal-title" id="myModalLabel">用例操作步骤</h4></div><div class="modal-body"><div style="overflow: auto"><table><tr><th>日志</th><td>#foreach($msg in $testResult.twooutparam)<pre>$msg</pre>#end</td></tr>#if($testResult.status==2)<tr><th>异常</th><td><pre>$testResult.throwableTrace</pre></td></tr>#end</table></div></div><div class="modal-footer"><button type="button" class="btn btn-default" data-dismiss="modal">Close</button></div></div></div></div></td></tr>#end</table>#end
<a href="#top">Android前端UI自动化</a>
<!-- jQuery (Bootstrap 的所有 JavaScript 插件都依赖 jQuery,所以必须放在前边) -->
<script src="https://cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js"></script>
<!-- 加载 Bootstrap 的所有 JavaScript 插件。你也可以根据需要只加载单个插件。 -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js"></script>
</body>

注意:
report.vm存放路径,否则路径不对会找不到

4.2、执行xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
<suite name="UI自动化" parallel="tests" thread-count="1"><listeners><listener class-name="appout.reporter.ReporterListener"></listener></listeners><test name="M6TGLMA721108530"><parameter name="udid" value="M6TGLMA721108530"/><parameter name="port" value="4723"/><classes><class name="appout.appcase.LoginTest"/></classes></test>
</suite>

三、总结

只要通过上面代码就能自定义自己的报告,希望给大家一点帮助,其实这个模板只有改下就能成为接口测试报告。

相关代码:

  • https://github.com/zuozewei/blog-example/tree/master/auto-test/comsevenday

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

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

相关文章

【错误的集合】力扣python

最初想法 def findErrorNums(nums):n len(nums)duplicate -1missing -1for num in nums:if nums[abs(num) - 1] < 0:duplicate abs(num)else:nums[abs(num) - 1] * -1for i in range(n):if nums[i] > 0:missing i 1breakreturn [duplicate, missing] 遇到力扣大佬…

RedisTemplate操作Redis详解之连接Redis及自定义序列化

连接到Redis 使用Redis和Spring时的首要任务之一是通过IoC容器连接到Redis。为此&#xff0c;需要java连接器&#xff08;或绑定&#xff09;。无论选择哪种库&#xff0c;你都只需要使用一组Spring Data Redis API&#xff08;在所有连接器中行为一致&#xff09;&#xff1a;…

【STM32 |程序实例】按键控制、光敏传感器控制蜂鸣器

目录 前言 按键控制LED 光敏传感器控制蜂鸣器 前言 上拉输入&#xff1a;若GPIO引脚配置为上拉输入模式&#xff0c;在默认情况下&#xff08;GPIO引脚无输入&#xff09;&#xff0c;读取的GPIO引脚数据为1&#xff0c;即高电平。 下拉输入&#xff1a;若GPIO引脚配置为下…

如何将pdf文件换成3d模型?---模大狮模型网

PDF文件是一种广泛用于文档传输和共享的格式&#xff0c;但在某些情况下&#xff0c;我们可能希望将其中的内容转换为更具交互性和视觉效果的3D模型。本文将介绍如何将PDF文件转换为3D模型&#xff0c;为您展示实现这一想象的步骤。 选择合适的PDF文件&#xff1a; 首先&#…

CentOs搭建Kubernetes集群

kubeadm minikube 还是太“迷你”了&#xff0c;方便的同时也隐藏了很多细节&#xff0c;离真正生产环境里的计算集群有一些差距&#xff0c;毕竟许多需求、任务只有在多节点的大集群里才能够遇到&#xff0c;相比起来&#xff0c;minikube 真的只能算是一个“玩具”。 Kuber…

如何开通微软必应bing国内竞价广告账户?

微软必应Bing作为全球第二大搜索引擎&#xff0c;凭借其特有的用户群和市场定位&#xff0c;为中国广告主开辟了一片潜力无限的蓝海市场。云衔科技通过专业实力&#xff0c;为广告主提供全方位支持&#xff0c;从开户到代运营&#xff0c;助力企业扬帆起航。 一、微软必应bing…

远程监控供水设备运行状态

随着城市化进程的加快&#xff0c;供水设备的安全稳定运行对于保障居民日常生活和工业生产至关重要。然而&#xff0c;传统的供水设备管理方式往往受限于人力、物力和时间的限制&#xff0c;难以实现对供水设备运行状态的全面监控和实时管理。在这一背景下&#xff0c;HiWoo Cl…

海洋环境保护论文阅读记录

海洋环境保护 论文1&#xff1a;Critical role of wave–seabed interactions in the extensive erosion of Yellow River estuarine sediments 波浪-海床相互作用在黄河河口广泛侵中的关键作用 estuatine 河口的&#xff0c;港湾的 erodibility侵蚀度 sediment erodibility …

GEVernova推出GEV新能源平台,引领新能源未来

近日&#xff0c;全球领先的能源设备制造和服务公司 GE Vernova 宣布推出 GEV 新能源平台&#xff0c;这是一个将金融、科技和产业深度融合的全新投资平台。GEV 新能源平台旨在为用户提供一站式可持续新能源投资解决方案&#xff0c;助力全球新能源转型和可持续发展。 新能源已…

YOLOv9-20240507周更说明|更新MobileNetv4等多种轻量化主干

专栏地址&#xff1a;目前售价售价69.9&#xff0c;改进点70 专栏介绍&#xff1a;YOLOv9改进系列 | 包含深度学习最新创新&#xff0c;助力高效涨点&#xff01;&#xff01;&#xff01; 本周已更新说明&#xff1a; ### ⭐⭐更新时间&#xff1a;2024/5/12⭐⭐ 1. YOLOv9…

【Web后端】web后端开发简介_Servlet简介

1.web后端开发简介 Java企业级开发&#xff0c;也就是学习]avaEE(Enterprise Edition)版本,是一种结构和一套标准。在应用中开发的标准就是Servlet、jsp和JavaBean技术。jsp技术现在已基本处于淘汰状态&#xff0c;简单了解即可web后端开发&#xff0c;基于B/S模式的开发体系。…

【ArcGIS Pro微课1000例】0058:玩转NetCDF多维数据集

一、NetCDF介绍 NetCDF(network Common Data Form)网络通用数据格式是由美国大学大气研究协会(University Corporation for Atmospheric Research,UCAR)的Unidata项目科学家针对科学数据的特点开发的,是一种面向数组型并适于网络共享的数据的描述和编码标准。NetCDF广泛应…

从XML配置角度理解Spring AOP

1. Spring AOP与动态代理 1.1 Spring AOP和动态代理的关系 Spring AOP使用动态代理作为其主要机制来实现面向切面的编程。这种机制允许Spring在运行时动态地创建代理对象&#xff0c;这些代理对象包装了目标对象&#xff08;即业务组件&#xff09;&#xff0c;以便在调用目标对…

MySQL基础使用指南

难度就是价值所在。大家好&#xff0c;今天给大家分享一下关于MySQL的基础使用&#xff0c;MySQL 是一个流行的关系型数据库管理系统&#xff0c;被广泛应用于各种类型的应用程序开发中。本文中将介绍 MySQL 的基础使用方法&#xff0c;包括创建数据库、创建表格以及进行增删改…

污水设备远程监控

随着环保意识的日益增强&#xff0c;污水处理作为城市建设和环境保护的重要一环&#xff0c;越来越受到社会各界的关注。然而&#xff0c;传统的污水处理设备管理方式往往存在着效率低下、响应速度慢、维护成本高等问题。为了解决这些痛点&#xff0c;HiWoo Cloud平台凭借其强大…

【数据结构与算法 刷题系列】合并两个有序链表

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;数据结构与算法刷题系列&#xff08;C语言&#xff09; 目录 一、问题描述 二、解题思路详解 合并两个有序链表的思路 解题的步…

庙算兵棋推演AI开发初探(4-调用AI模型)

前面讲了如何开展编写规则脚本型Agent&#xff08;智能体&#xff09;的方法&#xff0c;现在探究一下如何调用知识型&#xff08;一般而言的训练出的模型&#xff09;智能体的方法。 这次调用的是庙算平台的demo&#xff08;网址见图&#xff09; 下载了“知识强化学习型”…

鸿蒙开发:【从TypeScript到ArkTS的适配规则】

从TypeScript到ArkTS的适配规则 ArkTS通过规范约束了TypeScript&#xff08;简称TS&#xff09;中过于灵活而影响开发正确性或者给运行时带来不必要额外开销的特性。本文罗列了所有在ArkTS中限制的TS特性&#xff0c;并提供了重构代码的建议。ArkTS保留了TS大部分的语法特性&a…

python数据分析——seaborn绘图1

参考资料&#xff1a;活用pandas库 matplotlib库是python的和兴绘图工具&#xff0c;而seaborn基于matplotlib创建&#xff0c;它为绘制统计图提供了更高级的接口&#xff0c;使得只用少量代码就能生成更美观、更复杂的可视化效果。 seaborn库和pandas以及其他pydata库&#xf…

【计算机网络】数据链路层 组帧 习题4

组帧 发送方根据一定的规则将网络层递交的分组封装成帧(也称为组帧)。 组帧时&#xff0c;既要加首部&#xff0c;也要加尾部&#xff0c;原因是&#xff0c;在网络信息中&#xff0c;帧是以最小单位传输的。所以接收方要正确地接收帧&#xff0c;就必须清楚该帧在一串比特串中…