android unit test mock框架使用记录

news2024/11/15 5:08:25

写在前面

之前上班时,开发一个功能之后,还需要编写测试用例,使用的框架是mock。
为什么防止以后用到时忘了,在这里记录一下。
由于团队没有人使用Espresso进行unit test,所以本人对该框架并不熟悉。想了解该框架的使用,请移步其他文章。

  • 准备
  • unit test概述
  • Mockito框架
  • 编写unit 的思路
  • unit test-实例

准备

// 如果需要测试LiveData,务必引入该库
testImplementation "androidx.test:rules:1.2.0"
// 用于生成一些测试过程中需要用到的类
testImplementation "org.mockito:mockito-core:3.0.0"
testImplementation "org.mockito:mockito-inline:2.21.0"

如果某些网络请求需要依赖json文件模拟获取请求结果,可以使用下面这种方式加载json文件。

// 注意:这是unit test所需的代码,所以最好把该文件创建在unit test的包下面
object FileLoader {
    fun loadFile(fileName: String): String {
        val inputStream = Thread.currentThread().contextClassLoader!!.getResourceAsStream(fileName)
        return inputStream.readBytes().decodeToString()
    }
}
// 然后在main目录下创建resources文件夹,再在里面创建相应的json文件或其他类型的文件。

// 如
// aaa.json
{

}
// KotlinTest.kt
class KotlinTest {
    @Test
    fun test(){
        val result = FileLoader.loadFile("aaa.json")
        println(result)
    }
}
// 结果
{

}

工具

不清楚是哪个android studio的版本,反正右击test目录看看有没有这个就行了,没有就更新android studio的版本。
这个工具的作用是:可以查看当前测试的覆盖率。
我所在的公司是对覆盖率有要求的,虽然不是以该工具为准,但该工具可以辅助我们在开发的时候确保哪些类和方法有准确被测试,哪些没有,从而了解代码要怎么修改才能让覆盖率提升。
tool
使用方式

// 覆盖率测试类
class CoverageTest {
    fun function1() {
    }

    fun fucntion2() {

    }

    fun function3() {
        throw RuntimeException()
        val a = 0
    }
}

// 测试类
class KotlinTest {
    @Test
    fun test() {
        val ct = CoverageTest()
        ct.function1()
        try {
            ct.function3()
        } catch (_: Exception) {
        }
        ct.function4()
    }
}

在test包或者KotlinTest上面鼠标右键,选择上面的test with Coverage,运行之后可能会出现这样一个弹窗。
在这里插入图片描述
选择replace或add to都可以,之后就会看到这样一个界面。
在这里插入图片描述
可以在右边看到,class、method和line各自测试的占比。
左边则是:哪些代码测试通过,哪些没有测试。
其中,function3有一段throw RuntimeException的代码,在测试的时候抛出了异常,所以该方法部分代码并没有执行,从而影响测试的覆盖率。
再看看function4,可以看到该方法有if-else,所以有部分代码是没有执行到的。从这个例子可以看到,如果想要提升测试的覆盖率,那就尽量地将所有if-else、try-catch等的代码都覆盖到,从而提升测试覆盖率。

再说说这个工具的另一个好处,在我实际编写unit test的时候,有时会发现android studio乱报测试异常的代码行数。就比如

val a = 1

有时android studio就会报这种代码出现异常,但这样的代码怎么可能会出现异常,显然是有问题的。所以这个时候就可以使用coverage来辅助检测,看看unit test运行到那里就没有继续运行了,从而找出问题代码。

unit test概述

在编写测试用例之前,需要先明确为什么要编写unit test,如果没有找到一个编写unit test的目的,那编写unit test就会变成一个应付上级的任务。

在百度大概查了一下,感觉这段话比较好的说明了unit test的目的:单元测试是由程序员自己来完成,最终受益的也是程序员自己。可以这么说,程序员有责任编写功能代码,同时也就有责任为自己的代码编写单元测试。执行单元测试,就是为了证明这段代码的行为和我们期望的一致。

在20年参加过JetBrains举办的一次网络研讨会 ,里面就有一小段的时间在讨论unit test的作用。我觉得里面说得非常对,大概意思是:在重构之前最好先编写unit test,目的是在重构过程中可以不断地通过unit test去验证代码原有的逻辑是否被破坏。后面我还记得原有的bug在重构之后也必须原封不动的保留下来,是不是在这个视频说的就忘了,反正记得看过/听过这样一句话。

