当前位置: 首页 > 编程日记 > 正文

【译】使用Kotlin和RxJava测试MVP架构的完整示例 - 第1部分

原文链接:android.jlelse.eu/complete-ex…

最近我创建了一个playground项目来了解更多关于Kotlin和RxJava的信息。 这是一个非常简单的项目,但有一部分,我进行了一些尝试:测试。

在kotlin的测试上可能会有一些陷阱,而且由于它是新出的,所以没有太多的例子。 我认为分享我的经验帮助你来避免踩坑是一个好主意。

关于架构

该应用程序遵循基本MVP架构。 它使用Dagger2进行依赖注入,RxJava2用于数据流。

这些库根据不同的条件提供来自网络或本地存储的数据。 我们使用Retrofit进行网络请求,以及Room作为本地数据库。

我不会详细讲解架构和这些工具。 我想大多数人已经熟悉了他们。 您可以在此提交中查看:

github.com/kozmi55/Kot…

我们将从测试数据库开始,然后向上层测试。

测试数据库

对于数据库,我们使用Android架构组件中的Room Persistence Library。 它是SQLite上的抽象层,可以减少样板代码。

这是最简单的部分。 我们不需要对Kotlin或RxJava做任何具体的事情。 我们先来看看UserDao界面的代码,以决定我们应该测试什么。

@Dao
interface UserDao {@Query("SELECT * FROM user ORDER BY reputation DESC LIMIT (:arg0 - 1) * 30, 30")fun getUsers(page: Int) : List<User>@Insert(onConflict = OnConflictStrategy.REPLACE)fun insertAll(users: List<User>)
}复制代码

getUsers函数根据页码从数据库中请求下一个30个用户。

insertAll插入列表中的所有用户。

我们可以从这里发现几件事情,需要测试什么:

  • 检查插入的用户是否与检索到的用户相同。
  • 检查检索用户正确排序。
  • 检查我们是否插入具有相同ID的用户,它将替换旧的记录。
  • 检查是否查询页面,最多可以有30个用户。
  • 检查我们是否查询第二页,我们将获得正确数量的元素。

下面的代码片段显示了5例这样的实现。

@RunWith(AndroidJUnit4::class)
class UserDaoTest {lateinit var userDao: UserDaolateinit var database: AppDatabase@Beforefun setup() {val context = InstrumentationRegistry.getTargetContext()database = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java).build()userDao = database.userDao()}@Afterfun tearDown() {database.close()}@Testfun testInsertedAndRetrievedUsersMatch() {val users = listOf(User(1, "Name", 100, "url"), User())userDao.insertAll(users)val allUsers = userDao.getUsers(1)assertEquals(users, allUsers)}@Testfun testUsersOrderedByCorrectly() {val users = listOf(User(1, "Name", 100, "url"),User(2, "Name2", 500, "url"),User(3, "Name3", 300, "url"))userDao.insertAll(users)val allUsers = userDao.getUsers(1)val expectedUsers = users.sortedByDescending { it.reputation }assertEquals(expectedUsers, allUsers)}@Testfun testConflictingInsertsReplaceUsers() {val users = listOf(User(1, "Name", 100, "url"),User(2, "Name2", 500, "url"),User(3, "Name3", 300, "url"))val users2 = listOf(User(1, "Name", 1000, "url"),User(2, "Name2", 700, "url"),User(4, "Name3", 5500, "url"))userDao.insertAll(users)userDao.insertAll(users2)val allUsers = userDao.getUsers(1)val expectedUsers = listOf(User(4, "Name3", 5500, "url"),User(1, "Name", 1000, "url"),User(2, "Name2", 700, "url"),User(3, "Name3", 300, "url"))assertEquals(expectedUsers, allUsers)}@Testfun testLimitUsersPerPage_FirstPageOnly30Items() {val users = (1..40L).map { User(it, "Name $it", it *100, "url") }userDao.insertAll(users)val retrievedUsers = userDao.getUsers(1)assertEquals(30, retrievedUsers.size)}@Testfun testRequestSecondPage_LimitUsersPerPage_showOnlyRemainingItems() {val users = (1..40L).map { User(it, "Name $it", it *100, "url") }userDao.insertAll(users)val retrievedUsers = userDao.getUsers(2)assertEquals(10, retrievedUsers.size)}
}复制代码

