精华内容
下载资源
问答
  • 前言 作为一个程序员,我们工作不可缺少的就是单元测试,当我们依赖第三方时,而三方还没有接口数据给我们时,这时候需要我们mock数据了 ...Mockito 是一个针对 Java 的单元测试模拟框架,它与 EasyMock 和

    前言

    作为一个程序员,我们工作不可缺少的就是单元测试,当我们依赖第三方时,而三方还没有接口数据给我们时,这时候需要我们mock数据了

    正文

    what

    作为一个单元测试,独立且完整是核心,完整性如何保证,这时候就需要mock了, Mock 框架可以很好的将被测功能与其他功能隔离开,市面上有很多的 Mock 框架,比如 Mockito、Jmock、easyMock 等。借助这些工具我们可以很轻松的 Mock 出我们想要的依赖。
    Mockito 是一个针对 Java 的单元测试模拟框架,它与 EasyMock 和 jMock 很相似;
    为了简化单元测试过程中测试上下文 ( 或者称之为测试驱动函数以及桩函数 ) 的搭建而开发的工具。
    在有这些模拟框架之前,为了编写某一个函数的单元测试,程序员必须进行十分繁琐的初始化工作,以保证被测试函数中使用到的环境变量以及其他模块的接口能返回预期的值,有些时候为了单元测试的可行性,甚至需要牺牲被测代码本身的结构。
    单元测试模拟框架则极大的简化了单元测试的编写过程:在被测试代码需要调用某些接口的时候,直接模拟一个假的接口,并任意指定该接口的行为。
    这样就可以大大的提高单元测试的效率以及单元测试代码的可读性。

    How

    如何实现Mock对象?

    使用mock对象进行测试的时候,我们总共需要3个步骤,分别是:

    ----- 使用一个接口来描述这个对象

    ----- 为产品代码实现这个接口

    ----- 以测试为目的,在mock对象中实现这个接口

    use

    springboot+mokito 实例

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    

    SpringBoot 测试支持由两个模块提供:

    spring-boot-test 包含核心项目
    spring-boot-test-autoconfigure 支持测试的自动配置
    通常我们只要引入 spring-boot-starter-test 依赖就行,它包含了一些常用的模块 Junit、Spring Test、AssertJ、Hamcrest、Mockito 等
    SpringBoot 使用了 Junit4 作为单元测试框架,所以注解与 Junit4 是一致的。

    常用注解

    @Mock:用于模拟对象, 有助于最大程度地减少重复的模拟对象。由于参数名称(字段名称)用于标识模拟, 因此它使测试代码和验证错误更易于阅读。 @Mock批注在org.mockito包中可用。
    以下代码段显示了如何使用@mock批注:
    @Mock
    ToDoService servicemock;
    注意:@Mock注释始终与@RunWith注释一起使用。
    @RunWith:这是一个类级别的注释。它用于保持测试干净并改善调试。它还会检测测试中可用的未使用的存根, 并使用@Mock注释对模拟进行初始化。 @RunWith批注在org.mockito.junit包中可用。
    以下代码段显示了如何使用@RunWith批注:
    @RunWith(MockitoJUnitRunner.class)
    public class ToDoBusinessMock {

    }
    在上面的代码片段中, MockitoJUnitRunner类用于检查是否创建了所有模拟并在需要时自动装配。
    @InjectMocks:它标记应在其上执行注入的字段或参数。它允许速记模拟和间谍注入, 并最大程度地减少重复的模拟和间谍注入。在Mockito中, 通过setter注入, 构造函数注入和属性注入来注入模拟。 @InjectMocks批注在org.mockito包中可用。
    以下代码段显示了如何使用@InjectMocks批注:
    @InjectMocks
    ToDoBusiness business;
    @Captor:它允许创建字段级参数捕获器。它与Mockito的verify()方法一起使用, 以获取调用方法时传递的值。像其他批注一样, org.mockito包中也提供了@Captor批注。
    以下代码段显示了如何使用@Captor批注:
    @Captor
    ArgumentCaptor argumentCaptor;
    @Spy-允许创建部分模拟的对象。换句话说, 它允许将现场实例快速包装在间谍对象中。与其他注释一样, org.mockito包中也提供了@Spy注释。
    以下代码段显示了如何使用@Spy批注:
    @Spy
    ArrayList arraylistSpy;
    注意:@mock()与@InjectMocks
    被@Mock标注的对象会自动注入到被@InjectMocks标注的对象中

    常用方法

    在这里插入图片描述

    缺点

    对private/static/final方法的Mock(模拟)

    PowerMock

    PowerMock是对EasyMock和mockito的扩展,实现mockito不具备的功能,比如mock静态方法

    <dependency>
        <groupId>org.powermock</groupId>
        <artifactId>powermock-module-junit4</artifactId>
        <version>2.0.0</version>
        <scope>test</scope>
    </dependency>
    
    <dependency>
        <groupId>org.powermock</groupId>
        <artifactId>powermock-api-mockito2</artifactId>
        <version>2.0.0</version>
        <scope>test</scope>
    </dependency>
    

    总结

    让我们一起来mock 数据吧

    展开全文
  • PowerMock 也是一个单元测试模拟框架,它是在其它单元测试模拟框架的基础上做出的扩展。通过提供定制的类加载器以及一些字节码篡改技巧的应用,PowerMock 现了对静态方法、构造方法、私有方法以及 Final 方法的模拟...
  • vfsStream :流包装器的虚拟文件系统有助于单元测试模拟真正的文件系统。它可用于任何单元测试框架,比如PHPUnit)或SimpleTest。
  • 然后放值放不进去 1、测试类里面创建以下 private MockHttpServletRequestrequest; private MockHttpServletResponseresponse; @Before public void setUp(){ request =new MockHttpServletRequest(); request.set...

    先创建MockHttpServletRequestrequest 和MockHttpServletResponseresponse,注意不要加任何多余的东西,当时自作聪明在这个两个值上加了mock,也没报错。然后放值放不进去

    1、测试类里面创建以下
    private MockHttpServletRequestrequest;

    private MockHttpServletResponseresponse;

    @Before

    public void setUp(){

    request =new MockHttpServletRequest();

    request.setCharacterEncoding(“UTF-8”);

    response =new MockHttpServletResponse();

    }

    2、放值
    request.getSession(true).setAttribute(“1”,“1”);
    3、方法调用
    service.test(request)

    展开全文
  • 考虑可读性,对于方法名使用表达能力强的方法名,对于测试范式可以考虑使用一种规范,如RSpec-style。不要使用逻辑流关键字(If/ese、for、do/while、switch/case),在一个测试方法中  考虑可读性,对于方法名使用...
  • 真的没有那么困难参考资料本文来自于RationalEdge:为创建使用模拟对象的单元测试,使用相对简单的技术产生更多的无缺陷代码。 如今,程序员比以往更多地认识到他们有责任创建编写较好的单元测试。无论一个开发人员...
  • 主要介绍了spring单元测试模拟rabbitmq的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
  • 一个在单元测试模拟HTTP请求的库
  • Android单元测试模拟测试详解

    千次阅读 2017-10-11 17:37:51
    集成一些工具,规范开发规范,使得代码更加稳定( 如通过 phabricator differential 发diff时提交需要执行的单元测试,在开发流程上就可以保证远端代码的稳定性)。 2. 测什么? 一般单元测试: 列出想要测试覆盖...

    测试与基本规范

    为什么需要测试?

    • 为了稳定性,能够明确的了解是否正确的完成开发。
    • 更加易于维护,能够在修改代码后保证功能不被破坏。
    • 集成一些工具,规范开发规范,使得代码更加稳定( 如通过 phabricator differential 发diff时提交需要执行的单元测试,在开发流程上就可以保证远端代码的稳定性)。

    2. 测什么?

    • 一般单元测试:
      • 列出想要测试覆盖的异常情况,进行验证。
      • 性能测试。
    • 模拟测试: 根据需求,测试用户真正在使用过程中,界面的反馈与显示以及一些依赖系统架构的组件的应用测试。

    3. 需要注意

    • 考虑可读性,对于方法名使用表达能力强的方法名,对于测试范式可以考虑使用一种规范, 如 RSpec-style。方法名可以采用一种格式,如: [测试的方法]_[测试的条件]_[符合预期的结果]
    • 不要使用逻辑流关键字(If/else、for、do/while、switch/case),在一个测试方法中,如果需要有这些,拆分到单独的每个测试方法里。
    • 测试真正需要测试的内容,需要覆盖的情况,一般情况只考虑验证输出(如某操作后,显示什么,值是什么)。
    • 考虑耗时,Android Studio默认会输出耗时。
    • 不需要考虑测试private的方法,将private方法当做黑盒内部组件,测试对其引用的public方法即可;不考虑测试琐碎的代码,如getter或者setter
    • 每个单元测试方法,应没有先后顺序;尽可能的解耦对于不同的测试方法,不应该存在Test A与Test B存在时序性的情况。

    4. 创建测试

    • 选择对应的类
    • 将光标停留在类名上
    • 按下ALT + ENTER
    • 在弹出的弹窗中选择Create Test

    Android Studio中的单元测试与模拟测试

    control + shift + R (Android Studio 默认执行单元测试快捷键)。

    1. 本地单元测试

    直接在开发机上面进行运行测试。
    在没有依赖或者仅仅只需要简单的Android库依赖的情况下,有限考虑使用该类单元测试。

    ./gradlew check

    (1)代码存储 

    如果是对应不同的flavor或者是build type,直接在test后面加上对应后缀(如对应名为myFlavor的单元测试代码,应该放在src/testMyFlavor/java下面)。

    src/test/java

    (2)Google官方推荐引用

    复制代码
    dependencies {
        // Required -- JUnit 4 framework,用于单元测试,google官方推荐
        testCompile 'junit:junit:4.12'
        // Optional -- Mockito framework,用于模拟架构,google官方推荐
        //  http://www.manongjc.com/article/1546.html
        testCompile 'org.mockito:mockito-core:1.10.19'
    }
    复制代码

     

    (3)JUnit

    Annotation
    Annotation描述
    @Test public void method()定义所在方法为单元测试方法
    @Test (expected = Exception.class)如果所在方法没有抛出Annotation中的Exception.class->失败
    @Test(timeout=100)如果方法耗时超过100毫秒->失败
    @Test(expected=Exception.class)如果方法抛了Exception.class类型的异常->通过
    @Before public void method()这个方法在每个测试之前执行,用于准备测试环境(如: 初始化类,读输入流等)
    @After public void method()这个方法在每个测试之后执行,用于清理测试环境数据
    BeforeClass public static void method()这个方法在所有测试开始之前执行一次,用于做一些耗时的初始化工作(如: 连接数据库)
    AfterClass public static void method()这个方法在所有测试结束之后执行一次,用于清理数据(如: 断开数据连接)
    @Ignore或者@Ignore("Why disabled")忽略当前测试方法,一般用于测试方法还没有准备好,或者太耗时之类的
    @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class TestClass{}使得该测试方法中的所有测试都按照方法中的字母顺序测试
    Assume.assumeFalse(boolean condition)如果满足condition,就不执行对应方法

     

    2. 模拟测试 

    需要运行在Android设备或者虚拟机上的测试。

    主要用于测试: 单元(Android SDK层引用关系的相关的单元测试)、UI、应用组件集成测试(Service、Content Provider等)。

    ./gradlew connectedAndroidTest

    (1)代码存储:

    src/androidTest/java

    (2)Google官方推荐引用

    复制代码
    dependencies {
        androidTestCompile 'com.android.support:support-annotations:23.0.1'
        androidTestCompile 'com.android.support.test:runner:0.4.1'
        androidTestCompile 'com.android.support.test:rules:0.4.1'
        // Optional -- Hamcrest library
        androidTestCompile 'org.hamcrest:hamcrest-library:1.3'
        // Optional -- UI testing with Espresso
        //  http://www.manongjc.com/article/1546.html
        androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.1'
        // Optional -- UI testing with UI Automator
        androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.1'
    }
    复制代码

     

    (3)常见的UI测试

    需要模拟Android系统环境。

    主要三点:
    1. UI加载好后展示的信息是否正确。
    2. 在用户某个操作后UI信息是否展示正确。
    3. 展示正确的页面供用户操作。

    (4)Espresso

    谷歌官方提供用于UI交互测试

    复制代码
    import static android.support.test.espresso.Espresso.onView;
    import static android.support.test.espresso.action.ViewActions.click;
    import static android.support.test.espresso.assertion.ViewAssertions.matches;
    import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
    import static android.support.test.espresso.matcher.ViewMatchers.withId;
    
    // 对于Id为R.id.my_view的View: 触发点击,检测是否显示
    onView(withId(R.id.my_view)).perform(click())               
                                .check(matches(isDisplayed()));
    // 对于文本打头是"ABC"的View: 检测是否没有Enable
    onView(withText(startsWith("ABC"))).check(matches(not(isEnabled()));
    // 按返回键
    pressBack();
    // 对于Id为R.id.button的View: 检测内容是否是"Start new activity"
    // http://www.manongjc.com/article/1537.html
    onView(withId(R.id.button)).check(matches(withText(("Start new activity"))));
    // 对于Id为R.id.viewId的View: 检测内容是否不包含"YYZZ"
    onView(withId(R.id.viewId)).check(matches(withText(not(containsString("YYZZ")))));
    // 对于Id为R.id.inputField的View: 输入"NewText",然后关闭软键盘
    onView(withId(R.id.inputField)).perform(typeText("NewText"), closeSoftKeyboard());
    // 对于Id为R.id.inputField的View: 清除内容
    onView(withId(R.id.inputField)).perform(clearText());
    复制代码

     

    启动一个打开ActivityIntent
    复制代码
    @RunWith(AndroidJUnit4.class)
    public class SecondActivityTest {
        @Rule
        public ActivityTestRule<SecondActivity> rule =
                new ActivityTestRule(SecondActivity.class, true,
                                      // 这个参数为false,不让SecondActivity自动启动
                                      // 如果为true,将会在所有@Before之前启动,在最后一个@After之后关闭
                                      false);
        @Test
        public void demonstrateIntentPrep() {
            Intent intent = new Intent();
            intent.putExtra("EXTRA", "Test");
            // 启动SecondActivity并传入intent
            rule.launchActivity(intent);
            // 对于Id为R.id.display的View: 检测内容是否是"Text"
            // http://www.manongjc.com/article/1532.html
            onView(withId(R.id.display)).check(matches(withText("Test")));
        }
    }
    复制代码

     

    (5)异步交互

    建议关闭设备中”设置->开发者选项中”的动画,因为这些动画可能会是的Espresso在检测异步任务的时候产生混淆: 窗口动画缩放(Window animation scale)、过渡动画缩放(Transition animation scale)、动画程序时长缩放(Animator duration scale)。

    针对AsyncTask,在测试的时候,如触发点击事件以后抛了一个AsyncTask任务,在测试的时候直接onView(withId(R.id.update)).perform(click()),然后直接进行检测,此时的检测就是在AsyncTask#onPostExecute之后。

    复制代码
    // 通过实现IdlingResource,block住当非空闲的时候,当空闲时进行检测,非空闲的这段时间处理异步事情
    public class IntentServiceIdlingResource implements IdlingResource {
        ResourceCallback resourceCallback;
        private Context context;
    
        public IntentServiceIdlingResource(Context context) { this.context = context; }
    
        @Override public String getName() { return IntentServiceIdlingResource.class.getName(); }
    
        @Override public void registerIdleTransitionCallback( ResourceCallback resourceCallback) { this.resourceCallback = resourceCallback; }
    
        @Override public boolean isIdleNow() {
          // 是否是空闲
          // 如果IntentService 没有在运行,就说明异步任务结束,IntentService特质就是启动以后处理完Intent中的事务,理解关闭自己
          // http://www.manongjc.com/article/1531.html
            boolean idle = !isIntentServiceRunning();
            if (idle && resourceCallback != null) {
              // 回调告知异步任务结束
                resourceCallback.onTransitionToIdle();
            }
            return idle;
        }
    
        private boolean isIntentServiceRunning() {
            ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
            // Get all running services
            List<ActivityManager.RunningServiceInfo> runningServices = manager.getRunningServices(Integer.MAX_VALUE);
            // check if our is running
            for (ActivityManager.RunningServiceInfo info : runningServices) {
                if (MyIntentService.class.getName().equals(info.service.getClassName())) {
                    return true;
                }
            }
            return false;
        }
    }
    
    // 使用IntentServiceIdlingResource来测试,MyIntentService服务启动结束这个异步事务,之后的结果。
    @RunWith(AndroidJUnit4.class)
    public class IntegrationTest {
    
        @Rule
        public ActivityTestRule rule = new ActivityTestRule(MainActivity.class);
        IntentServiceIdlingResource idlingResource;
    
        @Before
        public void before() {
            Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
            Context ctx = instrumentation.getTargetContext();
            idlingResource = new IntentServiceIdlingResource(ctx);
            // 注册这个异步监听
            Espresso.registerIdlingResources(idlingResource);
    
        }
        @After
        public void after() {
            // 取消注册这个异步监听
            Espresso.unregisterIdlingResources(idlingResource);
    
        }
    
        @Test
        public void runSequence() {
            // MainActivity中点击R.id.action_settings这个View的时候,会启动MyIntentService
            onView(withId(R.id.action_settings)).perform(click());
            // 这时候IntentServiceIdlingResource#isIdleNow会返回false,因为MyIntentService服务启动了
            // 这个情况下,这里会block住.............
            // 直到IntentServiceIdlingResource#isIdleNow返回true,并且回调了IntentServiceIdlingResource#onTransitionToIdle
            // 这个情况下,继续执行,这时我们就可以测试异步结束以后的情况了。
            onView(withText("Broadcast")).check(matches(notNullValue()));
        }
    }
    复制代码

     

    (6)自定义匹配器
    复制代码
    // 定义
    public static Matcher<View> withItemHint(String itemHintText) {
      checkArgument(!(itemHintText.equals(null)));
      return withItemHint(is(itemHintText));
    }
    
    public static Matcher<View> withItemHint(final Matcher<String> matcherText) {
      checkNotNull(matcherText);
      return new BoundedMatcher<View, EditText>(EditText.class) {
    
        @Override
        public void describeTo(Description description) {
          description.appendText("with item hint: " + matcherText);
        }
    
        @Override
        protected boolean matchesSafely(EditText editTextField) {
          // 取出hint,然后比对下是否相同
          // http://www.manongjc.com/article/1524.html
          return matcherText.matches(editTextField.getHint().toString());
        }
      };
    }
    
    // 使用
    onView(withItemHint("test")).check(matches(isDisplayed()));
    复制代码

     

    拓展工具

    1. AssertJ Android

    square/assertj-android
    极大的提高可读性。

    复制代码
    import static org.assertj.core.api.Assertions.*;
    
    // 断言: view是GONE的
    assertThat(view).isGone();
    
    MyClass test = new MyClass("Frodo");
    MyClass test1 = new MyClass("Sauron");
    MyClass test2 = new MyClass("Jacks");
    
    List<MyClass> testList = new ArrayList<>();
    testList.add(test);
    testList.add(test1);
    
    // 断言: test.getName()等于"Frodo"
    assertThat(test.getName()).isEqualTo("Frodo");
    // 断言: test不等于test1并且在testList中
    // http://www.manongjc.com/article/1519.html
    assertThat(test).isNotEqualTo(test1)
                     .isIn(testList);
    // 断言: test.getName()的字符串,是由"Fro"打头,以"do"结尾,忽略大小写会等于"frodo"
    assertThat(test.getName()).startsWith("Fro")
                                .endsWith("do")
                                .isEqualToIgnoringCase("frodo");
    // 断言: testList有2个数据,包含test,test1,不包含test2
    assertThat(list).hasSize(2)
                    .contains(test, test1)
                    .doesNotContain(test2);
    
    // 断言: 提取testList队列中所有数据中的成员变量名为name的变量,并且包含name为"Frodo"与"Sauron"
    //      并且不包含name为"Jacks"
    assertThat(testList).extracting("name")
                        .contains("Frodo", "Sauron")
                        .doesNotContain("Jacks");
    复制代码

     

    2. Hamcrest

    JavaHamcrest
    通过已有的通配方法,快速的对代码条件进行测试
    org.hamcrest:hamcrest-junit:(version)

    复制代码
    import static org.hamcrest.MatcherAssert.assertThat;
    import static org.hamcrest.Matchers.is;
    import static org.hamcrest.Matchers.equalTo;
    
    // 断言: a等于b
    assertThat(a, equalTo(b));
    assertThat(a, is(equalTo(b)));
    assertThat(a, is(b));
    // 断言: a不等于b
    assertThat(actual, is(not(equalTo(b))));
    
    List<Integer> list = Arrays.asList(5, 2, 4);
    // 断言: list有3个数据
    assertThat(list, hasSize(3));
    // 断言: list中有5,2,4,并且顺序也一致
    assertThat(list, contains(5, 2, 4));
    // 断言: list中包含5,2,4
    assertThat(list, containsInAnyOrder(2, 4, 5));
    // 断言: list中的每一个数据都大于1
    // http://www.manongjc.com/article/1507.html
    assertThat(list, everyItem(greaterThan(1)));
    // 断言: fellowship中包含有成员变量"race",并且其值不是ORC
    assertThat(fellowship, everyItem(hasProperty("race", is(not((ORC))))));
    // 断言: object1中与object2相同的成员变量都是相同的值
    assertThat(object1, samePropertyValuesAs(object2));
    
    Integer[] ints = new Integer[] { 7, 5, 12, 16 };
    // 断言: 数组中包含7,5,12,16
    assertThat(ints, arrayContaining(7, 5, 12, 16));
    复制代码

     

    (1)几个主要的匹配器:
    Mather描述
    allOf所有都匹配
    anyOf任意一个匹配
    not不是
    equalTo对象等于
    is
    hasToString包含toString
    instanceOf,isCompatibleType类的类型是否匹配
    notNullValue,nullValue测试null
    sameInstance相同实例
    hasEntry,hasKey,hasValue测试Map中的EntryKeyValue
    hasItem,hasItems测试集合(collection)中包含元素
    hasItemInArray测试数组中包含元素
    closeTo测试浮点数是否接近指定值
    greaterThan,greaterThanOrEqualTo,lessThan,lessThanOrEqualTo数据对比
    equalToIgnoringCase忽略大小写字符串对比
    equalToIgnoringWhiteSpace忽略空格字符串对比
    containsString,endsWith,startsWith,isEmptyString,isEmptyOrNullString字符串匹配

    (2)自定义匹配器

    复制代码
    // 自定义
    import org.hamcrest.Description;
    import org.hamcrest.TypeSafeMatcher;
    
    public class RegexMatcher extends TypeSafeMatcher<String> {
        private final String regex;
    
        public RegexMatcher(final String regex) { this.regex = regex; }
        @Override
        public void describeTo(final Description description) { description.appendText("matches regular expression=`" + regex + "`"); }
    
        @Override
        public boolean matchesSafely(final String string) { return string.matches(regex); }
    
        // 上层调用的入口
        public static RegexMatcher matchesRegex(final String regex) {
            return new RegexMatcher(regex);
        }
    }
    
    // 使用
    String s = "aaabbbaaa";
    assertThat(s, RegexMatcher.matchesRegex("a*b*a"));
    复制代码

     

    3. Mockito

    Mockito
    Mock对象,控制其返回值,监控其方法的调用。
    org.mockito:mockito-all:(version)

    复制代码
    // import如相关类
    import static org.mockito.Mockito.mock;
    import static org.mockito.Mockito.verify;
    
    // 创建一个Mock的对象
     MyClass test = mock(MyClass.class);
    
    // 当调用test.getUniqueId()的时候返回43
    when(test.getUniqueId()).thenReturn(43);
    // 当调用test.compareTo()传入任意的Int值都返回43
    when(test.compareTo(anyInt())).thenReturn(43);
    // 当调用test.compareTo()传入的是Target.class类型对象时返回43
    when(test.compareTo(isA(Target.class))).thenReturn(43);
    // 当调用test.close()的时候,抛IOException异常
    doThrow(new IOException()).when(test).close();
    // 当调用test.execute()的时候,什么都不做
    doNothing().when(test).execute();
    
    // 验证是否调用了两次test.getUniqueId()
    // http://www.manongjc.com/article/1503.html
    verify(test, times(2)).getUniqueId();
    // 验证是否没有调用过test.getUniqueId()
    verify(test, never()).getUniqueId();
    // 验证是否至少调用过两次test.getUniqueId()
    verify(test, atLeast(2)).getUniqueId();
    // 验证是否最多调用过三次test.getUniqueId()
    verify(test, atMost(3)).getUniqueId();
    // 验证是否这样调用过:test.query("test string")
    verify(test).query("test string");
    
    // 通过Mockito.spy() 封装List对象并返回将其mock的spy对象
    List list = new LinkedList();
    List spy = spy(list);
    
    // 指定spy.get(0)返回"foo"
    doReturn("foo").when(spy).get(0);
    
    assertEquals("foo", spy.get(0));
    复制代码

    对访问方法时,传入参数进行快照

    复制代码
    import org.mockito.ArgumentCaptor;
    import org.mockito.Captor;
    import static org.junit.Assert.assertEquals;
    
    @Captor
    private ArgumentCaptor<Integer> captor;
    
    @Test
    public void testCapture(){
      MyClass test = mock(MyClass.class);
    
      test.compareTo(3, 4);
      verify(test).compareTo(captor.capture(), eq(4));
    
      assertEquals(3, (int)captor.getValue());
    
      // 需要特别注意,如果是可变数组(vargars)参数,如方法 test.doSomething(String... params)
      // 此时是使用ArgumentCaptor<String>,而非ArgumentCaptor<String[]>
      ArgumentCaptor<String> varArgs = ArgumentCaptor.forClass(String.class);
      test.doSomething("param-1", "param-2");
      verify(test).doSomething(varArgs.capture());
    
      // 这里直接使用getAllValues()而非getValue(),来获取可变数组参数的所有传入参数
      assertThat(varArgs.getAllValues()).contains("param-1", "param-2");
    }
    复制代码

    (1)对于静态的方法的Mock:

    可以使用 PowerMock:

    org.powermock:powermock-api-mockito:(version) & org.powermock:powermock-module-junit4:(version)(For PowerMockRunner.class)
    复制代码
    @RunWith(PowerMockRunner.class)
    @PrepareForTest({StaticClass1.class, StaticClass2.class})
    public class MyTest {
    
      @Test
      public void testSomething() {
        // mock完静态类以后,默认所有的方法都不做任何事情
        mockStatic(StaticClass1.class);
        when(StaticClass1.getStaticMethod()).andReturn("anything");
    
        // 验证是否StaticClass1.getStaticMethod()这个方法被调用了一次
        verifyStatic(time(1));
        StaticClass1.getStaticMethod();
    
        when(StaticClass1.getStaticMethod()).andReturn("what ever");
    
        // 验证是否StaticClass2.getStaticMethod()这个方法被至少调用了一次
        verifyStatic(atLeastOnce());
        StaticClass2.getStaticMethod();
    
        // 通过任何参数创建File的实力,都直接返回fileInstance对象
        whenNew(File.class).withAnyArguments().thenReturn(fileInstance);
      }
    }
    复制代码

    或者是封装为非静态,然后用Mockito:

    class FooWraper{
      void someMethod() {
        Foo.someStaticMethod();
      }
    }

     

    4. Robolectric

    Robolectric
    让模拟测试直接在开发机上完成,而不需要在Android系统上。所有需要使用到系统架构库的,如(HandlerHandlerThread)都需要使用Robolectric,或者进行模拟测试。

    主要是解决模拟测试中耗时的缺陷,模拟测试需要安装以及跑在Android系统上,也就是需要在Android虚拟机或者设备上面,所以十分的耗时。基本上每次来来回回都需要几分钟时间。针对这类问题,业界其实已经有了一个现成的解决方案: Pivotal实验室推出的Robolectric。通过使用Robolectrict模拟Android系统核心库的Shadow Classes的方式,我们可以像写本地测试一样写这类测试,并且直接运行在工作环境的JVM上,十分方便。

     

    5. Robotium

    RobotiumTech/robotium
    (Integration Tests)模拟用户操作,事件流测试。

    @RunWith(RobolectricTestRunner.class)
    @Config(constants = BuildConfig.class)
    public class MyActivityTest{
    复制代码
    @Test
      public void doSomethingTests(){
        // 获取Application对象
        Application application = RuntimeEnvironment.application;
    
        // 启动WelcomeActivity
        WelcomeActivity activity = Robolectric.setupActivity(WelcomeActivity.class);
        // 触发activity中Id为R.id.login的View的click事件
        // http://www.manongjc.com/article/1502.html
        activity.findViewById(R.id.login).performClick();
    
        Intent expectedIntent = new Intent(activity, LoginActivity.class);
        // 在activity之后,启动的Activity是否是LoginActivity
        assertThat(shadowOf(activity).getNextStartedActivity()).isEqualTo(expectedIntent);
      }
    }
    复制代码

    通过模拟用户的操作的行为事件流进行测试,这类测试无法避免需要在虚拟机或者设备上面运行的。是一些用户操作流程与视觉显示强相关的很好的选择。

     

    6. Test Butler

    linkedin/test-butler
    避免设备/模拟器系统或者环境的错误,导致测试的失败。

    通常我们在进行UI测试的时候,会遇到由于模拟器或者设备的错误,如系统的crash、ANR、或是未预期的Wifi、CPU罢工,或者是锁屏,这些外再环境因素导致测试不过。Test-Butler引入就是避免这些环境因素导致UI测试不过。

    该库被谷歌官方推荐过,并且收到谷歌工程师的Review。

     

    拓展思路

    1. Android Robots

    Instrumentation Testing Robots – Jake Wharton

    假如我们需要测试: 发送 $42 到 “foo@bar.com”,然后验证是否成功。

    (1)通常的做法

    Android单元测试与模拟测试详解

    Android单元测试与模拟测试详解

    (2)Robot思想

    在写真正的UI测试的时候,只需要关注要测试什么,而不需要关注需要怎么测试,换句话说就是让测试逻辑与View或Presenter解耦,而与数据产生关系。

    首先通过封装一个Robot去处理How的部分:

    Android单元测试与模拟测试详解

     

    然后在写测试的时候,只关注需要测试什么:

    Android单元测试与模拟测试详解

    最终的思想原理

    Android单元测试与模拟测试详解



    展开全文
  • 单元测试】FFF模拟框架

    千次阅读 2019-08-26 20:58:13
    在学习单元测试的过程中,使用「模拟框架」隔离依赖是一项必须要掌握的技术。目前模拟框架有很多,琳琅满目,参差不齐。针对C语言模拟框架的初学者,我推荐FFF框架,因为该框架简单,易上手,而且有助于初学者掌握...

    1. 前言

    在学习单元测试的过程中,使用「模拟框架」隔离依赖是一项必须要掌握的技术。目前模拟框架有很多,琳琅满目,参差不齐。针对C语言模拟框架的初学者,我推荐FFF框架,因为该框架简单,易上手,而且有助于初学者掌握模拟框架的幕后原理。有了基础,再去学习更复杂、更高端的框架,就游刃有余了。

    注:在众多的C语言模拟框架中,FFF框架算是一个比较冷门的框架,至少截止我书稿本文时是如此,在网上鲜有相关资料,可见算不上主流,但FFF框架很适合模拟框架的初学者,这也正是我撰写本文的目的。

    2. FFF框架简介

    官网:https://github.com/meekrosoft/fff

    FFF全称Fake Function Framework,是一个用于单元测试的C语言轻量型模拟框架。整个框架就一个头文件fff.h,全部用宏定义实现的框架,非常简洁,你只要include该头文件,就能使用该框架了。

    FFF框架的优点:

    • 很容易创建C语言的存根和模拟对象。
    • 它很简单,只需包含一个头文件,就可以开始了。

    3. 入门体验

    3.1 下载fff.h头文件

    git clone https://github.com/meekrosoft/fff.git
    

    我们只需要其中的fff.h头文件即可,其他不需要。

    说明:克隆下来的代码中有个gtest文件夹,这是谷歌的Google Test单元测试框架,FFF框架是模拟框架,需要区分两者,本文重点讲解的是FFF模拟框架,不会涉及单元测试框架。

    3.2 初次体验

    直接上例子:

    void UI_init(void)
    {
        DISPLAY_init();
    }
    

    UI_init函数调用了DISPLAY_init接口。在没有DISPLAY_init接口实现代码的情况下(只知道接口的声明,如下所示),如何对UI_init函数进行单元测试?

    void DISPLAY_init();
    

    使用FFF框架很容易创建DISPLAY_init模拟函数,只要三行代码:

    #include "fff.h"
    DEFINE_FFF_GLOBALS;
    FAKE_VOID_FUNC(DISPLAY_init);
    

    完整测试代码下如下:

    // test.c
    #include <stdio.h>
    #include <stdlib.h>
    #include <assert.h>
    
    #include "fff.h"
    DEFINE_FFF_GLOBALS;
    FAKE_VOID_FUNC(DISPLAY_init);
    
    void UI_init(void)
    {
        DISPLAY_init();
    }
    
    #define ASSERT_EQ(A, B) assert((A) == (B))
    
    int main()
    {
        UI_init();
        ASSERT_EQ(DISPLAY_init_fake.call_count, 1);
        return 0;
    }
    

    可以编译成功:

    gcc -o test test.c
    

    为何如此神奇,只要两个宏(DEFINE_FFF_GLOBALS 和 FAKE_VOID_FUNC)就能模拟出接口,FFF框架幕后到底干了什么?要解开谜底,只要进行一次宏展开就能知晓。对test.c进行宏展开:

    gcc -E -P test.c >> test.prescan
    

    查看test.prescan展开后的代码(只列出两个宏对应的关键代码,略有删减):

    /* 以下是 DEFINE_FFF_GLOBALS 宏展开后的代码 */
    typedef void(*fff_function_t)(void);
    typedef struct {
        fff_function_t call_history[(50u)];
        unsigned int call_history_idx;
    } fff_globals_t;
    fff_globals_t fff;
    
    /* 以下是 FAKE_VOID_FUNC(DISPLAY_init) 宏展开后的代码 */
    typedef struct DISPLAY_init_Fake {
        unsigned int call_count;
        unsigned int arg_history_len;
        unsigned int arg_histories_dropped;
        int custom_fake_seq_len;
        int custom_fake_seq_idx;
        void(*custom_fake)(void);
        void(**custom_fake_seq)(void);
    } DISPLAY_init_Fake;
    DISPLAY_init_Fake DISPLAY_init_fake;
    
    void DISPLAY_init(void)
    {
        if (DISPLAY_init_fake.call_count < (50u)) {
        } else {
            DISPLAY_init_fake.arg_histories_dropped++;
        }
        DISPLAY_init_fake.call_count++;
    
        if (fff.call_history_idx < (50u))
            fff.call_history[fff.call_history_idx++] = (fff_function_t)DISPLAY_init;;
        if (DISPLAY_init_fake.custom_fake_seq_len) {
            if (DISPLAY_init_fake.custom_fake_seq_idx < DISPLAY_init_fake.custom_fake_seq_len) {
                DISPLAY_init_fake.custom_fake_seq[DISPLAY_init_fake.custom_fake_seq_idx++]();
            } else {
                DISPLAY_init_fake.custom_fake_seq[DISPLAY_init_fake.custom_fake_seq_len - 1]();
            }
        }
        if (DISPLAY_init_fake.custom_fake)
            DISPLAY_init_fake.custom_fake();
    }
    
    void DISPLAY_init_reset(void)
    {
        memset(&DISPLAY_init_fake, 0, sizeof(DISPLAY_init_fake));
        DISPLAY_init_fake.arg_history_len = (50u);
    };
    

    耐心看完以上代码,基本上就能知道是咋回事了。

    • DEFINE_FFF_GLOBALS 宏定义了一个结构体全局变量,用于记录、跟踪函数调用的历史记录。

    • FAKE_VOID_FUNC 宏定义是关键所在,其语法为:

      FAKE_VOID_FUNC(fn [,arg_types*])
      
      • 该宏定义了一个名为fn的模拟函数,模拟函数返回值类型为void,形参列表为arg_types(可选项,不填就表示形参为void)。
      • 该宏还定义了一个名为fn_fake的结构体全局变量,该结构体包含有关模拟函数fn的所有状态信息,例如call_count会在每次调用模拟函数fn时递增。
      • 该宏还定义了一个名为fn_reset的函数,用于重置模拟函数fn的状态。fn_reset函数往往是在执行测试用例前(或后)被调用,即setup或teardown中调用,以免影响其他测试用例,或者被其他测试用例影响。

    总结起来就是,示例中 FAKE_VOID_FUNC(DISPLAY_init) 宏定义了一个结构体,两个函数:

    • DISPLAY_init_Fake 结构体
    • void DISPLAY_init(void) 函数
    • void DISPLAY_init_reset(void) 函数

    至此,基本上就整明白FFF模拟框架的幕后原理了。

    4. 深入学习

    接下来,更深入的了解下FFF框架。

    4.1 模拟函数形参

    如果要定义带有形参的模拟函数,比如:

    void DISPLAY_output(char * message);
    

    可以这样:

    FAKE_VOID_FUNC(DISPLAY_output, char *);
    

    测试用例(UI_write_line函数会调用DISPLAY_output接口):

    void test(void)
    {
        char msg[] = "helloworld";
        UI_write_line(msg);
        ASSERT_EQ(DISPLAY_output_fake.call_count, 1);
        ASSERT_EQ(strcmp(DISPLAY_output_fake.arg0_val, msg), 0);
    }
    

    在FAKE_VOID_FUNC宏定义中,函数名之后紧接的是函数的形参列表(示例中是char指针),每个形参在fn_fake结构体中都有argN_val变量与之对应(N从0开始)。欲知代码详情,宏展开。

    4.2 模拟函数返回值

    如果要定义带有函数返回值的模拟函数,应该使用FAKE_VALUE_FUNC宏,其语法为:

    FAKE_VALUE_FUNC(return_type, fn [,arg_types*]);
    
    • return_type是模拟函数fn的返回值类型,为必填项。
    • fn是模拟函数名,为必填项。
    • arg_types是模拟函数fn的形参列表,为可选项。

    例如:

    unsigned int DISPLAY_get_line_capacity();
    unsigned int DISPLAY_get_line_insert_index();
    

    可以这样:

    FAKE_VALUE_FUNC(unsigned int, DISPLAY_get_line_capacity);
    FAKE_VALUE_FUNC(unsigned int, DISPLAY_get_line_insert_index);
    

    测试用例:

    void test(void)
    {
        // 设定 DISPLAY_get_line_insert_index 函数预期返回值
        DISPLAY_get_line_insert_index_fake.return_val = 1;
    
        ASSERT_EQ(DISPLAY_get_line_insert_index(), 1);
    }
    

    欲知代码详情,宏展开。模拟更复杂的函数,例如:

    double pow(double base, double exponent);
    

    可以这样:

    FAKE_VALUE_FUNC(double, pow, double, double);
    

    4.3 重置模拟函数状态

    好的单元测试会隔离每个测试用例,因此重置模拟函数fn的状态对每个单元测试都至关重要。每个模拟函数fn都有对应的fn_reset接口,用于重置fn的状态信息和呼叫计数。最好的做法是在测试用例的setup中调用fn_reset以重置模拟函数fn的状态。例如:

    void setup(void)
    {
        // Register resets
        RESET_FAKE(DISPLAY_init);
        RESET_FAKE(DISPLAY_clear);
        RESET_FAKE(DISPLAY_output_message);
        RESET_FAKE(DISPLAY_get_line_capacity);
        RESET_FAKE(DISPLAY_get_line_insert_index);
        FFF_RESET_HISTORY();
    }
    

    RESET_FAKE 宏会调用相应模拟函数的fn_reset接口以重置fn的状态。而 FFF_RESET_HISTORY 宏用于重置函数调用历史记录,后面章节会讲到函数调用历史记录。

    4.4 模拟函数调用记录

    如果你要测试一个函数,这个函数依次调用了functionA、functionB、functionA接口,想在测试用例中检查接口调用顺序是否符合预期,怎么测?FFF框架内部维护着所有模拟函数的调用历史记录,因此很容易测试。例如:

    FAKE_VOID_FUNC(voidfunc2, char, char);
    FAKE_VALUE_FUNC(long, longfunc0);
    
    void test(void)
    {
        longfunc0();
        voidfunc2();
        longfunc0();
    
        ASSERT_EQ(fff.call_history[0], (void *)longfunc0);
        ASSERT_EQ(fff.call_history[1], (void *)voidfunc2);
        ASSERT_EQ(fff.call_history[2], (void *)longfunc0);
    }
    

    如果要重置函数调用历史记录,可以使用 FFF_RESET_HISTORY() 宏,一般是在setup中调用该宏。

    4.5 模拟函数参数记录

    默认情况下,框架内部会记录每个模拟函数的最后十次被调用的参数值,每个伪函数的每个参数值都会记录。

    void test(void)
    {
        voidfunc2('g', 'h');
        voidfunc2('i', 'j');
        ASSERT_EQ('g', voidfunc2_fake.arg0_history[0]);
        ASSERT_EQ('h', voidfunc2_fake.arg1_history[0]);
        ASSERT_EQ('i', voidfunc2_fake.arg0_history[1]);
        ASSERT_EQ('j', voidfunc2_fake.arg1_history[1]);
    }
    

    注意,RESET_FAKE 会清除对应伪函数的参数历史记录。

    4.6 模拟函数返回值序列

    在单元测试中,有时候会多次调用同一个外部依赖函数,并且期望每次调用都返回不同值。FFF框架实现此操作的方法是,为模拟函数指定返回值序列。例如:

    // faking "long longfunc();"
    FAKE_VALUE_FUNC(long, longfunc0);
    
    void test(void)
    {
        long myReturnVals[3] = { 3, 7, 9 };
        SET_RETURN_SEQ(longfunc0, myReturnVals, 3);
        ASSERT_EQ(myReturnVals[0], longfunc0());
        ASSERT_EQ(myReturnVals[1], longfunc0());
        ASSERT_EQ(myReturnVals[2], longfunc0());
        ASSERT_EQ(myReturnVals[2], longfunc0());
        ASSERT_EQ(myReturnVals[2], longfunc0());
    }
    

    通过使用SET_RETURN_SEQ宏指定返回值序列,模拟函数将按顺序返回数组中给出的值。当到达序列的末尾时,模拟函数将继续无限期地返回序列中的最后一个值。

    4.7 宏备忘录

    描述例子
    FAKE_VOID_FUNC(fn [,arg_types*]);定义一个模拟函数,函数返回值类型为void,函数名为fn,函数形参为arg_types(可选项)。FAKE_VOID_FUNC(DISPLAY_init);
    FAKE_VOID_FUNC(DISPLAY_output_message, const char*);
    FAKE_VALUE_FUNC(return_type, fn [,arg_types*]);定义一个模拟函数,函数返回值类型为return_type,函数名为fn,函数形参为arg_types(可选项)。FAKE_VALUE_FUNC(int, DISPLAY_get_line_insert_index);
    FAKE_VOID_FUNC_VARARG(fn [,arg_types*], …);定义一个带有可变参数的模拟函数,函数返回值类型为void,函数名为fn,函数形参为arg_types(可选项)。FAKE_VOID_FUNC_VARARG(fn, const char*, …)
    FAKE_VALUE_FUNC_VARARG(return_type, fn [,arg_types*], …);定义一个带有可变参数的模拟函数,函数返回值类型为return_type,函数名为fn,函数形参为arg_types(可选项)。FAKE_VALUE_FUNC_VARARG(int, fprintf, FILE*, const char*, …)
    RESET_FAKE(fn);重置模拟函数fn的状态信息。RESET_FAKE(DISPLAY_init);

    4.8 更多学习

    更多学习,可以参阅FFF框架官网中的使用手册(英文):https://github.com/meekrosoft/fff

    展开全文
  • 用于C单元测试模拟功能。 什么是单元测试,什么是模拟单元测试检查特定功能在特定条件下是否执行特定操作。 Mocks使单元测试可以说:“这是预期调用的列表以及在这种情况下应使用的参数,请告诉我我的代码是否...
  • 尽量减少模拟代码,同时仍然提供执行业务逻辑单元测试所需的所有必要方案
  • 单元测试 - stub - 桩 - mock - 模拟 1. 单元测试 单元测试 (unit testing) 是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,要根据实际情况去具体含义,C 语言中单元指一个函数,Java 中...
  • 单元测试中使用Mockito模拟异常抛出 1.对待有返回值的方法 使用when().thenThrow()方法 @Test public void whenConfigNonVoidRetunMethodToThrowEx_thenExIsThrown() { when(dictMock.getMeaning(anyString())) ...
  • PowerMocker单元测试

    2019-08-28 23:37:10
    PowerMock 也是一个单元测试模拟框架,它是在其它单元测试模拟框架的基础上做出的扩展。通过提供定制的类加载器以及一些字节码篡改技巧的应用,PowerMock 现了对静态方法、构造方法、私有方法以及 Final 方法的模拟...
  • 模拟IC自动测试系统主要针对模拟IC的直流参数和交流参数进行测试,其中直流参数的测试是整个测试过程的重要部分。直流参数测试单元可以为芯片提供稳定的、精确的电压或电流,主要实现两种功能:一种是对待测芯片施加...
  • 单元测试过程中,对于某些不容易构造或者不容易获取的对象,用一个虚拟对象来创建以便测试的方法。 为什么使用mock测试 ​ 对模块进行集成测试时,希望能够通过输入URL对Controller进行测试,如果通过启动...
  • 小学语文一年级田园园地单元复习以及期中测试期末考试测试模拟试卷
  • 模拟实现单元测试中的异步测试

    千次阅读 2017-08-16 10:18:22
    随着前端项目复杂程度的增加,单元测试成为了前端开发过程中代码质量控制不可或缺的一部分。与后端单元测试有所不同的地方是前端开发中经常会出现异步请求的场景。针对这种情况,单元测试也就需要加入异步测试的方法...
  • 单元测试模拟HttpContext.Current

    千次阅读 2016-01-28 15:06:11
    第一次写Web单元测试,由于以前的DLL大量引用有HttpContext.Current的代码,结果每次运行到这类代码都会由于HttpContext.Current==null导致出错。 百度了一下发现可以在单元测试代码里直接给HttpContext.Current赋值...
  • mockfs-用于单元测试的简单模拟文件系统 通过替换os和glob模块中的函数,mockfs使得测试依赖于文件系统的代码成为可能。
  • 使用模拟框架即时创建模拟和存根,并使用单元测试框架对您的代码进行单元测试。 这一切都在一个包中,随时可用且易于使用! 安装和配置 下载“TestSuite”文件夹并将其放在您喜欢的任何目录中。 进入TestSuite...
  • 使用H2数据库来模拟进行单元测试

    万次阅读 2016-07-05 09:49:07
    在写DAO层的单元测试时,我们往往会遇到一个问题,测试用例所依赖的数据库数据被修改或删除了,或者在一个新的环境下所依赖的数据库不存在,导致单元测试无法通过,进而构建失败。 在这种情况下,使用H2内存数据库...
  • 目录一、单元测试的定义二、实例理解2.1可通过的测试一个模拟的登录测试用例测试代码运行结果2.2不可通过的测试一个模拟的登录测试用例测试代码运行结果三、单元测试的自动生成 一、单元测试的定义 单元测试是由开发...
  • 最近在看.net单元测试艺术,我也喜欢单元测试,今天介绍一下如何测试异常、如何测试返回值、如何测试模拟对象的参数传递、如何测试数据库访问代码。单元测试框架使用的是NUnit,模拟框架使用的是:Rhino.Mocks。 1...
  • 单元测试工具之Mockito

    千次阅读 2019-05-27 10:07:14
    Mockito 是一个针对 Java 的单元测试模拟框架,是为了简化单元测试过程中测试上下文 ( 或者称之为测试驱动函数以及桩函数 ) 的搭建而开发的工具。在有这些模拟框架之前,为了编写某一个函数的单元测试,程序员必须...
  • SpringMVC单元测试之MockMVC,模拟登录

    千次阅读 2018-09-26 09:43:17
    在一些实际开发中,很多情况下需要对数据库进行操作,但是这里的操作就设计到用户权限,所谓权限验证就是拿到...但是我们都知道,实际项目中单元测试是必不可少的,尤其对controller层的接口进行逐个测试,需要的...
  • 软件包github.com/elliotchance/redismock对于与Redis交互的单元测试应用程序很有用。 它使用了。 与使用真实或伪造的Redis(在下文中有更多介绍)不同, redismock提供了正常且美观的redismock ,以提高控制行为...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 98,907
精华内容 39,562
关键字:

单元测试模拟