我是否需要模拟不调用的接口,例如用户名和密码字段为空?我试图先编写测试,但对于是否应使用模拟方法感到困惑。
我的登录测试
private val authRepository: AuthRepository = mockk()
private val userManager: AccountManager = mockk()
private lateinit var authUseCase: AuthUseCase
@BeforeEach
fun setUp() {
clearMocks(authRepository)
clearMocks(userManager)
authUseCase = AuthUseCase(authRepository, userManager)
}
/**
* Scenario: Login check with empty fields:
* * Given I am on the login page
* * When I enter empty username
* And I enter empty password
* And I click on the "Login" button
* * Then I get empty fields error.
*/
@Test
fun `Empty fields result empty fields error`() {
// Given
// When
val expected = authUseCase.login("", "", false)
// Then
verify(exactly = 0) { authRepository.login(or(any(), ""), or(any(), ""), any()) }
expected assertEquals EMPTY_FIELD_ERROR
}
即使由于用户名和/或字段为空而未调用它们,我是否也必须为测试或accountManager的给定部分模拟接口?
这是我打算在测试后编写的登录方法的最终版本
class AuthUseCase(
private val authRepository: AuthRepository,
private val accountManager: AccountManager
) {
private var loginAttempt = 1
/*
STEP 1: Throw exception for test to compile and fail
*/
// fun login(
// userName: String,
// password: String,
// rememberMe: Boolean = false
// ): AuthenticationState {
// throw NullPointerException()
// }
/*
STEP3: Check if username or password is empty
*/
// fun login(
// userName: String,
// password: String,
// rememberMe: Boolean = false
// ): AuthenticationState {
//
//
// if (userName.isNullOrBlank() || password.isNullOrBlank()) {
// return EMPTY_FIELD_ERROR
// }else {
// throw NullPointerException()
// }
//
// }
/**
* This is the final and complete version of the method.
*/
fun login(
userName: String,
password: String,
rememberMe: Boolean
): AuthenticationState {
return if (loginAttempt >= MAX_LOGIN_ATTEMPT) {
MAX_NUMBER_OF_ATTEMPTS_ERROR
} else if (userName.isNullOrBlank() || password.isNullOrBlank()) {
EMPTY_FIELD_ERROR
} else if (!checkUserNameIsValid(userName) || !checkIfPasswordIsValid(password)) {
INVALID_FIELD_ERROR
} else {
// Concurrent Authentication via mock that returns AUTHENTICATED, or FAILED_AUTHENTICATION
val authenticationPass =
getAccountResponse(userName, password, rememberMe)
return if (authenticationPass) {
loginAttempt = 0
AUTHENTICATED
} else {
loginAttempt++
FAILED_AUTHENTICATION
}
}
}
private fun getAccountResponse(
userName: String,
password: String,
rememberMe: Boolean
): Boolean {
val authResponse =
authRepository.login(userName, password, rememberMe)
val authenticationPass = authResponse?.authenticated ?: false
authResponse?.token?.let {
accountManager.saveToken(it)
}
return authenticationPass
}
private fun checkUserNameIsValid(field: String): Boolean {
return field.length >15 && field.endsWith("@example.com")
}
private fun checkIfPasswordIsValid(field: String): Boolean {
return field.length in 6..10
}
}
我应该仅在所有其他状态都通过时才进行模拟,并从存储库中获得模拟响应并与客户经理进行互动吗?
应在测试部分中提供什么?
编辑:
我将此测试的给定部分更新为
@Test
fun `Empty fields result empty fields error`() {
// Given
every { authRepository.login(or(any(), ""), or(any(), "")) } returns null
// When
val expected = authUseCase.login("", "", false)
// Then
verify(exactly = 0) { authRepository.login(or(any(), ""), or(any(), "")) }
expected assertThatEquals EMPTY_FIELD_ERROR
}
这种行为测试是否有问题?
答案 0 :(得分:1)
我建议您不需要“空字段结果为空字段错误”测试中的验证。我还建议您为每个空白字段编写单独的测试。如果您执行严格的TDD,则在编写代码时将测试每个条件。即 “空用户名应出错”将是第一个测试,并且要测试第一个条件,然后是“空用户应出错”(在完成两次单独的编写第二次测试后,您的代码可能像
if (userName.isNullOrBlank()) {
return EMPTY_FIELD_ERROR
}
if (password.isNullOrBlank() {
return EMPTY_FIELD_ERROR
}
上述两项测试通过后,您可以重构为
if (userName.isNullOrBlank() || password.isNullOrBlank()) {
EMPTY_FIELD_ERROR
}
一旦开始测试checkUserNameIsValid和checkIfPasswordIsValid的条件语句,则需要向类中引入authRepository和accountManager(构造函数注入),然后您将在使用它们时开始模拟调用。通常,模拟框架会伪造一个对象(即代码将运行,但不会返回任何有意义的结果)。当您要测试特定行为时,应该以返回实际的模拟数据为目标,即,在测试成功登录时,应该从authRepository.login返回有效对象。通常,我不使用@BeforeEach中的设置方法,而使用工厂方法或构建器来创建我的受测类。我不熟悉kotlin语法,因此充其量只能做一些sudo代码来演示您的生成器或工厂函数的外观。
// overloaded factory function
fun create() {
val authRepository: AuthRepository = mockk()
val userManager: AccountManager = mockk()
return AuthUseCase(authRepository, userManager);
}
fun create(authRepository: AuthRepository) {
val userManager: AccountManager = mockk()
return AuthUseCase(authRepository, userManager);
}
fun create(authRepository: AuthRepository, userManager: AccountManager) {
return AuthUseCase(authRepository, userManager);
}
您将需要了解如何在kotlin中创建构建器,但最终结果是该构建器总是开始将被测类的依赖项设置为模拟,它什么也不做,但允许您更改那些模拟。
例如
AuthUseCase authUseCase = AuthUseCaseBuilder.Create().WithAuthRepository(myMockAuthRepository).Build();
最后一件事。我故意在上面讨论了loginAttempt检查,因为AuthUseCase是一个服务类,将由多个用户使用,并且在请求的生命周期内都有效,在这种情况下,您不想维护该类内的状态(即loginAttempt变量与该类具有相同的生存期。最好将每个用户名的尝试次数记录在数据库表中,并且每次成功登录后都需要重置尝试次数。
希望这会有所帮助。