集成测试变得容易 第一部分:存储库测试






4.40/5 (12投票s)
一步步实现您的第一个单元测试。
引言
我们都熟悉在业务应用程序中编码时一个非常常见的场景。事实上,这是我们希望将对象保存到数据库的最常见场景。但是我们如何知道我们的代码是否正常运行呢?嗯,有很多方法可以知道。有些是好方法,有些则不是。让我们通过一个场景来讨论。
示例场景
假设我们要保存一个 `Student` 对象,该类将包含 `Name`、`Phone`、`Email` 和 `Id`。为了保存这个对象,我们应该创建一个名为 `StudentRepository` 的存储库类。在这个类中,我们将编写数据保存代码。
现在我想提几点。为每个存储库类创建接口是一种很好的做法。它是存储库模式的一个子集。
在我们的例子中,我们应该创建 `IStudentRepository`,其中我们将只定义 `Save` 方法,并让 `StudentRepository` 类实现此接口。
所以,总而言之,我们目前有三个项目。它们是 `DataAccess`、`DataAccess.Contract` 和 `Domain`。以下是类定义和项目架构。
实体框架
为了实现数据库访问代码,我们不会使用传统的 `SqlClient` 库类,也不会编写传统的 SQL 查询。相反,我们将使用 Entity Framework。Entity Framework (EF) 是一个对象关系映射器,它使 .NET 开发人员能够使用领域特定对象处理关系数据。它消除了开发人员通常需要编写的大部分数据访问代码。
要在我们的项目中使用该框架,我们需要安装它。但是我们将在哪里找到那个库呢?嗯。Microsoft 开发了一个扩展,通过它我们可以非常容易地在我们的项目中安装、更新和卸载外部库。下面是如何将 Entity Framework 安装到我们的 `DataAccess` 项目中。
演练:使用 Nuget 安装 Entity Framework
- 右键单击 `DataAccess` 项目的引用选项。
- 找到 Entity Framework 项(下图中第二个)。
- 单击该项的“安装”按钮。
- 当系统要求您接受许可协议时,单击“接受”。
- 成功安装后,您应该会看到绿色的标志,而不是“安装”按钮。
安装 Entity Framework 后,我们应该创建一个 `DataContext` 类,它将继承 `DBContext` 类,并且我们将在 `OnModelCreating` 方法下将我们的 `Student` 类与表名映射。当第一次调用 `DataContext` 类时,Entity Framework 将查找数据库并在数据库不存在时自行创建。接下来,它将根据我们在 `OnModelCreating` 方法中给出的指令创建表。从下面的类中,系统将在 `.\SQLEXPRESS` 服务器中创建一个名为 `MockingPractices.DataAccess.DataContext` 的数据库(因为我们没有给出任何连接字符串,它将在默认服务器中执行此操作)。并且,`Student` 表也被创建。
数据库
DataContext 类
namespace MockingPractices.DataAccess
{
using System.Data.Entity;
using MockingPractices.Domain;
public class DataContext:DbContext
{
public DbSet<Student> Students { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>().ToTable("Student");
base.OnModelCreating(modelBuilder);
}
}
}
IStudentRepository 接口
namespace MockingPractices.DataAccess.Contract
{
using MockingPractices.Domain;
public interface IStudentRepository
{
int Save(Student student);
}
}
StudentRepository 类
namespace MockingPractices.DataAccess
{
using MockingPractices.DataAccess.Contract;
using MockingPractices.Domain;
public class StudentRepository : IStudentRepository
{
public int Save(Student student)
{
using (var context=new DataContext())
{
Student studentAdded = context.Students.Add(student);
context.SaveChanges();
return studentAdded.Id;
}
}
}
}
单元测试
现在我们将找到一种测试代码的方法。在传统方式中,我们必须创建一个简单的用户界面(可能是 Windows 窗体应用程序),其中会有一个按钮。如果我们点击按钮,我们将创建一个 `student` 对象并将其传递给存储库方法。以下是场景。
类图
基于用户界面的测试架构
但是,如果我们频繁更改代码并需要测试,那么每次我们都必须运行 UI 并点击按钮进行测试。如果我们需要为单个存储库的 `save` 方法测试多个场景,事情会变得更糟。因此,我们可以理解的是,我们需要一个可以自动管理这些方法调用的系统。我们不需要每次开发代码更改时都点击用户界面。下面可能是提议的架构之一。
自动化测试架构
这种类型的测试被称为自动化单元测试,目前有几个框架用于此目的。NUnit 和 Visual Studio 的内部单元测试框架都非常常见。让我们在我们的解决方案中使用第二个。以下是如何为您的解决方案创建单元测试项目。如果您仍然不明白,请不要担心。继续阅读下面的内容。 " />
转到:添加新项目 --> Visual C# --> 测试 --> 单元测试项目
输入一个合适的名称,然后按“确定”按钮。
每个单元测试框架都有一些类,编译器通过这些类了解它应该运行哪些类。对于这个,这个标签是 `TestClass`,并且所需的测试方法必须在其顶部有 `TestMethod` 标签。
在我们的例子中,我们可以将默认类重命名为 `StudentRepositoryTest` 类,并将给定的模板方法重命名为 `SaveShouldReturnSavedId` 方法。
因此,测试方法现在看起来像下面这样
单元测试的哲学
让我们从一个非常悲伤但真实的场景开始。假设有一天你处于浪漫模式,想和你的爱人说话,你说“我爱你,亲爱的”。你期望听到什么?我们都期望听到“我也爱你,宝贝”或类似的回复。但是如果你的爱人对你大喊大叫,那么这种大喊大叫肯定不会符合你的期望。我说的对吗?
在编程世界中,我们期望我们的方法能够顺利运行,没有任何问题。我们确切地知道从特定方法中期望得到什么。在我们的场景中,我们期望我们的存储库方法保存 `student` 对象并返回该对象的保存 ID。我们还知道 ID 不能为零,因为我们数据库的主键是自增数字,并且对象无法保存到 ID 为 0 的行中。因此,我们的期望是返回的值不应等于零。对吗?有道理吗?
那么我们如何在代码中实现这个场景呢?
Microsoft 创建了一个名为 `Assert` 的类。它的职责是匹配期望值与返回/实际值。如果期望值与实际值匹配,它会显示绿色,但如果期望值不匹配,它会显示红色。所以代码将如下所示
namespace MockingPractices.DataAccess.Test
{
using System.Transactions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MockingPractices.DataAccess.Contract;
using MockingPractices.Domain;
[TestClass]
public class StudentRepositoryTest
{
[TestMethod]
public void SaveShouldReturnSavedId()
{
using (TransactionScope scope = new TransactionScope())
{
IStudentRepository studentRepository = new StudentRepository();
Student student = new Student()
{
Id = 0,
Name = "test name",
Email = "testemail@email.com",
Phone = "123456"
};
int id = studentRepository.Save(student);
Assert.AreNotEqual(0, id);
}
}
}
}
现在,如果我们遵循下图,我们可以指示系统运行测试方法并执行该方法中的代码。
演练:使用 Visual Studio 单元测试框架运行测试方法
- 将光标移动到下图中给出的图标上。
- 点击它并按下“运行”。
- 将出现一个窗口,指示运行测试的状态。
- 如果测试成功运行,应该出现下图。
如果您严格遵循我上面的说明,您现在应该会看到如下所示的绿色标志。因此,我们可以使用单元测试来测试我们的数据库代码,而无需任何用户界面的帮助。
但是上面的工作存在一些问题。
- 我们不应该将测试数据保留在数据库中,并且
- 我们不应该自己创建辅助对象。下面是解决这些问题的方法。
主要规则是,我们必须在 `test` 方法之后回滚数据库的 `insert` 操作。我们可以使用 `TransactionScope` 类来实现这一点。
其次,我们应该使用一个框架来创建这些辅助存根。我们将使用 `NBuilder` 为我们构建对象。`NBuilder` 可以使用 Nuget 进行安装。
通过查看下图,您应该可以在项目中看到已安装的包
因此,在编写代码解决这些问题之后,我们的代码应该如下所示
namespace MockingPractices.DataAccess.Test
{
using System.Transactions;
using FizzWare.NBuilder;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MockingPractices.DataAccess.Contract;
using MockingPractices.Domain;
[TestClass]
public class StudentRepositoryTest
{
[TestMethod]
public void SaveShouldReturnSavedId()
{
using (TransactionScope scope = new TransactionScope())
{
IStudentRepository studentRepository = new StudentRepository();
Student student = Builder<Student>.CreateNew().With(s => s.Id = 0).Build();
int id = studentRepository.Save(student);
Assert.AreNotEqual(0, id);
}
}
}
}
只需按下“运行”命令,即可运行任意数量的测试。您再也无需一次又一次地打开用户界面并输入值并自行计算预期值。这就是单元测试的真正美妙之处。