跳转至

44 RESTful Web Services(8):如何在现有代码的基础上构造测试?

你好,我是徐昊。今天我们继续使用TDD的方式实现RESTful Web Services。

回顾架构愿景与任务列表

目前我们的架构愿景如下:


任务列表为:

  • ResourceServlet

  • 将请求派分给对应的资源(Resource),并根据返回的状态、超媒体类型、内容,响应Http请求

    • 使用OutboundResponse的status作为Http Response的状态
    • 使用OutboundResponse的headers作为Http Response的Http Headers
    • 通过MessageBodyWriter将OutboundResponse的GenericEntity写回为Body
    • 如果找不到对应的MessageBodyWriter,则返回500族错误
    • 如果找不到对应的HeaderDelegate,则返回500族错误
    • 如果找不到对应的ExceptionMapper,则返回500族错误
    • 如果entity为空,则忽略body
    • 当资源方法抛出异常时,根据异常响应Http请求

    • 如果抛出WebApplicationException,且response不为null,则使用response响应Http

    • 如果抛出的不是WebApplicationException,则通过异常的具体类型查找ExceptionMapper,生产response响应Http请求
    • 当其他组件抛出异常时,根据异常响应Http请求

    • 调用ExceptionMapper时

    • 调用HeaderDelegate时
    • 调用MessageBodyWriter时
    • 通过Providers查找ExceptionMapper时
    • 通过Providers查找MessageBodyWriter时
    • 通过RuntimeDelegate查找HeaderDelegate时
    • RuntimeDelegate
  • 为MediaType提供HeaderDelegate

  • 为CacheControl提供HeaderDelegate
  • 为Cookie提供HeaderDelegates
  • 为EntityTag提供HeaderDelegate
  • 为Link提供HeaderDelegate
  • 为NewCookie提供HeaderDelegate
  • 为Date提供HeaderDelegate
  • 提供OutboundResponseBuilder
  • OutboundResponseBuilder
  • OutboundResponse

代码为:

package geektime.tdd.rest;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.GenericEntity;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.MessageBodyWriter;
import jakarta.ws.rs.ext.Providers;
import jakarta.ws.rs.ext.RuntimeDelegate;
import java.io.IOException;
import java.util.function.Supplier;

public class ResourceServlet extends HttpServlet {
    private Runtime runtime;
    private Providers providers;

    public ResourceServlet(Runtime runtime) {
        this.runtime = runtime;
        this.providers = runtime.getProviders();
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ResourceRouter router = runtime.getResourceRouter();
        respond(resp, () -> router.dispatch(req, runtime.createResourceContext(req, resp)));
    }

    private void respond(HttpServletResponse resp, Supplier<OutboundResponse> supplier) {
        try {
            respond(resp, supplier.get());
        } catch (WebApplicationException exception) {
            respond(resp, () -> (OutboundResponse) exception.getResponse());
        } catch (Throwable throwable) {
            respond(resp, () -> from(throwable));
        }
    }

    private void respond(HttpServletResponse resp, OutboundResponse response) throws IOException {
        resp.setStatus(response.getStatus());
        MultivaluedMap<String, Object> headers = response.getHeaders();
        for (String name : headers.keySet())
            for (Object value : headers.get(name)) {
                RuntimeDelegate.HeaderDelegate headerDelegate = RuntimeDelegate.getInstance().createHeaderDelegate(value.getClass());
                resp.addHeader(name, headerDelegate.toString(value));
            }
        GenericEntity entity = response.getGenericEntity();
        if (entity != null) {
            MessageBodyWriter writer = providers.getMessageBodyWriter(entity.getRawType(), entity.getType(), response.getAnnotations(), response.getMediaType());
            writer.writeTo(entity.getEntity(), entity.getRawType(), entity.getType(), response.getAnnotations(), response.getMediaType(),
                    response.getHeaders(), resp.getOutputStream());
        }
    }

    private OutboundResponse from(Throwable throwable) {
        ExceptionMapper mapper = providers.getExceptionMapper(throwable.getClass());
        return (OutboundResponse) mapper.toResponse(throwable);
    }
}

视频演示

下面让我们继续:

思考题

在进入下节课之前,希望你能认真思考如下两个问题。

  1. 请依据视频演示中的做法,继续对测试代码进行重构。
  2. 我知道不少同学在学习课程的同时,还补习了很多相关知识,那么很欢迎把你的书单分享出来,供其他同学参考。

也欢迎把你的项目代码分享出来。相信经过你的思考与实操,学习效果会更好!

精选留言(3)
  • aoe 👍(2) 💬(1)

    @TestFactory + 标签 + 反射刷新了我对自动化测试认知!

    2022-07-02

  • Luke 👍(0) 💬(1)

    踩了个坑。如果测试时遇到 StackOverflowError 的,看看 stub 的 OutboundResponse 是否都是默认值。

    2022-08-26

  • 枫中的刀剑 👍(0) 💬(0)

    看老师重构代码是真的舒服,信手拈来,一气呵成。 动态测试配合函数式还能这么用,学到了。

    2022-06-29