使用IdleTest进行TDD单元测试驱动开发演练(2)

 

【前言】

1. 有关上篇请参见《使用IdleTest进行TDD单元测试驱动开发演练(1)》,有关本篇用到Entity Framework Code First请参见《使用NuGet助您玩转代码生成数据————Entity Framework 之 Code First》,而用的个人类库参照IdleTest
2. 本文只用了简单的Entity Framework演练单元测试,着重于Testing,而不是实现,并不会涉及事务、效率等问题。

3. 回顾上一篇里面讲到的是针对业务层的测试,正如敏捷中厉行的多与用户沟通,在书《C# 测试驱动开发(Professional Test Driven Development with C#)》中作者就推荐TDD中单元测试的编写应有业务人员与需求人员参与,不是参与编码,而是参与单元测试的用例制定,当然了不涉及业务层面的代码也不需要如此。比如注册功能有多少种场景都可以在单元测试中体现出来,这时就要针对每种场景编写至少一个单元测试的方法,其命名也就尤为重要,因为要让他们看懂每个方法对应什么样的场景。以下就是我改造后的对UserService进行测试的代码,其中每个类对应一个功能模块,类中的每个方法则对应该功能的每一种场景,这样以便于与需求以及相关业务人员确定开发需求后再编码,减少了开发中的需求变更。

public abstract class BaseUserServiceTest
{
protected UserTestHelper UserTestHelper = new UserTestHelper();

private IUserRepository userRepository;

protected IList<UserModel> ExistedUsers;

protected abstract IUserService UserService
{
get;
}

/// <summary>
/// IUserRepository模拟对象
/// </summary>
public virtual IUserRepository UserRepository
{
get
{
if (this.userRepository == null)
{
StubIUserRepository stubUserRepository = new StubIUserRepository();
//模拟Get方法
stubUserRepository.GetExpressionOfFuncOfUserModelBooleanFuncOfIQueryableOfUserModelIOrderedQueryableOfUserModelString
= (x, y, z) =>
{
return this.ExistedUsers.Where<UserModel>(x.Compile());
};

//模拟GetSingle方法
stubUserRepository.GetSingleString = p => this.ExistedUsers.FirstOrDefault<UserModel>(o => o.LoginName == p);

//模拟Insert方法
stubUserRepository.InsertUserModel = (p) => this.ExistedUsers.Add(p);

this.userRepository = stubUserRepository;
}

return this.userRepository;
}
}

[TestInitialize]
public void InitUserList()
{
//每次测试前都初始化
this.ExistedUsers = new List<UserModel> { UserTestHelper.ExistedUser };
}

#region Login Test
[TestCategory("登陆场景")]
public virtual void 当用户信息全部为空或账户为空或密码为空或账户错误或密码错误或账户密码均错误都登陆失败()
{
//验证登陆失败的场景
AssertCommon.AssertBoolean<UserModel>(
new UserModel[] {
null, new UserModel(),
new UserModel { LoginName = string.Empty, Password = UserTestHelper.ExistedPassword }, //账户为空
new UserModel { LoginName = UserTestHelper.ExistedLoginName, Password = string.Empty }, //密码为空
new UserModel {
LoginName = UserTestHelper.ExistedLoginName, Password = UserTestHelper.NotExistedPassword
}, //密码错误
new UserModel {
LoginName = UserTestHelper.NotExistedLoginName, Password = UserTestHelper.NotExistedPassword
}, //账户密码错误
new UserModel {
LoginName = UserTestHelper.NotExistedLoginName, Password = UserTestHelper.ExistedLoginName
} //账户错误
}, false, p => UserService.Login(p));
}

[TestCategory("登陆场景")]
public virtual void 当账户密码全部正确时登陆成功()
{
//账户密码正确,验证成功,这里假设正确的账户密码是"zhangsan"、"123456"
UserModel model = new UserModel
{
LoginName = UserTestHelper.ExistedLoginName,
Password = UserTestHelper.ExistedPassword
};
AssertCommon.AssertBoolean(true, UserService.Login(model));
}

#endregion

#region RegisterTest
[TestCategory("注册场景")]
public virtual void 当用户信息全为空或账户为空或密码为空或账户已存在时注册失败()
{
//验证注册失败的场景
AssertCommon.AssertBoolean<UserModel>(
new UserModel[] {
null, new UserModel(),
new UserModel { LoginName = string.Empty, Password = UserTestHelper.NotExistedPassword }, //账户为空
new UserModel { LoginName = UserTestHelper.NotExistedLoginName, Password = string.Empty }, //密码为空
new UserModel {
LoginName = UserTestHelper.ExistedLoginName, Password = UserTestHelper.NotExistedPassword
}, //账户已存在
}, false, p => UserService.Register(p));
}

[TestCategory("注册场景")]
public virtual void 当账号密码均不为空且账号未存在则注册成功并且注册后的用户信息与注册输入的保持完全一致()
{

//验证注册成功的场景
//密码与他人相同也可注册
UserModel register1 = new UserModel { LoginName = "register1", Password = UserTestHelper.ExistedPassword };
UserModel register2 = new UserModel { LoginName = "register2", Password = UserTestHelper.NotExistedPassword };
UserModel register3 = new UserModel
{
LoginName = "register3",
Password = UserTestHelper.NotExistedPassword,
Age = 18
};

AssertCommon.AssertBoolean<UserModel>(
new UserModel[] { register1, register2, register3 }, true, p => UserService.Register(p));

//获取用户且应与注册的信息保持一致
UserModel actualRegister1 = UserService.GetModel(register1.LoginName);
UserTestHelper.AssertEqual(register1, actualRegister1);

UserModel actualRegister2 = UserService.GetModel(register2.LoginName);
UserTestHelper.AssertEqual(register2, actualRegister2);

UserModel actualRegister3 = UserService.GetModel(register3.LoginName);
UserTestHelper.AssertEqual(register3, actualRegister3);
}
#endregion

//该方法可不需要业务人员参与
public virtual void GetModelTest()
{
AssertCommon.AssertIsNull<string, UserModel>(TestCommon.GetEmptyStrings(), true, p => UserService.GetModel(p));
AssertCommon.AssertIsNull(true, UserService.GetModel(UserTestHelper.NotExistedLoginName));

UserModel actual = UserService.GetModel(UserTestHelper.ExistedLoginName);
UserTestHelper.AssertEqual(UserTestHelper.ExistedUser, actual);
}
}