在setup方法中,我们需要配置我们的数据库。 在每次测试之前,我们使用Room的内存数据库创建一个干净的数据库。

测试在这里非常简单,不需要进一步解释。 我们在每个测试中遵循的基本模式如
下所示:

  1. 将数据插入数据库
  2. 从数据库查询数据
  3. 对所检索的数据作出断言

我们可以使用Kotlin Collections API中的函数来简化测试数据的创建,就像这部分代码一样:

val users = (1..40L).map { User(it, "Name $it", it *100, "url") }复制代码

我们创建了一个范围,然后将其映射到用户列表。 这里有多个Kotlin概念:范围,高阶函数,字符串模板。

Commit: github.com/kozmi55/Kot…

测试UserRepository

对于repository和interactor,我们将使用相同的工具。

  • 使用Mockit模拟类的依赖。
  • TestObserver用于测试Observables(在我们的例子中是Singles)

但首先我们需要启用该选项来mock最终的类。 在kotlin里,默认情况下每个class都是final的。 幸运的是,Mockito 2已经支持模拟 final class,但是我们需要启用它。

我们需要在以下位置创建一个文本文件:test / resources / mockito-extensions /,名称为org.mockito.plugins.MockMaker,并附带以下文本:mock-maker-inline

Place of the file in Project view

现在我们可以开始使用Mockito来编写我们的测试。 首先,我们将添加最新版本的Mockito和JUnit。

testImplementation 'org.mockito:mockito-core:2.8.47'
testImplementation 'junit:junit:4.12'复制代码

UserRepository的代码如下:

class UserRepository(private val userService: UserService,private val userDao: UserDao,private val connectionHelper: ConnectionHelper,private val preferencesHelper: PreferencesHelper,private val calendarWrapper: CalendarWrapper) {private val LAST_UPDATE_KEY = "last_update_page_"fun getUsers(page: Int, forced: Boolean): Single<UserListModel> {return Single.create<UserListModel> { emitter: SingleEmitter<UserListModel> ->if (shouldUpdate(page, forced)) {loadUsersFromNetwork(page, emitter)} else {loadOfflineUsers(page, emitter)}}}private fun shouldUpdate(page: Int, forced: Boolean) = when {forced -> true!connectionHelper.isOnline() -> falseelse -> {val lastUpdate = preferencesHelper.loadLong(LAST_UPDATE_KEY + page)val currentTime = calendarWrapper.getCurrentTimeInMillis()lastUpdate + Constants.REFRESH_LIMIT < currentTime}}private fun loadUsersFromNetwork(page: Int, emitter: SingleEmitter<UserListModel>) {try {val users = userService.getUsers(page).execute().body()if (users != null) {userDao.insertAll(users.items)val currentTime = calendarWrapper.getCurrentTimeInMillis()preferencesHelper.saveLong(LAST_UPDATE_KEY + page, currentTime)emitter.onSuccess(users)} else {emitter.onError(Exception("No data received"))}} catch (exception: Exception) {emitter.onError(exception)}}private fun loadOfflineUsers(page: Int, emitter: SingleEmitter<UserListModel>) {val users = userDao.getUsers(page)if (!users.isEmpty()) {emitter.onSuccess(UserListModel(users))} else {emitter.onError(Exception("Device is offline"))}}
}复制代码

getUsers方法中,我们创建一个Single,它会发送users或一个error。 根据不同的条件,shouldUpdate方法决定用户是否应该从网络加载或从本地数据库加载。

