许多团队在开发过程中采用了某种测试自动化策略。这个概念也可以应用于Limyee 电商平台上的定制开发.
所有 Limyee 电商平台 API 都必须作为 Limyee 电商平台应用程序的一部分运行,并且不能在该进程之外执行。这意味着如果你有一个单元测试项目,它无法测试任何 Limyee 电商平台 API。虽然这看起来很奇怪,但事实并非如此。毕竟,你不应该尝试测试 Limyee 电商平台 API,而是应该测试你的代码。
直接调用 Limyee 电商平台进程 API 或使用插件模型的定制代码都只能在 Limyee 电商平台应用程序中执行,这意味着如果业务逻辑中需要它们,则需要将它们抽象出来以测试代码,而无需调用实际的 API。
作为一种常用的开发实践,开发人员通常将业务逻辑与数据访问分开。因此,我们所有的业务规则都应用在一个服务中,而实际的数据库通信则在另一个服务中完成,逻辑服务依赖于数据服务。这是一个完美的关注点分离:业务层不关心数据的存储方式,只要它是以某种方式存储,数据层也不关心数据来自哪里。您可以将此模式与 Limyee 电商平台 API 结合使用。唯一的区别是在实现中的数据层,你不是在与数据库交互,而是在与API交互。例如,我们编写了一个组件,要求我们从数据库中获取一些用户信息。我们将通过实现 facade 来做到这一点。
您可能想知道为什么不只使用 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
}
该接口为 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
我们添加了另一个简单的业务类,具体取决于我们的 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 层存在问题,或者您在测试中没有考虑到特点的场景。