【Beta阶段】计划阶段要求 - 技术规格说明书 - 灵境 | week12

项目 内容
这个作业属于哪个课程 2022春季软件工程(罗杰 任健)
这个作业的要求在哪里 团队项目-计划阶段要求

Part1 架构与技术栈

1.1 整体架构

img

本项目的整体架构如上图所示。

下面我们将对涉及的技术栈进行详细说明:

1.2 客户端框架

采用基于C#的Unity框架及其内置各种功能插件进行安卓客户端设计。Unity提供一整套完善的软件解决方案 ,可用于创作、运营和变现任何实时互动的2D和3D内容,支持平台包括手机、平板电脑、PC、游戏主机、增强现实和虚拟现实设备。

1.3 服务端框架

采用SpringBoot+Mybatis-Plus进行主数据库和缓存数据库的管理,同时融合Websocket+Netty提供高并发网络服务。

SpringBoot背靠Spring和Java社区,可以一键快速搭建Spring框架,简化初始配置 ,可与许多主流第三方开源框架进行结合。

使用Unity内置Mirror框架进行Unity多人3D场景在线同步功能实现。

1.4 数据库

采用了MySQL数据库,服务端使用Mybatis-Plus包实现MVC架构,通过映射前端的数据到DTO层,数据库的数据映射到DOMAIN层,返回给前端的数据映射到VO层来对数据进行高效管理。同时,辅助使用Redis进行数据缓存。

1.5 云环境

采用了阿里云提供的轻量应用服务器,具体配置如下:

镜像:Ubuntu 20.04 LTS

运算组件:2核CPU、4GB内存

云SSD系统盘:50GB SSD云硬盘

流量包:1200GB流量包

Part2 设计与分析

2.1 系统层面

2.1.1 代码设计

  • 功能模块化

    • 客户端

      对于客户端来说,我们要构建出一个包含许多场景的应用,每个场景都有属于自己的功能。因此,需要每个场景都是独立的,这样模块化的设计方式使得不管是前期的开发还是后期的测试都更加便捷

    • 后端(游戏后端和数据后端)

      由于应用更贴近于游戏的需求,因此将后端分为游戏后端和数据后端也是一种功能模块化的方式,游戏后端主要负责多人同步,数据后端主要负责数据管理

  • 数据抽象化

    • 对于模型,将每个模型存储在数据后端,而模型数据的传递依靠其对应的URL,在最终需要渲染的场合再通过URL向数据后端请求模型,减少了数据传递的复杂度
    • 对于其他用户数据,后端将所有需要的数据进行封装,以整体的形式传递给前端,减少了多次传输的消耗

2.1.2 架构设计

  • 界面和实现的分离

    总体来说,我们项目中包含了客户端、游戏后端、数据后端。客户端是呈现给用户的界面,即为前端的设计。而考虑到我们的前端有类似于游戏的多人在线同步的需求,普通的后端无法满足该需求,所以我们采用了游戏后端与数据后端分离的设计,客户端在获取用户相关的数据时,将向数据后端发送请求,而在获取游戏相关的数据则与游戏后端进行沟通。

  • 错误处理

    对于客户端,错误发生在unity的代码逻辑上。这一部分错误可以在测试阶段通过多次测试发现并修改在代码上,同时在产品发布后,可以针对反馈信息来进行错误的收集。

    对于游戏后端,错误主要发生在服务器网络上,包括了网络传输出错、服务器性能等错误行为,这大部分是不可抗力的问题,我们可以采用出错时更完善地处理错误,防止用户掉线等情况的出现。

    对于数据后端,错误发生在数据的处理上,包括了数据不存在,权限失败等错误行为,这些错误可以在测试阶段进行捕获。另一方面,有些错误是系统允许的,需要对用户的非法行为做出应对,属于程序的正常行为。

  • 应对需求变化的灵活性

    • 多端快速扩展的设计

      前端的用户输入采用了unity的InputSystem,可以将不同平台的输入处理为相同的方式来进行前端的操作。这意味着我们可以轻松地将前端部署在多个平台上,如PC、手机、VR设备、AR模式等,在不同的平台我们只需要添加InputSystem的操作映射,无需对前端的其他地方更改。若后续需要在其他平台部署,我们可以迅速地完成部署需求。

    • 前后端分离的模式

      前端严格按照多个场景分离设计,每一个场景是独立运行的,有新的需求直接在相应的场景中进行修改即可,不会对其他场景造成影响。

    • 团队协作原则

      我们采用coding平台的团队协作进行开发,每一个需求都指定到具体的人上,详细的描述需求,另外也可发布缺陷、任务等,可在碰到技术问题时艾特其他人共同解决,灵活协商。

2.1.3 测试设计

①单元测试

前端单元测试

Unity 测试框架包(Unity Test Framework,UTF)是一款工具,可在“编辑模式”和“播放模式”下以及在目标平台(如独立平台、Android 或 iOS)上测试代码。

常规 NUnit 测试(在“编辑模式”和“播放模式”下运行):