BaseUserServiceTest

    public abstract class BaseUserServiceTest
{
protected UserTestHelper UserTestHelper = new UserTestHelper(); private IUserRepository userRepository; protected IList<UserModel> ExistedUsers; protected abstract IUserService UserService
{
get;
} /// <summary>
/// IUserRepository模拟对象
/// </summary>
public virtual IUserRepository UserRepository
{
get
{
if (this.userRepository == null)
{
StubIUserRepository stubUserRepository = new StubIUserRepository();
//模拟Get方法
stubUserRepository.GetExpressionOfFuncOfUserModelBooleanFuncOfIQueryableOfUserModelIOrderedQueryableOfUserModelString
= (x, y, z) =>
{
return this.ExistedUsers.Where<UserModel>(x.Compile());
}; //模拟GetSingle方法
stubUserRepository.GetSingleString = p => this.ExistedUsers.FirstOrDefault<UserModel>(o => o.LoginName == p); //模拟Insert方法
stubUserRepository.InsertUserModel = (p) => this.ExistedUsers.Add(p); this.userRepository = stubUserRepository;
} return this.userRepository;
}
} [TestInitialize]
public void InitUserList()
{
//每次测试前都初始化
this.ExistedUsers = new List<UserModel> { UserTestHelper.ExistedUser };
} #region Login Test
[TestCategory("登陆场景")]
public virtual void 当用户信息全部为空或账户为空或密码为空或账户错误或密码错误或账户密码均错误都登陆失败()
{
//验证登陆失败的场景
AssertCommon.AssertBoolean<UserModel>(
new UserModel[] {
null, new UserModel(),
new UserModel { LoginName = string.Empty, Password = UserTestHelper.ExistedPassword }, //账户为空
new UserModel { LoginName = UserTestHelper.ExistedLoginName, Password = string.Empty }, //密码为空
new UserModel {
LoginName = UserTestHelper.ExistedLoginName, Password = UserTestHelper.NotExistedPassword
}, //密码错误
new UserModel {
LoginName = UserTestHelper.NotExistedLoginName, Password = UserTestHelper.NotExistedPassword
}, //账户密码错误
new UserModel {
LoginName = UserTestHelper.NotExistedLoginName, Password = UserTestHelper.ExistedLoginName
} //账户错误
}, false, p => UserService.Login(p));
} [TestCategory("登陆场景")]
public virtual void 当账户密码全部正确时登陆成功()
{
//账户密码正确,验证成功,这里假设正确的账户密码是"zhangsan"、"123456"
UserModel model = new UserModel
{
LoginName = UserTestHelper.ExistedLoginName,
Password = UserTestHelper.ExistedPassword
};
AssertCommon.AssertBoolean(true, UserService.Login(model));
} #endregion #region RegisterTest
[TestCategory("注册场景")]
public virtual void 当用户信息全为空或账户为空或密码为空或账户已存在时注册失败()
{
//验证注册失败的场景
AssertCommon.AssertBoolean<UserModel>(
new UserModel[] {
null, new UserModel(),
new UserModel { LoginName = string.Empty, Password = UserTestHelper.NotExistedPassword }, //账户为空
new UserModel { LoginName = UserTestHelper.NotExistedLoginName, Password = string.Empty }, //密码为空
new UserModel {
LoginName = UserTestHelper.ExistedLoginName, Password = UserTestHelper.NotExistedPassword
}, //账户已存在
}, false, p => UserService.Register(p));
} [TestCategory("注册场景")]
public virtual void 当账号密码均不为空且账号未存在则注册成功并且注册后的用户信息与注册输入的保持完全一致()
{ //验证注册成功的场景
//密码与他人相同也可注册
UserModel register1 = new UserModel { LoginName = "register1", Password = UserTestHelper.ExistedPassword };
UserModel register2 = new UserModel { LoginName = "register2", Password = UserTestHelper.NotExistedPassword };
UserModel register3 = new UserModel
{
LoginName = "register3",
Password = UserTestHelper.NotExistedPassword,
Age = 18
}; AssertCommon.AssertBoolean<UserModel>(
new UserModel[] { register1, register2, register3 }, true, p => UserService.Register(p)); //获取用户且应与注册的信息保持一致
UserModel actualRegister1 = UserService.GetModel(register1.LoginName);
UserTestHelper.AssertEqual(register1, actualRegister1); UserModel actualRegister2 = UserService.GetModel(register2.LoginName);
UserTestHelper.AssertEqual(register2, actualRegister2); UserModel actualRegister3 = UserService.GetModel(register3.LoginName);
UserTestHelper.AssertEqual(register3, actualRegister3);
}
#endregion //该方法可不需要业务人员参与
public virtual void GetModelTest()
{
AssertCommon.AssertIsNull<string, UserModel>(TestCommon.GetEmptyStrings(), true, p => UserService.GetModel(p));
AssertCommon.AssertIsNull(true, UserService.GetModel(UserTestHelper.NotExistedLoginName)); UserModel actual = UserService.GetModel(UserTestHelper.ExistedLoginName);
UserTestHelper.AssertEqual(UserTestHelper.ExistedUser, actual);
}
}

