Mock 测试

Mock 测试Mock基本概念介绍mock测试就是在测试过程中,对于某些不容易构造或者不容易获取的对象,用一个虚拟的对象来创建以便测试的测试方法。好处团队并行工作 团队间不需互相等待对方进度,只需约定好相互之间的数据规范(接口文档),即可使用mock构建出可用接口,然后尽快进行开发和自测,提前发现缺陷 测试驱动开发TDD(Test-DrivenDevelopment) 单元测试是TDD实现的基石,而TDD经常会碰到协同模块尚未开发完成的情况,但有了mock,当接口定义好后,测试人

大家好,又见面了,我是你们的朋友全栈君。

Mock 基本概念介绍

mock 测试就是在测试过程中,对于某些不容易构造或者不容易获取的对象,用一个虚拟的对象来创建以便测试的测试方法。

好处

  1. 团队并行工作
    团队间不需互相等待对方进度,只需约定好相互之间的数据规范(接口文档),即可使用 mock 构建出可用接口,然后尽快进行开发和自测,提前发现缺陷
  2. 测试驱动开发 TDD (Test-Driven Development)
    单元测试是 TDD 实现的基石,而 TDD 经常会碰到协同模块尚未开发完成的情况,但有了 mock,当接口定义好后,测试人员就可以创建一个 Mock,把接口添加到自动化测试环境,提前创建测试。
  3. 测试覆盖率
    若一个接口在不同的状态下要返回不同的值,常见做法是复现这种状态然后再去请求接口,而这种方法很可能因操作时机或方式不当导致失败,甚至污染后端存储如数据库等, 但用 mock 则不用担心
  4. 隔离系统
    使用某些接口时,为避免系统数据库被污染,可以将接口调整为 Mock 模式,以保证数据库纯净。
  5. 方便演示

Mock 框架介绍

因为项目主要基于 Java 开发, 因此下面主要介绍 Java 相关的 mock 框架, 其他语言思想类似

moco

moco 框架在开发 Mock 服务的时候提供了一种不需任何编程语言的方式, 可以通过撰写它约束的 json 建立服务, 并通过命令独立启动对应的服务, 这可以快速开发和启动运行所需的 Mock 服务. 除此之外, 也可以编写服务代码来进行测试. 下面进行简单举例:

  1. 使用 json 配置文件启动 mock 服务

 

# foo.json
[
  {
    "response" :
      {
        "text" : "Hello, Moco"
      }
  }
]

 

java -jar moco-runner-1.1.0-standalone.jar  http -p 12306 -c foo.json

这时访问 http://localhost:12306/ 将会返回 Hello, Moco

  1. 在项目中使用 moco Java API
    除了使用 json 配置文件作为独立服务启动外, 还可以使用 Java API 来启动 mock 服务, 下面是代码片段:

 

@RunWith(SpringRunner.class)
@SpringBootTest(classes = MockServletContext.class)
public class MockAPITest {
    @Test
    public void should_response_as_expected() throws Exception {
        HttpServer server = httpServer(12307);
        server.response("foo");
        running(server, new Runnable() {
            @Override
            public void run() throws IOException {
                CloseableHttpResponse response = HttpClients.createDefault().execute(new HttpGet("http://localhost:12307"));
                String content = EntityUtils.toString(response.getEntity(), "UTF-8");
                assertThat(content, is("foo"));
            }
        });
    }
}

moco 还支持 HTTPS 和 Socket, 支持与 JUnit 集成等, 详细内容见文档使用说明

SpringMVC 单元测试 – MockMvc

MockMvc 实现了对 Http 请求的模拟,可以方便对 Controller 进行测试,测试速度快、不依赖网络环境,且提供了验证的工具。下面是具体示例:

  • HelloController

 

//HelloController
@RestController
public class HelloController {
    @RequestMapping("/hello")
    public String index() {
        return "Hello World";
    }
}
  • UserController

 

//UserController
@Slf4j
@RestController
@RequestMapping(value = "/users")     // 通过这里配置使下面的映射都在/users下
public class UserController {
    // 创建线程安全的Map
    static Map<Long, User> users = Collections.synchronizedMap(new HashMap<Long, User>());

    @RequestMapping(value = "/", method = RequestMethod.GET)
    public List<User> getUserList() {
        // 处理"/users/"的GET请求,用来获取用户列表
        // 还可以通过@RequestParam从页面中传递参数来进行查询条件或者翻页信息的传递
        List<User> r = new ArrayList<User>(users.values());
        return r;
    }

