安卓项目如何做单元测试

news2024/11/29 4:37:04

前言

先说一下创建篇文章的目的,近期负责搭建公司的单元测试框架,于是查阅了网上的很多文章,以及参考了github上很多的项目例子,并且也进行了相当多的尝试。这其中花费了很多的精力,大约有两三周的时间,远远超乎我的预期。这其中有很多原因,比如安卓的单元测试项目依赖性太高,相关文章太老旧,github上项目复杂度不够等等。所以写一篇安卓如何去做单元测试的文章,来分享给有同样需求的小伙伴,避免踩到我踩过的坑。
另外就是很多人都把单元测试和功能测试搞混了,往往把单元测试做成了验证某些功能。一开始,我也被这样的问题所误导了,甚至于某些互联网大厂文章中讲解的单元测试,也是基于功能来验证的。
对于简单的项目,基于功能来验证确实可以实现,但是如果项目比较负责,整个功能流程很长的话,就会发现这样做的弊端:
1.需要梳理整个流程的功能,功能点多,单元测试代码就会很长,而且整个流程的相关依赖都需要mock。
2.耦合严重,因为涉及到了很多环节,任意一个环节的改动,都会导致单元测试代码失效。
所以,单元测试,更正确的方式应该是针对方法的级别,而方法中的所有依赖项,就是需要我们通过mock来解决的。

一.单元测试介绍

安卓单元测试介绍

目前单元测试主要分为两大类:
1.基于JVM虚拟机的单元测试,对应项目中的test目录,缺点是不支持安卓的环境。
2.基于真机的单元测试,对应项目中的androidTest目录,由于跑在真机上,自然是支持安卓环境的。缺点是不方便自动化集成。

考虑到后面准备把单元测试搞成一种可验证的规范,所以最终决定把方向定在了基于JVM虚拟机的这种方式。因为后续我们打算,提交代码后触发jenkins检查,只有通过单元测试的代码才允许merge,而这种方式必须依赖自动化集成。

单元测试优势:

1.项目重构时。单元测试会帮助我更好的识别出影响点。
2.让项目结构更合理。如果项目结构不合理,单元测试会很难写。
3.说明功能。单元测试用例会比注释更加清晰,告诉我们一个方法实现了哪些功能。
4.寻找冗余的点。写单元测试的时候,会帮助我们发现一些项目中一些冗余的点,比如某个方法多调用了1次。
5.方法的客观评价。通过单元测试的测试方法行数,可以客观评价项目中的方法复杂度(方法复杂度不易过高,过高的复杂度建议拆成多个方法)。

单元测试初步调研:

由于没有安卓的环境,所以如果跑在JVM上,就需要主动去模拟安卓的各种环境,而目前能够满足mock安卓环境的框架中,最好的无疑就是Robolectric了,并且这也是官方推荐的框架。解决了安卓环境的的mock问题后,我们还要解决方法中依赖项的问题,这样的框架有mockito和powermock。
mockito:基于cglib的动态代理实现的,可以mock掉替换掉类中的方法,构造方法,静态方法。
powermock:mockito的增强版本,基于JDK动态代理的方式,所以可以实现任何的替换。但是缺点是需要替换原有的运行注解,会导致跑单测有问题,而且很久不维护了。
虽然powermock的功能更大的的强大,但是由于不怎么维护,而且其框架中的一些特性和Robolectric是冲突的导致跑单测会有一些问题,所以最终选择使用mockito来解决我们所遇到的mock的问题。

二.简单的单元测试例子

相信大多数的读者,之前对单元测试的经验并不多,所以如果直接上来就讲我们实际项目中的使用,效果一定不会很好。所以,我们先来通过一些最简单的例子来讲解一下单元测试的用法。
先讲解最基本的三个例子:
1.最基本的单元测试验证;
2.验证某个方法是否被执行;
3.模拟各种不同的输入值。

1.最基本的单元测试验证

public class UnitTestDemo {
    public int add(int a, int b) {
        return a + b;
    }
}

我们的验证点为:a+b=c
则单元测试代码如下:

public class UnitTestDemoTest {
    UnitTestDemo demo;

    @Before
    public void init() {
        demo = new UnitTestDemo();
    }

    @Test
    public void testAdd() {
        int add = demo.add(3, 5);
        assertEquals(add, 8);
    }
}

2.验证某个方法是否被执行