结合这两句话,我觉得可以做一个总结:可以使用unit test保证代码在重构前后原有的逻辑不被破坏。而后面的bug也必须保留,我个人认为:这也是原有的逻辑不被破坏的一个要求之一。因为重构的目的是修改代码结构/架构,而不是改bug。既然是修改代码结构,那就不能涉及到逻辑层面的修改。逻辑层面的修改应该在做其他事情的时候去做,而不是在重构的过程去做。

当然了,编写unit test并不是为了应对代码重构,而是为了验证自己的想法是否准确。当完成一个功能之后,顺手编写unit test对自己的代码逻辑进行验证并且验证通过,这个时候心里就对自己编写的代码有底了。所以时间允许的情况下,我个人认为编写unit test还是有必要的。我就有过在编写unit testd的时候才发现代码有一个不明显的bug的情况。不过如果业务经常变动,就暂时别写unit test了,等业务稳定下来再去写。因为频繁变动带来的是需要频繁修改unit test的代码,如果项目里面还配置了jenkin,导致jenkin动不动就提示unit test测试失败,就不好玩了。

Mockito框架

为什么需要Mock

在编写unit test过程中,很多对象并不能轻易获取或者没办法直接构造(如: android.content.Context),而当我们想要创建这些对象的时候,会发现需要导入很多类才能将该类创建出来,这个时候就可以使用Mock将该对象创建出来。比如:
如果需要一个TextView对象,这个时候可以直接new,但new的时候需要一个Context对象,然而创建一个Context又需要很多类。最后会发现,光new一个对象就会浪费大量时间。而如果使用Mock,则可以:

val textView = Mockito.mock(TextView::class.java)
println(textView)

/ /或者是

class MockTest {
    @Mock
    private lateinit var textView: TextView
    
    @Test
    fun test() {
        MockitoAnnotations.initMocks(this)
        println(textView)
    }
}

// 这两种方法都是可以的,具体想怎么使用就看自己的需要吧。

可以看到,使用Mock就可以如此简单的创建一个TextView,连Context都不需要了。
再看看Mock的其他用法,因为Mock框架非常强大,可以解决编写unit test大部分的问题。

Mock常见用法

verify方法

该方法的作用是:测试指定方法是否被调用,先看一下方法前面及文档。

在这里插入图片描述
尽管该方法的文档并没有提到实际测试的对象需要一个Mock出来的对象,但实际测试的时候是需要的,这个需要注意一下。

在这里插入图片描述


测试某个方法是否被调用

在这里插入图片描述
这里需要注意一下,textView调用的textColor和Mock调用的textColor必须一致

如果参数不一样

在这里插入图片描述
可以看到,测试是失败的,而且也已经将失败信息写得很清楚了。所以遇到问题不要慌,大部分情况下是可以在报错信息找到答案的。

verify的其他方法

上面的图片可以看到,verify实际上是调用另一个方法,并传入了times(1)这个参数,看看这个是怎么做的。
在这里插入图片描述
可以看到,这个是调用VerificationModeFactory里面的这个一个方法。既然是Factory,那就意味着他肯定提供了一些常用方法让我们去调用,看看他自带的方法。
在这里插入图片描述

  • atLeastOnce:至少调用一次
  • atLeast:至少调用n次
  • times:调用n次
  • atMostOnce:最多调用一次
  • atMost:最多调用n次
代码示例

在这里插入图片描述

其他常用方法

thenReturn和thenAnswer
当在调用一个Mock对象的方法的时候,需要指定某个返回值,就可以使用这2个方法。
这2个方法的区别是

  • thenReturn:没办法获取到调用的参数值,适合那些无需根据参数内容返回相应数据的方法。
  • thenAnswer:可以获取到调用方法信息、调用对象、调用参数等信息,适合那些需要根据调用的数据返回相应的数据的方法。

在这里插入图片描述
在这里插入图片描述
mock和spy
上面提供的代码基本都是使用mock创建一个对象,但其实还有一个和mock类似的方法:spy

  • mock:当调用mock对象的方法的时候,不会执行该方法的本身代码
  • spy:当调用mock对象的方法的时候,会执行该方法的本身代码