还有一点需要注意的是CalendarWrapper字段。 这是一个简单的包装器,有一个返回当前时间的方法。 在它帮助下,我们可以模拟我们测试的时间。

那么我们应该在这里测试什么? 在这里最重要的测试是在shouldUpdate方法背后的逻辑。 让我们为它做一些测试。

测试这个的方法是先调用getUsers方法,并在返回的Single去调用test方法。 test方法会创建一个TestObserver并将其订阅到Single

TestObserver是一种特殊类型的Observer,它记录事件并允许对它们进行断言。

我们还必须模拟UserRepository的依赖关系,并且存储一些他们的方法来返回指定的数据。 我们可以像在Java中一样使用Mockito,或者使用Niek Haarman的Mockito-Kotlin库。 我们将在这个例子中使用Mockito,但如果您好奇,可以检查Github资料库。

如果我们要使用Mockito的when方法,我们需要把它放在反引号之间,因为它是Kotlin中的保留字。 为了使这看起来更好,我们可以使用as关键字引入具有不同名称的when方法。

import org.mockito.Mockito.`when` as whenever复制代码

现在我们可以使用whenever方法进行stubbing。

class UserRepositoryTest {@Mocklateinit var mockUserService: UserService@Mocklateinit var mockUserDao: UserDao@Mocklateinit var mockConnectionHelper: ConnectionHelper@Mocklateinit var mockPreferencesHelper: PreferencesHelper@Mocklateinit var mockCalendarWrapper: CalendarWrapper@Mocklateinit var mockUserCall: Call<UserListModel>@Mocklateinit var mockUserResponse: Response<UserListModel>lateinit var userRepository: UserRepository@Beforefun setup() {MockitoAnnotations.initMocks(this)userRepository = UserRepository(mockUserService, mockUserDao, mockConnectionHelper, mockPreferencesHelper, mockCalendarWrapper)}@Testfun testGetUsers_isOnlineReceivedOneItem_emitListWithOneItem() {val userListModel = UserListModel(listOf(User()))setUpStubbing(true, 1000 * 60 * 60 * 12 + 1, 0, modelFromUserService = userListModel)val testObserver = userRepository.getUsers(1, false).test()testObserver.assertNoErrors()testObserver.assertValue { userListModelResult: UserListModel -> userListModelResult.items.size == 1 }verify(mockUserDao).insertAll(userListModel.items)}@Testfun testGetUsers_isOfflineOneItemInDatabase_emitListWithOneItem() {val modelFromDatabase = listOf(User())setUpStubbing(false, 1000 * 60 * 60 * 12 + 1, 0, modelFromDatabase = modelFromDatabase)val testObserver = userRepository.getUsers(1, false).test()testObserver.assertNoErrors()testObserver.assertValue { userListModelResult: UserListModel -> userListModelResult.items.size == 1 }}private fun setUpStubbing(isOnline: Boolean, currentTime: Long, lastUpdateTime: Long,modelFromUserService: UserListModel = UserListModel(emptyList()),modelFromDatabase: List<User> = emptyList()) {whenever(mockConnectionHelper.isOnline()).thenReturn(isOnline)whenever(mockCalendarWrapper.getCurrentTimeInMillis()).thenReturn(currentTime)whenever(mockPreferencesHelper.loadLong("last_update_page_1")).thenReturn(lastUpdateTime)whenever(mockUserService.getUsers(1)).thenReturn(mockUserCall)whenever(mockUserCall.execute()).thenReturn(mockUserResponse)whenever(mockUserResponse.body()).thenReturn(modelFromUserService)whenever(mockUserDao.getUsers(1)).thenReturn(modelFromDatabase)}
}复制代码

