Day924.自动化测试 -系统重构实战

自动化测试

Hi,我是阿昌,今天学习记录的是关于自动化测试的内容。

自动化测试是一个很容易产生“争议”的话题,也经常会有一些很有意思的问题。

  • 自动化测试不是应该由测试同学来编写吗,开发是不是没有必要学吧?
  • 之前一个自动化测试都没写过,怎么开始落地呢?
  • 编写自动化测试代码意味着要写更多的代码,这能带来什么好处呢?

在这个过程中的代码”


一、示例介绍

这个示例是一个登录的场景。

当用户在登录页面输入正确的账户和密码时,能正常跳转到登录界面,否则提示登录失败的信息。

下面是关键的代码。

  • 登录页面代码
public class LoginActivity extends AppCompatActivity {
    private LoginLogic loginLogic = new LoginLogic();

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        final EditText usernameEditText = findViewById(R.id.username);
        final EditText passwordEditText = findViewById(R.id.password);
        final Button loginButton = findViewById(R.id.login);
        loginButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                boolean success = loginLogic.login(LoginActivity.this,usernameEditText.getText().toString(),
                        passwordEditText.getText().toString());
                if (success) {
                    //登录成功跳转主界面
                    startActivity(new Intent(LoginActivity.this, MainActivity.class));
                } else {
                    //登录失败进行提示
                    Toast.makeText(LoginActivity.this, "login failed", Toast.LENGTH_LONG).show();
                }
            }
        });
    }
}
  • 登录逻辑代码
public class LoginLogic {
    public boolean login(Context context,String username, String password) {
        if (!isUserNameValid(username) || !isPasswordValid(password)) {
            return false;
        } else {
            //通过服务器判断账户及密码的有效性
            boolean result = checkFromServer(username, password);
            if (result) {
                //登录成功保持本地的信息
                SharedPreferencesUtils.put(context, username, password);
            }
            return result;
        }
    }
    // 为了进行演示,去除通过服务器鉴定的逻辑,当用户输入特定账号及密码为时则验证成功
    private static boolean checkFromServer(String username, String password) {
        if (username.equals("123@163.com") && password.equals("123456")) {
            return true;
        }
        return false;
    }
    private boolean isUserNameValid(String username) {
        if (username == null) {
            return false;
        }
        if (username.contains("@")) {
            return Patterns.EMAIL_ADDRESS.matcher(username).matches();
        } else {
            return !username.trim().isEmpty();
        }
    }
    private boolean isPasswordValid(String password) {
        return password != null && password.trim().length() > 5;
    }
}

注意,这里为了简化演示,将验证的逻辑写死在本地了。另外,账户密码有两个核心的验证规则。

  • 账户不能为空,需要符合邮箱规则。
  • 密码不能为空,长度需要超过 5 个字符。

二、搭建测试环境

当通过默认的编辑器创建新的项目工程时,编辑器会自动创建好测试的运行配置,一般无需修改。

如果要增加测试框架,就把测试框架的 Maven 坐标添加到对应的 dependencies 中即可。

Gradle 中的测试相关配置代码是后面这样。

android{
	  defaultConfig {
	    testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
	}
	dependencies {
	    testImplementation 'junit:junit:4.13.2'
	    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
	    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
	}
}

接下来,在默认的 /src/test 或 /src/androidTest 目录下编写用例。

注意,test 目录的用例运行不依赖于设备,androidTest 目录下的用例运行需要依赖设备。

在这里插入图片描述

一般来说,自动化测试分为小型、中型和大型三种,逐个看看这三种测试怎么落地。


三、小型自动化测试实践

小型测试是指单元测试,用于验证应用的行为,一次验证一个类。

在这个示例中,LoginLogic 主要承担的是登录逻辑,这里就以账户密码的验证逻辑为例,演示一下小型测试的编写,这两个逻辑的主要规则是这样。

  • 账户不能为空,需要符合邮箱规则。
  • 密码不能为空,长度需要超过 5 个字符。

接着,设计对应的测试用例。

注意,用例的设计应该包含正常和异常的验证场景,具体的测试场景是后面这样。

  • 输入大于 6 个字符长度的密码,验证成功。
  • 输入为 Null 的字符,验证失败。
  • 输入小于 5 个字符长度的密码,验证失败。
  • 输入等于 5 个字符长度的密码,验证失败。