什么意思?假设调用的某个方法里面有100行代码,mock出来的对象是不会执行这100行代码的,而spy出来的对象则会都执行。最终影响到测试的覆盖率。而且由于mock不会执行本来的代码,导致该方法的代码逻辑不会执行。比如通过mock设置TextView里面的textSize,再调用verify方法检测TextView里面的textSize,结果肯定是不通过的,因为mock根本不会执行TextView里面的逻辑。
可以正常运行的情况
在这里插入图片描述
不能正常运行的情况
在这里插入图片描述
所以我个人建议,在编写unit test的时候,可以尽量使用spy创建一个对象。一方面,可以提升覆盖率。另一方面,可以尽可能的对业务代码进行测试。如果遇到一些方法或字段没办法在unit test的环境下创建出来,就多使用thenReturn、thenAnswer。

编写unit 的思路

既然要编写unit test,那就必须在编写前先确定思路,否则就会出现在编写的过程中想到一种编写方式就用一种。最终导致编写出来的unit test的代码风格不统一,明明是一个人写的代码,看起来却好像是几个人写的。
在编写unit test之前,还有另一件很重要的事情。代码的架构模式。无论是用MVC、Mvp、MVVM我个人认为都是没问题的,只是最好做到代码风格统一。连业务代码都不统一,unit test代码怎么可能保证统一。
再者,必须用好相应的架构模式。保证每个层级各施其责。比如:业务就绝对不要在UI层去处理,UI代码绝对不要编写在处理业务的代码里面。否则在编写unit test的时候,就往往需要Mock出来很多测试的那个层级不需要的对象。
还有一个比较容易被忽略的细节,方法定义尽量细化,每个方法都只做自己的任务,不要做多余的任务。否则也会出现类似上面一样的问题,为了保证整个方法可以通过测试,需要Mock出来很多对象。
在介绍如何编写unit test之前,有一件事先说清楚,在后面就不提了。在编写完所有的unit test之后,一定一定要在test包鼠标右键点击**Run 'Tests in xxx'**。因为有些case单独运行的时候是不会出现问题的,但如果和其他方法一起运行,就会出现问题,我就被这种问题坑了几次。并且修改完业务代码之后,也记得跑一遍unit test,否则真的有可能会出现测试不通过的问题。

在这里插入图片描述

unit test-实例

用MVP架构模式来举例,由于这里并不是在介绍MVP架构模式,所以就业务代码只贴关键代码。

业务代码

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
这里的LoginActivity里面的intiPresenterinitView修饰符都为public是为了方便在编写unit test的时候,可以直接从外部调用,也可以写成private,然后在test的时候使用反射。这个就看团队的规范和个人爱好吧,我个人认为编写unit test的时候,用反射也没有关系。

unit test

回到unit test-实例

在要测试的类类名点击右键就可以比较方便地创建一个Test类。
在这里插入图片描述

Presenter测试

回到unit test-实例

Presenter主要是对业务进行测试,测试Presenter里面的业务代码的执行结果是否符合预期。一旦发现不符合预期,则有可能编写的测试代码有问题,亦或是业务代码本身就有问题,只是没有被测出来等等。Presenter的测试代码尽量不要涉及UI测试,否则会导致整个测试类看起来非常乱。既包含业务测试,又包含UI测试。
LoginSuccessRequesterMock

object LoginSuccessRequesterMock : ILoginContact.ILoginRequester {
    override fun login(userName: String, password: String, loginListener: loginListener) {
        loginListener(LoginRequester.REQUEST_SUCCESS, LoginModel(userName, password))
    }
}

LoginFailedRequesterMock

object LoginFailedRequesterMock : ILoginContact.ILoginRequester {
    override fun login(userName: String, password: String, loginListener: loginListener) {
        loginListener(LoginRequester.REQUEST_FAILED, null)
    }
}

LoginPresenterTest

class LoginPresenterTest {
    // 这里的Presenter只有一个测试方法,所以不想要创建一个成员变量也是没问题的
    // 但考虑到大部分Presenter都至少不止一个方法,所以建议Presenter都使用成员变量
    private lateinit var presenter: LoginPresenter
    private lateinit var view: ILoginContact.ILoginView

    @Before
    fun setUp() {
        // 使用spy的目的是,为了方便测试Presenter里面的某些方法是否被调用
        presenter = Mockito.spy(LoginPresenter)
        // 由于View在这里只是负责测试逻辑是否正确,所以完全可以使用mock,无需使用spy或是new
        view = Mockito.mock(ILoginContact.ILoginView::class.java)
        presenter.addView(view)
    }