public class UnitTestDemo {
    public ThirdPartySource source = new ThirdPartySource();
    
    public void showMessage() {
        source.thirdShowMessage();
    }
}

我们的验证点为:thirdShowMessage方法是否执行
则单元测试代码如下:

@RunWith(RobolectricTestRunner.class)
public class UnitTestDemoTest {
    UnitTestDemo demo;
    
    @Before
    public void init() {
        demo = new UnitTestDemo();
    }
    
    @Test
    public void testShowMessage() {
        ThirdPartySource mockSource = Mockito.mock(ThirdPartySource.class);
        demo.source = mockSource;
        demo.showMessage();
        Mockito.verify(mockSource).thirdShowMessage();
        Mockito.verify(mockSource, times(1)).thirdShowMessage();
    }
}

3.模拟各种不同的输入值

public class UnitTestDemo {
    public ThirdPartySource source = new ThirdPartySource();
    
    public String getMessage() {
        int num = source.getNum();
        if (num < 10) {
            return "0";
        }
        if (num == 10) {
            return "10";
        }
        return "100";
    }
}

我们的验证点有三个:
1.输入小于10的数,返回值是否为字符串"0";
2.输入10,返回值是否为字符串"10";
3.输入大于10的数,返回值是否为字符串"100"。
则单元测试代码如下:

@RunWith(RobolectricTestRunner.class)
public class UnitTestDemoTest {
    UnitTestDemo demo;

    @Before
    public void init() {
        demo = new UnitTestDemo();
    }
    
    @Test
    public void getMessage() {
        ThirdPartySource mockSource = Mockito.mock(ThirdPartySource.class);
        demo.source = mockSource;
    
        Mockito.when(mockSource.getNum()).thenReturn(1);
        String message = demo.getMessage();
        assertEquals("0", message);
    
        Mockito.when(mockSource.getNum()).thenReturn(10);
        message = demo.getMessage();
        assertEquals("10", message);
    
        Mockito.when(mockSource.getNum()).thenReturn(101);
        message = demo.getMessage();
        assertEquals("100", message);
    }
}

4.稍稍总结一下
整理一下上面的方法,以及其所对应的验证点,如下:

方法验证点
add输入两个参数之和是否等于目标值
showMessagethirdShowMessage方法是否执行
getMessage1.输入小于10的数,返回值是否为字符串"0";2.输入10,返回值是否为字符串"10",3.输入大于10的数,返回值是否为字符串"100"

三.基于现有项目做单元测试

有了对单元测试最基本的了解,我们就可以开始结合我们的实际项目,来讲解如何做单元测试了。
首先我们介绍一下基本的单元测试规范;
然后再讲一下gradle的配置问题,因为不同版本gradle版本的兼容问题会有很大问题。
最后,我们介绍下如何基于现有项目去做单元测试的。

3.1 单测规范

1.类对类,一一对应
如果需要对MainActivity写单元测试,则需要在test文件夹下,相同包名,创建MainActivityTest的单元测试类。
单元测试类的类名为:原类名+Test。
在这里插入图片描述

2.方法对方法,多对多
2.方法对方法。针对某个方法写代码单测,推荐使用一对一的场景。
比如针对updateErrorIv方法写单元测试,则单元测试的方法为:testIvErrorState()
当然,涉及到某些具体相关的业务,会出现多个方法对应一个单元测试方法的情况,这种情况也是完全可以的。

3.2 配置流程

1.gradle版本

gradle版本并不是强制要求,只是方便读者更方便的运行本项目,主要配置两块即可。
gradle版本设置为:6.7.1-all,tools版本甚至为4.2.2。

1.gradle-wrapper.properties中配置gradle-6.7.1-all版本。
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip

2.项目中build.gradle配置版本如下:

buildscript {
    dependencies {
        classpath "com.android.tools.build:gradle:4.2.2"
    }
}

2.依赖配置

