三、单元测试--确保控制器按照希望执行:
单元测试是小型的脚本化测试,通常以与产品代码同样的语言来编写。它们以与系统其余部分隔离的形式来建立并演练单个组件的功能,目的是证实它能正确工作。
1.已提供的测试项目:
如果在新建项目时勾选了“创建单元测试项目”选项,Visual Studio会用Visual Studio Unit Testing Framework(Visual Studio单元测试框架)生成一个测试项目,包含HomeControllerTest类:
1 namespace Guestbook.Tests.Controllers 2 { 3 [TestClass] 4 public class HomeControllerTest 5 { 6 [TestMethod] 7 public void Index() 8 { 9 // Arrange 10 HomeController controller = new HomeController();//实例化控制器 11 12 // Act 13 ViewResult result = controller.Index() as ViewResult;//演练动作方法 14 15 // Assert 16 Assert.IsNotNull(result);//断言结果 17 } 18 19 [TestMethod] 20 public void About() 21 { 22 // Arrange 23 HomeController controller = new HomeController(); 24 25 // Act 26 ViewResult result = controller.About() as ViewResult; 27 28 // Assert 29 Assert.AreEqual("Your application description page.", result.ViewBag.Message); 30 } 31 32 [TestMethod] 33 public void Contact() 34 { 35 // Arrange 36 HomeController controller = new HomeController(); 37 38 // Act 39 ViewResult result = controller.Contact() as ViewResult; 40 41 // Assert 42 Assert.IsNotNull(result); 43 } 44 }
每个测试都有三个阶段--Arrange(准备)、Act(动作)、Assert(断言)。通常将这种编写测试的模式称为“A/A/A”模式或“3A”模式。让单元测试采用这种固定的A/A/A编程模式有利于编写格式统一的测试。
2.测试GuestbookController:
在之前的代码中GuestbookController直接实例化并使用GuestbookContext对象访问数据库。这意味着如果没有建好数据库或者数据库中没有测试数据,就不能对控制器进行测试。
代替上述对GuestbookContext的直接访问,我们引入一个存储库,让它提供一个对GuestbookEntry对象执行数据访问操作的网关:
1.新增接口:
1 namespace Guestbook.Repository 2 { 3 public interface IGuestbookRepository 4 { 5 IList<GuestbookEntry> GetMostRecentEntries(); 6 GuestbookEntry FindById(int? id); 7 IList<CommentSummary> GetCommentSummary(); 8 void AddEntry(GuestbookEntry entry); 9 void Edit(GuestbookEntry entry); 10 void DeleteById(int id); 11 } 12 }
2.实现接口:
logs_code_hide('4dd83202-c92d-48b3-af71-6b60961cfa06',event)" src="/Upload/Images/2014111402/2B1B950FA3DF188F.gif" alt="" />1 namespace Guestbook.Repository 2 { 3 public class GuestbookRepository : IGuestbookRepository 4 { 5 private GuestbookContext _db = new GuestbookContext(); 6 public IList<GuestbookEntry> GetMostRecentEntries() 7 { 8 return (from entry in _db.Entries 9 orderby entry.DateAdded descending 10 select entry).Take(20).ToList(); 11 } 12 public void AddEntry(GuestbookEntry entry) 13 { 14 _db.Entries.Add(entry); 15 _db.SaveChanges(); 16 } 17 public GuestbookEntry FindById(int? id) 18 { 19 var entry = _db.Entries.Find(id); 20 return entry; 21 } 22 public IList<CommentSummary> GetCommentSummary() 23 { 24 var entries = from entry in _db.Entries 25 group entry by entry.Name into groupedByName 26 orderby groupedByName.Count() descending 27 select new CommentSummary 28 { 29 NumberOfComments=groupedByName.Count(), 30 UserName=groupedByName.Key 31 }; 32 return entries.ToList(); 33 34 } 35 public void Edit(GuestbookEntry entry) 36 { 37 _db.Entry(entry).State = EntityState.Modified; 38 _db.SaveChanges(); 39 } 40 public void DeleteById(int id) 41 { 42 var entry = _db.Entries.Find(id); 43 _db.Entries.Remove(entry); 44 _db.SaveChanges(); 45 } 46 } 47 }View Code
3.修改GuestbookController:
1 namespace Guestbook.Controllers 2 { 3 public class GuestbookController : Controller 4 { 5 private IGuestbookRepository _repository; 6 7 public GuestbookController() 8 { 9 _repository = new GuestbookRepository(); 10 } 11 12 public GuestbookController(IGuestbookRepository repository) 13 { 14 _repository = repository; 15 } 16 public ActionResult Index() 17 { 18 var mostRecentEntries=_repository.GetMostRecentEntries(); 19 return View(mostRecentEntries); 20 } 21 public ActionResult Details(int? id) 22 { 23 if (id == null) 24 { 25 return new HttpStatusCodeResult(HttpStatusCode.BadRequest); 26 } 27 var entry = _repository.FindById(id); 28 if (entry == null) 29 { 30 return HttpNotFound(); 31 } 32 return View(entry); 33 } 34 public ActionResult Create() 35 { 36 return View(); 37 } 38 [HttpPost] 39 [ValidateAntiForgeryToken] 40 public ActionResult Create([Bind(Include="Id,Name,Message,DateAdded")] GuestbookEntry entry) 41 { 42 if (ModelState.IsValid)//检查验证是否成功 43 { 44 _repository.AddEntry(entry); 45 return RedirectToAction("Index"); 46 } 47 48 return View(entry);//失败时重新渲染表单 49 } 50 public ActionResult Edit(int? id) 51 { 52 if (id == null) 53 { 54 return new HttpStatusCodeResult(HttpStatusCode.BadRequest); 55 } 56 var entry = _repository.FindById(id); 57 if (entry == null) 58 { 59 return HttpNotFound(); 60 } 61 return View(entry); 62 } 63 [HttpPost] 64 [ValidateAntiForgeryToken] 65 public ActionResult Edit([Bind(Include="Id,Name,Message,DateAdded")] GuestbookEntry entry) 66 { 67 if (ModelState.IsValid) 68 { 69 _repository.Edit(entry); 70 return RedirectToAction("Index"); 71 } 72 return View(entry); 73 } 74 public ActionResult Delete(int? id) 75 { 76 if (id == null) 77 { 78 return new HttpStatusCodeResult(HttpStatusCode.BadRequest); 79 } 80 var entry = _repository.FindById(id); 81 if (entry == null) 82 { 83 return HttpNotFound(); 84 } 85 return View(entry); 86 } 87 [HttpPost, ActionName("Delete")] 88 [ValidateAntiForgeryToken] 89 public ActionResult DeleteConfirmed(int id) 90 { 91 _repository.DeleteById(id); 92 return RedirectToAction("Index"); 93 } 94 95 public ActionResult CommentSumary() 96 { 97 var entries = _repository.GetCommentSummary(); 98 return View(entries.ToList()); 99 } 100 } 101 }View Code
4.不与数据库交互的IGuestbookRepository接口的模仿实现:
1 namespace Guestbook.Repository 2 { 3 public class FakeGuestbookRepository:IGuestbookRepository 4 { 5 public List<GuestbookEntry> _entries = new List<GuestbookEntry>(); 6 public IList<GuestbookEntry> GetMostRecentEntries() 7 { 8 return new List<GuestbookEntry> 9 { 10 new GuestbookEntry 11 { 12 DateAdded=new DateTime(2014,11,13), 13 Id=1, 14 Message="Test Message", 15 Name="陈家洛" 16 } 17 }; 18 } 19 public void AddEntry(GuestbookEntry entry) 20 { 21 _entries.Add(entry); 22 } 23 public GuestbookEntry FindById(int? id) 24 { 25 return _entries.SingleOrDefault(x => x.Id == id); 26 } 27 public IList<CommentSummary> GetCommentSummary() 28 { 29 return new List<CommentSummary> 30 { 31 new CommentSummary 32 { 33 UserName="陈家洛", 34 NumberOfComments=3 35 } 36 }; 37 38 } 39 public void Edit(GuestbookEntry entry) 40 { 41 // 42 } 43 public void DeleteById(int id) 44 { 45 // 46 } 47 } 48 }View Code
5.测试Index动作:
1 namespace Guestbook.Tests.Controllers 2 { 3 [TestClass] 4 public class GuestbookControllerTest 5 { 6 [TestMethod] 7 public void Index_RendersView() 8 { 9 var controller = new GuestbookController(new FakeGuestbookRepository()); 10 var result=controller.Index() as ViewResult; 11 Assert.IsNotNull(result); 12 } 13 [TestMethod] 14 public void Index_gets_most_rencent_entries() 15 { 16 var controller = new GuestbookController(new FakeGuestbookRepository()); 17 var result=(ViewResult)controller.Index(); 18 var entries=(IList<GuestbookEntry>) result.Model; 19 Assert.AreEqual(1,entries.Count); 20 } 21 22 } 23 }
第一个测试调用Index动作,并简单断言它渲染一个视图;第二个断言传递给视图一个GuestbookEntry对象列表。
源码下载 密码:bwmq