秒级启动的集成测试框架

news2024/11/17 4:43:13

d67c6c222b3ffd5c01ea35197e78b935.gif

本文介绍了一种秒级启动的集成测试框架,使用该框架可以方便的修改和完善测试用例,使得测试用例成为测试过程的产物。

49fe59e18f3c174487c010636cf69581.png

背景

传统的单元测试,测试的范围往往非常有限,常常覆盖的是一些工具类、静态方法或者较为底层纯粹的类实现,但是一般整个应用代码是比较复杂的,存在不同分层。在DDD中,一般包括:防腐层、领域层、服务层、应用层。越到上层的类其依赖关系越复杂,这些上层的类对象往往不太适合用单测来覆盖。

但是集成测试的启动速度较慢,随着工程的增大,启动速度会越来越慢。这就导致修改和 Debug 测试用例变得非常耗时,一部分人甚至会放弃写测试,而通过系统界面或手动接口测试(Postman等)方式来保证功能正确性。但这样做之后,在未来重构或者开发新需求时很难完整回归已有功能。完整回归已有功能将是每次发布的负担,回归遗漏可能引发线上故障。

测试的执行速度至关重要,这往往会影响人们是否自觉地完成测试覆盖。在实际开发过程中,我们可能需要本地反复执行某些测试用例,并不断修改,如果应用能在10秒内启动完成,那么开发是高效的,否则可能会让人试图通过其他方式来测试功能的正确性。

8ed98c7294a0471ba37bf819b9c086e9.png

解决方案

针对这一问题,一个比较直观的想法是让集成测试执行速度和单元测试一个数量级。

一般的Java工程都使用了Spring框架,其应用启动慢,往往是一些涉及网络通信的Bean的初始化过程比较耗时,比如RPC框架、缓存、数据库等,这些中间件的Bean对象初始化都需要和外部建立网络连接,等待数据推送等,有的涉及多次网络通信,将这些Bean完全Mock掉,可大大加快应用启动速度。

很直观的解法是将这些耗时的Bean替换为MockBean,有两种方式:

  1. 使用Spring的@Primary注解,并禁止耗时Bean的初始化

  2. Mock Spring容器

第一种方式的困难在于Bean初始化的方式多种多样,有的在init方法中,有的通过BeanPostProcessor动态创建,要精准的禁止这类逻辑的执行是比较困难的。

第二种方式则是自己实现一个Mock的Spring框架,基于约定的方式实现Mock对象的自动加载,以及普通Bean对象和Spring一样的方式初始化,从而实现应用的快速启动。

f71307cf29075aac9e4c7d0e4b9ac715.png

Mock Spring容器

我们基于第二种思路实现了Mock的Spring容器,但仅仅实现其了基础功能,因为通常我们的工程没有用到Spring比较复杂的能力(大多数工程都是如此)。工程中采用约定大于配置的方式,可以减少Mock的工作量。在Mock Spring框架时其实最需要的是自动构建依赖树的能力,即根据当前Bean对象的依赖关系,按需动态创建一系列其关联的Bean。而且对于外部依赖,可以基于某种约定来优先加载Mock对象,保证所有对象Bean创建是按需的,且不需要网络等待,这样可以实现对象依赖树秒级创建,集测秒级启动。其他特殊的功能可以通过其他方式来绕过,本方案也在不断完善中。从实践来看,启动大约需要1-10秒。

本方案的基本思路如下:

outside_default.png

  1. 记录接口与实现类的关系,是为了根据接口查找实现类,实现按需加载

  2. Mock对象相当于加了@Primary注解,在同类型中会优先被注入,保证覆盖中间件等外部依赖Bean对象

  3. 初始化基础Bean对象,是优先加载@Configuration修饰的类中定义的Bean对象

以下是工程中定义的扫包代码片段,每个测试执行Bean都是按需加载,不会将所有Bean全部创建。

// 确定扫包路径,扫包规则,只有@Component等注解修饰的类才会被注册为Bean
Predicate<Class<?>> classFilter = clazz -> !clazz.getSimpleName().endsWith("Test");
Set<Class<?>> beanClasses = ClassScanUtil.scanPackages(
    classFilter, 
    // 应用包路径
    "com.nbf.gateway",
);

以上的应用包路径和Spring Boot应用的扫包路径一致。

以下是Bean初始化简化后的逻辑:

protected <T> T getBeanObject(Class<T> requiredType) {
    // 首先查找Bean的真实类型
    Set<Class<?>> beanClassList = implClassMap.get(requiredType);
    int size = CollectionUtils.size(beanClassList);
    Class<?> beanClass;
    if (size > 1) {
        throw new BusinessException(CommonErrorCode.UNKNOWN_EXCEPTION, requiredType.getName() + "包含多个实现类");
    } else if (size == 1){
        beanClass = beanClassList.iterator().next();
    } else {
        beanClass = requiredType;
    }


    T bean;
    Constructor<?> constructor = ListUtils.firstElementOf(beanClass.getConstructors());
    if (null == constructor) {
        throw new BusinessException(new Exception("class: " + beanClass.getName() + " 构造器为null."));
    }
    Class<?>[] classes = constructor.getParameterTypes();
    Object[] params = new Object[classes.length];
    for (int i = 0; i < classes.length; ++i) {
        params[i] = this.getBean(classes[i]);
    }


    try {
        //noinspection unchecked
        bean = (T)constructor.newInstance(params);
    } catch (Exception e) {
        throw new BusinessException(e);
    }


    // 处理@Autowired@和Resource
    this.processMemberBean(bean);


    // 执行初始化逻辑
    Method[] methods = beanClass.getDeclaredMethods();
    for (Method method : methods) {
        if (method.getAnnotation(PostConstruct.class) != null) {
            try {
                method.invoke(bean);
            } catch (Exception e) {
                throw new BusinessException(e);
            }
        }
    }


    return bean;
}

以下是在测试类中获取Bean对象的方法,类似@Autowired。MockApplicationContext即是我们Mock Spring容器类的名字。

public class GroupVersionRepositoryUnitTest {


    private final static GroupTunnel groupTunnel = MockApplicationContext.getBeanOfType(GroupTunnel.class);
}

ffa1c88b9f9aaa6441fd9649ddb0eab1.png

Mock数据库

基于以上的思路,我们还需要Mock数据库、外部依赖、中间件。下面小节将重点介绍Mock数据库的一种实现。

  第一层Mock:Example

Mock数据库,最直观的想法就是使用HashMap,也在很多的工程中有用到。看到很多的实现是,在测试中,我们调用DAO层相关代码替换为在HashMap中操作对应数据。这样的实现有两个比较明显的缺点:

  1. 每个数据操作都需要手动翻译为对Map的数据操作,费时费力,容易存在翻译偏差

  2. 每次翻译过程,需要case by case处理

当然能做到这种替换,还有个前提是,我们将DAO层的操作都统一封装到了一层,这样才能实现使用Mock对象替换的方式实现整体替换。

  • 解决方案

目前大多数的Java工程都使用Mybatis,解决思路是实现一套类似Mybatis的查询工具类,让写Mock实现和真实的DAO层方法调用类似,让翻译过程尽量简单直观。

为此,我们定义了MockExample对应Mybatis的查询参数Param,MockCriteria对应Criteria(用户暂时不感知),MockTunnelUtil对应DAO,Mock对象和Mybatis真实对象映射关系如下图所示:

outside_default.png

MockExample、MockCiteria都以DO(Data Object)作为泛型参数,用于指定操作哪张表

原始某段真实Mybatis查询代码如下:

@Override
public ApiInfoDO get(String apiInfoId) {    
    ApiInfoQuery query = new ApiInfoQuery();
    query.createCriteria()
       .andApiInfoIdEqualTo(apiInfoId);
    List<ApiInfoDO> apiInfoDOList = apiInfoDao.selectByQueryWithBLOBs(query);
    return firstElementOf(apiInfoDOList);
}
翻译后的Mock实现如下:
@Override
public ApiInfoDO get(String apiInfoId) {    
    MockExample<SlsJobDO> example = new MockExample<>();
    example.createCriteria()   
         .andEqualTo(apiInfoId, ApiInfoDO::getApiInfoId);
    List<ApiInfoDO> apiInfoDOList = MockTunnelUtil.selectByExample(this, example);
    return firstElementOf(apiInfoDOList);
}

这里有三点需要对照修改:

  1. 创建查询参数,比如:ApiInfoQuery,需要替换为创建MockExample

  2. 查询条件增加属性的方法引用,比如:ApiInfoDO::getApiInfoId

  3. 使用MockTunnelUtil代替DAO进行查询

MockExample实现主要使用了断言Predicate,以下是In条件的实现: 