下面新建一个 LoginLogicTest 的测试类,按照上述的测试场景编写用例。

这里我会采用 given(输入)、when(执行)、then(结果)的形式,让用例更加结构化,便于理解和维护。

具体的测试用例代码如下:


public class LoginLogicTest {
    @Test
    public void should_return_false_when_password_is_null() {
        LoginLogic loginLogic = new LoginLogic();
        String password = null;
        boolean result = loginLogic.isPasswordValid(password);
        Assert.assertFalse(result);
    }
    @Test
    public void should_return_false_when_password_length_is_less_than_5() {
        LoginLogic loginLogic = new LoginLogic();
        String password = "1234";
        boolean result = loginLogic.isPasswordValid(password);
        Assert.assertFalse(result);
    }
    @Test
    public void should_return_false_when_password_length_is_equal_5() {
        LoginLogic loginLogic = new LoginLogic();
        String password = "12345";
        boolean result = loginLogic.isPasswordValid(password);
        Assert.assertFalse(result);
    }
    @Test
    public void should_return_true_when_password_length_greater_than_5() {
        LoginLogic loginLogic = new LoginLogic();
        String password = "123456";
        boolean result = loginLogic.isPasswordValid(password);
        Assert.assertTrue(result);
    }
}

通过点击用例旁的运行箭头可以执行用例,如下图所示。

在这里插入图片描述

运行完就可以直接查看运行结果了,如下图所示。

在这里插入图片描述

可以看出小型测试的执行时间还是比较快的,4 个用例总共用了 7 ms。

此外,还可以用另一种方式执行测试用例:使用命令行./gradlew test,运行 test 目录下的测试用例,如下图所示。
在这里插入图片描述

执行完测试后,在 /build/reports/tests 下可以查看到对应的测试报告,报告截图如下所示,从中能得到每个用例具体的执行情况和执行时间。

在这里插入图片描述


四、中型自动化测试实践

中型测试是指集成测试,用于验证模块内堆栈级别之间的互动或相关模块之间的互动。

常用的测试框架有两种:Robolectric 和 Espresso。

在登录示例中,当 LoginLogic 的 login 方法被调用时,程序主逻辑首先会执行对账户名和密码的校验,接着通过服务器对账户密码的有效性做校验。

当登录成功时,程序主逻辑会通过 SharedPreferences 保存用户的信息,并在最后返回登录的状态。

从示例代码中可以看出,LoginActivity 类主要都是 UI 的操作,所以对该类主要覆盖的是 UI 相关的测试;

对于 LoginLogic 的类核心方法,login 主要负责整体的业务验证逻辑。

如何通过 Espresso 和 Robolectric 对这两个类进行中型自动化测试的覆盖。

1、Espresso 的使用

Espresso 是 Google 官方提供的界面测试框架,使用简洁且可靠。

它可以声明预期、交互和断言,不用直接访问底层应用的 Activity 和视图,可以防止测试不稳定,提高测试运行的速度。

根据用户 UI 上的主要操作,将覆盖以下两个主要的业务场景。

  • 用户输入正确的用户名(123@163.com)和密码(123456),点击登录按钮能成功跳转到登录界面。
  • 用户输入错误的用户名(123)和密码(456),点击登录按钮提示登录失败的 Toast。

根据测试场景,使用 Espresso 对 LoginActivity 设计的测试用例代码如下。

 public class LoginActivityTest {
    @Test
    public void should_start_main_activity_when_execute_login_given_valid_username_and_password() {
        ActivityScenario.launch(LoginActivity.class);
        onView(withId(R.id.username)).perform(typeText("123@163.com"));
        onView(withId(R.id.password)).perform(typeText("123456"));
        Intents.init();
        onView(withId(R.id.login)).perform(click());
        intended(allOf(
                toPackage("com.jkb.junbin.autotestdemo"),
                hasComponent(hasClassName(MainActivity.class.getName()))));
    }
    @Test
    public void should_show_failed_toast_when_execute_login_given_invalid_username_and_password() {
        ActivityScenario<LoginActivity> launch = ActivityScenario.launch(LoginActivity.class);
        onView(withId(R.id.username)).perform(typeText("123"));
        onView(withId(R.id.password)).perform(typeText("456"));
        onView(withId(R.id.login)).perform(click());
        View decorView = null;
        launch.onActivity(activity -> {
            activity.getWindow().getDecorView();
        });
        onView(withText("login failed")).inRoot(withDecorView(not(decorView))).check(matches(isDisplayed()));
    }
}