[TestClass]
public class UserServiceTest : BaseUserServiceTest
{
protected override IUserService UserService
{
get { return new UserService(this.UserRepository); }
}

[TestMethod]
public override void GetModelTest()
{
base.GetModelTest();
}

[TestMethod]
public override void 当用户信息全部为空或账户为空或密码为空或账户错误或密码错误或账户密码均错误都登陆失败()
{
base.当用户信息全部为空或账户为空或密码为空或账户错误或密码错误或账户密码均错误都登陆失败();
}

[TestMethod]
public override void 当账户密码全部正确时登陆成功()
{
base.当账户密码全部正确时登陆成功();
}

[TestMethod]
public override void 当用户信息全为空或账户为空或密码为空或账户已存在时注册失败()
{
base.当用户信息全为空或账户为空或密码为空或账户已存在时注册失败();
}

[TestMethod]
public override void 当账号密码均不为空且账号未存在则注册成功并且注册后的用户信息与注册输入的保持完全一致()
{
base.当账号密码均不为空且账号未存在则注册成功并且注册后的用户信息与注册输入的保持完全一致();
}
}

UserServiceTest

    [TestClass]
public class UserServiceTest : BaseUserServiceTest
{
protected override IUserService UserService
{
get { return new UserService(this.UserRepository); }
} [TestMethod]
public override void GetModelTest()
{
base.GetModelTest();
} [TestMethod]
public override void 当用户信息全部为空或账户为空或密码为空或账户错误或密码错误或账户密码均错误都登陆失败()
{
base.当用户信息全部为空或账户为空或密码为空或账户错误或密码错误或账户密码均错误都登陆失败();
} [TestMethod]
public override void 当账户密码全部正确时登陆成功()
{
base.当账户密码全部正确时登陆成功();
} [TestMethod]
public override void 当用户信息全为空或账户为空或密码为空或账户已存在时注册失败()
{
base.当用户信息全为空或账户为空或密码为空或账户已存在时注册失败();
} [TestMethod]
public override void 当账号密码均不为空且账号未存在则注册成功并且注册后的用户信息与注册输入的保持完全一致()
{
base.当账号密码均不为空且账号未存在则注册成功并且注册后的用户信息与注册输入的保持完全一致();
}
}

4. 这里我已经在上一篇的基础上进行了一些重构:

  在解决方案文件夹“Tests”下新建类库项目“IdleTest.TDDEntityFramework.TestUtilities”,并添加引用“IdleTest.dll”、“IdleTest.MSTest.dll” 
(参考上一篇)和“IdleTest.TDDEntityFramework.Models”。接着在项目下添加类“UserTestHelper”。

public class UserTestHelper
{
public string ExistedLoginName = "zhangsan";

public string ExistedPassword = "123456";

public string NotExistedLoginName = "zhangsan1";

public string NotExistedPassword = "123";

public UserModel ExistedUser
{
get { return new UserModel { LoginName = ExistedLoginName, Password = ExistedPassword }; }
}

public UserModel NotExistedUser
{
get {
return new UserModel {
LoginName = NotExistedLoginName, Password = NotExistedPassword, Age = 30 };
}
}

public void AssertEqual(UserModel expected, UserModel actual)
{
AssertCommon.AssertIsNull(false, expected);
AssertCommon.AssertIsNull(false, actual);

AssertCommon.AssertEqual<string>(expected.LoginName, actual.LoginName);
AssertCommon.AssertEqual<string>(expected.Password, actual.Password);
AssertCommon.AssertEqual<int>(expected.Age, actual.Age);
}

}