app目录下的build.gradle下,配置如下依赖:

    testImplementation('junit:junit:4.13.2') {
        exclude group: 'org.hamcrest', module: 'hamcrest-core'
    }
    testImplementation "io.mockk:mockk:1.12.2"
    testImplementation "org.assertj:assertj-core:3.22.0"
    testImplementation "org.robolectric:robolectric:4.9.2"
    testImplementation('org.mockito:mockito-core:3.6.28') {
        exclude group: 'net.bytebuddy', module: 'byte-buddy'
        exclude group: 'net.bytebuddy', module: 'byte-buddy-agent'
    }
    testImplementation 'org.mockito:mockito-inline:5.2.0'
    testImplementation "androidx.test:core:1.3.0"

    testImplementation("org.hamcrest:hamcrest-core:1.3")
    testImplementation("org.assertj:assertj-core:2.6.0")
    testImplementation 'android.arch.core:core-testing:1.0.0-alpha3'
        testImplementation('org.bouncycastle:bcprov-jdk15on:1.65') {
        force = true
    }

3.创建单元测试类

创建单元测试类,@Before代表执行前的初始化操作。@Test代表执行单元测试操作。

@RunWith(RobolectricTestRunner.class)
public class MainActivityTest {
    @Before
    public void init() {
       
    }
    
    @Test
    public void testAny() {
    
    }
}

3.3.单测样例介绍

MVP模式类型

页面主要功能介绍:
1.进入页面,注册自定义监听,退出页面,取消注册自定义监听。
2.首次进入或者回到页面的时候,请求数据并刷新页面,并且只请求一次。
3.展示fragment。
4.点击图标,会弹出dialog。

Activity单元测试介绍

相关代码:

public class MVPActivity extends FragmentActivity implements IMVPActivityContract.IMainActivityView, View.OnClickListener {

    MVPPresenter presenter;
    ImageView imgeView;
    TextView textDesc;
    boolean isFirst;
    AdapterListener listener;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        initListener();
        init();
    }

    private void initView() {
        imgeView = findViewById(R.id.image_view);
        textDesc = findViewById(R.id.text_desc);
        presenter = new MVPPresenter();
        imgeView.setOnClickListener(this);
    }

    private void initListener() {
        if (listener == null) {
            listener = new AdapterListener();
            DataAdapaterClient.getInstance().registerDataNotifyListener("key", listener);
        }
    }

    private void init() {
        new Handler().post(() -> {
            presenter.onAttach(this);
            //refresh Activity page
            presenter.requestInfo();
            //show fragment
            MVPFragment fragment = new MVPFragment();
            getSupportFragmentManager().beginTransaction().replace(R.id.frame_layout, fragment).commitAllowingStateLoss();
        });
    }

    @Override
    protected void onResume() {
        super.onResume();
        //If the first time,refresh data
        if (isFirst) {
            presenter.requestInfo();
        } else {
            isFirst = true;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (listener != null) {
            DataAdapaterClient.getInstance().unRegisterDataNotifyListener(listener);
        }
    }

    @Override
    public void refreshPage(boolean isShow, String message) {
        imgeView.setVisibility(isShow ? View.VISIBLE : View.GONE);
        textDesc.setText(message);
    }

    @Override
    public void onClick(View v) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("title");
        builder.setMessage("this is detail message!");
        builder.create().show();
    }

    public AdapterListener getAdapterListener() {
        return listener;
    }

    public class AdapterListener implements DataAdapaterClient.DataChangedListener {

        @Override
        public void onDataChanged(String var1, String var2) {
            //do somethind
        }
    }
}

页面代码逻辑梳理:
1.initView方法中,主要为初始化presenter,以及给成员变量中的view赋值;
2.initListener时,初始化listener,并且注册监听。onDestory时,取消注册;
3.init中,请求数据,并且注册fragment;
4.onResume中,非首次进入时要请求数据来刷新页面;
5.refreshPage中,根据所传数据,刷新当前页面;
6.onClick中,点击按钮,展示弹框。

汇总整理后,方法和验证点如下:
在这里插入图片描述
所以根据上面的汇总,我们最终的单元测试代码如下:
MVPActivityTest

presenter单元测试介绍

相关代码:

public class MVPPresenter implements IMVPActivityContract.IMainActivityPresenter {
    IMVPActivityContract.IMainActivityView mView;

    public void onAttach(IMVPActivityContract.IMainActivityView view) {
        this.mView = view;
    }

    @Override
    public void requestInfo() {
        //请求数据,订阅,并显示
        Consumer<InfoModel> consumer = this::processInfoAndRefreshPage;
        Flowable<InfoModel> observable = DataSource.getInstance().getDataInfo();
        Disposable disposable = observable
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(consumer);
    }