Espresso 提供的 API 能方便地进行元素的定位、执行操作和断言。

在上述两个用例中,用 onView 定位元素,用 perform 执行操作,用 check 进行断言。

如果想了解 Espresso 更多的操作 API,可以参考官网的介绍。

执行完上述用例后,运行结果是下图这样:

在这里插入图片描述

可以看到,这两个测试用例在模拟器中整体的运行时间在 5s 左右。

相比小型测试,中型测试耗时会更长。

并且用例运行需要依赖设备,这让运行测试的成本更高。

用例的执行过程如下图所示:

在这里插入图片描述


2、Robolectric 的使用

Robolectric 框架能为 Android 带来快速可靠的测试。

具体来说,依赖该框架的测试用例无须在真机或者模拟器上运行,在本地工作站上的 JVM 内完成运行即可,一般只需要几秒。

下面以 LoginLogic 的 login 方法为例,介绍一下 Robolectric 的使用。

这里需要覆盖下面三个主要的业务场景。

  • 传入空的字符串或者密码,返回失败。
  • 传入错误的账户及密码,返回失败。
  • 传入正确的账户及密码,返回成功,并且进行数据缓存。

后面是测试用例的代码。

@RunWith(RobolectricTestRunner.class)
public class LoginLoginMediumTest {
    private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
    @Test
    public void should_return_false_when_given_invalid_username_or_password() {
        LoginLogic loginLogic = new LoginLogic();
        boolean nullUserNameResult = loginLogic.login(mContext, null, "123");
        Assert.assertFalse(nullUserNameResult);
        boolean nullPasswordResult = loginLogic.login(mContext, "123", null);
        Assert.assertFalse(nullPasswordResult);
    }
    @Test
    public void should_return_false_when_given_error_username_and_password() {
        //验证错误的账户及密码
        boolean result = new LoginLogic().login(mContext, "123", "456");
        Assert.assertFalse(result);
    }
    @Test
    public void should_return_true_when_given_correct_username_and_password() {
        String username = "123@163.com";
        String password = "123456";
        //验证正确的账户及密码
        boolean result = new LoginLogic().login(mContext, username, password);
        Assert.assertTrue(result);
        //验证存在缓存信息
        String cachePassword = (String) SharedPreferencesUtils.get(mContext, username, "");
        Assert.assertEquals(password, cachePassword);
    }
}

关于 Robolectric 的 API,可以参考官网的介绍。

执行上述测试用例后,运行结果如下图。

在这里插入图片描述

从结果可以看出,用 Robolectric 框架进行测试,用例的执行时间在毫秒到秒之间。其中第二个用例的执行耗时超过 2s,是因为启动 Robolectric 框架需要一定的时间。同时我们也能体会到 Robolectric 的核心优势:无须依赖设备,可以快速在本地的 JVM 进行验证,得到快速的反馈。


五、大型自动化测试实践

大型测试是指端到端测试,用于验证跨越了应用的多个模块的用户操作流程。

前面介绍的 Espresso 和 Robolectric 主要是针对单个页面的测试场景,在实际的应用业务场景中,还有涉及跨应用和系统 UI 交互的场景。

通常会用 UI Automator 完成大型测试。

UI Automator 是一个界面测试框架,适用于整个系统和多个已安装应用间的跨应用功能界面测试。

它提供了一组 API,用于构建在用户应用和系统应用上执行交互的界面测试。

通过 UI Automator API,可以在测试设备中执行打开“设置”菜单或应用启动器等操作。

相比 Espresso 和 Robolectric 编写的白盒测试用例,UI Automator 测试框架非常适合编写黑盒式自动化测试,此类测试的测试代码不依赖于目标应用的内部实现细节。下面继续对登录示例进行完整的功能测试。

完整的用户测试场景是这样的:

用户在手机的任意界面,返回桌面启动测试的应用登录界面,然后输入正确的用户名和密码,成功跳转到主界面,验证主界面上显示的用户信息是否正确。这个用户场景会涉及到多个应用,其中包括了桌面和目标测试应用。

另外,应用内还会涉及到多个页面,主要是登录界面和主界面。

现在使用 UI Automator 框架进行测试,用例代码是这样。