    @RequestMapping(value = "/", method = RequestMethod.POST)
    public String postUser(@ModelAttribute User user) {
        // 处理"/users/"的POST请求,用来创建User
        // 除了@ModelAttribute绑定参数之外,还可以通过@RequestParam从页面中传递参数
        users.put(user.getId(), user);
        return "success";
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public User getUser(@PathVariable Long id) {
        // 处理"/users/{id}"的GET请求,用来获取url中id值的User信息
        // url中的id可通过@PathVariable绑定到函数的参数中
        return users.get(id);
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.PUT)
    public String putUser(@PathVariable Long id, @ModelAttribute User user) {
        // 处理"/users/{id}"的PUT请求,用来更新User信息
        User u = users.get(id);
        u.setName(user.getName());
        u.setAge(user.getAge());
        users.put(id, u);
        return "success";
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
    public String deleteUser(@PathVariable Long id) {
        // 处理"/users/{id}"的DELETE请求,用来删除User
        users.remove(id);
        return "success";
    }
    // 测试 
    @RequestMapping(value = "/postByJson", method = RequestMethod.POST)
    public String postByJson(@RequestBody User user, String method) {
        log.info("user: {};   method: {}", user, method);
        return "success";
    }
}

  • 单元测试类 HttpMockTest

 

public class HttpMockTest {

    private MockMvc mvc;
    private final static ObjectMapper objectMapper = new ObjectMapper();

    @Before
    public void setUp() throws Exception {
        mvc = MockMvcBuilders.standaloneSetup(
                new HelloController(),
                new UserController()).build();
    }

    @Test
    public void getHello() throws Exception {
        mvc.perform(MockMvcRequestBuilders.get("/hello").accept(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(content().string(equalTo("Hello World")));
    }

    @Test
    public void testUserController() throws Exception {
        // 测试UserController
        RequestBuilder request = null;

        // 1、get查一下user列表,应该为空
        request = get("/users/");
        mvc.perform(request)
                .andExpect(status().isOk())
                .andExpect(content().string(equalTo("[]")));

        // 2、post提交一个user
        request = post("/users/")
                .param("id", "1")
                .param("name", "测试大师")
                .param("age", "20");
        mvc.perform(request)
                .andDo(MockMvcResultHandlers.print())
                .andExpect(content().string(equalTo("success")));

        // 3、get获取user列表,应该有刚才插入的数据
        request = get("/users/");
        mvc.perform(request)
                .andExpect(status().isOk())
                .andExpect(content().string(equalTo("[{\"id\":1,\"name\":\"测试大师\",\"age\":20}]")));

        // 4、put修改id为1的user
        request = put("/users/1")
                .param("name", "测试终极大师")
                .param("age", "30");
        mvc.perform(request)
                .andExpect(content().string(equalTo("success")));

        // 5、get一个id为1的user
        request = get("/users/1");
        mvc.perform(request)
                .andExpect(content().string(equalTo("{\"id\":1,\"name\":\"测试终极大师\",\"age\":30}")));

        // 6、del删除id为1的user
        request = delete("/users/1");
        mvc.perform(request)
                .andExpect(content().string(equalTo("success")));

        // 7、get查一下user列表,应该为空
        request = get("/users/");
        mvc.perform(request)
                .andExpect(status().isOk())
                .andExpect(content().string(equalTo("[]")));

        // 8、json作为参数
        request = post("/users/postByJson")
                .param("method", "postByJson")
                .content(objectMapper.writeValueAsString(new User(1L, "USER", 23)))
                .contentType(MediaType.APPLICATION_JSON);
        mvc.perform(request).andExpect(status().is(200))
                .andExpect(content().string("success"));
    }
}

Mockito & PowerMock

Mockito 是 GitHub 上使用非常广泛的 Java Mock 框架, star 数 11k, 在包括 openstack4jkubernetes-client/java 等都有用到. Mockito 与 JUnit 结合使用, 能隔离外部依赖以便对自己的业务逻辑代码进行单元测试在编写单元测试需要调用某一个接口时,可以模拟一个假方法,并任意指定方法的返回值。Mockito 的工作原理是通过创建依赖对象的 proxy,所有的调用先经过 proxy 对象,proxy 对象拦截了所有的请求再根据预设的返回值进行处理。但缺点是 Mockito 2 版本对静态方法、final 方法、private 方法和构造函数的功能支持并不完善, 因此 PowerMock 则在 Mockito 原有的基础上做了扩展,通过修改类字节码并使用自定义 ClassLoader 加载运行的方式来实现 mock 静态方法、final 方法、private 方法和构造函数等功能。

Mockito & PowerMock 一般测试步骤

1. mock: 模拟对象

用 mock()/@Mock 或 spy()/@Spy 创建模拟对象, 两者创建出来的模拟对象区别是: 使用 mock 生成的对象,所有方法都是被 mock 的,除非某个方法被 stub 了,否则返回值都是默认值; 使用 spy 生产的 spy 对象,所有方法都是调用的 spy 对象的真实方法,直到某个方法被 stub 后

2. stub: 定义桩函数

可以通过 when()/given()/thenReturn()/doReturn()/thenAnswer() 等来定义 mock 对象如何执行, 如果提供的接口不符合需求, 还可以通过实现 Answer 接口来自定义实现

3. run: 执行调用

执行实际方法的调用,此时被 mock 的对象将返回自定义的桩函数的返回值

4. verify: 可选, 对调用进行验证, 如是否被调用, 调用次数等

这一步可以对 mock 对象的方法是否被调用以及被调用次数进行验证,同时还可以对参数捕获进行参数校验

下面以操作 Redis 和 RabbitMQ 来进行简单举例。

Redis

 

// redis 操作类
class RedisDemo {

    private Jedis jedis;

    public void setUp() {
        jedis = new Jedis("127.0.0.1", 6379);
        jedis.connect();
    }

    public boolean isAdmin(String user) {
        String ret = jedis.get("name");
        if (user.equals(ret)) {
            return true;
        }
        return false;
    }

    public void set(String key, String val) {
        jedis.set(key, val);
    }

    public String get(String key) {
        String s = jedis.get(key);
        return s;
    }

    void out(){
        System.out.println("ss");
    }
}

// 单元测试类
@RunWith(PowerMockRunner.class) //让测试运行于PowerMock环境
public class RedisMockitoTest {

    @Mock //此注解会自动创建1个mock对象并注入到@InjectMocks对象中
    private Jedis jedis;

    @InjectMocks
    private RedisDemo demo;

    @Mock
    StringOperator stringOperator;

    //第1种方式
    @Test
    public void redisTest1() throws Exception {
        Mockito.when(jedis.get("name")).thenReturn("admin");
        boolean admin = demo.isAdmin("admin");
        assertTrue(admin);
    }

    //第2种方式
    @Test
    public void redisTest2() {
        RedisDemo demo = mock(RedisDemo.class);
        ReflectionTestUtils.setField(demo, "jedis", jedis);
        when(demo.isAdmin("admin")).thenReturn(true);
        boolean admin = demo.isAdmin("admin");
        assertTrue(admin);
    }

    //第3种方式
    @Test
    public void redisTest3() {
        RedisDemo demo = mock(RedisDemo.class);
        doReturn(true).when(demo).isAdmin("admin");
        System.out.println(demo.isAdmin("admin"));
    }
}

RabbitMQ

 

@Component
public class DirectReceiver {
    @Autowired
    RabbitTemplate rabbitTemplate;

    public Object getMsg() {
        return rabbitTemplate.receiveAndConvert("queue_demo");
    }
}

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = Main.class)
public class RecvMessage {
    @Spy
    RabbitTemplate rabbitTemplate;

    @InjectMocks
    @Autowired
    DirectReceiver receiver;

    @Test
    public void recvTest() {
        doReturn("Mock answer").when(rabbitTemplate).receiveAndConvert("queue_demo");
        System.out.println(rabbitTemplate.receiveAndConvert("queue_demo"));
    }
}

更多示例

 

public class Node {
    private int num;
    private String name;

    public static Node getStaticNode() {
        return new Node(1, "static node");
    }

    public Node() {
    }

    public Node(String name) {
        this.name = name;
    }

    public Node(int num) {
        this.num = num;
    }

    public Node(int num, String name) {
        this.num = num;
        this.name = name;
    }
}

public class LocalServiceImpl implements ILocalService {

    @Autowired
    private IRemoteService remoteService;

    @Override
    public Node getLocalNode(int num, String name) {
        return new Node(num, name);
    }

    @Override
    public Node getRemoteNode(int num) {
        return remoteService.getRemoteNode(num);
    }

    @Override
    public Node getRemoteNode(String name) throws MockException {
        try {
            return remoteService.getRemoteNode(name);
        } catch (IllegalArgumentException e) {
            throw e;
        }
    }

    @Override
    public void remoteDoSomething() {
        remoteService.doSometing();
    }

}

public class RemoteServiceImpl implements IRemoteService {

    @Override
    public Node getRemoteNode(int num) {
        return new Node(num, "Node from remote service");
    }

    @Override
    public final Node getFinalNode() {
        return new Node(1, "final node");
    }

    @Override
    public Node getRemoteNode(String name) throws MockException {
        if (StringUtils.isEmpty(name)) {
            throw new MockException("name不能为空", name);
        }
        return new Node(name);
    }

    @Override
    public void doSometing() {
        System.out.println("remote service do something!");
    }

    @Override
    public Node getPrivateNode() {
        return privateMethod();
    }

    private Node privateMethod() {
        return new Node(1, "private node");
    }

    @Override
    public Node getSystemPropertyNode() {
        return new Node(System.getProperty("abc"));
    }
}

// 单元测试类
@RunWith(MockitoJUnitRunner.class) //让测试运行于Mockito环境
public class LocalServiceImplMockTest {

    @InjectMocks //此注解表示这个对象需要被注入mock对象
    private LocalServiceImpl localService;
    @Mock //此注解会自动创建1个mock对象并注入到@InjectMocks对象中
    private RemoteServiceImpl remoteService;
    @Captor
    private ArgumentCaptor<String> localCaptor;

    //如果不使用上述注解,可以使用@Before方法来手动进行mock对象的创建和注入,但会多几行代码
    /*@Before
    public void setUp() throws Exception {
        localService = new LocalServiceImpl();
        remoteService = mock(RemoteServiceImpl.class);
        Whitebox.setInternalState(localService, "remoteService", remoteService);
    }*/

    /**
     * any系列方法指定多参数情况
     */
    @Test
    public void testAny() {
        Node target = new Node(1, "target");
        when(remoteService.getRemoteNode(anyInt())).thenReturn(target); //静态导入Mockito.when和ArgumentMatchers.anyInt后可以简化代码提升可读性

        Node result = localService.getRemoteNode(20); //上面指定了调用remoteService.getRemoteNode(int)时,不管传入什么参数都会返回target对象
        assertEquals(target, result);   //可以断言我们得到的返回值其实就是target对象
        assertEquals(1, result.getNum());   //具体属性和我们指定的返回值相同
        assertEquals("target", result.getName());   //具体属性和我们指定的返回值相同
    }

    /**
     * 指定mock多次调用返回值
     */
    @Test
    public void testMultipleReturn() {
        Node target1 = new Node(1, "target");
        Node target2 = new Node(1, "target");
        Node target3 = new Node(1, "target");
        when(remoteService.getRemoteNode(anyInt())).thenReturn(target1).thenReturn(target2).thenReturn(target3);
        //第一次调用返回target1、第二次返回target2、第三次返回target3

        Node result1 = localService.getRemoteNode(1); //第1次调用
        assertEquals(target1, result1);
        Node result2 = localService.getRemoteNode(2); //第2次调用
        assertEquals(target2, result2);
        Node result3 = localService.getRemoteNode(3); //第3次调用
        assertEquals(target3, result3);
    }

    /**
     * 指定mock对象已声明异常抛出的方法抛出受检查异常
     */
    @Test
    public void testCheckedException() {
        try {
            Node target = new Node(1, "target");
            when(remoteService.getRemoteNode("name")).thenReturn(target).thenThrow(new MockException("message", "exception")); //第一次调用正常返回,第二次则抛出一个Exception

            Node result1 = localService.getRemoteNode("name");
            assertEquals(target, result1); //第一次调用正常返回

            Node result2 = localService.getRemoteNode("name"); //第二次调用不会正常返回,会抛出异常
            assertEquals(target, result2);
        } catch (MockException e) {
            assertEquals("exception", e.getName()); //验证是否返回指定异常内容
            assertEquals("message", e.getMessage()); //验证是否返回指定异常内容
        }
    }

    /**
     * 校验mock对象和方法的调用情况
     */
    public void testVerify() {
        Node target = new Node(1, "target");
        when(remoteService.getRemoteNode(anyInt())).thenReturn(target);

        verify(remoteService, Mockito.never()).getRemoteNode(1); //mock方法未调用过

        localService.getRemoteNode(1);
        verify(remoteService, times(1)).getRemoteNode(anyInt()); //目前mock方法调用过1次

        localService.getRemoteNode(2);
        verify(remoteService, times(2)).getRemoteNode(anyInt()); //目前mock方法调用过2次
        verify(remoteService, times(1)).getRemoteNode(2); //目前mock方法参数为2只调用过1次
    }

    /**
     * mock对象调用真实方法
     */
    @Test
    public void testCallRealMethod() {
        when(remoteService.getRemoteNode(anyInt())).thenCallRealMethod(); //设置调用真实方法
        Node result = localService.getRemoteNode(1);

        assertEquals(1, result.getNum());
        assertEquals("Node from remote service", result.getName());
    }

    /**
     * 利用ArgumentCaptor捕获方法参数进行mock方法参数校验
     */
    @Test
    public void testCaptor() throws Exception {
        Node target = new Node(1, "target");
        when(remoteService.getRemoteNode(anyString())).thenReturn(target);

        localService.getRemoteNode("name1");
        localService.getRemoteNode("name2");
        verify(remoteService, atLeastOnce()).getRemoteNode(localCaptor.capture()); //设置captor

        assertEquals("name2", localCaptor.getValue()); //获取最后一次调用的参数
        List<String> list = localCaptor.getAllValues(); //按顺序获取所有传入的参数
        assertEquals("name1", list.get(0));
        assertEquals("name2", list.get(1));
    }

    /**
     * 校验mock对象0调用和未被验证的调用
     */
    @Test(expected = NoInteractionsWanted.class)
    public void testInteraction() {

        verifyZeroInteractions(remoteService); //目前还未被调用过,执行不报错

        Node target = new Node(1, "target");
        when(remoteService.getRemoteNode(anyInt())).thenReturn(target);

        localService.getRemoteNode(1);
        localService.getRemoteNode(2);
        verify(remoteService, times(2)).getRemoteNode(anyInt());
        // 参数1和2的两次调用都会被上面的anyInt()校验到,所以没有未被校验的调用了
        verifyNoMoreInteractions(remoteService);

        reset(remoteService);
        localService.getRemoteNode(1);
        localService.getRemoteNode(2);
        verify(remoteService, times(1)).getRemoteNode(1);
        // 参数2的调用不会被上面的校验到,所以执行会抛异常
        verifyNoMoreInteractions(remoteService);
    }
}

WireMock

WireMock 是在阅读 kubernetes-client/java 代码时发现的, 在其中有大量使用,它是基于 HTTP API 的 mock 服务框架,和前面提到的 moco 一样,它可以通过文件配置以独立服务启动, 也可以通过代码控制,同时 Spring Cloud Contract WireMock 模块也使得可以在 Spring Boot 应用中使用 WireMock,具体介绍见 Spring Cloud Contract WireMock 。除此之外, WireMock 还提供了在线 mock 服务 MockLab 。下面是 WireMock 在 K8S API 上的示例:

 

public class K8SApiTest {
    @Rule
    public WireMockRule wireMockRule = new WireMockRule(8000);

    private GenericKubernetesApi<V1Job, V1JobList> jobClient;

    ApiClient apiClient;


    @Before
    public void setup() {
        apiClient = new ClientBuilder().setBasePath("http://localhost:" + 8000).build();
        jobClient =
                new GenericKubernetesApi<>(V1Job.class, V1JobList.class, "batch", "v1", "jobs", apiClient);
    }

    // test delete
    @Test
    public void delJob() {
        V1Status status = new V1Status().kind("Status").code(200).message("good!");
        stubFor(
                delete(urlEqualTo("/apis/batch/v1/namespaces/default/jobs/foo1"))
                        .willReturn(aResponse().withStatus(200).withBody(new Gson().toJson(status))));

        KubernetesApiResponse<V1Job> deleteJobResp = jobClient.delete("default", "foo1", null);
        assertTrue(deleteJobResp.isSuccess());
        assertEquals(status, deleteJobResp.getStatus());
        assertNull(deleteJobResp.getObject());
        verify(1, deleteRequestedFor(urlPathEqualTo("/apis/batch/v1/namespaces/default/jobs/foo1")));
    }

    @Test
    public void getNs() throws ApiException {
        Configuration.setDefaultApiClient(apiClient);

        V1Namespace ns1 = new V1Namespace().metadata(new V1ObjectMeta().name("name"));

        stubFor(
                get(urlEqualTo("/api/v1/namespaces/name"))
                        .willReturn(
                                aResponse()
                                        .withHeader("Content-Type", "application/json")
                                        .withBody(apiClient.getJSON().serialize(ns1))));

        CoreV1Api api = new CoreV1Api();
        V1Namespace ns2 = api.readNamespace("name", null, null, null);
        assertEquals(ns1, ns2);
    }
}

总结

以上,就是关于 Mock 服务框架及使用的简单介绍, 详细用法还需要参考相应的文档或源码。关于 Mock 服务框架的选择, 在 《微服务接口:怎么用 Mock 解决混乱的调用关系?》 一文中提到,首先要基于团队的技术栈来选择,这决定了完成服务”替身”的速度;其次,Mock 要方便快速修改和维护,并能马上发挥作用。而关于 Mock 服务的设计,首先要简单,其次处理速度比完美的 Mock 服务更重要;最后,Mock 服务要能轻量化启动,并能容易销毁。

参考:

mock

moco

SpringMVC 单元测试 – MockMvc

Mockito & PowerMock

Wiremock

作者:深度沉迷学习
链接:https://www.jianshu.com/p/d3749d8324f4
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/151362.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 反射getmethod参数_java通过反射获取属性值

    反射getmethod参数_java通过反射获取属性值1、forName方法forName是一个静态方法,其作用:通过调用来获取类名对应的Class对象,同时将Class对象加载进来。如果将类名保存在字符串(如xml)中,就可以在程序运行时,动态调用加载。注意:只有调用的参数是类名或者方法时,才可用。2、newInstance()方法作用:将对象实例化。返回类型为Object。与new的区别在于,new可以带参,而newInstance()不可以,…

  • 妙手跨境电商erp下载_跨境电商平台开源系统

    妙手跨境电商erp下载_跨境电商平台开源系统概述化繁为简的一站式跨境电商免费开源ERP管理平台,简洁的操作,助您高效工作。目前开源智造·Odoo跨境电商解决方案,默认标准支持如下电商平台:Amazon、eBay、WooCommerce、Shopify提升运营效率数据驱动,提升精细化运营能力与财务、供应链高效协同轻松掌控发展优化流程,把控风险,决策支撑解决财务痛点数据及时准确,提升财务效率告别繁琐对账功能特性产品管理智能,便捷、结构化…

  • 流量精灵刷流量的实例教程

    流量精灵刷流量的实例教程流量精灵大概是去年的这个时候出来的一款提升网站访问量的软件,由于软件比较优秀,当时我就及时关注这款软件,现在时隔一年的时候,流量精灵在低调的发展下,版本也更新到了2.1.7。而以前只是写了一下流量精灵的测评文章,感觉已不能满足大家的需求,所以今天为广大的新手及新站长朋友写上详细的使用教程。流量精灵的功能介绍:主要就是刷流量的。这里的流量主要是指网站的流量,网站流量简单一点说就是访…

  • 我给鸿星尔克写了一个720°看鞋展厅

    我给鸿星尔克写了一个720°看鞋展厅最近因为鸿星尔克给河南捐了5000万物资,真的是看哭了很多的网友,普通一家公司捐款5000万可能不会有这样的共情,但是看了鸿星尔克的背景之后,发现真的是令人心酸。鸿星尔克2020年的营收是28亿,但是利润却是亏损2个亿,甚至连微博的官方账号都舍不得开会员,在这种情况下,还豪气地捐赠5000万,真的是破防了。网友还称鸿星尔克,特别像是老一辈人省吃俭用一分一毛攒起来的存款,小心翼翼存在铁盒里。一听说祖国需要,立马拿出铁盒子,哗~全导给你。让上最贵的鞋,拿出了双249的。然后我去鸿星尔克的官网看了看他.

  • JS工厂模式_工厂模式进行封装

    JS工厂模式_工厂模式进行封装JS设计模式一:工厂模式

  • cortex-m3权威指南_core M3

    cortex-m3权威指南_core M3Cortex-M3Bit-Banding1.概述CM3的存储器系统支持所谓的“位带”(bit-band)操作。通过它,实现了对单一bit的原子操作。位带操作仅适用于一些特殊的存储器区域中。从汇编角度看:与传统方法的比较:在位带区中,每个比特都映射到别名地址区的一个字——这是个只有LSB才有效的字。支持位带操作的两个内存区的范围是:0x2000_0000-0x2…

    2022年10月13日

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号