UserTestHelper

    public class UserTestHelper
{
public string ExistedLoginName = "zhangsan"; public string ExistedPassword = "123456"; public string NotExistedLoginName = "zhangsan1"; public string NotExistedPassword = "123"; public UserModel ExistedUser
{
get { return new UserModel { LoginName = ExistedLoginName, Password = ExistedPassword }; }
} public UserModel NotExistedUser
{
get {
return new UserModel {
LoginName = NotExistedLoginName, Password = NotExistedPassword, Age = 30 };
}
} public void AssertEqual(UserModel expected, UserModel actual)
{
AssertCommon.AssertIsNull(false, expected);
AssertCommon.AssertIsNull(false, actual); AssertCommon.AssertEqual<string>(expected.LoginName, actual.LoginName);
AssertCommon.AssertEqual<string>(expected.Password, actual.Password);
AssertCommon.AssertEqual<int>(expected.Age, actual.Age);
} }

5. 再在项目“IdleTest.TDDEntityFramework.ServiceTest”引用刚添加的项目“IdleTest.TDDEntityFramework.TestUtilities”。

6. 接着生成并运行测试,在测试资源管理器中单击右键,滑动鼠标到“分组依据”后选中“特征”,如下图所示,此时便可以看到比较适合非开发人员的测试方法名。

  使用“[TestCategory]”声明的测试方法可以在测试资源管理器中按照特征来排列。

  我这里为了简便,把一个细分功能只划分为成功与失败两个方法,其实应该还可以划分得更细些,比如账户名为空登陆失败、密码为空登陆失

败分为两个测试方法。当然了,如果在需求并不复杂的情况下,也可以不用这么划分,比如上述的登陆与注册需求就很简单,完全可以不用细化,这 
里只作为演示下罢了。

【一】对Repository层做测试前准备

(本篇将使用与上篇类似的方式完成仓储层(Repository)开发)
7. 由于使用Entity Framework Code First,因而需对Model增加一些特性(Attribute)声明。在编写以下代码前需在项 
目“IdleTest.TDDEntityFramework.Models”添加引用“System.ComponentModel.DataAnnotations”。

[Table("UserInfo")]
public class UserModel
{
[Key]
[MaxLength(50)]
public string LoginName { get; set; }

[MaxLength(50)]
public string Password { get; set; }

public int Age { get; set; }
}

UserModel

    [Table("UserInfo")]
public class UserModel
{
[Key]
[MaxLength(50)]
public string LoginName { get; set; } [MaxLength(50)]
public string Password { get; set; } public int Age { get; set; }
}

8. 项目“IdleTest.TDDEntityFramework.Repositories”的变动:添加引用“IdleTest.TDDEntityFramework.Models”;打开程序包管理器控制台,如下图所示在默认项目选择“IdleTest.TDDEntityFramework.Repositories”,并在命令中输入“Install-Package EntityFramework”(PS 现在才发现Entity Framework已经到了6.0了,不过原有功能应该都还在);

在项目下添加类“SqlFileContext”。

    public class SqlFileContext : DbContext
{
public DbSet<UserModel> Users { get; set; } public SqlFileContext() : base("DefaultConnectionString") { }
}

【二】、编写UserRepository的测试UserRepositoryTest

(由于以下的测试不需要业务人员参与,故我又可以按照我喜欢的方式来命名单元测试了)

9. 在解决方案文件夹“Tests”下创建单元测试项目“IdleTest.TDDEntityFramework.RepositoryTest”,添加引用 “IdleTest.TDDEntityFramework.TestUtilities”、“IdleTest.TDDEntityFramework.IRepositories”、“IdleTest.TDDEntityFramework.Models” 和 “IdleTest.TDDEntityFramework.Repositories” 以及 “IdleTest”、“IdleTest.MSTest”(类似上一篇);继续添加“EntityFramework.dll”的引用如下图所示。

10. 对刚添加的“IdleTest.TDDEntityFramework.Repositories”与“EntityFramework”引用“添加Fakes程序集”。

11. 由于 “IdleTest.TDDEntityFramework.IRepositories” 有两个接口 “IUserRepository”、“IRepository”,因而我这里也创建两个对应的测试类“RepositoryTest”、“BaseUserRepositoryTest”。

public abstract class RepositoryTest<TEntity, TKey> where TEntity : class
{
protected abstract IRepository<TEntity, TKey> Repository { get; }

public virtual void GetSingleTest()
{
AssertCommon.AssertIsNull(true, Repository.GetSingle(default(TKey)));
}

public virtual void InsertTest()
{
AssertCommon.ThrowException(true, () => Repository.Insert(default(TEntity)));
AssertCommon.ThrowException(true, () => Repository.Insert(null));
}
}