[Test]
public void GameObject_CreatedWithGiven_WillHaveTheName()
{
    var go = new GameObject("UMetaObject");
    Assert.AreEqual("UMetaObject", go.name);
}

“播放模式”下的示例:

[UnityTest]
public IEnumerator GameObject_WithRigidBody_WillBeAffectedByPhysics()
{
    var go = new GameObject();
    go.AddComponent<Rigidbody>();
    var originalPosition = go.transform.position.y;

    yield return new WaitForFixedUpdate();

    Assert.AreNotEqual(originalPosition, go.transform.position.y);
}

“编辑模式”下的示例:

[UnityTest]
public IEnumerator EditorUtility_WhenExecuted_ReturnsSuccess()
{
    var utility = RunEditorUtilityInTheBackgroud();
    while (utility.isRunning)
    {
        yield return null;
    }
    Assert.IsTrue(utility.isSuccess);
}

通过 Test Runner API 从任何脚本以编程方式运行测试。可以检索将在“编辑模式”、“播放模式”或同时在这两种模式下运行的测试的列表(不运行它们)。可以在每个测试的开始和结束、测试周期(即整个测试程序集)中的每个级别、每个单独的测试固定例程以及每个测试类和测试上挂接一些注册/取消注册回调。

在每个测试开始时,可以获得有关将要运行的测试路线的信息。测试完成后,可以看到测试结果。

除了在 Unity Editor 中以“播放模式”运行 UTF 之外,还可以使用新的自定义点在目标设备上运行它。这是在构建 Player 之前调用的;可以修改 Player 构建选项,例如,更改测试运行设置和指定构建位置。

void Execute(ExecutionSettings executionSettings);
void RegisterCallbacks<T>(T testCallbacks, int priority = 0) where T : ICallbacks;
void UnregisterCallbacks<T>(T testCallbacks) where T : ICallbacks;
void RetrieveTestList(TestMode testMode, Action<ITestAdaptor> callback);
后端单元测试

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

  • spring-boot-test 包含核心项目
  • spring-boot-test-autoconfigure 支持测试的自动配置

通常我们只要引入 spring-boot-starter-test 依赖就行,它包含了一些常用的模块 Junit、Spring Test、AssertJ、Hamcrest、Mockito 等。

下面是一个基本的单元测试例子,对某个方法的返回结果进行断言:

@Service
public class UserService {
    public String getName() {
        return "UMeta";
    }
}
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {
    @Autowired
    private UserService service;

    @Test
    public void getName() {
        String name = service.getName();
        assertThat(name, is("UMeta"));
    }
}
Controller 测试