    /**
     * action
     */
    public void processInfoAndRefreshPage(InfoModel infoModel) {
        int status = infoModel.status;
        String statusDesc = infoModel.statusDesc;
        mView.refreshPage(status != 200, statusDesc);
    }
}

presenter逻辑梳理:
1.onAttach方法进行页面绑定,验证点为:mView不为空。
2.requestInfo方法发起请求,返回值会调用processInfoAndRefreshPage方法进行刷新。
3.processInfoAndRefreshPage方法根据传参不同,执行不同的逻辑判断,然后刷新界面。

汇总整理后,方法和验证点如下:
在这里插入图片描述
所以根据上面的汇总,我们最终的单元测试代码如下:
MVPPresenterTest

四.项目常见问题解决

实际运行项目的时候,往往会遇到各种各样的问题。下面,我们列举一些经常遇到的问题,来讲解下我们是如何解决的。

4.1常见问题汇总

1.类中引用SO问题

因为单元测试基于JVM虚拟机,执行的是java的流程,并不会执行相关的安卓打包流程,所以引用的SO文件不会打包进最终产物当中,因此,直接运行项目,会提示SO找不到。
所以,更合适的方式是把相关的类进行mock替换掉。

如果遇到业务使用到了SO文件,则需要进行对这个对象进行mock,解除依赖。
比如,我们Model层获取数据时,使用到了SO。

	public class MVPPresenter{
	    @Override
	    public void requestInfo() {
		//请求数据,订阅,并显示
		Consumer<InfoModel> consumer = this::processInfoAndRefreshPage;
		Flowable<InfoModel> observable = DataSource.getInstance().getDataInfo();
		Disposable disposable = observable
			.subscribeOn(Schedulers.io())
			.observeOn(AndroidSchedulers.mainThread())
			.subscribe(consumer);
	    }	
    	}
	public class DataSource{
		public Flowable<InfoModel> getDataInfo() {
			//this us jni
			Java2CJNI java2CJNI = new Java2CJNI();
			Log.i("SoView", java2CJNI.java2C());
			return Flowable.create(emitter -> {
			    InfoModel infoModel = new InfoModel();
			    infoModel.status = 100;
			    infoModel.statusDesc = "fail";
			    emitter.onNext(infoModel);
			}, BackpressureStrategy.BUFFER);
		    }
	}
    

则我们可以通过mock对象MVPPresenter或DataSource来实现,从而避免请求到真正的getDataInfo()方法。
相关单测代码如下:

    MVPPresenter mockPresenter = mock(MVPPresenter.class);
    Mockito.doAnswer(invocation -> {
        //do nothing
        return null;
    }).when(mockPresenter).requestInfo();

2.解决XML中View类引用SO问题

如果XML引用了某个View的类,并且这个类使用到了SO,则需要整体这个View类。
比如:SoView中使用到了SO文件。
则会产生如下报错,因为

Caused by: java.lang.UnsatisfiedLinkError: no Java2C in java.library.path: [/Users/xxxx/Library/Java/Extensions, /Library/Java/Extensions, /Network/Library/Java/Extensions, /System/Library/Java/Extensions, /usr/lib/java, .]
	at java.base/java.lang.ClassLoader.loadLibrary(ClassLoader.java:2670)

我们要整体替换掉这个使用SoView的类。
首先,创建SoViewMock类,参考:

@Implements(com.xt.unittestdemo.view.SoView.class)
public class SoViewMock extends ShadowView {

    @Before
    public void setUp() {

    }

    @Implementation
    public void __constructor__(Context context) {
        __constructor__(context, null);
    }

    @Implementation
    public void __constructor__(Context context, AttributeSet attrs) {
    }


    @Implementation
    protected void onAttachedToWindow() {

    }

    @Implementation
    protected void onDetachedFromWindow() {

    }

    @Implementation
    public void stop() {

    }
}

其次,单元测试类中进行响应的配置,替换掉SoView类。相关代码如下:

@Config(shadows = {SoViewMock.class}, manifest = Config.NONE, sdk = Build.VERSION_CODES.P)
@RunWith(RobolectricTestRunner.class)
public class MVPActivityTest {

3.解决Activity中成员变量mock问题

我们Activity对象,一般要使用Robolectric构造的,而不能直接new,否则会不走Activity的生命周期。
但是Activity中的成员变量,则需要使用mock的,因为只有mock的才能进行执行次数以及其它相关的验证,所以,如何替换Activity中的成员变量,就是一个我们要解决的问题。
经过反复的尝试,最终发现了一个可行的方案,即MainActivity的onCreate()中经过handler转发后再使用成员变量进行相关操作,我们在观察到执行完onCreate()方法后,使用mock对象替换原始对象。

//原始类
public class MVPActivity extends Activity{
	protected void onCreate(@NonNull Bundle savedInstanceState) {
		new Handler().post(() -> {
			...使用成员变量进行相关操作
			presenter.requestInfo();
		};
	}
}

//单元测试类
@RunWith(RobolectricTestRunner.class)
public class MVPActivityTest {
	@Test
    	public void testMethod() {
		ActivityController<MVPActivity> controller = Robolectric.buildActivity(MVPActivity.class);
		MVPPresenter mockPresenter = mock(MVPPresenter.class);
		MVPActivity mainActivity = controller.create().start().get();
		//替换presenter操作
        	mainActivity.presenter = mockPresenter;
		controller.postCreate(null).resume().visible().topActivityResumed(true);
		//避免主线程looper阻塞
		ShadowLooper shadowLooper = ShadowLooper.getShadowMainLooper();
            	shadowLooper.runToEndOfTasks();
		//验证操作
		verify(mockPresenter, times(1)).requestInfo();
	}
}

这样,我们通过使用mock的presenter替换原有Activity中的presenter,从而方便我们对presenter中的相关方法进行验证,并且还不影响Activity的生命周期。

4.解决单例类的mock问题

虽然我们可以使用第一种的方案去mock整个单例类,但是这种mock的类,是不方便替换其中的方法。所以,对于单例类,我们可以使用替换getInstance方法的方式来进行替换。
这里以DataAdapaterClient为例,相关类结构如下:

public class DataAdapaterClient {
    Map<String, DataChangedListener> listenerMap = new HashMap<>();
    public static DataAdapaterClient getInstance() {
        return DataAdapaterClient.SingletonHolder.SINGLETON;
    }

    private static class SingletonHolder {
        private static final DataAdapaterClient SINGLETON = new DataAdapaterClient();
        private SingletonHolder() {
        }
    }
 }

我们可以通过mock生成DataAdapaterClient对象,然后通过hook掉静态方法getInstance(),实现每次返回的都是我们mock后的ataAdapaterClient对象。相关代码如下:

    DataAdapaterClient mockClient = mock(DataAdapaterClient.class);
        try (MockedStatic<DataAdapaterClient> ignored2 = mockStatic(DataAdapaterClient.class)) {
            when(DataAdapaterClient.getInstance()).thenReturn(mockClient);
        }
    }

5.解决异步的问题

比如我们presenter中,requestInfo方法中通过方法getDataInfo异步发送服务去请求。

    @Override
    public void requestInfo() {
        //请求数据,订阅,并显示
        Consumer<InfoModel> consumer = this::processInfoAndRefreshPage;
        Flowable<InfoModel> observable = DataSource.getInstance().getDataInfo();
        Disposable disposable = observable
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(consumer);
    }

由于我们是mock的手段,并不会真的去发送请求,所以,我们需要mock掉getDataInfo的返回值或者回调。

