服装购物网站策划书,买东西网站有哪些,高端品牌内衣,wordpress亮相翻译自 https://medium.com/mindorks/how-to-unit-test-private-methods-in-java-and-kotlin-d3cae49dccd ❓如何单元测试 Kotlin/Java 中的 private 方法❓
首先#xff0c;开发者应该测试代码里的 private 私有方法吗#xff1f;
直接信任这些私有方法#xff0c;测试到… 翻译自 https://medium.com/mindorks/how-to-unit-test-private-methods-in-java-and-kotlin-d3cae49dccd ❓如何单元测试 Kotlin/Java 中的 private 方法❓
首先开发者应该测试代码里的 private 私有方法吗
直接信任这些私有方法测试到调用它们的公开方法感觉就够了吧。
对于这个争论每个开发者都会有自己的观点。
但回到开头的问题本身到底有没有一种合适的途径来实现私有方法的单元测试
截止到目前在面对单元测试私有方法的问题时一般有如下几种选择 不去测试私有方法 *选择信任直接躺平* 将目标方法临时改成 public 公开访问权限 可我不愿意这样做这不符合代码规范。作为一名开发者我要遵循最佳实践 使用嵌套的测试类 *将测试代码和生产代码混到一起不太好吧我再强调一遍我是很优秀的开发者要遵循最佳实践* 使用 Java 反射机制 *听起来还行可以试试这个方案*
大家都知道通过 Java 反射机制可以访问到其他类中的私有属性和方法而且写起来也不麻烦在单元测试里采用该机制应该也很容易上手。
注意
只有将代码作为独立的 Java 程序运行时这个方案才适用就像单元测试、常规的 Java 应用程序。但如果在 Java Applet 上执行反射则需要对 SecurityManager 做些干预。由于这不是高频场景本文不对其作额外阐述。 Java 8 中添加了对反射方法参数的支持使得开发者可以在运行时获得参数名称。 访问私有属性
Class 类提供的 getField(String name) 和 getFields() 只能返回公开访问权限的属性访问私有权限的属性则需要调用 getDeclaredField(String name) 或 getDeclaredFields()。
下面是一个简单的代码示例一个拥有私有属性的类以及如何通过 Java 反射来访问这个属性。
public class PrivateObject {private String privateString null;public PrivateObject(String privateString) {this.privateString privateString;}
}PrivateObject privateObject new PrivateObject(The Private Value);
Field privateStringField PrivateObject.class.getDeclaredField(privateString);privateStringField.setAccessible(true);String fieldValue (String) privateStringField.get(privateObject);System.out.println(fieldValue fieldValue);上述代码将打印出如下结果内容来自于 PrivateObject 实例的私有属性 privateString 的值。
fieldValue The Private Value需要留意的是getDeclaredField(privateString) 能返回私有属性没错但其范围仅限 class 本身不包含其父类中定义的属性。
还有一点是需要调用 Field.setAcessible(true)目的在于关闭反射里该 Field 的访问检查。
这样的话如果访问的属性是私有的、受保护的或者包可见的即使调用者不满足访问条件仍然可以在反射里获取到该属性。当然非反射的正常代码里依然无法获取到该属性不受影响。
访问私有方法
和访问私有属性一样访问私有方法需要调用 Class 类提供的 getDeclaredMethod(String name, Class[] parameterTypes) 或 Class.getDeclaredMethods()。
同样的我们展示一段代码示例定义了私有方法的类以及通过反射访问它。
public class PrivateObject {private String privateString null;public PrivateObject(String privateString) {this.privateString privateString;}private String getPrivateString(){return this.privateString;}
}PrivateObject privateObject new PrivateObject(The Private Value);
Method privateStringMethod PrivateObject.class.getDeclaredMethod(getPrivateString, null);privateStringMethod.setAccessible(true);String returnValue (String)
privateStringMethod.invoke(privateObject, null);System.out.println(returnValue returnValue);打印出的结果来自于 PrivateObject 实例中私有方法 getPrivateString() 的调用结果。
returnValue The Private Value注意点和访问私有属性一样
getDeclaredMethod() 存在 class 本身的范围限制不能获取到父类中定义的任何方法需要调用 Method.setAcessible(true) 来关闭反射中的 Method 的访问权限检查确保即便不满足访问条件亦能在反射中成功访问
了解完通过反射来访问私有属性、方法的知识之后让我们用在 unit test 中来测试本来难以覆盖到的私有方法。
LoginPresenter.kt
比如我们的代码库中存在如下类 LoginPresenter并且咱们想要去单元测试其私有方法 saveAccount()。
class LoginPresenter Inject constructor(private val view: LoginView,private val strategy: CancelStrategy,private val navigator: AuthenticationNavigator,private val tokenRepository: TokenRepository,private val localRepository: LocalRepository,private val settingsInteractor: GetSettingsInteractor,private val analyticsManager: AnalyticsManager,private val saveCurrentServer: SaveCurrentServerInteractor,private val saveAccountInteractor: SaveAccountInteractor,private val factory: RocketChatClientFactory,val serverInteractor: GetConnectingServerInteractor
) {private var currentServer serverInteractor.get() ?: defaultTestServerprivate val token tokenRepository.get(currentServer)private lateinit var client: RocketChatClientprivate lateinit var settings: PublicSettingsfun setupView() {setupConnectionInfo(currentServer)setupForgotPasswordView()}private fun setupConnectionInfo(serverUrl: String) {currentServer serverUrlclient factory.get(currentServer)settings settingsInteractor.get(currentServer)}private fun setupForgotPasswordView() {if (settings.isPasswordResetEnabled()) {view.showForgotPasswordView()}}fun authenticateWithUserAndPassword(usernameOrEmail: String, password: String) {launchUI(strategy) {view.showLoading()try {val token retryIO(login) {when {settings.isLdapAuthenticationEnabled() -client.loginWithLdap(usernameOrEmail, password)usernameOrEmail.isEmail() -client.loginWithEmail(usernameOrEmail, password)else -client.login(usernameOrEmail, password)}}val myself retryIO(me()) { client.me() }myself.username?.let { username -val user User(id myself.id,roles myself.roles,status myself.status,name myself.name,emails myself.emails?.map { Email(it.address ?: , it.verified) },username username,utcOffset myself.utcOffset)localRepository.saveCurrentUser(currentServer, user)saveCurrentServer.save(currentServer)localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, username)saveAccount(username)saveToken(token)analyticsManager.logLogin(AuthenticationEvent.AuthenticationWithUserAndPassword,true)view.saveSmartLockCredentials(usernameOrEmail, password)navigator.toChatList()}} catch (exception: RocketChatException) {when (exception) {is RocketChatTwoFactorException - {navigator.toTwoFA(usernameOrEmail, password)}else - {analyticsManager.logLogin(AuthenticationEvent.AuthenticationWithUserAndPassword,false)exception.message?.let {view.showMessage(it)}.ifNull {view.showGenericErrorMessage()}}}} finally {view.hideLoading()}}}fun forgotPassword() navigator.toForgotPassword()private fun saveAccount(username: String) {val icon settings.favicon()?.let {currentServer.serverLogoUrl(it)}val logo settings.wideTile()?.let {currentServer.serverLogoUrl(it)}val thumb currentServer.avatarUrl(username, token?.userId, token?.authToken)val account Account(settings.siteName() ?: currentServer,currentServer,icon,logo,username,thumb)saveAccountInteractor.save(account)}private fun saveToken(token: Token) tokenRepository.save(currentServer, token)
}LoginPresenterTest.kt
单元测试的整体如下
class LoginPresenterTest {private val view mock(LoginView::class.java)private val strategy mock(CancelStrategy::class.java)private val navigator mock(AuthenticationNavigator::class.java)private val tokenRepository mock(TokenRepository::class.java)private val localRepository mock(LocalRepository::class.java)private val settingsInteractor mock(GetSettingsInteractor::class.java)private val analyticsManager mock(AnalyticsManager::class.java)private val saveCurrentServer mock(SaveCurrentServerInteractor::class.java)private val saveAccountInteractor mock(SaveAccountInteractor::class.java)private val factory mock(RocketChatClientFactory::class.java)private val serverInteractor mock(GetConnectingServerInteractor::class.java)private val token mock(Token::class.java)const val currentServer: String https://open.rocket.chatconst val USERNAME: String user121const val PASSWORD: String 123456lateinit var loginPresenter: LoginPresenterprivate val account Account(currentServer, currentServer, null,null, USERNAME, UPDATED_AVATAR)Beforefun setUp() {MockitoAnnotations.initMocks(this)when(strategy.isTest).thenReturn(true)when(serverInteractor.get()).thenReturn(currentServer)loginPresenter LoginPresenter(view, strategy, navigator, tokenRepository, localRepository, settingsInteractor,analyticsManager, saveCurrentServer, saveAccountInteractor, factory, serverInteractor)}Testfun check account is saved() {...}
}通过反射机制私有方法 saveAccount() 的单测则可以很方便地进行。
class LoginPresenterTest {...Testfun check account is saved() {loginPresenter.setupView()val method loginPresenter.javaClass.getDeclaredMethod(saveAccount, String::class.java)method.isAccessible trueval parameters arrayOfNullsAny(1)parameters[0] USERNAMEmethod.invoke(loginPresenter, *parameters)verify(saveAccountInteractor).save(account)}
}本文浅显易懂希望能向你展示反射的魔力帮助开发者在单元测试中优雅、便捷地 cover 到私有方法
最后感谢你的阅读。