RepositoryTest

    public abstract class RepositoryTest<TEntity, TKey> where TEntity : class
{
protected abstract IRepository<TEntity, TKey> Repository { get; } public virtual void GetSingleTest()
{
AssertCommon.AssertIsNull(true, Repository.GetSingle(default(TKey)));
} public virtual void InsertTest()
{
AssertCommon.ThrowException(true, () => Repository.Insert(default(TEntity)));
AssertCommon.ThrowException(true, () => Repository.Insert(null));
}
}

12. 限于篇幅,本文只对IRepository的“GetSingle”和“Insert”方法进行测试,其他方法类似,后续完成所有测试再将代码上传至http://idletest.codeplex.com/。

13. 继续编写类 “BaseUserRepositoryTest”,它与上一篇的 “BaseUserServiceTest” 非常相似。

public abstract class BaseUserRepositoryTest : RepositoryTest<UserModel, string>
{
protected UserTestHelper UserTestHelper = new UserTestHelper();

protected abstract IUserRepository UserRepository { get;}

protected IList<UserModel> ExistedUsers;

[TestInitialize]
public virtual void Init()
{
this.ExistedUsers = new List<UserModel> { UserTestHelper.ExistedUser };
}

public override void GetSingleTest()
{
base.GetSingleTest();

AssertCommon.AssertIsNull<string, UserModel>(
TestCommon.GetEmptyStrings(), true, p => UserRepository.GetSingle(p));
AssertCommon.AssertIsNull(true, UserRepository.GetSingle(UserTestHelper.NotExistedLoginName));

UserModel actual = UserRepository.GetSingle(UserTestHelper.ExistedLoginName);
UserTestHelper.AssertEqual(UserTestHelper.ExistedUser, actual);
}

public override void InsertTest()
{
base.InsertTest();

//验证添加成功的场景
//密码与他人相同也可添加
UserModel register1 = new UserModel { LoginName = "register1", Password = UserTestHelper.ExistedPassword };
UserModel register2 = UserTestHelper.NotExistedUser;
AssertCommon.ThrowException<UserModel>(
new UserModel[] { register1, register2 }, false, p => UserRepository.Insert(p));

//获取用户且应与注册的信息保持一致
UserModel actualRegister1 = UserRepository.GetSingle(register1.LoginName);
UserTestHelper.AssertEqual(register1, actualRegister1);

UserModel actualRegister2 = UserRepository.GetSingle(register2.LoginName);
UserTestHelper.AssertEqual(register2, actualRegister2);

//验证添加失败的场景,使用ThrowException来验证添加
AssertCommon.ThrowException<UserModel>(
new UserModel[] {
register1, //不能重复添加
//由于LoginName对应数据库字段为主键,故不能为空
new UserModel { LoginName = string.Empty, Password = UserTestHelper.NotExistedPassword },
}, true, p => UserRepository.Insert(p));
}
}

BaseUserRepositoryTest

 public abstract class BaseUserRepositoryTest : RepositoryTest<UserModel, string>
{
protected UserTestHelper UserTestHelper = new UserTestHelper(); protected abstract IUserRepository UserRepository { get;} protected IList<UserModel> ExistedUsers; [TestInitialize]
public virtual void Init()
{
this.ExistedUsers = new List<UserModel> { UserTestHelper.ExistedUser };
} public override void GetSingleTest()
{
base.GetSingleTest(); AssertCommon.AssertIsNull<string, UserModel>(
TestCommon.GetEmptyStrings(), true, p => UserRepository.GetSingle(p));
AssertCommon.AssertIsNull(true, UserRepository.GetSingle(UserTestHelper.NotExistedLoginName)); UserModel actual = UserRepository.GetSingle(UserTestHelper.ExistedLoginName);
UserTestHelper.AssertEqual(UserTestHelper.ExistedUser, actual);
} public override void InsertTest()
{
base.InsertTest(); //验证添加成功的场景
//密码与他人相同也可添加
UserModel register1 = new UserModel { LoginName = "register1", Password = UserTestHelper.ExistedPassword };
UserModel register2 = UserTestHelper.NotExistedUser;
AssertCommon.ThrowException<UserModel>(
new UserModel[] { register1, register2 }, false, p => UserRepository.Insert(p)); //获取用户且应与注册的信息保持一致
UserModel actualRegister1 = UserRepository.GetSingle(register1.LoginName);
UserTestHelper.AssertEqual(register1, actualRegister1); UserModel actualRegister2 = UserRepository.GetSingle(register2.LoginName);
UserTestHelper.AssertEqual(register2, actualRegister2); //验证添加失败的场景,使用ThrowException来验证添加
AssertCommon.ThrowException<UserModel>(
new UserModel[] {
register1, //不能重复添加
//由于LoginName对应数据库字段为主键,故不能为空
new UserModel { LoginName = string.Empty, Password = UserTestHelper.NotExistedPassword },
}, true, p => UserRepository.Insert(p));
}
}