    @Test
    public void testRequestInfo() {
        Flowable<InfoModel> noNetWorkFlowable = Flowable.create(emitter -> {
            InfoModel baseEntity = new InfoModel();
            InfoModel accountInfoEntity = new InfoModel();
            accountInfoEntity.status = 200;
            accountInfoEntity.statusDesc = "success";
            emitter.onNext(baseEntity);
        }, BackpressureStrategy.BUFFER);

        DataSource mock = mock(DataSource.class);
        try (MockedStatic<DataSource> ignored = mockStatic(DataSource.class)) {
            when(DataSource.getInstance()).thenReturn(mock);
            when(mock.getDataInfo()).thenReturn(noNetWorkFlowable);        
            presenter.requestInfo();
            ...
            //进行相关验证
        }
    }

4.2 常用排查手段

1.使用GPT
2.百度/google
3.参照网上现有的项目:
比如:Anki-Android
以及本项目:RobolectricDemo

开源项目RobolectricDemo欢迎大家fork/PR,补充更多场景的单元测试场景和解决方案。

五.参考文档

地址介绍
https://github.com/ankidroid/Anki-Androidgithub上单测覆盖率较高的项目
https://github.com/mockito/mockitogithub上mockito项目
https://github.com/robolectric/robolectricgithub上robolectric项目
https://github.com/powermock/powermockgithub上powermock项目
https://developer.android.com/reference/androidx/test/core/app/ActivityScenario官方关于ActivityScenario的介绍ActivityScenario用于替代ActivityController

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

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

相关文章

淘系抓包流程(淘宝数据无法抓包解决方式)

淘系抓包流程 结合frida和adb工具以及mumu模拟器进行抓包。 具体的关系图: frida的安装 frida安装&#xff0c;直接安装官网的脚手架。frida官网使用python的pip安装&#xff0c;python > 3。 安装后使用查看版本命令来确认是否安装。 pip install frida-tools frida --ve…

【严重】VMware Aria Operations for Logs v8.10.2 存在反序列化漏洞(CVE-2023-20864)

漏洞描述 VMware Aria Operations for Logs前身是vRealize Log Insight&#xff0c;VMware用于处理和管理大规模的日志数据产品。 VMware Aria Operations for Logs 8.10.2版本中存在反序列化漏洞&#xff0c;具有 VMware Aria Operations for Logs 网络访问权限的未经身份验…

“SCSA-T学习导图+”系列:交换技术之STP

本期引言&#xff1a; 在通信工程当中&#xff0c;从物理层面上&#xff0c;我们可以采用冗余链路保证网络的健壮性。冗余是指出于系统安全和可靠性等方面的考虑&#xff0c;人为地对一些关键部件或功能进行重复的配置。当系统发生故障时&#xff0c;比如某一设备发生损坏&…

【Linux】Linux下的gbd调试,你学废了吗

操作系统核心数centos 3.10.032位单核 gbd调试方法-以线程运行时调试为例 线程死锁状态时查看栈升级gbd通过gdb在程序运行时进行调试 线程死锁状态时查看栈 在线程-线程安全之互斥中&#xff0c;我们自己写了一个模拟实现的线程死锁情况 我们用gbd调试查看了当前线程的调用&a…

拉链表制作

1.拉链表的应用场景 拉链表适合于&#xff1a;数据会发生变化&#xff0c;但是大部分是不变化的&#xff08;即&#xff1a;缓慢变化维。还需要保留历史数据做分析的场景&#xff09; 2.拉链表的形成过程 关键的过程 第四步&#xff1a;获取变化的数据&#xff08;创建和修改…

Flask连接MySQL

本文章涉及到Flask框架和HTML内容&#xff0c;相关知识可查看链接 HTML-form表单和提交_html form 提交_小梁今天敲代码了吗的博客-CSDN博客https://blog.csdn.net/weixin_43780415/article/details/130110722 前端引入和html标签_小梁今天敲代码了吗的博客-CSDN博客https://b…

vscode下drawio无法使用

问题描述&#xff1a; Vscode下&#xff0c; 刚下载drawio这个插件&#xff0c;在vscode左边EXPLORER下&#xff0c;没有Test这个页面,导致vscode无法使用drawio。 解决办法&#xff1a; 在自己需要的目录下&#xff0c;新建一个文件&#xff0c;例如test,并命名为test.drawi…

数据结构算法

直接插入排序 1.从第一个元素开始&#xff0c;该元素可以认为已经被排序 2.取下一个元素tem&#xff0c;从已排序的元素序列从后往前扫描 3.如果该元素大于tem&#xff0c;则将该元素移到下一位 4.重复步骤3&#xff0c;直到找到已排序元素中小于等于tem的元素 5.tem插入到该元…

5.1劳动节,致敬最可爱的人!Cocos社区杰出贡献者出炉

Cocos 引擎的生态建设与繁荣&#xff0c;离不开社区开发者的辛勤付出。 2022.5 ~ 2023.5 年度期间&#xff0c;有这样一批 Cocos 社区开发者&#xff0c;他们使用 Cocos Creaor 引擎创作内容与产品、分享技术和经验&#xff0c;为 Cocos 社区默默贡献自己的一份力量&#xff0c…

改进YOLOv8:替换轻量化骨干网络Efficient V1、Efficient V2《重新思考卷积神经网络的模型缩放》)

