-
单元测试的范围
代码中往往会存在一定的依赖。一个简单的业务可能会依赖于第三方服务,邮件服务、微信服务、钉钉服务等。一个功能模块也可能会和数据库有交互。
那么问题来了,你的单元测试的范围时什么?- 只是对业务代码进行测试
- 对数据库、邮件服务等第三方服务的测试
- 对从业务到数据库以及第三方服务的全链路测试
下面,废话不多说,我们上通过实际代码来阐述几种情况:
//组织机构服务类
public class OrganizationService : ServiceBase
{
/// <summary>
/// 组织机构依赖的对象IOrganization,数据库交互类,可以理解为EF中的DBSet<Organization>()。
/// </summary>
private IOrganization _organization;
public IOrganization OrganizationManager
{
get
{
if (_organization == null)
{
_organization = Engine.Organization;
}
return _organization;
}
}
public OrganizationService() { }
/// <summary>
/// 可以通过Moq框架注入IOrganization进行了测试
/// </summary>
/// <param name="organization"></param>
public OrganizationService(IOrganization organization)
{
this._organization = organization;
}
/// <summary>
/// 根据组织类型获取组织列表
/// </summary>
/// <param name="unitType">组织类型</param>
/// <returns>组织列表类型</returns>
public List<OrgJobViewModel> GetRoleList(UnitType unitType)
{
List<OrgJobViewModel> listRole = new List<OrgJobViewModel>();
List<OThinker.Organization.Unit> listunits = OrganizationManager.GetAllUnits(unitType);
if (listunits != null && listunits.Count > 0)
{
foreach (OThinker.Organization.Unit u in listunits)
{
//上级角色
OrgPost parentPost = OrganizationManager.GetUnit(post.SuperiorID) as OrgPost;
listRole.Add(new OrgJobViewModel { });
}
}
return listRole;
}
}
需要被测试的服务:OrganizationService 。
OrganizationManager:组织机构管理器,主要用于和数据库交互,其中也包含部分业务逻辑。
GetRoleList:被测试的方法,单元测试需要测试的方法。验证此方法的业务逻辑。
明确测试的主体,才能编写健壮和合理的单元测试。
这里我们使用Moq对第三方服务进行Mock,简单的介绍一下Moq框架。
- Moq是一个用于模拟外部以依赖的框架。
- 他可以去模拟一个非密封类,接口或者类都可以。
- 他可以去创建一个空的类,通过 mock.Setup(),来将方法override。
- 实际方法调用的参数,或者调用的实体对象还是需要自己通过代码去模拟。
针对GetRoleList业务逻辑
[TestMethod]
public void GetRoleList_HasList_Return_List()
{
//1 创建mock对象
Mock<IOrganization> mock = new Mock<IOrganization>();
List<Unit> units =CreateOrgPostList();
// 2 设置需要被Mock的方法
mock.Setup(m => m.GetAllUnits(UnitType.Post)).Returns(units);
// It.IsAny<string>() 参数为任意的string
// (string s) => units.FirstOrDefault(d => d.ObjectID == s) 当参数为s时,返回 units.FirstOrDefault(d => d.ObjectID == s);
mock.Setup(m => m.GetUnit(It.IsAny<string>())).Returns((string s) => units.FirstOrDefault(d => d.ObjectID == s));
//3 注入Mock
OrganizationService organization = new OrganizationService(mock.Object);
var list = organization.GetRoleList(UnitType.Post);
Assert.AreEqual(units.Count, list.Count);
Assert.AreEqual(units[0].ObjectID, list[0].ObjectID);
}
我们通过Moq框架将IOrganization注入到OrganizationService 中,将关注点聚焦到GetRoleList的业务逻辑上,无论数据库是否交互成功,第三方服务是否畅通,业务逻辑测试通过,单元测试即通过。
优点:不依赖于第三方;代码解耦。
缺点:针对数据库交互服务,需要其他单元测试覆盖
针对GetRoleList业务逻辑以及数据库交互
[TestMethod]
public void GetRoleList_HasList_Return_List()
{
List<Unit> units =CreateOrgPostList();
OrganizationService organization = new OrganizationService();
//将数据实例化到数据库中
organization .SaveUnits(units);
var list = organization.GetRoleList(UnitType.Post);
Assert.AreEqual(units.Count, list.Count);
Assert.AreEqual(units[0].ObjectID, list[0].ObjectID);
//将测试数据清空
organization .DeleteUnits(units);
}
这种针对的测试点是GetRoleList的业务逻辑以及OrganizationService 和数据库的交互逻辑,因为我们这里测试的数据是真实的保存到数据库并且从数据库中取出来。
优点:一次性对整个方法进行测试,保证方法的可靠性
缺点:代码过于耦合,并且在模拟数据上工作量较大
想清楚你需要测试的主体,单元测试的粒度,才能书写出健壮可靠的单元测试。
-
单元测试的目的
基于单元测试的主体来分析,是为了保证测试的单元功能正常,在进行修改后,可以通过自动化测试来保证功能的可靠性。可以将一些重复的或者繁琐的手工操作通过代码来进行。基于代码的层面去分析代码的正确性。
因此,在划分单元时就就显得至关重要。
就拿上面的例子来讲:
OrganizationService 为一个单元,验证功能的完整以及可靠性
或者
OrganizationService 为一个单元,验证OrganizationService 的业务逻辑。
IOrganization 为一个单元,验证数据库交互的可靠性。划分的单元粒度不同,测试目的也不同。
-
单元测试的框架
我们使用的测试框架为微软的MSUNNIT
Mock 框架是 Moq参考:
Moq QuickStart
MSUNIT