@RunWith(AndroidJUnit4.class)
public class SmellTest {
    private static final String BASIC_SAMPLE_PACKAGE
            = "com.example.sample";
    private static final int LAUNCH_TIMEOUT = 5000;
    private UiDevice mDevice;
    @Before
    public void startActivityFromHomeScreen() {
        // 初始化UiDevice
        mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
        // 回到主界面
        mDevice.pressHome();
        // 等待launcher
        final String launcherPackage = mDevice.getLauncherPackageName();
        assertThat(launcherPackage, notNullValue());
        mDevice.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)),
                LAUNCH_TIMEOUT);
        // 启动目标APP
        Context context = ApplicationProvider.getApplicationContext();
        final Intent intent = context.getPackageManager()
                .getLaunchIntentForPackage(BASIC_SAMPLE_PACKAGE);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
        context.startActivity(intent);
        // 等待应用启动
        mDevice.wait(Until.hasObject(By.pkg(BASIC_SAMPLE_PACKAGE).depth(0)),
                LAUNCH_TIMEOUT);
    }
    //账户密码登录成功后主界面显示用户名
    @Test
    public void should_show_username_in_main_activity_when_login_success() {
        //输入账户名
        mDevice.findObject(By.res(BASIC_SAMPLE_PACKAGE, "username"))
                .setText("123");
        //输入密码
        mDevice.findObject(By.res(BASIC_SAMPLE_PACKAGE, "password"))
                .setText("123");
        //点击登录
        mDevice.findObject(By.res(BASIC_SAMPLE_PACKAGE, "login"))
                .click();
        //验证主界面上显示用户名信息
        UiObject2 text = mDevice
                .wait(Until.findObject(By.res(BASIC_SAMPLE_PACKAGE, "text")),
                        500);
        assertEquals(text.getText(), "123");
    }
}

UI Automator 测试框架提供了一个 UiDevice 类,用于在运行目标应用的设备上访问和执行操作。

通过调用 findObject 方法,可以定位到元素和执行操作。

关于 UI Automator 更多的 API 使用,你可以参考官网文档 。

执行上述测试用例后,运行结果如下图所示:

在这里插入图片描述

下面这个动图展示了用例的执行过程,可以对比一下,这个过程如果用手工执行需要多久。

在这里插入图片描述

通过执行结果可知,该用例的执行时间实际为 9s,比中小型测试的执行时间更长,并且需要依赖真机或模拟器。

不过,该用例基本都是模拟用户对界面的点击操作,更贴近实际用户的真实使用场景。


六、总结

  • 小型测试能够快速帮验证代码中的核心逻辑和算法,通常使用的是 Junit 或者 Robolectric 等测试框架;
  • 中型测试能够帮验证代码中的一些核心组件交互流程,通常会用 Espresso 或者 Robolectric 等框架来完成;
  • 大型测试则能帮验证端到端的用户使用场景,通常使用的是 UIAutomator 或者 Appium 等框架。

在这里插入图片描述

重新思考一下开头的三个问题:

  • 第一个是测试由谁来写的问题。中小型的测试大部分都是根据代码设计来编写的。编写者需要了解原来代码的设计,精确到各个方法以及方法内部的条件分支和异常处理。所以,中小型测试应该由开发人员来编写。另外,也鼓励开发人员参与到大型端到端自动化测试的编写中。因为只有开发代码和测试代码一起共同维护,成本才是最低的。
  • 第二个问题是:之前一个自动化测试都没写过,怎么开始落地?对于开发人员来说,编写自动化测试用例的难度其实比功能开发设计还简单。
  • 第三个问题是关于自动化测试价值的问题。这里有一个前提:不认为开发完代码就意味着结束,结束应该是在有足够的质量保证的前提下。

所以,自动化测试是应用开发过程中不可或缺的一部分。通过持续运行测试,可以在发布版本之前验证其正确性、功能行为和易用性。

具体来讲,自动化测试给开发同学带来的帮助有这样三点:

  1. 自动化测试能提供多样化的编译调试。通常测试的运行时间在毫秒至秒之间,有助于提高我们编译调试的效率。
  2. 自动化测试能加强开发代码自测,帮我们快速获得故障反馈。通常我们在本地编写完代码后,就可以马上运行测试,检查功能是否正确。这样的好处是能在开发早期尽早发现问题。
  3. 自动化测试还能提供更安全的代码重构,当有了自动化测试这个安全守护网,可以放心地优化代码,不必担心引发新的问题,也可以尽可能避免其他人乱改代码破坏原有的逻辑。因为一旦有修改破坏了之前的自动化测试用例,CI 门禁就会立即检查出来,避免代码合入。