    @Test
    fun login() {
        // 方法的参数建议单独创建,方便测试
        val userName = "username"
        val password = "password"
        // 由于在Presenter里面将网络请求叫给了Requester去做,所以在编写unit test的时候
        // 就可以更方便地控制请求的结果,只需要设置了不同的Requester就行了,无需修改Presenter里面的代码
        val loginSuccessRequester = LoginSuccessRequesterMock
        val loginFailedRequester = LoginFailedRequesterMock

        // 设置请求成功的时候
        // 可以看到在LoginPresenter的Requester是:ILoginRequester,所以这种情况下就可以自由切换Requester,而不是只能LoginRequester的实现类
        presenter.requester = loginSuccessRequester
        presenter.login(userName, password)
        // 测试login方法是否被调用
        Mockito.verify(presenter).login(userName, password)
        // 测试View是否调用了showLoading
        Mockito.verify(view).showLoading(RequestType.REQUEST_LOGIN)
        // 测试View是否调用了hideLoading
        Mockito.verify(view).hideLoading(RequestType.REQUEST_LOGIN)
        // 测试loginModel是否相等
        Assert.assertEquals(presenter.loginModel, LoginModel(userName, password))
        // 测试View是否调用了onLoginSuccess
        // 这里需要注意的是:由于这里的LoginModel使用的是data class,所以会自动override toString方法
        // 并且mock额比较方式就是使用toString,所以如果发现明明代码没问题,但却测试没办法通过
        // 就可以试试手动override toString方法
        Mockito.verify(view).onLoginSuccess(LoginModel(userName, password))

        // 设置请求失败的时候
        presenter.requester = loginFailedRequester
        presenter.login(userName, password)
        // 这里,由于上面调用了1次,所以这里就变成了调用了2次,所以这种情况下就可以需要使用,VerificationMode
        // 要使用times还是atLeast就看业务需要和个人习惯吧
        Mockito.verify(presenter,Mockito.atLeast(1)).login(userName, password)
        Mockito.verify(view,Mockito.atLeast(1)).showLoading(RequestType.REQUEST_LOGIN)
        Mockito.verify(view,Mockito.atLeast(1)).hideLoading(RequestType.REQUEST_LOGIN)
        //由于请求失败,所以会调用showNetError方法,所以这里需要测试
        Mockito.verify(view).showNetError(RequestType.REQUEST_LOGIN)
    }

    @After
    fun tearDown() {
        presenter.removeView(view)
    }
}

View测试

回到unit test-实例

View测试只是测试调用特定方法之后,相应的View有没有变成和预期一样的结果。所以这里没必要编写过多的业务代码,尽可能地少编写业务代码辅助测试。所以View层的测试代码就会变得比较简单。只是会比较麻烦,因为View的很多代码对于java来说是不存在的,所以需要手动mock出来。
LoginActivityTest

class LoginActivityTest {
    private lateinit var view: LoginActivity

    @Before
    fun setUp() {
        view = Mockito.spy(LoginActivity::class.java)
    }

    @Test
    fun testPresenter() {
        // 为了提升覆盖率, 所以有一些看起来好像不相关的代码,也最好形式上调用一下
        view.initPresenter()
        val inViewPresenter = view::class.java.getDeclaredField("presenter").let {
            it.isAccessible = true
            it.get(view) as LoginPresenter
        }
        Assert.assertTrue(inViewPresenter.views.contains(view))
        view.deinitPresenter()
        Assert.assertFalse(inViewPresenter.views.contains(view))
    }

    @Test
    fun initView() {
        // 这里直接使用了view的id,这是kotlin的一个扩展库(虽然最新已废弃)
        // 使用过show kotlin bytecode的朋友应该都知道,实际上最终编译出来的代码是使用 _$_findViewCache
        // 这里一个变量来存储,所以为了保证在调用initView的时候不会出现空指针异常,需要在调用之前将所需
        // 的view设置进去
        val button = Mockito.mock(Button::class.java)
        view::class.java.getDeclaredField("_\$_findViewCache").also {
            it.isAccessible = true
            it.set(view, hashMapOf(R.id.login_btn to button))
        }
        view.initView()
        // 验证Button是否调用了setOnClickListener,由于在initView里面,使用的是匿名对象
        // 所以如果直接编写:Mockito.verify(button).setOnClickListener{},会测试失败
        // 这个时候就可以使用Mockito.any(View.OnClickListener::class.java)来充当View.OnClickListener对象
        Mockito.verify(button).setOnClickListener(Mockito.any(View.OnClickListener::class.java))

        // 如果View是Activity,并且使用的是findViewById的方式,则可以这样做
        // val delegate = Mockito.mock(AppCompatDelegate::class.java)
        // Mockito.`when`(view.delegate).thenReturn(delegate)
        // Mockito.`when`(delegate.findViewById<View>(R.id.login_btn)).thenReturn(Mockito.mock(Button::class.java))
        // 授人以鱼不如授人以渔,我所在的团队中,View并不是使用Activity的方式
        // 上面这段代码其实是看了Activity的findViewById之后写出来的,所以如果View是以其他形式出现
        // 可以去看该对象的源码,从而找到解决出异常的办法
    }