Spring 提供了 MockMVC 用于支持 RESTful 风格的 Spring MVC 测试,使用 MockMvcBuilder 来构造 MockMvc 实例。MockMvc 有两个实现:

  • StandaloneMockMvcBuilder:指定 WebApplicationContext,它将会从该上下文获取相应的控制器并得到相应的 MockMvc

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class UserControllerTest  {
        @Autowired
        private WebApplicationContext webApplicationContext;
        private MockMvc mockMvc;
        @Before
        public void setUp() throws Exception {
            mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }
    
  • DefaultMockMvcBuilder:通过参数指定一组控制器,这样就不需要从上下文获取了

    @RunWith(SpringRunner.class)
    public class UserControllerTest  {
        private MockMvc mockMvc;
        @Before
        public void setUp() throws Exception {
            mockMvc = MockMvcBuilders.standaloneSetup(new UserController()).build();
        }
    }
    

下面是一个简单的用例,对 UserController/v1/users/{id} 接口进行测试。

@RestController
@RequestMapping("v1/users")
public class UserController {
    @GetMapping("/{id}")
    public User get(@PathVariable("id") String id) {
        return new User(1, "UMeta");
    }

    @Data
    @AllArgsConstructor
    public class User {
        private Integer id;
        private String name;
    }
}
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserControllerTest {
    @Autowired
    private WebApplicationContext webApplicationContext;
    private MockMvc mockMvc;

    @Before
    public void setUp() {
        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }

    @Test
    public void getUser() {
        mockMvc.perform(get("/v1/users/1")
                .accept(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(status().isOk())
           .andExpect(content().string(containsString("\"name\":\"UMeta\"")));
    }
}
Mock 数据

在单元测试中,Service 层的调用往往涉及到对数据库、中间件等外部依赖。而在单元测试 AIR 原则中,单元测试应该是可以重复执行的,不应受到外界环境的影响的。此时我们可以通过 Mock 一个实现来处理这种情况。

如果不需要对静态方法,私有方法等特殊进行验证测试,则仅仅使用 SpringBoot 自带的 Mockito 即可完成相关的测试数据 Mock。若需要则可以使用 PowerMock,简单实用,结合 Spring 可以使用注解注入。

例如下面代码中,ProjectService 中通过 ProjectMapperselectById 方法进行数据库查询操作:

@Service
public class ProjectService {
    @Autowired
    private ProjectMapper mapper;

    public ProjectDO detail(String id) {
        return mapper.selectById(id);
    }
}

此时我们可以对 Mock 一个 ProjectMapper 对象替换掉 IOC 容器中原生的 Bean,来模拟数据库查询操作,如:

@RunWith(SpringRunner.class)
@SpringBootTest
public class ProjectServiceTest {
    @MockBean
    private ProjectMapper mapper;
    @Autowired
    private ProjectService service;

    @Test
    public void detail() {
        ProjectDemoDO model = new ProjectDemoDO();
        model.setId("1");
        model.setName("umeta-demo");
        Mockito.when(mapper.selectById("1")).thenReturn(model);
        ProjectDemoDO entity = service.detail("1");
        assertThat(entity.getName(), containsString("umeta-demo"));
    }
}

②压力测试

  • 测试内容
    • Java 服务器暂定支持 1000 级别的并发,因此针对访问较为频繁的几个页面进行压力测试,查看性能是否符合要求。
  • 测试场景设置
    • 对于逻辑简单的部分,使用使用性能测试工具,如 ApacheBenchmark ab 进行并发数为 1000 的测试;
    • 对于逻辑复杂的部分,在 SpringBoot 内部编写相关测试函数进行测试。
  • 服务器状态监视
    • 使用 htop 工具监视服务器的 CPU、内存使用情况并记录数据。
  • 测试结果收集与分析
    • 由于产品后端部署环境有很多参数可以调节,例如,MySQL可以配置max_connectionsthread_cache_sizeback_log等参数,因此,我们可以收集设置不同参数下系统的状态信息与当前并发数,构造关系表格进行分析,理想情况下应该能找出并发瓶颈。

③真实测试

真实测试是对我们的系统进行一系列实际使用时会遇到的真实情况的模拟。我们团队将从以下方面对真实的情况进行分类并进行测试

  • 功能测试
    • 手动测试,模拟真实用户进行功能实现与否的测试
    • 自动化测试,设计测试用例,实际上就是针对代码的单元测试
  • 专项测试
    • 兼容性测试,使用不同设备,不同型号,不同分辨率对用户界面进行测试
    • 安装卸载升级测试
    • 交叉事件测试,包含运行时接收来电/短信/消息推送、运行时出现系统弹窗、运行时切换外部设备等
    • 用户体验度测试,包含界面设计、功能易用性、横竖屏切换、系统功能响应等
    • 稳定性测试通过测试工具实现在一定时间范围内的无序操作,来检测应用的稳定运行能力,重点在于考察应用在测试期间出现程序无响应或闪退的频率
  • 安全测试
    • 对异常逻辑、hack 接口、网络攻击防御等情况进行测试,系统不得有安全漏洞或隐患
  • 性能测试
    • 客户端:包含 CPU/内存占用、界面流畅度、流量/电量消耗、启动时间等
    • 服务端:使用性能测试工具,如 ApacheBenchmark ab

2.1.4 性能分析

“灵境”致力于小场景社交服务、校园场景社交服务以及高校社交,用户可进行本校社交、跨校社交、个人空间装饰以及相互访问等活动,在使用过程中可能面临大量用户同时登陆使用的情况,存在高并发情形。

前后端分离,客户端主要提供用户浏览的页面,基于C#的Unity框架及其内置各种功能插件进行安卓客户端设计,后端分为分为游戏后端和数据后端,采用SpringBoot+Mybatis-Plus以及Websocket+Netty,其中游戏后端负责实现多人同步问题,数据后端进行数据管理。

快速的错误处理,当客户端、游戏后端和数据后端发生错误时,能及时收集相关错误数据,进行分析并快速做出应对措施,以保证用户良好的体验。

2.2 业务流程层面

2.2.1 前端

注:基本的输入格式表单验证不包括在下表中

功能模块 输入假设 错误处理
登录 账号存在 根据后端接口异常显示错误提示
注册 账号未注册;手机号有效 根据后端/验证码API的异常显示错误提示
我的高校 已注册账号并认证高校 引导用户进行认证
高校场景 服务器能够承载 提示服务器过忙,请用户重新选择服务器
高校场景
我的空间
用户全屏显示 组件横屏竖屏适配

2.2.2 后端

问题 输入假设 错误处理
写操作异常输入 前端所有输入经过格式和有效性检查 不存储数据,抛出异常
删/查操作 删除/查找对应的对象存在 返回空值,必要时抛出异常
增加重复 注册账号不存在 抛出重复注册账号异常
资源缺失 需要加载的人物、场景等建模存在 抛出资源缺失异常
服务器过载 用户数未超过上限 不分配资源,抛出异常

Part3 更新日志

时间 更新内容
2022-04-19 18:00 按照班级作业要求完成技术规格说明书
2022-05-22 13:45 根据Beta阶段计划和功能修改了架构图以及部分技术描述
posted @ 2022-05-19 21:10  头发茂盛队  阅读(60)  评论(0编辑  收藏  举报