想了解更多内容,单元请访问:
和华为官方合作共建的测试鸿蒙技术社区
https://harmonyos.51cto.com
单元测试是测试某个类的某个方法能否正常工作的一种手段。
单元测试的单元粒度:一般一个public方法需要一个test case
junit4 + mockito + powermock
junit4:JUnit是应用Java最基础的测试框架,主要的单元作用就是断言
Mock的作用:解决测试类对其他类的依赖问题。Mock的测试类所有方法都是空,所有变量都是应用初始值。
PowerMock:PowerMock是单元Mockito的扩展增强版,支持mock private、测试static、应用final方法和类,单元还增加了很多反射方法可以方便修改静态和非静态成员等。测试功能比Mockito增加很多。应用
// build.gradle中引入powermock testImplementation org.powermock:powermock-api-mockito2:2.0.2 testImplementation org.powermock:powermock-module-junit4:2.0.21、新建测试类(快捷导航键: ctrl+shift+T),新建测试用例名
2、setUp 初始化一些公共的东西
3、编写测试代码,执行操作
4、验证结果
一般我们依据被测方法是否有返回值选用不同的验证方法。
有返回值的,直接调用该方法得到返回结果,使用JUnit的Asset验证结果;
没有返回值的,则看方法最终调用了依赖对象的哪个方法,然后再校验依赖对象的该方法有没有被调用,以及获取到的参入参数是否正确
举例说明:
public void login(String username, String password) { if (username == null || username.length() == 0) { return; } if (password == null || password.length() < 6) { return; } mUserManager.performLogin(username, password); }我们要验证该login方法是否正确,则依据传入的参数,判断mUserManager的performLogin方法是否得要了调用。
a.验证这个对象的某些方法的调用情况,调用了多少次,参数是什么等等
b.指定这个对象的某些方法的行为,返回特定的值,或者是执行特定的动作
注意:mock出来的对象并不会自动替换掉正式代码里面的对象,你必须要有某种方式把mock对象应用到正式代码里面
Mockito的使用主要分三步:Mock/spy对象 + 打桩 + 验证
示例:
when(mockObj.methodName(params)).thenReturn(result) mock: 所有方法都是空方法,非void方法都将返回默认值,比如int方法返回0,对象方法将返回null,而void方法将什么都不做。 适用于类对外部依赖较多,只关新少数函数的具体实现; spy:跟正常类对象一样,是正常对象的替身。适用场景跟mock相反,类对外依赖较少,关心大部分函数的具体实现。首先使用PowerMock必须加注解@PrepareForTest和@RunWith(PowerMockRunner.class)。注解@PrepareForTest里写的是静态方法所在的类,如果@RunWith被占用。这时我们可以使用@Rule来解决
@Rule public PowerMockRule rule = new PowerMockRule(); mock静态方法 @RunWith(PowerMockRunner.class) public class PowerMockitoStaticMethodTest { @Test @PrepareForTest({ Banana.class}) public void testStaticMethod() { PowerMockito.mockStatic(Banana.class); //<-- mock静态类 Mockito.when(Banana.getColor()).thenReturn("绿色"); Assert.assertEquals("绿色", Banana.getColor()); //更改类的私有属性 Whitebox.setInternalState(Banana.class, "COLOR", "红色的"); } } mock私有方法 @RunWith(PowerMockRunner.class) public class PowerMockitoPrivateMethodTest { @Test @PrepareForTest({ Banana.class}) public void testPrivateMethod() throws Exception { Banana mBanana = PowerMockito.mock(Banana.class); PowerMockito.when(mBanana.getBananaInfo()).thenCallRealMethod(); PowerMockito.when(mBanana, "flavor").thenReturn("苦苦的"); Assert.assertEquals("苦苦的黄色的", mBanana.getBananaInfo()); //验证flavor是否调用了一次 PowerMockito.verifyPrivate(mBanana).invoke("flavor"); } } mock final方法,使用方式同 mock 私有方法 mock 构造方法 @Test @PrepareForTest({ Banana.class}) public void testNewClass() throws Exception { Banana mBanana = PowerMockito.mock(Banana.class); PowerMockito.when(mBanana.getBananaInfo()).thenReturn("大香蕉"); //如果new新对象,则返回这个上面设置的这个对象 PowerMockito.whenNew(Banana.class).withNoArguments().thenReturn(mBanana); //new新的对象 Banana newBanana = new Banana(); Assert.assertEquals("大香蕉", newBanana.getBananaInfo()); }自定义@Rule很简单,就是实现TestRule 接口,实现apply方法。
public class MyRule implements TestRule { @Override public Statement apply(final Statement base, final Description description) { return new Statement() { @Override public void evaluate() throws Throwable { // evaluate前执行方法相当于@Before String methodName = description.getMethodName(); // 获取测试方法的名字 System.out.println(methodName + "测试开始!"); base.evaluate(); // 运行的测试方法 // evaluate后执行方法相当于@After System.out.println(methodName + "测试结束!"); } }; } }RxJava的火热程度不用多说,由于其基于事件流的链式调用、逻辑简洁 & 使用简单的特点,深受各大开发者的欢迎。我们经常用它来进行线程的切换操作
例如:
public void threadSwitch() { Observable.just("one", "two", "three", "four", "five") .subscribeOn(Schedulers.newThread()) .observeOn(OpenHarmonySchedulers.mainThread()) .subscribe(new Observer<String>() { @Override public void onSubscribe(@NonNull Disposable d) { } @Override public void onNext(@NonNull String s) { System.out.println(s); if (callBack != null) { callBack.success(s); } } @Override public void onError(@NonNull Throwable e) { if (callBack != null) { callBack.failed(); } } @Override public void onComplete() { } }); }Observable.just执行在子线程中, callBack回调执行在主线程中
基于mockito,我们直接写出对应的单元测试代码:
@Test public void threadSwitch() { presenter.threadSwitch(); // 验证callBack的success方法被调用了5次 verify(callBack,times(5)).success(anyString()); }执行此用例,我们会发现它会报如下错误:
java.lang.ExceptionInInitializerError at io.reactivex.rxjava3.openharmony.schedulers.OpenHarmonySchedulers.lambda$static$0(Unknown Source) at io.reactivex.rxjava3.openharmony.plugins.RxOpenHarmonyPlugins.callRequireNonNull(Unknown Source) at io.reactivex.rxjava3.openharmony.plugins.RxOpenHarmonyPlugins.initMainThreadScheduler(Unknown Source) at io.reactivex.rxjava3.openharmony.schedulers.OpenHarmonySchedulers.<clinit>(Unknown Source) at kale.ui.shatter.test.RxSchedulerPresenter.threadSwitch(RxSchedulerPresenter.java:65) at kale.ui.shatter.test.RxSchedulerTestTest.threadSwitch(RxSchedulerTestTest.java:52) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26) at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63) at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329) at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293) at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) at org.junit.runners.ParentRunner.run(ParentRunner.java:413) at org.mockito.internal.runners.DefaultInternalRunner$1.run(DefaultInternalRunner.java:79) at org.mockito.internal.runners.DefaultInternalRunner.run(DefaultInternalRunner.java:85) at org.mockito.internal.runners.StrictRunner.run(StrictRunner.java:39) at org.mockito.junit.MockitoJUnitRunner.run(MockitoJUnitRunner.java:163) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69) at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33) at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220) at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.intellij.rt.execution.application.AppMainV2.main(AppMainV2.java:128) Caused by: java.lang.RuntimeException: Stub! at ohos.eventhandler.EventRunner.getMainEventRunner(EventRunner.java:110) at io.reactivex.rxjava3.openharmony.schedulers.OpenHarmonySchedulers$MainHolder.<clinit>(Unknown Source) ... 41 more那么怎么解决呢?那就是设置用到的Schedulers.进行hook,修改用例如下:
@Test public void threadSwitch() { RxJavaPlugins.setIoSchedulerHandler(scheduler -> Schedulers.trampoline()); RxJavaPlugins.setComputationSchedulerHandler(scheduler -> Schedulers.trampoline()); RxJavaPlugins.setNewThreadSchedulerHandler(scheduler -> Schedulers.trampoline()); RxOpenHarmonyPlugins.setInitMainThreadSchedulerHandler(scheduler -> Schedulers.trampoline()); presenter.threadSwitch(); // 验证callBack的success方法被调用了5次 verify(callBack,times(5)).success(anyString()); }原理就是当进行线程调度时,都让它切换到Schedulers.trampoline(),这样我们就能正确的输出了。但通常情况下,我们使用到线程切换的场景会很多,这样写毕竟还是不够优雅,稍后我会给出更好的解决方式。
除了上面的线程切换场景,我们还经常会使用到时间轮询之类的场景,例如:
public void interval() { Observable.interval(1, TimeUnit.SECONDS) .take(5) .flatMap((Function<Long, ObservableSource<String>>) aLong -> Observable.just(aLong + "")) .subscribeOn(Schedulers.newThread()) .observeOn(OpenHarmonySchedulers.mainThread()) .subscribe(new Observer<String>() { @Override public void onSubscribe(@NonNull Disposable d) { } @Override public void onNext(@NonNull String s) { System.out.println(s); if (callBack != null) { callBack.success(s); } } @Override public void onError(@NonNull Throwable e) { if (callBack != null) { callBack.failed(); } } @Override public void onComplete() { } }); }我们每隔1秒发射一次数据,一共发送5次,我们写出以下单元测试:
@Test public void interval() { RxJavaPlugins.setIoSchedulerHandler(scheduler -> Schedulers.trampoline()); RxJavaPlugins.setComputationSchedulerHandler(scheduler -> Schedulers.trampoline()); RxJavaPlugins.setNewThreadSchedulerHandler(scheduler -> Schedulers.trampoline()); RxOpenHarmonyPlugins.setInitMainThreadSchedulerHandler(scheduler -> Schedulers.trampoline()); presenter.interval(); // 验证callBack的success方法被调用了5次 verify(callBack,times(5)).success(anyString()); }使用上面线程异步变同步的方法确实可以进行测试,但是需要等到5秒后才能执行完成,这显然不符合单元测试执行快的特点。这里,RxJava给我们提供了TestScheduler,调用TestScheduler的advanceTimeTo或advanceTimeBy方法来进行时间操作。
@Test public void interval() { TestScheduler testScheduler = new TestScheduler(); RxJavaPlugins.setIoSchedulerHandler(scheduler -> testScheduler); RxJavaPlugins.setComputationSchedulerHandler(scheduler -> testScheduler); RxJavaPlugins.setNewThreadSchedulerHandler(scheduler -> testScheduler); RxOpenHarmonyPlugins.setInitMainThreadSchedulerHandler(scheduler -> testScheduler); presenter.interval(); //将时间设到3秒后 testScheduler.advanceTimeTo(3,TimeUnit.SECONDS); verify(callBack,times(3)).success(anyString()); //将时间设到10秒后 testScheduler.advanceTimeTo(10,TimeUnit.SECONDS); verify(callBack,times(5)).success(anyString()); }这样我们就不用每次执行到该用例的时候,还得等待设定的时间。每次这样写毕竟也不够优雅,下面我给出基于rxjava3和Rxohos:1.0.0,使用TestRule来进行RxJava线程切换及时间操作的工具类,供大家参考:
/** * Created by xiongwg on 2021-07-08. * <p> * 这个类是让Obserable从异步变同步。 * * 注意: 当有操作时间的测试时,必须调用{ @link #setScheduler(Scheduler)}方法 */ public class RxJavaTestSchedulerRule implements TestRule { /** * 运行在当前线程,可异步变同步 */ public static final Scheduler DEFAULT_SCHEDULER = Schedulers.trampoline(); /** * 操作时间类的 Scheduler */ public static final Scheduler TIME_SCHEDULER = new TestScheduler(); private Scheduler mScheduler = DEFAULT_SCHEDULER; /** * 切换 Scheduler * * @param scheduler 单元测试用例执行所在的 Scheduler */ public void setScheduler(Scheduler scheduler) { if (scheduler != mScheduler) { mScheduler = scheduler; resetTestSchecduler(); } } @Override public Statement apply(final Statement base, Description description) { return new Statement() { @Override public void evaluate() throws Throwable { resetTestSchecduler(); base.evaluate(); } }; } public void advanceTimeBy(long delayTime, TimeUnit unit) { if (mScheduler instanceof TestScheduler) { ((TestScheduler) mScheduler).advanceTimeBy(delayTime, unit); } } public void advanceTimeTo(long delayTime, TimeUnit unit) { if (mScheduler instanceof TestScheduler) { ((TestScheduler) mScheduler).advanceTimeTo(delayTime, unit); } } public void triggerActions() { if (mScheduler instanceof TestScheduler) { ((TestScheduler) mScheduler).triggerActions(); } } private void resetTestSchecduler() { RxJavaPlugins.reset(); RxJavaPlugins.setIoSchedulerHandler(scheduler -> mScheduler); RxJavaPlugins.setComputationSchedulerHandler(scheduler -> mScheduler); RxJavaPlugins.setNewThreadSchedulerHandler(scheduler -> mScheduler); RxOpenHarmonyPlugins.reset(); RxOpenHarmonyPlugins.setInitMainThreadSchedulerHandler(scheduler -> mScheduler); }使用起来很简单
// 1、声明RxJavaTestSchedulerRule Rule @Rule public RxJavaTestSchedulerRule rxJavaTestSchedulerRule = new RxJavaTestSchedulerRule(); @Test public void interval() { //2、在需要进行时间操作的方法前,设置Scheduler为TIME_SCHEDULER rxJavaTestSchedulerRule.setScheduler(TIME_SCHEDULER); presenter.interval(); //3、操作时间,将时间设置为3秒后 rxJavaTestSchedulerRule.advanceTimeTo(3, TimeUnit.SECONDS); verify(callBack,times(3)).success(anyString()); //将时间设置为10秒后 rxJavaTestSchedulerRule.advanceTimeTo(10, TimeUnit.SECONDS); verify(callBack,times(5)).success(anyString()); }1、尝试Mock出该对象
2、在java单元测试包下新建同包名同类名的Java文件,重写调用到的方法
右击需要测试覆盖率的包名 ==> 点击“run test in ‘xxx’ with Coverage”
项目本地查看测试案例通过率
想了解更多内容,请访问:
和华为官方合作共建的鸿蒙技术社区
https://harmonyos.51cto.com