    @Test
    fun showLoading() {
        // 这里的AppCompatImageView是随便写的,只是为了让界面有一个View测试
        // 由于java api并不存在AppCompatImageView,所以只能mock出来,这里用spy的话,会出现一堆问题
        // 所以还是用mock比较方便
        val loadingView = Mockito.mock(AppCompatImageView::class.java)
        // 说实话,每次都要写这样一段代码,其实挺烦的,建议实际开发的时候抽成一个方法
        // 实际开发中,我就编写了一个工具,这样就可以减少重复代码的编写了
        view::class.java.getDeclaredField("_\$_findViewCache").also {
            it.isAccessible = true
            it.set(view, hashMapOf(R.id.loadingview to loadingView))
        }
        // 调用view.showLoading(RequestType.REQUEST_LOGIN),实际上会调用:loadingview.visibility = View.VISIBLE
        view.showLoading(RequestType.REQUEST_LOGIN)
        // 测试是否调用了visibility = View.VISIBLE
        Mockito.verify(loadingView).visibility = View.VISIBLE
    }

    @Test
    fun hideLoading() {
        val loadingView = Mockito.mock(AppCompatImageView::class.java)
        view::class.java.getDeclaredField("_\$_findViewCache").also {
            it.isAccessible = true
            it.set(view, hashMapOf(R.id.loadingview to loadingView))
        }
        // 如果在某些情况下,没办法使用类似上面的verify方式进行验证
        // 则可以使用这种方式辅助验证
        var visibility = -1
        Mockito.`when`(loadingView.setVisibility(View.GONE)).thenAnswer {
            if (it.arguments[0].equals(View.GONE)) {
                visibility = View.GONE
            }
        }
        view.hideLoading(RequestType.REQUEST_LOGIN)
        Assert.assertEquals(visibility, View.GONE)
    }

    @Test
    fun onLoginSuccess() {
        val textView = Mockito.mock(AppCompatTextView::class.java)
        view::class.java.getDeclaredField("_\$_findViewCache").also {
            it.isAccessible = true
            it.set(view, hashMapOf(R.id.result_tv to textView))
        }
        val model = LoginModel("u", "t")
        view.onLoginSuccess(model)
        Mockito.verify(textView).text = model.userName
    }
}

ViewModel测试

回到unit test-实例

MVP和MVVM一个比较大的区别是:MVVM使用了ViewModel对View进行更新,所以这里单独将ViewModel拿出来说明。
首先提醒一下,由于所在团队中不是使用databinding+viewmodel,所以不清楚如果代码这样写,test要怎么做,所以不提供相关示例。

TestViewModelActivity

class TestViewModelActivity : AppCompatActivity() {
    private lateinit var viewModel: TestViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test_view_model)
        viewModel = initViewModel()
        bindViewModel()
        setData()
    }

    fun initViewModel(): TestViewModel = ViewModelProvider(this)[TestViewModel::class.java]

    fun bindViewModel() {
        viewModel.text.observe(this) {
            textView.text = it
        }
    }

    fun setData() {
        viewModel.text.value = "test test test"
    }
}

TestViewModelActivityTest

class TestViewModelActivityTest {
    private lateinit var activity: TestViewModelActivity

    // 可以尝试把这行代码去掉,会发现测试setData方法的时候出现空指针异常
    // 因为LiveData最终通知数据更新离不开Handler,但这是Android的,所以google用了这个来解决这个问题
    @get:Rule
    val rule: TestRule = InstantTaskExecutorRule()

    @Before
    fun setUp() {
        activity = Mockito.spy(TestViewModelActivity())
    }