以上我们可以看到UserRepositoryTest的代码。 我们在这个例子中使用Mockito注解来初始化mocks,但是可以用不同的方法来完成。 每个测试包括3个步骤:

  1. 指定stubbed方法返回什么值。 我们使用setUpStubbing私有方法来避免我们的测试中的样板代码。 我们可以在每个具有不同参数的测试用例中调用此方法,这取决于正在测试的状态。 Kotlin的默认参数在这里非常有用,因为有时我们不需要指定每个参数。
  2. 调用getUsers方法,并通过在返回的Single上调用test方法来获取一个TestObserver。
  3. TestObserver或模拟对象上进行一些断言以验证预期的行为。 在这个例子中,我们使用assertNoErrors方法来验证Single不会发出错误。 我们使用的另一种方法是assertValue。 有了它的帮助,我们可以断言Single发出的值是不是正确。 执行此操作的方式是将lambda传递给assertValue方法,该方法返回一个布尔值。 如果它返回true,则断言将通过。 在这种情况下,我们验证发出的列表包含1个元素。 有很多其他方法可以在TestObserver上做出断言,这些可以在TestObserver的超类BaseTestConsumer的文档中找到。

在此提交中可以找到这些更改:

github.com/kozmi55/Kot…

测试 GetUsers interactor

测试GetUsers interactor的方法类似于我们用来测试UserRepository的方法。

GetUsers是一个非常简单的类,它的目的是将data层中的数据转换为presentation层中的数据。

class GetUsers(private val userRepository: UserRepository) {fun execute(page: Int, forced: Boolean) : Single<List<UserViewModel>> {val usersList = userRepository.getUsers(page, forced)return usersList.map { userListModel: UserListModel? ->val items = userListModel?.items ?: emptyList()items.map { UserViewModel(it.userId, it.displayName, it.reputation, it.profileImage) }}}
}复制代码

我们使用RxJava和Kotlin Collection API中的一些转换来实现想要的结果。

来看看我们的测试长什么样:

class GetUsersTest {@Mocklateinit var mockUserRepository: UserRepositorylateinit var getUsers: GetUsers@Beforefun setup() {MockitoAnnotations.initMocks(this)getUsers = GetUsers(mockUserRepository)}@Testfun testExecute_userListModelWithOneItem_emitListWithOneViewModel() {val userListModel = UserListModel(listOf(User(1, "Name", 100, "Image url")))setUpStubbing(userListModel)val testObserver = getUsers.execute(1, false).test()testObserver.assertNoErrors()testObserver.assertValue { userViewModels: List<UserViewModel> -> userViewModels.size == 1 }testObserver.assertValue { userViewModels: List<UserViewModel> ->userViewModels.get(0) == UserViewModel(1, "Name", 100, "Image url") }}@Testfun testExecute_userListModelEmpty_emitEmptyList() {val userListModel = UserListModel(emptyList())setUpStubbing(userListModel)val testObserver = getUsers.execute(1, false).test()testObserver.assertNoErrors()testObserver.assertValue { userViewModels: List<UserViewModel> -> userViewModels.isEmpty() }}private fun setUpStubbing(userListModel: UserListModel) {val fakeSingle = Single.create { e: SingleEmitter<UserListModel>? ->e?.onSuccess(userListModel) }whenever(mockUserRepository.getUsers(1, false)).thenReturn(fakeSingle)}
}复制代码

唯一的区别在于,我们创建一个假的从getUsers方法返回的Single对象。 我们使用Single将UserListModel发送给setUpStubbing方法,在这里我们创建了假的Single,并将其设置为getUsers方法的返回值。

剩下的代码使用与UserRepositoryTest中相同的概念。

Commit在这:github.com/kozmi55/Kot…

这是第一部分。 我们学习了如何在Kotlin测试中使用RxJava来处理一些常见问题,如何利用一些Kotlin功能来编写更简单的测试,并且还可以看看如何测试Room数据库。

在第二部分中,我将向您展示如何在TestScheduler的帮助下测试Presenter,以及如何使用Espresso和假数据来进行UI测试。 敬请关注。

