首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >接口到Force.com REST。

接口到Force.com REST。
EN

Code Review用户
提问于 2016-06-07 12:39:55
回答 1查看 72关注 0票数 3

我正在实现到Force.com REST的接口,对于我需要的API的哪些部分有非常具体的想法。我的问题是这个API抽象的使用者的可测试性。

我想出的第一件事是一个网关,每一个动作都有一个方法:

代码语言:javascript
复制
interface SalesforceApiGateway {
    fun getLastUpdated(type: ObjectType, backTill: Duration = Duration.ofDays(1)): LastUpdated
    fun getDetails(type: ObjectType, uuid: String, fields: List<String> = emptyList()): Details

    data class LastUpdated(val error: Throwable? = null, val response: LastUpdatedResponse? = null)
    data class Details(val error: Throwable? = null, val response: DetailsResponse? = null)
}

使用者将得到这个接口@Injected的一个实例,因此测试可以很容易地实现该接口并将其传递给被测试的单元。

但是添加更多的对象类型和保持响应有意义和静态类型变得越来越困难。这就是为什么我转到了以下几个方面:

代码语言:javascript
复制
interface SalesforceApiClient: HttpClient, BackendAuthenticator<SalesforceCredentials> {}

sealed class SalesforceRequest<T>(val apiClient: SalesforceApiClient) {
    fun getLastUpdated(backTill: Duration = Duration.ofDays(1)): List<String> {
        val req: HttpGet = buildHttpRequest()
        val resp = apiClient.execute(req)
        return ObjectMapper().readValue(resp.entity.content, 
            object: TypeReference<List<String>>() {})
    }

    fun getDetails(uuid: String): T {
        val req: HttpGet = buildDetailRequest(uuid)
        val resp = apiClient.execute(req)
        return ObjectMapper().readValue(resp.entity.content, getValueClass())
    }

    abstract fun getObjectType(): String
    abstract fun getFields(): List<String>
    abstract fun getValueClass(): Class<T>

    class UserRequest(api: SalesforceApiClient): SalesforceRequest<User>(api) {
        override fun getObjectType() = "USER"
        override fun getFields() = User::class.members.map { it.name }
        override fun getValueClass(): Class<User> = User::class.java
    }
}

瞧,键入的响应和一组请求,您可以在编译时进行推理。API消费者现在很高兴。

但我该怎么测试这个野兽呢?当然,我可以模拟SalesforceApiClient,它就是HttpClientBackendAuthenticator的总和。但是,我觉得Force.com REST响应的结构知识被泄露到了面向UserRequest用户的单元测试中。这种感觉告诉我,我的课堂设计有问题。我怎样才能做得更好?

EN

回答 1

Code Review用户

回答已采纳

发布于 2016-06-08 15:05:51

我还没有找到一种方法来正确地测试/模拟SalesforceRequest在行使它的消费者。

它所做的就是让我意识到

  1. 命名类Request,然后在其上提供多个“请求”-like方法是一个错误。每个SalesforceRequest子类型都是一个完整的API提供程序。
  2. 从1开始,我采用泛型方法使SalesforceApiGateway类型安全(这是请求类的初始驱动程序)。

最后,我得到了以下接口:

代码语言:javascript
复制
interface SalesforceObject {
    val Id: String
    val CreatedById: String
    val LastModifiedDate: Instant
}

interface SalesforceApiGateway {
    val API_VERSION: String
    val MAX_RECENT_DAYS: Duration

    fun <T: SalesforceObject> getLastUpdated(type: KClass<T>, backTill: Duration = Duration.ofDays(1)): LastUpdated

    fun <T: SalesforceObject> getLastUpdated(type: KClass<T>, backTill: Instant): LastUpdated
            = getLastUpdated(type, Duration.between(backTill, Instant.now(Clock.systemUTC())))

    fun <T: SalesforceObject> getDetails(type: KClass<T>, uuid: String, fields: List<String> = type.members.map { it.name }): Details<T>

    data class LastUpdatedResponse(val ids: List<String> = emptyList(),
                                   val latestDateCovered: String = ISO_DATES.format(ZonedDateTime.now(Clock.systemUTC())))
    data class LastUpdated(val error: Throwable? = null, val response: LastUpdatedResponse? = null)

    data class DetailsResponse<T>(val wrapped: T)
    data class Details<T>(val error: Throwable? = null, val response: DetailsResponse<T>? = null)
    companion object {
        val ISO_DATES = DateTimeFormatter.ISO_OFFSET_DATE_TIME
    }
}

好处是我现在可以使用Kotlin令人敬畏的默认参数来允许我的网关使用者不关心他们想要选择的字段,但是高级的将来的使用仍然可以覆盖这个选择。它还允许我表示,我希望网关实现负责对正确请求类型的正确反序列化响应。另外,我仍然可以保留我的界面,所以通过实现一个虚拟接口来进行测试是非常简单的。

在这个过程中有几点建议:

  1. 使用结果类来建模成功/失败,而不是自定义的“结果”类。我无法了解他们创建现有基本类型的结果的模型(比如我的List<String>)。也许下次吧。
  2. 使用改造 (感谢orangy提到这个!)为网关生成代码。然而,杰克沃顿确认Retrofit不允许我模板我的方法,因此每个SalesforceObject都有n个方法。我试过了,只是复制和粘贴我的方法,使我的皮肤爬行。
票数 1
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/131327

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档