    @Test
    fun setData() {
        val viewModel = TestViewModel()
        // 塞一个ViewModel进去
        activity::class.java.getDeclaredField("viewModel").also {
            it.isAccessible = true
            it.set(activity, viewModel)
        }
        activity.bindViewModel()
        // 比较遗憾的是:调用这之后并不会执行Observer里面的代码
        activity.setData()
        // 但可以通过观察viewModel里面的变量的value来确实测试有没有通过
        Assert.assertEquals(viewModel.text.value, "test test test")
    }
}

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

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

相关文章

WPF布局控件之DockPanel

DockPanel DockPanel&#xff0c;英文释义为停靠面板&#xff0c;那是怎么个停靠法呢&#xff1f;如下&#xff1a; <Window x:Class"LearnLayout.DockPanelWin"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http:/…

数字图像处理

文章目录图像复原上升阶跃边缘、下降阶跃边缘、脉冲状边缘和屋顶状边缘曲线及其一阶导数和二阶导数有哪些特征&#xff1f;Hough变换的基本思想是什么&#xff1f;基本概念图像增强灰度变换直方图&#xff1a;直方图特点matlab代码空间域滤波平滑空间滤波均值滤波器&#xff1a…

python如何实现多线程

今天本来打算学习学习多进程的&#xff0c;但是由于我现在的电脑没有Linux系统&#xff0c;无法通过Linux系统编辑一些多进程的程序&#xff0c;因此我打算从多线程入手。 多线程 我们的程序一般都是多任务的&#xff0c;如果你没有好好的利用好&#xff0c;运行时就会出现卡…

【读论文】TCL: an ANN-to-SNN Conversion with Trainable Clipping Layers

DAC 2021 背景 通过ANN2SNN的方法得到的SNN中&#xff0c;存在准确性和延迟之间的一种权衡关系&#xff0c;在较大的数据集&#xff08;如ImageNet&#xff09;上可能会有较高的延迟。 主要贡献 分析了转换后SNN精度与延迟之间存在权衡关系的原因&#xff0c;并指出了如何缓…

6587: 计算分段函数

描述本题目要求计算下列分段函数f(x)的值&#xff1a;输入输入在一行中给出实数x。输出在一行中按“f(x) result”的格式输出&#xff0c;其中x与result都保留两位小数。样例输入10样例输出f(10.00) 3.16提示C/C可在头文件中包含math.h&#xff0c;并调用sqrt函数求平方根&am…

JAVA入门教程||Java Scanner 类||Java 异常处理

Java Scanner 类 java.util.Scanner是Java5的新特征&#xff0c;我们可以通过 Scanner 类来获取用户的输入。 下面是创建 Scanner 对象的基本语法&#xff1a; Scanner s new Scanner(System.in); 接下来我们演示一个最简单的的数据输入&#xff0c;并通过 Scanner 类的 nex…

产品分析丨豆瓣APP

本文将从以下几个方面进行分析&#xff1a;1. 产品功能结构2. 竞品分析3. 用户分析4. 用户调研5. 功能分析与优化方案6. 总结01 产品功能结构产品架构由豆瓣的产品架构图可看出&#xff0c;豆瓣是兼具书影音的评分系统和兴趣社区&#xff0c;以广告、知识付费和电商业务作为商业…

图解LeetCode——剑指 Offer 52. 两个链表的第一个公共节点

一、题目 输入两个链表&#xff0c;找出它们的第一个公共节点。 二、示例 如下面的两个链表&#xff1a; 在节点 c1 开始相交。 注意&#xff1a; 如果两个链表没有交点&#xff0c;返回 null.在返回结果后&#xff0c;两个链表仍须保持原有的结构。可假定整个链表结构中没…

代码质量与安全 | ChatGPT能帮到你什么还有待探索,但人工智能真的可以帮你做自动化测试

当听到“人工智能”&#xff08;AI&#xff09;时&#xff0c;你会想到什么&#xff1f; 你可能会开始想象科幻电影中的先进的人形机器人或者未来科技&#xff0c;但是&#xff0c;人工智能聊天机器人程序Chat GPT的爆火已经证明&#xff0c;这种“未来主义”技术已经融入了我…

三个月自学自动化测试,薪资15K直接翻倍,鬼知道我经历了什么····