虽然自动化测试可以提升开发的效率和质量,但对于遗留系统来说,还有另外一个非常棘手的问题,那就是代码可测试性低。


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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Lesson 9.1 集成学习的三大关键领域、Bagging 方法的基本思想和 RandomForestRegressor 的实现

文章目录一、 集成学习的三大关键领域二、Bagging 方法的基本思想三、RandomForestRegressor 的实现在开始学习之前&#xff0c;先导入我们需要的库&#xff0c;并查看库的版本。 import numpy as np import pandas as pd import sklearn import matplotlib as mlp import sea…

【MySQL速通篇001】5000字超详细介绍MySQL部分重要知识点

&#x1f340; 写在前面 这篇5000多字博客也花了我几天的时间&#x1f602;&#xff0c;主要是我对MySQL一部分重要知识点的理解【后面当然还会写博客补充噻&#xff0c;欢迎关注我哟】&#xff0c;当然这篇文章可能也会有不恰当的地方【毕竟也写了这么多字&#xff0c;错别字可…

Linux常用命令——ldconfig命令

在线Linux命令查询工具 ldconfig 动态链接库管理命令 补充说明 ldconfig命令的用途主要是在默认搜寻目录/lib和/usr/lib以及动态库配置文件/etc/ld.so.conf内所列的目录下&#xff0c;搜索出可共享的动态链接库&#xff08;格式如lib*.so*&#xff09;,进而创建出动态装入程…

python框架有哪些,常用的python框架代码

Python的应用已经相当广泛了&#xff0c;可以做很多事情&#xff0c;而 Python本身就是一个应用程序&#xff0c;我们也可以说 Python是一个高级语言。由于 Python有很多包&#xff0c;所以我们不能把所有的 Python包都了解一下&#xff0c;也不能把所有的包都读一遍&#xff0…

35岁大龄程序员职业转型规划

35岁大龄程序员职业转型规划 I. 引言 在当今快速发展的IT行业&#xff0c;程序员这个职业变得越来越重要。但是&#xff0c;对于35岁以上的程序员来说&#xff0c;面临的职业困境也越来越严峻。他们不仅面临着技术的迭代更新&#xff0c;而且还面临着市场的竞争和年轻程序员的…

碳交易机制下考虑需求响应的综合能源系统优化运行

说明书 资源链接&#xff1a;https://download.csdn.net/download/qq_50594161/87610405https://download.csdn.net/download/qq_50594161/87610405 https://download.csdn.net/download/qq_50594161/87607550https://download.csdn.net/download/qq_50594161/87607550https:…

String源码深度刨析

前言 我们将从源码角度深度分析特点&#xff0c;来提升对他们的了解以及设计。 String、StringBuilder、StringBuffer的常见面试题及四大区别可以参考&#xff1a;String、StringBuilder、StringBuffer的四大区别解析 String public final class Stringimplements java.io.Se…

在JavaScript开发中,关于var、let和const你需要知道这些

文章目录&#x1f4cb;前言&#x1f3af;var&#x1f3af;let&#x1f3af;const&#x1f3af;相关面试题&#x1f4dd;总结&#x1f4cb;前言 计算机编程中最基本的一个就是使用名字&#xff08;或标识符&#xff09;表示值。绑定名字和值为我们提供了一种引用值和在程序中使…

阿里云ECS大测评

本篇博客是阿里云 ECS实例测评&#xff0c;这里采用阿里的C5.large计算型实例 本次对云服务器ECS产品能力的体验和建议&#xff0c;可帮助其他用户选用云服务,使用云服务器ECS创建应用场景测评 首先对实例的硬件(CPU&#xff0c;内存&#xff0c;磁盘)进行测试&#xff0c;采…

数智未来·持续创新 | 易趋受邀出席DSMC 2023中国制造业数智峰会

3月17日-18日&#xff0c;以“数智未来持续创新”为主题的DSMC 2023中国制造业数智峰会在长沙成功召开。由中国通信工业协会指导&#xff0c;信息侠主办&#xff0c;湖南省数字经济促进会、浙江省数字经济联合会、安徽省首席信息官协会联合支持。鉴于易趋项目管理软件在制造行业…

