Android Studio中Firebase登录方法单元测试正确性咨询
我正在做大学项目,作为单元测试新手,想确认我在Android Studio中针对Firebase登录方法写的单元测试是否正确。以下是我的注册方法代码和登录测试代码:
注册方法代码
public void signUp(String nickname, String email, String password,String dataOfBirth, String gender){ auth = FirebaseAuth.getInstance(); referenceDb = FirebaseDatabase.getInstance().getReference(); auth.createUserWithEmailAndPassword(email,password) .addOnCompleteListener(new OnCompleteListener<AuthResult>() { @Override public void onComplete(@NonNull Task<AuthResult> task) { if (task.isSuccessful()) { String uid = FirebaseAuth.getInstance().getCurrentUser().getUid(); UserDao userDao = new UserDao(); User currUser = new User(uid, nickname, email, password, dataOfBirth, gender); referenceDb.child("Users").child(uid).setValue(currUser).addOnCompleteListener(new OnCompleteListener<Void>() { @Override public void onComplete(@NonNull Task<Void> task) { if (task.isSuccessful()) { Toast.makeText(singUp_fragment.getActivity(), "SingUp successful", Toast.LENGTH_SHORT).show(); openHomeFragment(); } else { Toast.makeText(singUp_fragment.getActivity(), "SingUp failed. Please try again", Toast.LENGTH_SHORT).show(); singUp_fragment.progressBar.setVisibility(View.GONE); } } }); } else { Resources resources = singUp_fragment.getStartActivity().getResources(); singUp_fragment.cleanTextInputErrors(); try { throw task.getException(); } catch (NoInternetConnectionException e) { MotionToast.display( singUp_fragment.getStartActivity(), R.string.toast_warning_internet_connection, MotionToastType.WARNING_MOTION_TOAST ); } catch (FirebaseAuthInvalidUserException e) { singUp_fragment.setTextInputNicknameError( resources.getString(R.string.fragment_sign_up_invalid_nickname) ); } catch (FirebaseAuthUserCollisionException e) { singUp_fragment.setTextInputEmailError( resources.getString(R.string.fragment_sign_up_invalid_login) ); } catch (Exception e) { MotionToast.display( singUp_fragment.getStartActivity(), R.string.toast_error_unknown_error, MotionToastType.ERROR_MOTION_TOAST ); } } singUp_fragment.progressBar.setVisibility(View.GONE); singUp_fragment.setEnabledButtonSignUp(true); } }); }
登录测试代码
public class SingIn_Test { FirebaseAuth auth; boolean test; @Test public void ValidEmailPasswordTest(){ test = false; auth = FirebaseAuth.getInstance(); auth.signInWithEmailAndPassword("annal@live.it", "Annamaria2222").addOnCompleteListener(new OnCompleteListener<AuthResult>() { @Override public void onComplete(@NonNull Task<AuthResult> task) { if (task.isSuccessful()) { Assert.assertTrue(true); } else { Assert.assertFalse(false); } test = true; } }); while(!test){ } } @Test(expected = IllegalArgumentException.class) public void InvalidEmptyEmailPasswordTest(){ test = false; auth = FirebaseAuth.getInstance(); auth.signInWithEmailAndPassword("", "").addOnCompleteListener(new OnCompleteListener<AuthResult>() { @Override public void onComplete(@NonNull Task<AuthResult> task) { if (task.isSuccessful()) { Assert.assertTrue(true); } else { Assert.assertFalse(false); } test = true; } }); while(!test){ } } @Test(expected = IllegalArgumentException.class) public void InvalidEmptyEmailTest(){ test = false; auth = FirebaseAuth.getInstance(); auth.signInWithEmailAndPassword("", "Annamaria2222").addOnCompleteListener(new OnCompleteListener<AuthResult>() { @Override public void onComplete(@NonNull Task<AuthResult> task) { if (task.isSuccessful()) { Assert.assertTrue(true); } else { Assert.assertFalse(false); } test = true; } }); while(!test){ } } @Test(expected = IllegalArgumentException.class) public void InvalidEmptyPasswordTest(){ test = false; auth = FirebaseAuth.getInstance(); auth.signInWithEmailAndPassword("annal@live.it", "").addOnCompleteListener(new OnCompleteListener<AuthResult>() { @Override public void onComplete(@NonNull Task<AuthResult> task) { if (task.isSuccessful()) { Assert.assertTrue(true); } else { Assert.assertFalse(false); } test = true; } }); while(!test){ } } @Test(expected = IllegalArgumentException.class) public void InvalidFormatEmailPasswordTest(){ test = false; auth = FirebaseAuth.getInstance(); auth.signInWithEmailAndPassword("annal@live", "Annamaria").addOnCompleteListener(new OnCompleteListener<AuthResult>() { @Override public void onComplete(@NonNull Task<AuthResult> task) { if (task.isSuccessful()) { Assert.assertTrue(true); } else { Assert.assertFalse(false); } test = true; } }); while(!test){ } } @Test(expected = IllegalArgumentException.class) public void InvalidFormatPasswordTest(){ test = false; auth = FirebaseAuth.getInstance(); auth.signInWithEmailAndPassword("annal@live.it", "Annamaria").addOnCompleteListener(new OnCompleteListener<AuthResult>() { @Override public void onComplete(@NonNull Task<AuthResult> task) { if (task.isSuccessful()) { Assert.assertTrue(true); } else { Assert.assertFalse(false); } test = true; } }); while(!test){ } } @Test(expected = IllegalArgumentException.class) public void InvalidFormatEmailTest(){ test = false; auth = FirebaseAuth.getInstance(); auth.signInWithEmailAndPassword("annal@live", "Annamaria2222").addOnCompleteListener(new OnCompleteListener<AuthResult>() { @Override public void onComplete(@NonNull Task<AuthResult> task) { if (task.isSuccessful()) { Assert.assertTrue(true); } else { Assert.assertFalse(false); } test = true; } }); while(!test){ } } }
请问这些测试代码是否正确?
你的测试代码存在不少问题,下面逐一说明并给出改进方向:
1. 忙等待的性能问题
你用while(!test){}等待异步回调完成,这是典型的忙等待,会持续占用CPU资源,还可能因回调未触发、超时导致测试卡死。应该改用JUnit的CountDownLatch处理异步逻辑:
@Test public void ValidEmailPasswordTest() throws InterruptedException { CountDownLatch latch = new CountDownLatch(1); auth = FirebaseAuth.getInstance(); auth.signInWithEmailAndPassword("annal@live.it", "Annamaria2222").addOnCompleteListener(task -> { if (task.isSuccessful()) { Assert.assertTrue(task.isSuccessful()); } else { Assert.fail("登录失败: " + task.getException().getMessage()); } latch.countDown(); }); latch.await(5, TimeUnit.SECONDS); // 设置超时时间,避免无限等待 }
2. 断言逻辑无效
Assert.assertTrue(true)和Assert.assertFalse(false)永远不会失败,完全起不到验证结果的作用。正确做法是基于任务结果断言:
- 成功场景:
Assert.assertTrue(task.isSuccessful()) - 失败场景:
Assert.assertFalse(task.isSuccessful()),还可以进一步验证异常类型
3. 异常预期错误
你在多个测试上标注@Test(expected = IllegalArgumentException.class),但Firebase的signInWithEmailAndPassword方法在传入空值、格式错误时,不会直接抛出IllegalArgumentException,而是将异常封装在task.getException()中(比如FirebaseAuthInvalidCredentialsException)。这个注解永远不会触发,会导致测试错误通过。
正确的异常验证方式是在回调中检查:
@Test public void InvalidEmptyEmailTest() throws InterruptedException { CountDownLatch latch = new CountDownLatch(1); auth = FirebaseAuth.getInstance(); auth.signInWithEmailAndPassword("", "Annamaria2222").addOnCompleteListener(task -> { Assert.assertFalse(task.isSuccessful()); Assert.assertNotNull(task.getException()); Assert.assertTrue(task.getException() instanceof FirebaseAuthInvalidCredentialsException); latch.countDown(); }); latch.await(5, TimeUnit.SECONDS); }
4. 依赖真实服务,不属于单元测试
当前测试直接调用真实Firebase Auth接口,属于集成测试,而非单元测试。单元测试需要隔离外部依赖,可使用Firebase测试SDK或Mockito等Mock框架模拟FirebaseAuth行为,避免依赖网络、真实账号状态,让测试更快更稳定。
示例Mock代码:
public class SingIn_Test { @Mock FirebaseAuth auth; @Mock Task<AuthResult> mockTask; @Before public void setup() { MockitoAnnotations.openMocks(this); } @Test public void validEmailAndPassword_shouldSignInSuccessfully() throws InterruptedException { when(auth.signInWithEmailAndPassword("annal@live.it", "Annamaria2222")).thenReturn(mockTask); when(mockTask.isSuccessful()).thenReturn(true); CountDownLatch latch = new CountDownLatch(1); auth.signInWithEmailAndPassword("annal@live.it", "Annamaria2222").addOnCompleteListener(task -> { Assert.assertTrue(task.isSuccessful()); latch.countDown(); }); latch.await(5, TimeUnit.SECONDS); } }
5. 测试命名不清晰
测试方法名应遵循场景+预期的规则,比如validEmailAndPassword_shouldSignInSuccessfully,这样能直观看出测试目的。
总结
你的测试思路没问题,但实现细节存在较多错误,尤其是异步处理、断言逻辑和异常验证部分。建议先改用CountDownLatch处理异步,修正断言逻辑,再学习Mock框架隔离外部依赖,将测试从集成测试转为真正的单元测试。
内容的提问来源于stack exchange,提问作者saretta2