学习软件测试是迫不得已&#xff0c;幸好最后通过自己的付出&#xff0c;得到了满意的回报。希望大家能通过我的经历得到一些帮助和思路。 零基础自学遇到的第一个难题就是需要怎么学&#xff0c;刚开始一头雾水&#xff0c;只能先从网上买些书来看&#xff0c;但是收效甚微。…

Unity中获取地形的法线

序之前&#xff0c;生成了地形图&#xff1a;(42条消息) 从灰度图到地形图_averagePerson的博客-CSDN博客那末&#xff0c;地形的法线贴图怎么获取&#xff1f;大概分为两个部分吧&#xff0c;先拿到法线数据&#xff0c;再画到纹理中去。关于法线计算Unity - Scripting API: M…

第4讲 cameraserver.rc详解(下)

本讲是Android Camera Native Framework专题的第4讲&#xff0c;我们介绍cameraserver.rc详解&#xff08;下&#xff09;&#xff0c;包括如下内容&#xff1a;Android init语言简介cameraserver.rc详解serviceuser选项group选项ioprio选项task_profiles选项rlimit选项更多&am…

领域自适应 DA Domain Adaptation

领域自适应是与机器学习和转移学习相关的领域。 当我们的目标是从源数据分布中学习在不同&#xff08;但相关&#xff09;的目标数据分布上的良好性能模型时&#xff0c;就会出现这种情况。 例如&#xff0c;常见垃圾邮件过滤问题的任务之一在于使模型从一个用户&#xff08;源…

2023年金三银四必备软件测试常见面试题1500问!!!【测试思维篇】

五、测试思维5.1 打电话功能怎么去测&#xff1f;我们会从几个方面去测试&#xff1a;界面、功能、兼容性、易用性、安全、性能、异常。1&#xff09;界面我们会测试下是否跟界面原型图一致&#xff0c;考虑浏览器不同显示比例&#xff0c;屏幕分辨率。2&#xff09;功能&#…

SpringBoot:SpringBoot配置文件application.properties、application.yml 和 application.ymal(2)

SpringBoot配置文件1. 配置文件格式1.1 application.properties配置文件1.2 application.yml配置文件1.3 application.yaml配置文件1.4 三种配置文件优先级和区别2. yaml格式2.1 语法规则2.2 yaml书写2.2.1 字面量&#xff1a;单个的、不可拆分的值2.2.2 数组&#xff1a;一组按…

《分布式技术原理与算法解析》学习笔记Day24

分布式缓存 在计算机领域&#xff0c;缓存是一个非常重要的、用来提升性能的技术。 什么是分布式缓存&#xff1f; 缓存技术是指用一个更快的存储设备存储一些经常用到的数据&#xff0c;供用户快速访问。 分布式缓存是指在分布式环境或者系统下&#xff0c;把一些热门数据…

全面零信任?Dell搞了个“大动作”

1860年&#xff0c;清朝僧格林沁带领数万骑兵&#xff0c;朝着数千英法联军发起猛烈冲锋&#xff0c;企图依靠清朝凶狠的骑兵突击战术击溃对方。然而&#xff0c;面对已经完成了近代化的西方军队&#xff0c;原来无往不利的八旗骑兵被打的土崩瓦解&#xff0c;再无任何抵抗的能…

Editor工具开发基础四:窗口EditorWindow

目录 1.设置窗口位置和大小 2.设置窗口最大和最小 3.设置窗口标题 4.设置窗中窗 BeginWindows和EndWindows 5.添加窗中窗可拖动 GUI.DragWindow() 完整代码&#xff1a; public class EditorToolWindow : EditorWindow {public static EditorToolWindow ins;[MenuItem(&q…

Elasticsearch:保护你的 Elasticsearch 实例 - 如何使用带有内置证书的 Docker 镜像

使用 docker 来构建 Elasticsearch 集群为开发者们带来了极大的方便。在我之前的文章中&#xff1a; Elasticsearch&#xff1a;使用 Docker compose 来一键部署 Elastic Stack 8.x Elasticsearch&#xff1a;如何在 Docker 上运行 Elasticsearch 8.x 进行本地开发 Elastic&am…

JavaSE-线程池(5)- ThreadPoolExecutor常用方法

JavaSE-线程池&#xff08;5&#xff09;- ThreadPoolExecutor常用方法 invokeAll ExecutorService 接口中定义的方法&#xff0c;给定一组任务&#xff0c;在所有任务执行完成时返回一个 Futures 列表&#xff0c;其中包含它们的状态和结果。 /*** Executes the given task…