public <F> MockCriteria<DO> andIn(List<F> field, Function<DO, F> getter) {
    Predicate<DO> predicate = obj -> field.contains(getter.apply(obj));
    return this.addCondition(predicate);
}

  第二层Mock:DAO

上述实现大大简化了Mock 数据库的难度,但仍然存在如下缺点:

  1. 查询 & 修改逻辑变更,Mock逻辑需要跟着变更,存在比较严重的一致性问题

    1. 很多时候会忘记修改,导致Mock结果和实际运行不一致

  1. 如果Mybatis调用逻辑散落各处,没有统一收敛到一层,则Mock比较困难

为此我们需要将Mock的层再向下降一层,直接Mock DAO,在测试中调用DAO,则会调用到我们的Mock实现,做到Mock实现不依赖业务代码变化。

  • 思路

一个比较直观的解决方案是实现一套通用逻辑,将Mybatis的Param直接转换为MockExample,则不需要再手动去写那段翻译逻辑,即可自动将业务实现转换为Mock实现。

  • 难点

这里的一个难点是Mybatis生成的查询Criteria缺乏公共的父类,每个方法的名称都是和用户参数名相关的,比如andApiInfoIdEqualTo。

  • 解决方案

通过分析,我们可以发现,其实问题的根源在于Mybatis的Example、Criteria、Criterion缺乏公共的接口或基类。为了解决这个问题,我们定义了SqlParam、SqlCriterion、SqlCriteria,用来抽象这三个层次的对象。以下是这三个类的定义:

public interface SqlCriteria<Criterion> {


    List<Criterion> getCriteria();
}
public interface SqlCriterion {


    String getCondition();


    Object getValue();


    Object getSecondValue();
}
public interface SqlParam<Criteria> {


    /**
     * 是否分页
     */
    boolean isPage();


    /**
     * 获取页码(1开始)
     */
    Integer getPageIndex();


    /**
     * 获取页大小
     */
    Integer getPageSize();


    /**
     * 获取排序语句
     */
    String getOrderByClause();


    /**
     * 获取查询条件
     */
    List<Criteria> getOredCriteria();
}

我们在Mock DAO层的实现中,定义不同DO的这三个接口实现即可,这样我们就可以基于这些信息将Mybatis Param转换为MockExample了。以下是Mock DAO实现的样例:

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class MockApiInfoDaoImpl
extends AbstractMockDaoImpl<ApiInfoDO, ApiInfoQuery, Criteria, Criterion>
implements ApiInfoDao {


    private static final MockApiInfoDaoImpl INSTANCE = new MockApiInfoDaoImpl();


    public static MockApiInfoDaoImpl getInstance() {
        return INSTANCE;
    }


    @Override
    public Function<ApiInfoDO, Object> getIdGetter() {
        return ApiInfoDO::getId;
    }


    @Override
    protected SqlParam<Criteria> getSqlParam(ApiInfoQuery query) {
        return new SqlParam<Criteria>() {
            @Override
            public boolean isPage() {
                return query.getRows() != null;
            }
            @Override
            public Integer getPageIndex() {
                return query.getOffset() / query.getRows() + 1;
            }
            @Override
            public Integer getPageSize() {
                return query.getRows();
            }


            @Override
            public String getOrderByClause() {
                return query.getOrderByClause();
            }


            @Override
            public List<Criteria> getOredCriteria() {
                return query.getOredCriteria();
            }
        };
    }


    @Override
    protected SqlCriteria<Criterion> getSqlCriteria(Criteria criteria) {
        return criteria::getCriteria;
    }


    @Override
    protected SqlCriterion getSqlCriterion(Criterion criterion) {
        return new SqlCriterion() {
            @Override
            public String getCondition() {
                return criterion.getCondition();
            }
            @Override
            public Object getValue() {
                return criterion.getValue();
            }
            @Override
            public Object getSecondValue() {
                return criterion.getSecondValue();
            }
        };
    }
}

以下是Mybatis查询条件Param转换为MockExample的转换逻辑:

protected MockExample<DO> convert(Param param) {
    MockExample<DO> example = new MockExample<>();


    // 设置条件
    boolean first = true;
    SqlParam<Criteria> sqlParam = getSqlParam(param);
    for (Criteria criteria : sqlParam.getOredCriteria()) {
        MockCriteria<DO> mockCriteria;
        if (first) {
            mockCriteria = example.createCriteria();
            first = false;
        } else {
            mockCriteria = example.or();
        }


        SqlCriteria<Criterion> sqlCriteria = getSqlCriteria(criteria);
        for (Criterion criterion : sqlCriteria.getCriteria()) {
            SqlCriterion sqlCriterion = getSqlCriterion(criterion);
            String condition = sqlCriterion.getCondition();
            int index = condition.indexOf(NbfSymbolConstants.SPACE);
            String property = NbfStringUtils.underLineToCamel(condition.substring(0, index).trim());
            String getterMethod = "get" + StringUtils.capitalize(property);
            String operator = condition.substring(index + 1).trim();


            // 添加属性
            List<Object> valueList = new ArrayList<>();
            Object value = sqlCriterion.getValue();
            if (value != null) {
                valueList.add(value);
            }
            Object secondValue = sqlCriterion.getSecondValue();
            if (secondValue != null) {
                valueList.add(secondValue);
            }


            Function<DO, Object> getter = obj -> {
                try {
                    Method method = getDoClass().getDeclaredMethod(getterMethod);
                    return method.invoke(obj);
                } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                    throw new BusinessException(e);
                }
            };


            // 操作符
            OperatorEnum operatorEnum = OperatorEnum.of(operator);
            mockCriteria.and(operatorEnum, getter, valueList);
        }
    }


    // 设置分页
    if (sqlParam.isPage()) {
        example.setPagination(sqlParam.getPageIndex(), sqlParam.getPageSize());
    }
    // 设置排序
    String orderByClause = sqlParam.getOrderByClause();
    if (StringUtils.isNotBlank(orderByClause)) {
        example.setOrderByClause(orderByClause);
    }
    return example;
}

不同DAO的Mock实现基本类似,只要拷贝并修改泛型参数即可。

在上述的DAO层Mock实现MockApiInfoDaoImpl中,继承了基类AbstractMockDaoImpl,这是由于同一套Mybatis插件生成的DAO接口方法类似,我们可以定义一个抽象类实现这些接口,DAO层Mock实现继承该抽象类,则不需要再去实现DAO层的接口了。其部分实现如下:

public abstract class AbstractMockDaoImpl<DO, Param, Criteria, Criterion>
    extends AbstractMockTunnelImpl<DO, Param, Criteria, Criterion> {


    public long countByQuery(Param param) {
        MockExample<DO> example = this.convert(param);
        return MockTunnelUtil.countByExample(this, example);
    }


    public int deleteByQuery(Param param) {
        MockExample<DO> example = this.convert(param);
        return MockTunnelUtil.deleteByExample(this, example);
    }
}

  小结

这里我们介绍了完整Mock数据库的一种思路,这种Mock实现仍然存在一些缺陷:

  1. 暂时无法支持事务

  2. 无法实现数据库的特性,比如必填校验等

以上两点都可以在未来支持。它的优点也是比较明显的:

  1. 执行速度快

  2. 不依赖数据库已有数据,不会受数据库已有数据的影响,不会造成脏数据


e05551223dacb6ae6d329cf911d4e3ef.png

造数据

基于以上的两个基础设施:Mock Spring容器、Mock数据库,可以使得写测试变得更加容易,对于测试中比较费时费力的造数据,也可以更加快速的实现。

在日常的集成测试中,造数据是一个比较麻烦的事情,虽然我们使用测试的RollBack机制,可以保证对现有数据无污染。但是在某些依赖已有数据的情况,则比较麻烦。如果预先造了这样的数据,可能被其他人无意修改。而且在一些查询场景,已有数据可能对测试执行结果造成干扰。

有了这套完整的Mock工具,我们可以使用线上数据进行测试,更加快捷的回归 & 发现问题。

  造数据的几种方式

常见造数据的两种方式:

  1. 通过属性设值。即各种New对象,Set属性

  2. 通过JSON解析文件

第一种方式的开发维护成本较高,尤其是构建大对象时。

造数据的来源,也有两种方式:

  1. 通过DO(Data Object)去造数据,即把数据直接插入数据库

  2. 通过领域对象造数据,调用Repository去创建数据

根据数据来源也分为日常、线上。显然线上数据质量远高于日常,更容易发现问题。

  方案

将数据库查询到的线上(日常)库数据,转换为领域对象,有比较大的转换成本;如果转换为DO对象,也有一定成本,但成本较低。所以本方案采用了后一种方案。