14. 在项目 “IdleTest.TDDEntityFramework.RepositoryTest” 下添加类“UserRepositoryTest”

[TestClass]
public class UserRepositoryTest : BaseUserRepositoryTest
{
protected SqlFileContext dbContext;

protected IDisposable TestContext;

private IUserRepository userRepository
{
get { return new UserRepository(dbContext); }
}

protected override IRepository<UserModel, string> Repository
{
get { return userRepository; }
}

protected override IUserRepository UserRepository
{
get { return userRepository; }
}

[TestInitialize]
public override void Init()
{
base.Init();

if (dbContext == null)
{
TestContext = ShimsContext.Create(); //注意使用shim时必须先调用此方法(非全局可使用using)

ShimSqlFileContext context = new ShimSqlFileContext();
ShimDbSet<UserModel> shimDbSet = new ShimDbSet<UserModel>();

shimDbSet.AddT0 = p =>
{
if (this.ExistedUsers.Select(o => o.LoginName).Contains(p.LoginName)
|| string.IsNullOrEmpty(p.LoginName))
{
throw new Exception();
}

this.ExistedUsers.Add(p);
return p;
};

shimDbSet.FindObjectArray = p =>
{
if (p != null && p.Length > 0)
{
return this.ExistedUsers.FirstOrDefault(o => o.LoginName.Equals(p[0]));
}

return null;
};

context.UsersGet = () => shimDbSet;
dbContext = context;
}
}

[TestCleanup]
public virtual void Dispose()
{
this.TestContext.Dispose();
}

[TestMethod]
public override void InsertTest()
{
base.InsertTest();
}

[TestMethod]
public override void GetSingleTest()
{
base.GetSingleTest();
}
}

UserRepositoryTest

    [TestClass]
public class UserRepositoryTest : BaseUserRepositoryTest
{
protected SqlFileContext dbContext; protected IDisposable TestContext; private IUserRepository userRepository
{
get { return new UserRepository(dbContext); }
} protected override IRepository<UserModel, string> Repository
{
get { return userRepository; }
} protected override IUserRepository UserRepository
{
get { return userRepository; }
} [TestInitialize]
public override void Init()
{
base.Init(); if (dbContext == null)
{
TestContext = ShimsContext.Create(); //注意使用shim时必须先调用此方法(非全局可使用using) ShimSqlFileContext context = new ShimSqlFileContext();
ShimDbSet<UserModel> shimDbSet = new ShimDbSet<UserModel>(); shimDbSet.AddT0 = p =>
{
if (this.ExistedUsers.Select(o => o.LoginName).Contains(p.LoginName)
|| string.IsNullOrEmpty(p.LoginName))
{
throw new Exception();
} this.ExistedUsers.Add(p);
return p;
}; shimDbSet.FindObjectArray = p =>
{
if (p != null && p.Length > 0)
{
return this.ExistedUsers.FirstOrDefault(o => o.LoginName.Equals(p[0]));
} return null;
}; context.UsersGet = () => shimDbSet;
dbContext = context;
}
} [TestCleanup]
public virtual void Dispose()
{
this.TestContext.Dispose();
} [TestMethod]
public override void InsertTest()
{
base.InsertTest();
} [TestMethod]
public override void GetSingleTest()
{
base.GetSingleTest();
}
}

15. 编写测试类“UserRepositoryTest”时使用自动生成类生成“UserRepository”,并修改相应代码使编译通过

public class UserRepository : IUserRepository
{
public IEnumerable<UserModel> Get(
Expression<Func<Models.UserModel,
bool>> filter = null,
Func<IQueryable<Models.UserModel>,
IOrderedQueryable<Models.UserModel>> orderBy = null,
string includeProperties = "")
{
throw new NotImplementedException();
}

public UserModel GetSingle(string id)
{
throw new NotImplementedException();
}

public void Insert(UserModel entity)
{
throw new NotImplementedException();
}

public void Update(UserModel entityToUpdate)
{
throw new NotImplementedException();
}

public void Delete(string id)
{
throw new NotImplementedException();
}

public void Delete(UserModel entityToDelete)
{
throw new NotImplementedException();
}
}

UserRepository

    public class UserRepository : IUserRepository
{
public IEnumerable<UserModel> Get(
Expression<Func<Models.UserModel,
bool>> filter = null,
Func<IQueryable<Models.UserModel>,
IOrderedQueryable<Models.UserModel>> orderBy = null,
string includeProperties = "")
{
throw new NotImplementedException();
} public UserModel GetSingle(string id)
{
throw new NotImplementedException();
} public void Insert(UserModel entity)
{
throw new NotImplementedException();
} public void Update(UserModel entityToUpdate)
{
throw new NotImplementedException();
} public void Delete(string id)
{
throw new NotImplementedException();
} public void Delete(UserModel entityToDelete)
{
throw new NotImplementedException();
}
}