Thanks for reading my article.

相关文章:

智能改变未来,创新引领世界,第二届深圳国际人工智能展暨智能制造创新高峰论坛盛大启幕!

2021年5月20日&#xff0c;由深圳市科学技术协会、深圳市商务局、深圳市福田区人民政府共同指导&#xff0c;深圳市科技开发交流中心、深圳市人工智能行业协会联合主办的2021第二届深圳国际人工智能展开幕式暨智能制造创新高峰论坛在深圳会展中心&#xff08;福田&#xff09;启…

C#循环控制语句

本节课将介绍如何使用C#控制语句中的循环语句&#xff0c;本课目的如下&#xff1a; 1.学会"while"循环的用法。2.学会"do" 循环的用法。3.学会"for" 循环的用法。4.学会foreach循环的用法。5.进一步了解"break"语句的用法。6.如何使用…

2017-09-22 前端日报

2017-09-22 前端日报 精选 JavaScript 在 V8 中的元素种类及性能优化【译】异步递归&#xff1a;回调、Promise、Async[译]HTML&CSS Lesson5: 定位一个页面阻塞问题的排查过程前端分享之cookie的使用及单点登录An event for CSS position:stickyanvaka/ngraph.path: Path f…

C#选择控制语句

本节课将介绍如何使用C#选择控制语句&#xff0c;第三课将达到如下几个目的&#xff1a; 1.学会"if"语句的用法。2.学会"switch"语句的用法。3.学会在"switch"语句中如何使用"break"语句。4.理解"goto"语句的正确用法。在前…

将博客搬至51CTO

将博客搬至51CTO转载于:https://blog.51cto.com/imace/1540730

腾讯国风AI虚拟人学会作诗书法,背靠开源模型SongNet

5月21日&#xff0c;腾讯AI虚拟人艾灵再秀出新技能&#xff0c;首次展示AI作诗、AI书法等国风才艺&#xff0c;并与青年歌手白举纲跨次元合作&#xff0c;共同演唱国风新歌《百川千仞》。 AI“艾灵”诞生于腾讯AI Lab&#xff0c;来自实验性、探索性技术项目“多模态虚拟人”。…

Windows10安装Mysql5.7.19.0 msi 版本报错

安装环境&#xff1a;Windows10安装版本&#xff1a;MySql 5.7.19.0 msi1.安装5.7.19.0 msi版本Mysql时报如下错误&#xff1a;2.根据日志分析是缺少visual Studio 2013 Redistributable3.下载完成后&#xff0c;安装仍然显示失败&#xff1a;4.在网上下载各种vs测试&#xff0…

C#简单的欢迎程序

本节课通过介绍几个简单的程序&#xff0c;使得你对C#有所入门。本节程要达到如下几个目的&#xff1a; 1.理解一个C#程序的基本结构。2.初步了解"名称空间"的概念。3.初步了解"类"的概念。4.了解"Main"方法所做的工作。5.学会如何读取命令行输入…

知乎联合清华:开放国内最大个性化推荐实际交互数据集

5月21日&#xff0c;知乎联合清华大学对外开放基于知乎的大规模富文本查询和推荐数据集“ZhihuRec”。该数据集包含了知乎上的1亿个行为数据&#xff0c;是目前为止&#xff0c;国内用于个性化推荐的最大的实际交互数据集。 作为一个大型数据集&#xff0c;ZhihuRec具有社交化问…

SQL Server 2014 许可证(五)降级与升级

“版本”一词对应的英文单词有两个&#xff1a;&#xff08;1&#xff09; Version是指不同历史时期发生的产品&#xff0c;或者指产品不同的“代”&#xff0c;例如&#xff0c;SQL Server 2014 版本。&#xff08;2&#xff09; Edition是指在发行同一代产品&#xff08;Vers…

OCM_第十二天课程:Section6 —》数据库性能调优_ 资源管理器/执行计划