但是把数据库查询出来的数据拷贝出来,直接转换为DO所需要的JSON格式文件,也有较高的成本,所以这里直接使用字段拆分解析的方式读取其内容,再反射设值到DO对象中。这里有个问题,数据库查询出来的字段顺序可能和DO中字段定义顺序不一致,所以需要有个元信息文件,用于指定数据库查询出来数据的字段顺序。

以下是本测试框架的 TableLoadUtil#Load 方法,用于将数据库查询出来的数据转换为DO数据。第一个参数对应的文件内容是数据库查询出来的各行数据,第二个参数对应的文件内容是DO的字段顺序。

public class TableLoadUtil {


    /**
     * 根据元信息定义加载数据
     * @param fileName 表数据文件路径
     * @param metadata 表字段顺序元信息定义文件路径
     * @param clazz DO类
     */
    public static <T> List<T> load(String fileName, String metadata, Class<T> clazz);
}

这样就实现了通过数据库数据直接快速造数据的目的,推荐使用线上数据(但对敏感数据需要脱敏),保证测试质量。

我们需要将测试涉及到的表的少量行数据(不需要全量)查询出来,并添加到对应文件中。对于复杂场景,这种造数据的方式显然更加高效。而且可以做到每个测试的数据都是重新初始化的,互相隔离不影响。这些数据还可以在不同测试间共享。不需要启动完整的Spring容器,只需要启动Mock的Spring容器,保证测试启动(无论工程多么庞大)在10秒以内,大部分测试启动在3秒以内。

比较通用的做法是在测试基类里做数据的初始化和清理,具体的测试类继承该类,以下是一个线上应用的测试基类:

public class DataPrepareBaseOnTable {


    /**
     * 准备数据
     */
    @BeforeClass
    public static void prepare() {
        cleanUp();
        initData(BackendServiceConfigDO.class, MockBackendServiceConfigMapperImpl.getInstance()::insert);
    }


    /**
     * 清理数据
     */
    @AfterClass
    public static void cleanUp() {
        MockBackendServiceConfigMapperImpl.getInstance().getCache().clear();
    }


    /**
     * 初始化数据
     */
    public static <DO> void initData(Class<DO> clazz, Consumer<DO> insertMethod) {
        String objName = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() -2);
        List<DO> doList = TableLoadUtil.load(
            "table/" + objName + "/" + objName + ".txt",
            "table/Metadata/" + objName + ".txt",
            clazz);
        NbfListUtils.forEach(doList, insertMethod);
    }
}

这里可以设置为整个测试类初始化 & 清理一次数据,也可以设置为单个测试初始化一次(推荐)。

造数据的流程大致如下:

outside_default.png

  小结

上述小节介绍了一种,通过直接将数据库查询到的数据转为测试准备数据的方案,该方案的优点如下:

  1. 构造数据足够简单快捷

  2. 避免了测试数据被外部意外修改,数据变动过程可以通过git记录查到

  3. 各个测试之间测试数据隔离

  4. 测试执行速度快,绝大部分测试启动在10秒以内

  5. 测试数据质量较高,可完全使用线上数据

  6. 测试数据相对干净、纯粹,避免测试环境很多脏数据导致测试不稳定

以上的优点主要是相对于集成测试 + @RollBack的传统测试方式

该方案的成本主要在于:创建字段顺序文件。但对于每张表是一次性的,后续增加字段只需追加新增字段即可

可能的不足是:

  1. 如果数据库新增字段,可能需要更新对应表文件

  • 如果测试不涉及新增字段,大部分是向前兼容的

当数据量较大时,管理表文件可能有一定成本

    • 推荐使用文件行排序,避免插入重复数据,且要使得数据尽量少,仅包含测试需要的行数据

40bac6d8cb8eb47b9c743c6ea4cad7fd.png

Mock中间件

mock中间件相对较为简单,这里仅把我们的方案做简单介绍。

  Mock RPC框架

只需要创建Mock对象,记录测试case情况下,日常或线上该接口的出参即可,推荐用JSON文件保存,让Mock方法根据入参加载对应的出参JSON文件作为结果返回。


  Mock Redis

也通过HashMap进行Mock即可,实现复杂度取决于需要覆盖其功能的完整性。

  小结