16. 继续修改直至测试通过(前面说过这里只对其中两个方法进行测试)。然后按照上一篇文中的做法,再将UserRepository.cs文件移动到项目 “IdleTest.TDDEntityFramework.Repositories”并添加引用“IdleTest.TDDEntityFramework.IRepositories”,记得要修改命名空间是解决方案编译通过。

public class UserRepository : IUserRepository, IDisposable
{
private SqlFileContext dbContext;
private DbSet<UserModel> UserModelSet;

public UserRepository(SqlFileContext dbContext)
{
this.dbContext = dbContext;
this.UserModelSet = this.dbContext.Users;
}

public IEnumerable<UserModel> Get(
Expression<Func<UserModel,
bool>> filter = null,
Func<IQueryable<UserModel>,
IOrderedQueryable<UserModel>> orderBy = null,
string includeProperties = "")
{
IQueryable<UserModel> query = UserModelSet;

if (filter != null)
{
query = query.Where(filter);
}

foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}

if (orderBy != null)
{
return orderBy(query).ToList();
}
else
{
return query.ToList();
}
}

public UserModel GetSingle(string id)
{
return this.UserModelSet.Find(id);
}

public void Insert(UserModel entity)
{
this.UserModelSet.Add(entity);
this.dbContext.SaveChanges();
}

public void Update(UserModel entityToUpdate)
{
UserModelSet.Attach(entityToUpdate);
dbContext.Entry(entityToUpdate).State = EntityState.Modified;
this.dbContext.SaveChanges();
}

public void Delete(string id)
{
var entityToDelete = GetSingle(id);
Delete(entityToDelete);
}

public void Delete(UserModel entityToDelete)
{
if (dbContext.Entry(entityToDelete).State == EntityState.Detached)
{
UserModelSet.Attach(entityToDelete);
}
UserModelSet.Remove(entityToDelete);
this.dbContext.SaveChanges();
}

public void Dispose()
{
if (this.dbContext != null)
{
this.dbContext.Dispose();
}
}
}

UserRepository

    public class UserRepository : IUserRepository, IDisposable
{
private SqlFileContext dbContext;
private DbSet<UserModel> UserModelSet; public UserRepository(SqlFileContext dbContext)
{
this.dbContext = dbContext;
this.UserModelSet = this.dbContext.Users;
} public IEnumerable<UserModel> Get(
Expression<Func<UserModel,
bool>> filter = null,
Func<IQueryable<UserModel>,
IOrderedQueryable<UserModel>> orderBy = null,
string includeProperties = "")
{
IQueryable<UserModel> query = UserModelSet; if (filter != null)
{
query = query.Where(filter);
} foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
} if (orderBy != null)
{
return orderBy(query).ToList();
}
else
{
return query.ToList();
}
} public UserModel GetSingle(string id)
{
return this.UserModelSet.Find(id);
} public void Insert(UserModel entity)
{
this.UserModelSet.Add(entity);
this.dbContext.SaveChanges();
} public void Update(UserModel entityToUpdate)
{
UserModelSet.Attach(entityToUpdate);
dbContext.Entry(entityToUpdate).State = EntityState.Modified;
this.dbContext.SaveChanges();
} public void Delete(string id)
{
var entityToDelete = GetSingle(id);
Delete(entityToDelete);
} public void Delete(UserModel entityToDelete)
{
if (dbContext.Entry(entityToDelete).State == EntityState.Detached)
{
UserModelSet.Attach(entityToDelete);
}
UserModelSet.Remove(entityToDelete);
this.dbContext.SaveChanges();
} public void Dispose()
{
if (this.dbContext != null)
{
this.dbContext.Dispose();
}
}
}

【总结】

  本文啰啰嗦嗦写了一大堆,其重点在于编写服务层(业务层)的测试时通过改变一些编码习惯以便于业务人员的参与;其次则是UserRepositoryTest中的Init方法,对DbContext和DbSet进行了模拟,而我自己编写的继承DbContext的SqlFileContext类将不会被测试。

  其实再写本文前我也没有编写类似的单元测试,算是个人边实践边做的笔记,感觉对数据仓储(或者说数据访问层)的测试做到面面俱到仍然 
还是有难度。甚至我认为这种只对Entity Framework框架提供的操作进行封装的测试可能不太有必要。


感谢阅读,请留下您的意见或疑问! 能力有限,错漏难免,欢迎指点!

分割线:我的个人原创,请认准 http://freedong.cnblogs.com/ (转摘不标原文出处可耻)

 
 

