许多团队在开发过程中采用了某种测试自动化策略。这个概念也可以应用于Limyee 电商平台上的定制开发.
所有 Limyee 电商平台 API 都必须作为 Limyee 电商平台应用程序的一部分运行,并且不能在该进程之外执行。这意味着如果你有一个单元测试项目,它无法测试任何 Limyee 电商平台 API。虽然这看起来很奇怪,但事实并非如此。毕竟,你不应该尝试测试 Limyee 电商平台 API,而是应该测试你的代码。
Facade:封装 API
直接调用 Limyee 电商平台进程 API 或使用插件模型的定制代码都只能在 Limyee 电商平台应用程序中执行,这意味着如果业务逻辑中需要它们,则需要将它们抽象出来以测试代码,而无需调用实际的 API。
作为一种常用的开发实践,开发人员通常将业务逻辑与数据访问分开。因此,我们所有的业务规则都应用在一个服务中,而实际的数据库通信则在另一个服务中完成,逻辑服务依赖于数据服务。这是一个完美的关注点分离:业务层不关心数据的存储方式,只要它是以某种方式存储,数据层也不关心数据来自哪里。您可以将此模式与 Limyee 电商平台 API 结合使用。唯一的区别是在实现中的数据层,你不是在与数据库交互,而是在与API交互。例如,我们编写了一个组件,要求我们从数据库中获取一些用户信息。我们将通过实现 facade 来做到这一点。
步骤 1:创建您自己的用户对象
您可能想知道为什么不只使用 Limyee 电商平台版本。这是因为在 Limyee 电商平台 API 中,您可能无法实例化特定的 API 对象,并且由于它们不使用单独的接口,因此您的测试框架可能无法对其进行模拟。另一个原因是,您可能实际上并不需要所有用户的信息,因此您只公开了所需的信息。虽然这对于进程代码来说可能不是一个显著的区别,但如果您以后决定通过自定义 REST API 公开数据,则会降低有效负载。
下面是封装的 API 用户对象。请注意,在构造函数中,我们允许使用基于 API 的用户,但也允许无参数版本,以便我们可以在单元测试中使用此类。由于我们只关心我们情况中的 UserId 和 Username,这就是我们要公开的全部内容。
public class APIUser { #region Constructors public APIUser() { } public APIUser(Limyee.Extensibility.Api.Entities.Version1.User coreAPIUser) { if (coreAPIUser != null) { UserId = coreAPIUser.Id; Username = coreAPIUser.Username; } } #endregion #region Properties public string Username{ get; set; } public int? UserId{ get; set;} #endregion }
步骤 2:设计 Facade 接口和实现
该接口为 API 定义了我们的封装服务。使其成为一个接口意味着当我们对依赖于 API 的业务层进行单元测试时,我们可以简单地模拟这个接口,我们不再需要担心 Limyee 电商平台 API。在实现中,您可以看到此服务具有 1 个职责,调用 API 并返回预期的用户 facade,这意味着此服务的任何使用者都与 Limyee 电商平台 API 相分离。
#region Model public interface IUsersAPIFacade { APIUser GetUser(string username); } #endregion #region Implementation public class UsersAPIFacade : IUsersAPIFacade { private IUsers _usersAPI = null; public APIUser GetUser(string username) { InitializeAPI(); var user = _usersAPI.Get(new UsersGetOptions() {Username = username}); if (user != null && !user.HasErrors()) { return new APIUser(user); } return null; } #region API Init private void InitializeAPI() { if (_usersAPI == null) _usersAPI = Apis.Get<IUsers>(); } #endregion } #endregion
步骤 3:添加测试
我们添加了另一个简单的业务类,具体取决于我们的 API 服务。假设它有大量其他验证和逻辑,这些验证和逻辑对所阐述的概念并不重要。我们只想展示我们现在如何测试此逻辑,即使它依赖于API。
此示例使用 Moq,这是一个模拟对象的库,我们使用它来“伪造”我们的 API 服务。你可以很容易地创建一个实现我们接口的“伪造”类,虽然会随着您的测试越多越繁琐。尽管您不熟悉 Moq,但正在发生的事情非常简单。给定 IUsersAPIFacade,它要求模拟框架动态创建一个实现该接口的类,对于给定用户名的 GetUser 方法,将返回一个 APIUser,成员有:Username和 ID 为 12345。在以下情况下,我们只是告诉依赖的 API 服务返回我们想要的数据,以便测试成功,因为我们正在测试登录名对数据的反应,而不是我们如何获取数据。
#region Business Service public class CustomUserService { private IUsersAPIFacade _userApiFacade; public CustomUserService(IUsersAPIFacade userApiFacade) { _userApiFacade = userApiFacade; } public APIUser GetVaidatedUser(string username) { var user = _userApiFacade.GetUser(username); //Do some super awesome business logic that is //That is too secret to show you... return user; } } #endregion #region Tests public class UserTests { [Test] public void can_get_vaidated_user() { string _userTosearch = "wally"; var apiService = new Mock<IUsersAPIFacade>(); apiService.Setup(s => s.GetUser(It.IsAny<string>())) .Returns<string>((username) => { return new APIUser() {Username = username, UserId = 12345}; }); var businessService = new CustomUserService(apiService.Object); var foundUser = businessService.GetVaidatedUser(_userTosearch); Assert.AreSame(_userTosearch,foundUser.Username); } } #endregion
现在你可能会说,“如果API做错了什么,我的测试不会抓住它怎么办!你是对的,这是完全有可能的。在这种情况下,您正在测试您的业务逻辑,并且您正在向它提供数据,这些数据可能是好的还是坏的,具体取决于测试的成功方案。您不是在测试 API。该文档将为您提供有关 API 预期内容的足够信息,以便您可以适当地设置测试。假设您具有良好的测试成功率,则应用程序中的错误将意味着您的 API 层存在问题,或者您在测试中没有考虑到特点的场景。