本文介绍了一种秒级启动的集成测试框架,使用该框架可以方便的修改和完善测试用例,使得测试用例成为测试过程的产物。测试通过之后,也同时沉淀了覆盖多种测试场景的测试用例。可以方便的使用线上数据作为数据来源,保证测试的质量。甚至在遇到线上问题时,可以将这些数据作为数据来源,用测试用例执行来反复重现 & Debug这些问题,同时沉淀线上问题的测试用例,保证后续代码改造或重构不会重新触发该故障。

1c3941217f1d00ffcf7d8c9f226b308f.png

团队介绍

物流技术基础技术团队,主要技术产品:NBF(New-Retail Business Framework), 提供了服务DevOps,LowCode编排和云原生基础设施能力,旨在成为新零售PaaS平台化和SaaS产品化的技术底座。

¤ 拓展阅读 ¤

3DXR技术 | 终端技术 | 音视频技术

服务端技术 | 技术质量 | 数据算法

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

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

相关文章

群晖上搭建teamspeak3语音服务器

什么是 TeamSpeak &#xff1f; TeamSpeak &#xff08;简称 TS&#xff09;是一款团队语音通讯工具&#xff0c;但比一般的通讯工具具有更多的功能而且使用方便。它由服务器端程序和客户端程序两部分组成&#xff0c;如果不是想自己架设 TS 服务器&#xff0c;只需下载客户端程…

OpenCV学习(四)——轨迹栏(调色板与不同通道图像)

轨迹栏 4. OpenCV轨迹栏4.1 轨迹栏作为调色板4.2 轨迹栏显示不同通道图像 4. OpenCV轨迹栏 会用到以下主要两个函数 cv2.createTrackbar(trackbarName, windowName, value, count, onChange)创建轨迹栏 主要参数&#xff1a; trackbarName&#xff1a;轨迹栏名称windowName…

视频分辨率/帧率/码率选择参考

1. 视频码率与分辨率的参考表 1080&#xff0a;720的分辨率&#xff0c;用5000K左右&#xff1b; 720&#xff0a;576的分辨率&#xff0c;用3500K左右&#xff1b; 640&#xff0a;480的分辨率&#xff0c;用1500K左右。 2. 计算公式 基本算法&#xff1a;码率&#xff08;kb…

基于Canal同步MySQL数据到Elasticsearch

基于Canal同步MySQL数据到Elasticsearch 基于 canal 同步 mysql 的数据到 elasticsearch 中。 1、canal-server 相关软件的安装请参考&#xff1a;《Canal实现数据同步》 1.1 pom依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmln…

中国地名信息库

地名是社会基本公共信息&#xff0c;是历史文化的重要载体。 2014年至2018年&#xff0c;国家启动实施并完成了第二次全国地名普查工作&#xff0c;全国共计采集地名1320多万条&#xff0c;修测标绘地名图2.4万多幅&#xff0c;新设更新地名标志68万多块&#xff0c;普遍建立了…

server2012 通过防火墙开启局域网内限定IP进行远程桌面连接

我这里需要被远程桌面的电脑系统版本为windows server2012 1、打开允许远程连接设置 2、开启防火墙 3、设置允许“远程桌面应用”通过防火墙 勾选”远程桌面“ 3、入站规则设置 高级设置→入站规则→远程桌面-用户模式(TCP-In) 进入远程桌面属性的作用域——>远程IP地址—…

演讲比赛常见误区及解决方法

演讲比赛常见误区及解决方法 一、演讲内容选择错误 1. 主题选择不合理 许多参赛者选择的主题内容&#xff0c;与比赛题目要求或听众背景不符&#xff0c;难以引起听众的兴趣。正确选择主题应考虑以下几点&#xff1a; - 主题应与比赛题目要求相符合&#xff0c;切合比赛定位…

《C和指针》(5)操作符和表达式

问题 下面这个表达式的类型和值分别是什么? 答&#xff1a;该值为2.0&#xff0c;如果要进行浮点除法&#xff0c;请使用以下表达式 下面这个程序的结果是什么&#xff1f; 答&#xff1a;这是一个狡猾的问题。比较明显的回答是-10(2-3 *4),但实际上它因编译器而异。乘法运…

Android S从桌面点击图标启动APP流程 (五)

系列文章 Android S从桌面点击图标启动APP流程 (一)Android S从桌面点击图标启动APP流程 (二) Android S从桌面点击图标启动APP流程 (三) Android S从桌面点击图标启动APP流程 (四) Android S从桌面点击图标启动APP流程 (五) Android S从桌面点击图标启动APP流程 (六) An…

17、简单记录一下两个流媒体工具和推流测试