注&#xff1a;本文为原著&#xff08;其内容来自 腾科教育培训课堂&#xff09;。阅读本文注意事项如下&#xff1a;1&#xff1a;所有文章的转载请标注本文出处。2&#xff1a;本文非本人不得用于商业用途。违者将承当相应法律责任。3&#xff1a;该系列文章目录列表&#xf…

赠书 | 联邦学习如何在视觉领域应用?

前言&#xff1a;联邦学习是如何应用在视觉领域的&#xff1f;本文将通过一个获得了2020年AAAI人工智能创新应用奖的案例来向大家介绍。本案例是联邦学习在视觉、物联网、安防领域的实际应用&#xff0c;对分散在各地的摄像头数据&#xff0c;通过联邦学习&#xff0c;构建一个…

AME_Oracle自带AME审批链详解AME Standard Handler(概念)

2014-05-30 Created By BaoXinJian Oracle 自带了3大类&#xff0c;13个子类的审批链Action Type, 对应了13个标准的AME Standard Handler 1. 按主管层次审批 absolute job level / chains of authority based on absolute job levelfinal approver only / chains of authorit…

c# 中如何定义和接收消息

在C#中目前我还没有找到发送消息的类成员函数&#xff0c;所以只能采用通过调用WIN 32 API 的 SendMessage() 函数实现。由于 SendMessage的参数中需要得到窗体的句柄(handler) &#xff0c;所以又要调用另一个API FindWindow(), 两者配合使用&#xff0c;达到在不同窗体之间的…

java如何读写json文件

java如何读写json文件 在实际项目开发中&#xff0c;有时会遇到一些全局的配置缓存&#xff0c;最好的做法是配置redis数据库作为数据缓存&#xff0c;而当未有配置redis服务器时&#xff0c;读取静态资源文件&#xff08;如xml、json等&#xff09;也是一种实现方式&#xff0…

C#数组篇讲解

数组是我们经常用到的&#xff0c;我来介绍一下&#xff1a;数组是具有相同类型的一组数据。当访问数组中的数据时&#xff0c;可以通过下标来指明。c#中数组元素可以为任何数据类型&#xff0c;数组下标从0开始&#xff0c;即第一个元素对应的下标为0&#xff0c;以后逐个递增…

Spring AOP详解(转载)所需要的包

上一篇文章中&#xff0c;《Spring Aop详解&#xff08;转载&#xff09;》里的代码都可以运行&#xff0c;只是包比较多&#xff0c;中间缺少了几个相应的包&#xff0c;根据报错&#xff0c;几经百度搜索&#xff0c;终于补全了所有包。 截图如下&#xff1a; 在主测试类里面…

Mendix 披露低代码方法论,解读真实技术趋势

作者 | 宋慧头图 | 下载于视觉中国出品 | AI 科技大本营&#xff08;ID:rgznai100&#xff09;在 2021年初正式宣布进入中国市场之后&#xff0c;Mendix 在近日向媒体重点披露了关于低代码的技术方法论&#xff0c;以及近四个月在中国市场的进展。Mendix 的低代码技术方法论对于…

PHP中foreach详细解读

oreach 语法结构提供了遍历数组的简单方式。foreach 仅能够应用于数组和对象&#xff0c;如果尝试应用于其他数据类型的变量&#xff0c;或者未初始化的变量将发出错误信息。有两种语法&#xff1a; foreach (array_expression as $value) statement foreach (array_expression…

Android ViewPager使用具体解释

这是谷歌官方给我们提供的一个兼容低版本号安卓设备的软件包&#xff0c;里面包囊了仅仅有在安卓3.0以上能够使用的api。而viewpager就是当中之中的一个利用它&#xff0c;我们能够做非常多事情&#xff0c;从最简单的导航&#xff0c;到页面菜单等等。那怎样使用它呢&#xff…