这里写目录标题 1 EfficientNetV1中存在的问题2.EfficientNetV2网络框架3.YOLOv8添加Efficient V1代码yaml文件Efficient V1代码运行4. 添加Efficient V2代码yaml文件Efficient V2运行Efficient V1论文地址:https://arxiv.org/pdf/1905.11946.pdf Efficient V1代码地址:

Golang每日一练(leetDay0049) 二叉树专题(9)

目录 144. 二叉树的前序遍历 Binary-tree Preorder Traversal &#x1f31f; 145. 二叉树的前序遍历 Binary-tree Postorder Traversal &#x1f31f; 对比&#xff1a; 94. 二叉树的中序遍历 Binary-tree Inorder Traversal &#x1f31f; 146. LRU缓存 LRU Cache &am…

调用移动云OCR识别身份证

一.开通移动云OCR服务 在下面这个网址开通免费服务&#xff0c;&#xff0c;每个账号可免费使用500次&#xff0c;先要实名认证。 通用文字识别 (10086.cn)https://ecloud.10086.cn/home/product-introduction/Generalverify 有两种方式&#xff1a; 这里选择第二种 。 二…

炸裂的 Auto-GPT,帮我自动生成小视频!

大家好&#xff0c;我是程序员贺同学。 继前段时间爆火的 ChatGPT 后&#xff0c;又一个炸裂的开源项目 Auto-GPT 出现了。 仿佛一夜之间&#xff0c;AI 圈又出现了一个新晋顶流。我们来看看它有多&#x1f525;。 在 GitHub 上&#xff0c;仅最近不到两个礼拜&#xff0c;这个…

TCP/IP基础知识

文章目录 互联网与TCP/IP的关系TCP/IP与OSI参考模型硬件&#xff08;物理层&#xff09;互联网层&#xff08;网络层&#xff09;IPICMPARP 传输层TCPUDP 应用层WWW电子邮件&#xff08;E-Mail)文件传输&#xff08;FTP&#xff09;远程登录&#xff08;TELNET与SSH&#xff09…

Redis Set 用了 2 种数据结构来存储,到现在才知道

Sets 无序集合&#xff0c;他的功能就好像你熟悉的 Java 中的 HashSet 一样。集合是通过散列表实现的&#xff0c;所以添加、删除、查找元素的时间复杂度是 O(1)。 1. 是什么 Sets 是 String 类型的无序集合&#xff0c;集合中的元素是唯一的&#xff0c;集合中不会出现重复的数…

【百问百答】可靠性基础知识第三期

1.电连接器的基本性能有哪些? 三个基本性能&#xff1a;机械性能、电气性能和耐环境性能。 电连接器机械性能测试包括&#xff1a;插拔力测试、端子保持力测试、端子正向力测试、耐久性测试。 电气特性测试包括&#xff1a;绝缘电阻测试、 耐电压测试、 低电平电阻测试(LLCR…

【YOLO v1】模型搭建 | model | 代码

YOLO V1 模型 import torch import torch.nn as nn from torchsummary import summarydef build_block(in_channel, out_channel, kernel_size, stride1, maxpoolFalse):padding kernel_size//2block nn.Sequential(nn.Conv2d(in_channel, out_channel, kernel_sizekernel_si…

数据包守恒 TCP 拥塞控制

数据包守恒是包括拥塞控制在内的合理利用带宽的方法之基石&#xff0c;它维持了有效网络传输的稳定&#xff0c;过去 40 年是&#xff0c;未来还是。数据包守恒可以描述为&#xff1a; 当带宽恰好满载时&#xff0c;receiver 收到 1 个数据包后 sender 才能发送 1 个数据包。当…

LeetCode链表OJ题目 代码+思路分享

目录 删除有序数组中的重复项合并两个有序数组移除链表元素 删除有序数组中的重复项 链接: link 题目描述&#xff1a; 题目思路&#xff1a; 本题使用两个指针dst和src一前一后 相同情况&#xff1a; 如果nums[dst]nums[src]&#xff0c;那么src 不相同情况&#xff1a; 此…

基于B/S架构SpringBoot+Bootstrap框架的中小医院信息系统

一、开源项目简介 基于B/S架构&#xff0c;SpringBootBootstrap框架的中小医院信息系统。简单实现了挂号收费&#xff0c;门诊管理&#xff0c;划价收费&#xff0c;药房取药&#xff0c;体检管理&#xff0c;药房管理&#xff0c;系统维护等基础功能。 二、功能概述 本系统是…