我知道这是一个非常广泛的问题,这实际上是我的主要问题。我是单元测试的新手,我不知道在哪里可以找到有用的资源,因为我甚至不确定要搜索什么。我被分配到一个MVC网站项目中添加单元测试,这是我的团队第一次尝试实现这两种方法,但是我们一直使用的旧方法已经过时,没有优化,所以我们正在尝试采用更新和更有效的方法。
新的MVC项目仍然很小,是对一个旧的遗留网站的重写,这个网站是可怕的,难以管理的,每一个小的改变都需要人工测试几个小时才能完成。我们希望构建更好的东西,并相信MVC、实体框架和单元测试是实现这一目标的方法。以下是我如何根据我们研究过的其他MVC项目来设置我们的项目。
这是一个基本的控制器ActionResult…
public ActionResult SignUp()
{
return View("SignUp");
}
[HttpPost]
public ActionResult SignUp(UserSignUpView userSignUpView)
{
if (User.Identity.IsAuthenticated)
{
return RedirectToAction("Index", "Home");
}
else if (!ModelState.IsValid)
{
return View(userSignUpView);
}
string error = userManagerModel.CheckIfAccountExists(userSignUpView.LoginName, userSignUpView.Email);
if (error != null)
{
ModelState.AddModelError("", error);
return View("SignUp");
}
else
{
userManagerModel.AddAccount(userSignUpView);
FormsAuthentication.SetAuthCookie(userSignUpView.LoginName, false);
return RedirectToAction("Welcome", "Home");
}
}
这是一个基本的模型…
public class UserSignUpView
{
[Key]
public int UserID { get; set; }
[Display(Name = "Login ID")]
[Required(ErrorMessage = "* Required")]
public string LoginName { get; set; }
[Display(Name = "Email")]
[Required(ErrorMessage = "* Required")]
[EmailAddress(ErrorMessage = "Invalid Email Address")]
public string Email { get; set; }
[Display(Name = "Password")]
[Required(ErrorMessage = "* Required")]
[RegularExpression(@"^(?=.*[!@#$&*])(?=.*[0-9])(?=.*[a-zA-Z]).{8,20}$",
ErrorMessage = "Password must be between 8 and 20 characters long. Password must also contain at least 1 upper case letter, 1 number, and 1 special character.")]
public string Password { get; set; }
[Display(Name = "Confirm Password")]
[Required(ErrorMessage = "* Required")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
下面是与SignUp()操作相关的辅助方法…
public string CheckIfAccountExists(string loginName, string email)
{
using (var db = new Database())
{
if (db.Users.Where(o => o.LoginName.Equals(loginName)).Any()
&& db.Users.Where(o => o.Email.Equals(email)).Any())
{
return "Both Login and Email already exists";
}
else if (db.Users.Where(o => o.LoginName.Equals(loginName)).Any())
{
return "Login Name already taken.";
}
else if (db.Users.Where(o => o.Email.Equals(email)).Any())
{
return "An account with this email already exists.";
}
}
return null;
}
public void AddAccount(UserSignUpView userSignUpView)
{
using (var db = new Database())
{
string securePassword = //method that adds salt and hashes userSignUpView.Password
User user = new User();
user.LoginName = userSignUpView.LoginName;
user.SecurePassword = securePassword;
user.Email = userSignUpView.Email;
db.Users.Add(user);
db.SaveChanges();
}
}
所以我试图添加单元测试,从我读到的嘲笑数据库是我最好的选择,但我所有的尝试都失败了。linq、实体框架、模拟数据、mvc和单元测试的资源到处都有,但我还没有幸运地找到2008年以后编写的所有这些东西。是否有一种更好的方法来设置这些部分,使其与单元测试更有凝聚力?我说了这么多,听起来可能像小孩子在抓稻草,但我们只是想摆脱我们长期以来一直坚持的可怕的传统做法。
TLDR;有任何体面的和最新的教程单元测试的MVC实体框架?
给出简单的控制器示例,如何使该控制器更易于测试?
作为初学者,你应该考虑抽象掉实现依赖。
这意味着抽象userManagerModel
和FormsAuthentication
。(尽量避免与静态类耦合)
public interface IUserManager {
string CheckIfAccountExists(string loginName, string email);
void AddAccount(UserSignUpView userSignUpView);
}
public interface IAuthenticationService {
void SetAuthCookie(string userName, bool createPersistentCookie);
}
,这样当你想测试控制器的动作时,你可以用假的/模拟的实现来替换它。
public class AccountController : Controller {
IUserManager userManagerModel;
IAuthenticationService formsAuthentication;
public AccountController(IUserManager userManagerModel, IAuthenticationService formsAuthentication) {
this.userManagerModel = userManagerModel;
this.formsAuthentication = formsAuthentication;
}
public ActionResult SignUp() {
return View();
}
[HttpPost]
public ActionResult SignUp(UserSignUpView userSignUpView) {
if (User.Identity.IsAuthenticated) {
return RedirectToAction("Index", "Home");
} else if (!ModelState.IsValid) {
return View(userSignUpView);
}
string error = userManagerModel.CheckIfAccountExists(userSignUpView.LoginName, userSignUpView.Email);
if (error != null) {
ModelState.AddModelError("", error);
return View(userSignUpView);
} else {
userManagerModel.AddAccount(userSignUpView);
formsAuthentication.SetAuthCookie(userSignUpView.LoginName, false);
return RedirectToAction("Welcome", "Home");
}
}
}
看起来几乎和你原来的一样,但是你会注意到userManagerModel
是一个接口,而不是一个具体的类。
然后你可以手动或使用你选择的mock框架创建假/mock来测试控制器的功能,而不需要控制器与数据库的紧密耦合。
您将确保您的生产类实现接口并提供生产中期望的功能。
例如public class FormsAuthenticationService : IAuthenticationService {
public void SetAuthCookie(string userName, bool createPersistentCookie) {
FormsAuthentication.SetAuthCookie(userName, createPersistentCookie);
}
}