实时音视频的超级风口,开发者的机会在哪里?

2021年初因为Elon Musk“带货”而走红的音频社交App Clubhouse&#xff0c;又以肉眼可见的速度跌落神坛&#xff0c;下载量从2月的960 万/月跌至4月的92万/月。不过在5月&#xff0c;Clubhouse终于推出了安卓版&#xff0c;并表示接下来也会对所有用户开放。 另一边&#xff0c…

高可用集群之分布式文件系统

一、分布式文件系统和单机文件系统的区别&#xff1a;单机文件系统的分区只能被一台主机所挂载&#xff0c;不能同时被多台主机挂载使用&#xff0c;因为单机文件系统是通过系统内核层的锁机制来完成的&#xff0c;所以一个系统上可以有多个进程访问&#xff0c;但只能在一个时…

C#中基本知识

当数据转换到asp.net页面时&#xff0c;大部分都是以文本的形式存在的。有时候为了输出&#xff0c;单单使用显示或者隐式转换都是不行的&#xff0c;就需要本节说到的数据转换。 字符串输出&#xff1a; int intAge21; string strAgeintAge.ToString(); 转换DateTime值时&…

索引系列八--索引特性之有序难优化union

----UNION 是需要排序的drop table t1 purge;create table t1 as select * from dba_objects where object_id is not null;alter table t1 modify OBJECT_ID not null;drop table t2 purge;create table t2 as select * from dba_objects where object_id is not null;alter t…

OpenCV 实战:3 步实现图像降噪

来源 | 小白视觉志 头图 | 下载于视觉中国本文将展示如何通过三个简单的步骤来实现降噪。我们将使用机器学习训练的降噪模型&#xff0c;最好的降噪模型之一。程序可以判断图像是否有噪点吗&#xff1f;这应该是一个很有创意的想法&#xff0c;因为我们的降噪模型不够智能&…

一条数字链路连接的端口无法UP及后续相应故障的排除

故障现象1&#xff1a; 运营商检测线路正常&#xff0c;但是端口无法up。 解决办法&#xff1a; 1、翻转端口 2、将端口速率强制为1000M&#xff0c;全双工#speed 1000 #dup full 端口正常UP。 故障现象2&#xff1a; 线路丢包5%。 解决办法&#xff1a; 1、检查端口光功率&…

华为:HarmonyOS 即将开源!

整理 | 晋兆雨出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;5 月 24 日&#xff0c;在鸿蒙开发者创新大赛颁奖典礼上&#xff0c;华为消费者业务软件部总裁王成录宣布&#xff1a;HarmonyOS 将会开源开放&#xff0c;并在武汉大学等15个学校开展 HarmonyOS 课程。…

C#实现网段扫描

摘要 想必大家对小榕时光等扫描器都非常熟悉了&#xff0c;有没有自己写一个的冲动。最近微软推实施了.NET战略方案&#xff0c;C#是主推语言&#xff0c;你们是否有兴趣用C#来实现对局域网IP地址的扫描&#xff0c;尝试一下自己写的快乐&#xff0c;那么请跟我来。 正文 1.先介…

sbt配置nexus仓库

2019独角兽企业重金招聘Python工程师标准>>> 最近学习Scala&#xff0c;不可避免的要用到sbt。爱折腾的我把原本比较简单的事情搞的复杂了&#xff0c;来来回回搞了好久&#xff0c;记录下来&#xff0c;有同样爱折腾的盆友可以参考下。 sbt在windows下如果是默认安…

C#编程(十二)----------函数

类和结构 类和结构实际上都是创建对象的模板 ,每 个对象都包含数据 ,并 提供了处理和访问数据的方法。 类定义了类的每个对象 (称 为实例 )可 以包含什么数据和功能 。 例如 ,如 果 一 个类表示 一 个顾客 ,就可以定义字段 CustomerID、 FirstName、 LastNane和 Address,以 包含…