基本思想:在开发流媒体服务过程中,使用了两个流媒体工具,这里做一下简单的记录,以后可以翻阅和查看 一:流媒体服务工具之一:https://github.com/bluenviron/mediamtx/releases 它支持rtsp/rtmp/hls推流测试 二、流媒体工具:Releases EasyDarwin/EasyDarwin GitHub 该…

华为认证H12-831考试新增题库

279、 以下哪些数列能被正则表达式[^100|200]$匹配? A、300 200 100 B、200 100 300 C、100 200 300 D、100 300 200 试题答案&#xff1a;BC 试题解析&#xff1a;[^ ]表示不包括字符&#xff0c;$表示以某字符结尾。题目的正则表达式表示不以100 200 结…

【CMake】windows10下入门课程

【CMake】windows10下入门课程 提示:博主取舍了很多大佬的博文并亲测有效,分享笔记邀大家共同学习讨论 文章目录 【CMake】windows10下入门课程前言cmake安装初识cmake&#xff1a;新建helloworld项目cmake的入门使用法则总结 前言 CMake(Cross-Platform Make)是一个开源的跨平…

高效文件管理:自动生成文件夹及重命名的方法分享

在高效文件管理中&#xff0c;自动生成文件夹及重命名是一项非常实用的技巧。通过掌握这种方法&#xff0c;我们能够更轻松地整理和查找文件&#xff0c;提高工作效率。本文将分享云炫文件管理器自动生成文件夹及重命名的实用方法&#xff0c;帮助您实现高效的文件管理。现在跟…

OSPF NSSA区域配置

NSSA&#xff1a;Not-So-Stubby Area&#xff08;不太末节的区域&#xff09; 示例&#xff0c;拓朴如下&#xff1a; 思路&#xff1a; R1正常配置Area 0区域&#xff0c;R2的1口配置为区域0&#xff0c;2口配置为区域1&#xff0c;配置NSSA&#xff0c;R3配置为区域1…

如何在Windows和Linux系统上监听文件夹的变动?

文章目录 如何在Windows和Linux系统上监听文件夹的变动&#xff1f;读写文件文件系统的操作缓冲和流文件改变事件 如何在Windows和Linux系统上监听文件夹的变动&#xff1f; libuv库实现了监听整个文件夹的修改。本文详细介绍libuv库文件读写和监听的的实现方法。libuv库开发了…

【计算机网络笔记】Cookie技术

系列文章目录 什么是计算机网络&#xff1f; 什么是网络协议&#xff1f; 计算机网络的结构 数据交换之电路交换 数据交换之报文交换和分组交换 分组交换 vs 电路交换 计算机网络性能&#xff08;1&#xff09;——速率、带宽、延迟 计算机网络性能&#xff08;2&#xff09;…

Linux系统下安全控制策略SELinux解析

SELinux&#xff08;Security-Enhanced linux&#xff09;是增强版Linux&#xff0c;简称SELinux&#xff0c;它是一个Linux内核模块&#xff0c;也是Linux的一个安全子系统&#xff0c;主要以内核模块为支持&#xff0c;用户态程序进行权限策略管理。 背景 Linux系统下的roo…

镍氢充电管理芯片-IC AH2185

镍氢充电管理芯片AH2185&#xff1a;为便携式设备提供高效充电解决方案 随着科技的不断发展&#xff0c;便携式设备在人们的生活中扮演着越来越重要的角色。这些设备包括数码相机、电子词典、智能手机等&#xff0c;它们共同的特点是需要定期充电。为了满足这一需求&#xff0…

FFmpeg5.1.3编译动态库踩坑之旅(基于Linux虚拟机)

准备工作 环境准备 1.Windows安装Oracle VM VirtualBox 7.0.10&#xff0c;安装ubuntu-22.04.3。 坑一&#xff1a;无法往虚拟机里拖放复制文件&#xff0c;解决办法&#xff1a;登录Ubuntu虚拟机时切换到xorg方式登录&#xff0c;参考地址&#xff1a;Ubuntu Desktop 22.04…

LLVM学习笔记(55)

4.1.3. 降级 在前面的章节里&#xff0c;我们展示了目标机器特定节点与目标机器无关节点共存的一个图。你可能会问&#xff0c;如果这是指令选择的一个输入&#xff0c;为什么在SelectionDAG类中已经有一些目标机器特定的节点&#xff1f;要理解这&#xff0c;我们首先在下图概…