菜单

斯维夫特互连网请求之Moya的选用

2019年2月26日 - 金沙编程资讯

1、Token生成

Powermock for Java

https://github.com/powermock/powermock
它经过自定义类加载器和改动字节码来mock static methods, constructors,
final classes and methods, private methods, removal of static
initializers 等等

if (doc && doc._id) return res.tools.setJson(0, ‘注册成功’, {

//Moya 10的版本已经去掉了RxMoyaProvider代码,直接用MoyaProvider
let provider = MoyaProvider<MyService>()
provider.rx.request(.setUserStatus)
            .asObservable().mapJSON()
            .mapObject(type: UserStatusModel.self)
            .subscribe { [weak self] event in
                if self == nil{
                    return
                }
                switch event {
                case let .next(response):
                    //............
                    break
                case let .error(error):
                    print(error)
                    //这个地方,处了网络异常外,对错误码也可处理
                    if let err = error as? NSError{
                        if err.domain == "Network"{
                            switch err.code{
                            case 401:
                            print("param invalide")
                            break
                            default:
                            print("other error")    
                            }
                        }else{
                            print("other error") 
                        }
                    }else{
                        print("other error") 
                    }
                    break
                default:
                    break
                }
            }.disposed(by: disposeBag)

  

优秀示例

接下去, 让大家写多少个例子来表达 mock 和有关类库的用法…

就此函数体里可看出 doc.errcode, doc.errmsg 就是其一错误代码和错误音信了

1,Alamofire互联网框架请求数据
在iOS的工程中,此前用的是Alamofire网络框架,这一个框架的一个互联网请求示例如下:

接口  get  https://fabric.io/api/v2/organizations/organization_id/apps/app_id/growth_analytics/daily_active.json?start=1478736000&end=1478736000
头设置  Headers   Authorization: Bearer {access_token}

Mock 重视的类和章程

骨干步骤:

  1. mock 设置模拟行为
  2. call 调用被测试代码
  3. verify 检验期望行为

这里以 Guava Loading Cache
类为例, 测试它的着力表现是或不是相符预期

package com.github.walterfan.hellotest;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.RemovalCause;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
import com.google.common.util.concurrent.Uninterruptibles;
import lombok.extern.slf4j.Slf4j;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;

/**
 * Created by yafan on 23/1/2018.
 */
@Slf4j
public class LoadingCacheTest {
    private LoadingCache<String,  String> internalCache;

    @Mock
    private CacheLoader<String, String> cacheLoader;

    @Mock
    private RemovalListener<String, String> cacheListener;

    @Captor
    private ArgumentCaptor<RemovalNotification<String, String>> argumentCaptor;

    private Answer<String> loaderAnswer;

    private AtomicInteger loadCounter = new AtomicInteger(0);

    @BeforeMethod
    public void setup() {

        MockitoAnnotations.initMocks(this);

        this.internalCache = CacheBuilder.newBuilder()
                .maximumSize(3)
                .expireAfterWrite(1, TimeUnit.SECONDS)
                .removalListener(this.cacheListener)
                .build(this.cacheLoader);

        this.loaderAnswer = new Answer<String>() {
            @Override
            public String answer(InvocationOnMock invocationOnMock) throws Throwable {
                String key = invocationOnMock.getArgumentAt(0, String.class);
                switch(loadCounter.getAndIncrement()) {
                    case 0:
                        return "alice";
                    case 1:
                        return "bob";
                    case 2:
                        return "carl";
                    default:
                        return "unknown";
                }
            }
        };
    }

    @Test
    public void cacheTest() throws Exception {
        //Mock the return value of loader
        //Mockito.when(cacheLoader.load(Mockito.anyString())).thenReturn("alice");
        Mockito.when(cacheLoader.load(Mockito.anyString())).thenAnswer(loaderAnswer);

        assertTrue("alice".equals(internalCache.get("name")));

        //sleep for 2 seconds
        Uninterruptibles.sleepUninterruptibly(2, TimeUnit.SECONDS);
        assertTrue("bob".equals(internalCache.get("name")));

        verify(cacheLoader, times(2)).load("name");
        verify(cacheListener).onRemoval(argumentCaptor.capture());

        assertEquals(argumentCaptor.getValue().getKey(), "name");
        assertEquals(argumentCaptor.getValue().getValue(), "alice");
        assertEquals(argumentCaptor.getValue().getCause(), RemovalCause.EXPIRED);
    }
}

wechatSignUp(req, res, next) 

这么大家得以对那些错误码进行联合处理,data数据解析成功后回去

{
    "access_token": "ccccccccccccccccccccccc",
    "refresh_token": "refresh_tokenqqqqqqqqqqqqq"
}

Mock

Mock 是测试驱动开发必备之利器, 只要有事态, 有依靠, 做单元测试就不能没有
Mock
在 API 或 集成测试的时候, 假诺信赖于第②方的 API, 也每每使用 mock server
或 mock proxy

.then(doc 借使doc不为null,表明用户名曾经存在,就不用再度登记了嘛,

各类接口都亟需拼接那几个音讯,包涵api路径,请求的不二法门,参数,参数编码格式,新闻头等多少个重要部分,获取到网络数据后,再分析数据举行处理。
2,moya
moya的粤语表明:https://github.com/Moya/Moya/blob/master/Readme\_CN.md
它对互联网请求的url和parameter进行了更深的包裹
TargetType那些是选拔moya必供给兑现的2个共谋,这些里面大家基本能够看看它包裹的关键内容

 返回值

Mock 使用手续

表达什么啊, 除了你的次序的应用逻辑, 还有对此所mock的目的的并行验证

session_key    会话密钥

{
"code": 200,
"message": "",
"data": {}
}

 

Mock 静态方法

那里运用 Powermock 和 testng , 借使有 junit 的话, 用法稍有不相同
testng 需要从 PowerMockTestCase 继承
junit4 供给添加3个评释 @RunWith(PowerMockRunner.class)

package com.github.walterfan.hellotest;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;

import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;


@Slf4j
public class FileUtils {

    public static final FileFilter javaFileFilter = new FileFilter() {
        @Override
        public boolean accept(File file) {

            if(file.isDirectory()) {
                return true;
            }
            if(file.getName().endsWith(".java")) {
                return true;
            }

            return false;
        }
    };

    public static List<String> listFiles(File folder, FileFilter filter) {
        List<String> files = new ArrayList<>();
        listDir(new File("."), files, filter);
        return files;
    }

    public static void listDir(File folder, List<String> fileNames, FileFilter filter) {
        File[] files = folder.listFiles(filter);
        for (File file: files) {
            if(file.isFile()) {
                fileNames.add(file.getName());
            } else if (file.isDirectory()) {
                listDir(file, fileNames, filter);
            }
        }
    }
}

package com.github.walterfan.hellotest;



import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.modules.testng.PowerMockObjectFactory;
import org.powermock.modules.testng.PowerMockTestCase;
import org.testng.IObjectFactory;
import org.testng.annotations.Test;


import java.io.File;
import java.io.FileFilter;
import java.util.Arrays;
import java.util.List;

import static org.mockito.Matchers.eq;
import static org.testng.Assert.assertEquals;

//@RunWith(PowerMockRunner.class) -- for junit4
@PrepareForTest(FileUtils.class)
public class FileUtilsTest extends PowerMockTestCase {

    public  int howManyFiles(String path, FileFilter filter) {
        System.out.println("-----------");
        List<String> files = FileUtils.listFiles(new File(path), filter);
        files.forEach(System.out::println);
        return files.size();
    }


    @Test
    public void testHowManyFiles() {

        List<String> fileNames = Arrays.asList("a.java", "b.java", "c.java");
        PowerMockito.mockStatic(FileUtils.class);
        PowerMockito.when(FileUtils.listFiles(Mockito.any(), Mockito.any())).thenReturn(fileNames);

        int count = howManyFiles(".", FileUtils.javaFileFilter);
        assertEquals(count, 3);
    }
}

在 pom.xml 中加上

<dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-core</artifactId>
            <version>1.7.1</version>
            <scope>test</scope>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-api-mockito</artifactId>
            <version>1.7.1</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-module-junit4</artifactId>
            <version>1.7.1</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-module-testng</artifactId>
            <version>1.7.1</version>
            <scope>test</scope>
        </dependency>

其一函数先把用户名设置成null,密码设置成123456展开md5加密后的字符串,

至此,rxswift + moya就足以健康使用了
理所当然,moya的相比较alamofire还有不少好用的职能,后边再跟我们享受

  

模仿指标

一般都叫 Mock 或 Stub, 两者大概, 都是效仿被测组件对外正视的模仿, 存根
stub 就在那里, 不须要检查它和被测组件的相互, Mock
则能够用来检查于被测对象的互相

一经回去结果里成功获得了用户的openid

public protocol TargetType {

    /// The target's base `URL`.
    var baseURL: URL { get }

    /// The path to be appended to `baseURL` to form the full `URL`.
    var path: String { get }

    /// The HTTP method used in the request.
    var method: Moya.Method { get }

    /// Provides stub data for use in testing.这个数据是用在api测试时用的
    var sampleData: Data { get }

    /// The type of HTTP task to be performed.
    var task: Task { get }

    /// Whether or not to perform Alamofire validation. Defaults to `false`.
    var validate: Bool { get }

    /// The headers to be used in the request.
    var headers: [String: String]? { get }
}

2、返回值

3. mock 整个系统

与系统外部的交互全部mock 掉

简单的讲,模拟外部信赖要分裂内外的边界,找到合适的切入点

对于服务器端微信用户注册和登录向来未曾搞理解逻辑,看了一次代码,总算有点概念了

import Foundation
import RxSwift
import ObjectMapper
import RxCocoa

extension Observable {
    func mapObject<T: Mappable>(type: T.Type) -> Observable<T> {
        return self.map { response in
            //if response is a dictionary, then use ObjectMapper to map the dictionary
            //if not throw an error
            guard let dict = response as? [String: Any] else {
                throw RxSwiftMoyaError.ParseJSONError
            }
            guard (dict["code"] as? Int) != nil else{
                throw RxSwiftMoyaError.ParseJSONError
            }

            if let error = self.parseError(response: dict) {
                throw error
            }
            return Mapper<T>().map(JSON: dict["data"] as! [String : Any])!
        }
    }

    func mapArray<T: Mappable>(type: T.Type) -> Observable<[T]> {
        return self.map { response in
            guard response is [Any] else {
                throw RxSwiftMoyaError.ParseJSONError
            }

            guard let dicts = response as? [String: Any] else {
                throw RxSwiftMoyaError.ParseJSONError
            }

            guard (dicts["code"] as?Int) != nil else{
                throw RxSwiftMoyaError.ParseJSONError
            }

            if let error = self.parseError(response: dicts) {
                throw error
            }

            return Mapper<T>().mapArray(JSONArray: [dicts["data"] as! [String : Any]])
        }
    }

    func parseServerError() -> Observable {
        return self.map { (response) in
            let name = type(of: response)
            print(name)
            guard let dict = response as? [String: Any] else {
                throw RxSwiftMoyaError.ParseJSONError
            }
            if let error = self.parseError(response: dict) {
                throw error
            }
            return self as! Element
        }

    }

//最外层的dictionary解析,将data数据取去后转换成json对象
//如果是错误码,抛异常处理
    fileprivate func parseError(response: [String: Any]?) -> NSError? {
        var error: NSError?
        if let value = response {
            var code:Int?
            if let codes = value["code"] as? Int{
                code = codes
            }
            if  code != 200 {
                var msg = ""
                if let message = value["message"] as? String {
                    msg = message
                }
                error = NSError(domain: "Network", code: code!, userInfo: [NSLocalizedDescriptionKey: msg])
            }
        }
        return error
    }
}

enum RxSwiftMoyaError: String {
    case ParseJSONError
}

extension RxSwiftMoyaError: Error {

}
  1 <?php
  2 
  3 class ScriptUserDaily 
  4 {
  5   //保存第一次获取的 refresh_token ,用于下次刷新token用
  6     private $filePath = '/files/fabricToken.json';
  7 
  8     public function fire()
  9     {
 10         $access_token = $this->getRefreshToken();
 11         if(empty($access_token))
 12             $access_token = $this->getToken();
 13 
 14         $edata =  time();
 15         $sdata = $edata - 24 * 3600 * 5;
 16 
 17         $header = [
 18             "Authorization: Bearer ".$access_token
 19         ];
 20 
 21      //数据库获取应用,主要获取  organization_id  和 app_id
 22         $appinfo = FanAppInfo::byFabric()->get();
 23         foreach ($appinfo as $appItem){
 24             $fabricid = $appItem->fabricid;
 25             if(empty($fabricid))
 26                 continue;
 27 
 28        $organization_id = $appItem->organization_id;
 29             $url = "https://fabric.io/api/v2/organizations/$organization_id/apps/$fabricid/growth_analytics/daily_active.json?start=$sdata&end=$edata";
 30 
 31             $this->getDatas($url,$header,1);
 32 
 33             $url2 = "https://fabric.io/api/v2/organizations/$organization_id/apps/$fabricid/growth_analytics/daily_new.json?start=$sdata&end=$edata";
 34 
 35             $this->getDatas($url2,$header,2);
 36         }
 37     }
 38 
 39     private function getRefreshToken(){
 40         //获取 refresh_token 从文件中读取保存的refresh_token
 41         $path = $this->filePath;
 42 
 43         $JsonData = file_get_contents($path);
 44 
 45         $rejson = json_decode($JsonData, true);
 46         $refresh_token = $rejson['refresh_token'];
 47 
 48         //刷新 token
 49         $url = 'https://fabric.io/oauth/token';
 50         $header = [
 51             "content-type: application/json"
 52         ];
 53         $body = [
 54             'grant_type' => 'refresh_token',
 55             'refresh_token' => trim($refresh_token)
 56         ];
 57 
 58         $data = $this->curl_post($url,$header,json_encode($body));
 59         $rejson = json_decode($data, true);
 60 
 61         $access_token_new = '';
 62         $refresh_token_new = '';
 63         if(isset($rejson['refresh_token']))
 64             $refresh_token_new = $rejson['refresh_token'];
 65         if(isset($rejson['access_token']))
 66             $access_token_new = $rejson['access_token'];
 67 
 68         if(!empty($refresh_token_new)){
 69             $txt = [
 70                 'access_token' => $access_token_new,
 71                 'refresh_token' => $refresh_token_new
 72             ];
 73 
 74             //重新写入新的 refresh_token
 75            $this->writeRefreshToken($txt);
 76         }
 77 
 78         return $access_token_new;
 79     }
 80 
 81     private function getToken(){
 82         $url = 'https://fabric.io/oauth/token';
 83         $header = [
 84             "content-type: application/json"
 85         ];
 86         $body = [
 87             'grant_type' => 'password',
 88             'scope' => 'organizations apps issues features account twitter_client_apps beta software answers',
 89             'username' => '14141414@qq.com',
 90             'password' => '123456789',
 91             'client_id' => '2c18f8a77609ee6bbac9e53f3768fedc45fb96be0dbcb41defa706dc57d9c931',
 92             'client_secret' => '092ed1cdde336647b13d44178932cba10911577faf0eda894896188a7d900cc9'
 93         ];
 94 
 95         $data = $this->curl_post($url,$header,json_encode($body));
 96         $rejson = json_decode($data, true);
 97 
 98         $access_token_new = '';
 99         $refresh_token_new = '';
100         if(isset($rejson['refresh_token']))
101             $refresh_token_new = $rejson['refresh_token'];
102         if(isset($rejson['access_token']))
103             $access_token_new = $rejson['access_token'];
104 
105         if(!empty($refresh_token_new)){
106             $txt = [
107                 'access_token' => $access_token_new,
108                 'refresh_token' => $refresh_token_new
109             ];
110 
111             //重新写入新的 refresh_token
112             $this->writeRefreshToken($txt);
113         }
114 
115         return $access_token_new;
116     }
117 
118     private function writeRefreshToken($txt){
119         $path = $this->filePath;
120 
121         $myfile = fopen($path, "w");
122         $txt = json_encode($txt);
123         fwrite($myfile,$txt);
124         fclose($myfile);
125     }
126 
127     private function getDatas($url,$header,,$type){
128         $resData = $this->curl_get($url,$header);
129         $datas = json_decode($resData, true);
130 
131         if(!isset($datas['series']))
132             return '';
133 
134         $active = 0;
135         $news = 0;
136         foreach ($datas['series'] as $item){
137             $date = date('Y-m-d',$item[0]);
138 
139             if($type == 1){
140                 $active = intval($item[1]);
141             }elseif($type == 2){
142                 $news = intval($item[1]);
143             }
144 
145            //处理数据
146 
147         }
148     }
149 
150     private function curl_get($url, $header = [], $time = 5){
151       $ch = curl_init();
152       curl_setopt($ch, CURLOPT_URL, $url);
153       curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
154       curl_setopt($ch, CURLOPT_HEADER, 0);
155       if (!empty($header)) {
156           curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
157       }
158       curl_setopt($ch, CURLOPT_TIMEOUT, $time);
159       $result = curl_exec($ch);
160       curl_close($ch);
161       return $result;
162   }
163 
164   private function curl_post($url, $header = [], $body = [], $time = 5){
165       $ch = curl_init();
166       curl_setopt($ch, CURLOPT_URL, $url);
167       curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
168       if (!empty($body)) {
169           curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
170       }
171       curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
172       curl_setopt($ch, CURLOPT_HEADER, 0);
173       if (!empty($header)) {
174           curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
175       }
176       curl_setopt($ch, CURLOPT_TIMEOUT, $time);
177       $result = curl_exec($ch);
178       curl_close($ch);
179       return $result;
180   }
181 }

1. mock二个函数

与这一个函数的相互全体mock 掉

https://github.com/skyvow/m-mall-admin

func setUserStatus() {
    let parameters:[String : Any] = ["userid": userid!]
    Alamofire.request(URL_SET_USER_STATUS, method: .post, parameters: parameters, encoding: URLEncoding.default, headers: nil).responseObject{[weak self]
        (response :DataResponse<StatusModel>) in
        if self == nil{
            return
        }
        let ret = response.result.value
        if response.result.error != nil{
            ......
        }else{
            .......
        }
    }
}

3、利用 refresh_token
刷新新的token(讲获得的refresh_token<无过期时间>保存下来,刷新token):

mock 的粒度

基于你测试的对象大小,粒度自然有分别,根据测试三角形,小而美,越大越麻烦,
从小到大能够分成如下八个粒度

下一场在后台数据库的user表里搜寻是否曾经有注册过那个用户
this.model.findByName(doc.openid)

3,moya的施用示例
基本的运用示例:https://github.com/Moya/Moya/blob/master/docs/Examples/Basic.md
一律的接口封装成moya后

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图