数据出境是什么意思?我国数据出境合规要求是什么?

随着经济全球化深入以及云计算等技术的发展&#xff0c;数据在全球范围跨境流动。数据跨境在促进经济增长、加速创新的同时&#xff0c;对数据主权、数据权属、个人信息保护等一系列问题逐渐浮出水面。今天我们就先来了解一下数据出境是什么意思&#xff1f;我国数据出境合规要…

校招失败后,在外包公司熬了 2 年终于进了字节跳动,竭尽全力....

其实两年前校招的时候就往字节投了一次简历&#xff0c;结果很明显凉了&#xff0c;随后这个理想就被暂时放下了&#xff0c;但是这个种子一直埋在心里这两年除了工作以外&#xff0c;也会坚持写博客&#xff0c;也因此结识了很多优秀的小伙伴&#xff0c;从他们身上学到了特别…

我们现在怎样做父亲

离开了中学的课本后再没读过鲁迅的文章&#xff0c;今年想再读鲁迅。《我们现在怎样做父亲》这个题目本是鲁迅《坟》杂文集中的一篇&#xff0c;怎样做父亲是个人生大命题&#xff0c;毕竟一生中在这件事上不太能靠积累经验来熟练。所以&#xff0c;在做父亲这件事上不是一个技…

【BIM+GIS】BIM+GIS融合的意义与应用价值

文章目录 一、BIM、GIS解析二、BIM+GIS应用价值三、BIM+GIS应用四、BIM和GIS融合面临什么问题?一、BIM、GIS解析 BIM(建筑信息模型)是一种应用于工程设计、建造、管理的数据化工具,主要针对于微观单体建筑的应用,能够将建筑工程项目的各项相关信息数据集成在一个模型中,…

基于springboot实现生鲜超市管理的设计与实现演示【附项目源码】分享

基于springboot实现生鲜超市管理的设计与实现演示B/S的系统是通过能上网的电脑就可以使用&#xff0c;它最大的优点是不需要安装专门的软件&#xff0c;首先浏览器向服务器发出请求&#xff0c;然后服务器处理请求把信息再返回给浏览器。不需要再次对数据进行存取与计算数据&am…

行业分析| anyRTC智慧视频监控的应用

智慧视频监控是安全防范系统的重要组成部分&#xff0c;不仅可以达到一般视频监控系统的远程控制监控、视频回看&#xff0c;满足治安管理、城市管理、交通管理、应急指挥等需求&#xff0c;还具备防盗报警系统的预警信息作用&#xff0c;在预防、发现、控制、打击违法犯罪&…

零入门kubernetes网络实战-25->基于tap虚拟网络设备的测试用例以及协议栈封装解析介绍(helloworld级别)

《零入门kubernetes网络实战》视频专栏地址 https://www.ixigua.com/7193641905282875942 本篇文章视频地址(稍后上传) 本篇文章主要是分享一下tap虚拟网络设备。 创建tap网络设备的方式跟tun完全一样&#xff0c;只需要将类型改为tap即可。 这里不再占用篇幅介绍了。 1、本…

Matlab与ROS(1/2)---Message(三)

0. 简介 消息是ROS中交换数据的主要容器。主题和服务使用消息在节点之间传输数据。为了标识其数据结构&#xff0c;每条消息都有一个消息类型。例如&#xff0c;来自激光扫描仪的传感器数据通常以sensor_msgs/LaserScan类型的消息发送。每种消息类型标识消息中包含的数据元素。…

【Java Web】002 -- JS Vue快速入门

目录 一、JS快速入门 1、什么是JavaScript? 2、JS引入方式 ①、示例代码 3、JS基础语法 ①、书写语法 ②、变量 ③、数据类型 ④、运算符 ⑤、流程控制语句 4、JS函数 ①、第一种函数定义方式 function funcName(参数1&#xff0c;……) ②、第二种函数定义方式 var funcName …

Chatgpt4来了,测试小姐姐实测,在失业的边缘疯狂试探~

GPT-4是OpenAI于2023年3月发布的最新人工智能模型&#xff0c;它是继GPT-3.5之后的又一次重大突破。它的核心技术是基于Transformer的自回归语言模型&#xff0c;它使用了大量的无标注数据进行预训练&#xff0c;学习了自然语言和其他模态之间的通用表示和关系。我们今天来看看…
最新文章