TDD单元测试驱动的更多相关文章

  1. 使用IdleTest进行TDD单元测试驱动开发演练(3) 之 ASP.NET MVC

    一.[前言] (1)本文将用到IOC框架Unity,可参照<Unity V3 初步使用 —— 为我的.NET项目从简单三层架构转到IOC做准备>(2)本文的解决方案是基于前述<使用I ...

  2. 使用IdleTest进行TDD单元测试驱动开发演练(2)

    [前言] 1. 有关上篇请参见<使用IdleTest进行TDD单元测试驱动开发演练(1)>,有关本篇用到Entity Framework Code First请参见<使用NuGet助 ...

  3. 使用IdleTest进行TDD单元测试驱动开发演练(1)

    [前言] 开发工具:Visual Studio 2012 测试库:Visual Studio 2012自带的MSTest DI框架:Unity 数据持久层:Entity Framework 前端UI: ...

  4. TDD测试驱动开发

    TDD测试驱动开发 一.概念 TDD故名思意就是用测试的方法驱动开发,简单说就是先写测试代码,再写开发代码.传统的方式是先写代码,再测试,它的开发方式与之正好相反. TDD是极限编程的一个最重要的设计 ...

  5. C C++ TDD单元测试非常好的书

    http://product.china-pub.com/199003 测试驱动的嵌入式C语言开发 Test Driven Development for Embedded C <测试驱动的嵌入 ...

  6. 测试驱动开发(Test-Driven Development,简称TDD)--单元测试-->提高代码质量

    !!! 1.估算和做项目计划时要算上单元测试时间 2.开发之前写单元测试代码 盖房子的时候,工人师傅砌墙,会先用桩子拉上线,以使砖能够垒的笔直,因为垒砖的时候都是以这根线为基准的.TDD就像这样,先写 ...

  7. 我看TDD测试驱动开发

    今天在实验室给大家介绍了一下TDD和Docker,大家对TDD都比较感兴趣,包括老板,也问了一些问题. 还是从头来说TDD吧,TDD作为敏捷开发领域的领头军,充满魅力,同时也充满争议.一切从三大军规说 ...

  8. 5-13 Rspec实际; validates处理Errors, TDD, 单元测试和验收测试,capybara

    validates处理验证错误:详见ActiveModel::Errors文档 一,errors ActiveModel::Errors的实例包含所有的❌.每个错误:key是每个属性的name, va ...

  9. TDD测试驱动的javascript开发(3) ------ javascript的继承

    说起面向对象,人们就会想到继承,常见的继承分为2种:接口继承和实现继承.接口继承只继承方法签名,实现继承则继承实际的方法. 由于函数没有签名,在ECMAScript中无法实现接口继承,只支持实现继承. ...

随机推荐

  1. 使用excel微调button调整日期

    笔者:iamlaosong excel提供了一个调整的数字button.用来调节单元格增加或减少数量.因为它需要值是0-30000.所以不能直接用其调节日期.但能够使用"初始日期+调节值&q ...

  2. java基金会 之 HashMap统计csvWord文档

    一:知识的补充( 这个HashMap Map 和 c++的Map还是有非常大的区别,惊人的差异大的人,当然,两者的作用是相同的,但函数名出一个非常大的.即使iterator的差是非常大的 ) (1)H ...

  3. 杭电dp题集,附链接还有解题报告!!!!!

    Robberies 点击打开链接 背包;第一次做的时候把概率当做背包(放大100000倍化为整数):在此范围内最多能抢多少钱  最脑残的是把总的概率以为是抢N家银行的概率之和- 把状态转移方程写成了f ...

  4. Fluent Validation + NInject3 + MVC5

    Fluent Validation + NInject + MVC - Why & How : Part 1 http://fluentvalidation.codeplex.com/ htt ...

  5. SSAS系列——【07】多维数据(查询Cube)

    原文:SSAS系列——[07]多维数据(查询Cube) 1.什么是MDX? MDX叫做"多维表达式",是一种查询语言,是一种和SQL类似的查询语言,它基于 XML for Anal ...

  6. vistual studio 2012 安装失败,提示Microsoft Vistual Studio 2012 Devenv找不到元素,等错误信息

    在安装vistual studio 2012过程中,出现安装失败,提示Microsoft Vistual Studio 2012 Devenv找不到元素,等错误信息 解决方法是更新相应的server补 ...

  7. ArcGIS Runtime SDKs v10.2.4最新(Android、iOS、OSX和.NET)

    ArcGIS Runtime SDKs v10.2.4最新,它包含:Android.iOS.OS X和.NET四大平台,用户和开发人员可以登录Esri最新的SDK安装包.或者通过云盘下载(http:/ ...

  8. 財智V6.0(完美破解序列号特别版)

    財智V6.0(完美破解序列号特别版)               財智V6.0(完美破解序列号特别版)   財智6是眼下唯一在中央台报道的.比較成熟的国产理財软件.能全面管理家庭的日常收入.消费.储蓄 ...

  9. LeetCode——N-Queens II

    Follow up for N-Queens problem. Now, instead outputting board configurations, return the total numbe ...

  10. Effective C++ 10

    10.假设写了operator new,就要同一时候写operator delete. 为什么要写自己的operator new和delete,首先这不叫重载,这叫隐藏. new仅仅是用来申请空间,而 ...