如何使测试在内存中初始化并在每个测试中使用

时间:2019-02-12 05:44:29

标签: c# performance unit-testing

我正在尝试创建单元测试。我有班级用户:

 public class User
{
    public int UsersCount
    {
        get
        {
            using (MainContext context = new MainContext())
            {
                return context.Users.Count();
            }
        }
    }
    public Guid Id { get; set; } = Guid.NewGuid();
    public string UserName { get; set; }
    public string Password { get; set; }
    public Contact UserContact { get; set; }
}

我的第一个测试是UsersCount_Test测试,它测试UsersCount属性:

 [TestMethod]
    public void UsersCount_Test()
    {
        var user = new User();
        var context = new MainContext();
        int usersCount = context.Users.Count();
        context.Users.Add(new User());
        context.SaveChanges();
        Assert.AreEqual(usersCount + 1, user.UsersCount, $"It should be {usersCount + 1} because we're adding one more user");
    }

如果我在测试类中添加了新的测试方法(我正在使用单独的类来测试每个实体),则需要创建User的新实例。这就是我这样做的原因:

    public class BaseTest<T>
{
    public T TestEntity;

    public MainContext TestContext = new MainContext();
}

现在每个测试类都从该类继承。我还创建了测试初始化​​方法。现在我的测试类如下:

 [TestClass]
public class UserTest : BaseTest<User>
{
    [TestMethod]
    public void UsersCount()
    {
        int usersCount = TestContext.Users.Count();
        TestContext.Users.Add(new User());
        TestContext.SaveChanges();
        Assert.AreEqual(usersCount + 1, TestEntity.UsersCount, $"It should be {usersCount + 1} because we're adding one more user");
    }

    [TestInitialize]
    public void SetTestEntity()
    {
        TestEntity = new User();
    }
}

现在,我要向User添加新属性并编写一些逻辑:

  string phoneNumber;
    public string PhoneNumber { get { return phoneNumber; } set { SetUserContact(phoneNumber, value); phoneNumber = value; } }

    void SetUserContact(string oldContact, string newContact)
    {
        UserContact.ContactsList.Remove(oldContact);
        UserContact.ContactsList.Add(newContact);
    }

之后,我要创建新的测试:

     [TestMethod]
    public void ContactList_Test()
    {
        var newPhone = "+8888888888888";
        TestEntity.PhoneNumber = newPhone;
        Assert.IsTrue(TestEntity.UserContact.ContactsList.Any(a => a == newPhone), $"It should contains {newPhone}");
    }

测试失败,因为TestEntity的UserContact为空。我知道TestEntity应该由逻辑创建。之后,我修复测试初始化​​器方法:

 [TestInitialize]
    public void SetTestEntity()
    {
        TestEntity = new User() { UserContact = new Contact() };
    }

这是联系人模型

    public class Contact
{
    public Guid Id { get; set; } = Guid.NewGuid();
    public virtual List<string> ContactsList { get; set; } = new List<string>();
}

我的问题是如何只设置一次TestEntity,这是可能的(也许在调用SetTestEntity方法时将其保存在内存中并使用它)?因为SetTestentity方法在每个测试中都会创建一个新实体,所以需要花费更多的开发时间。 (例如,如果创建UserContact实例全部花费3秒钟,则测试运行超过3秒钟)。在这种情况下,另一种方法是在ContactLists测试中设置UserContact,但是我认为这不是一个好主意。将来当我们添加新的逻辑时,我需要修复每个测试。请给我任何建议和/或想法。

2 个答案:

答案 0 :(得分:4)

如果您真的必须在每次测试前运行TestInitialize。您只能使用ClassInitialize对类运行一次测试初始化​​。

但是

从我的角度来看,您的性能问题是由您的应用程序的设计和架构破坏了您的工作范围而又违反了单一职责原则。创建静态数据库实体或在测试中共享它不是一个解决方案,它只会增加技术负担。一旦您在测试中共享任何内容,就必须对其进行维护,并按定义分别进行单元测试 SHOULD ,以允许使用新数据测试每个场景。

您不应该创建依赖于MainContext的数据库模型。一个User应该真的知道数据库中有多少Users吗?如果不是,请创建将注入MainContext和方法GetUsersCount()的单独存储库,并通过添加几个调用特定实现的用户并检查是否添加了正确的用户数,来使用InMemoryDatabase对其进行单元测试,例如:

public interface IUsersRepository
    {
        int GetUsersCount();
    }

    public class UsersRepository : IUsersRepository
    {
        private readonly EntityFrameworkContext _context;

        public UsersRepository(EntityFrameworkContext context)
        {
            _context = context;
        }

        public int GetUsersCount()
        {
            return _context.Users.Count();
        }
    }

后来才真正使用上下文的方法应该用InMemoryDatabase进行测试,而对于使用IUserRepository的方法,应该对每个特定的方法进行模拟,因为它们是分开进行测试的。

答案 1 :(得分:0)

在每次测试之前和之后都运行TestInitialize和TestCleanup,以确保没有测试耦合。

如果要在ALL测试之前和之后只运行一次方法,请使用ClassInitialize和ClassCleanup属性装饰相关方法。

编写测试时,可以使用以下附加属性:

示例代码-

// Use ClassInitialize to run code before running the first test in the class
[ClassInitialize()]
public static void MyClassInitialize(TestContext testContext) { }

// Use ClassCleanup to run code after all tests in a class have run
[ClassCleanup()]
public static void MyClassCleanup() { }

// Use TestInitialize to run code before running each test 
[TestInitialize()]
public void MyTestInitialize() { }

// Use TestCleanup to run code after each test has run
[TestCleanup()]
public void MyTestCleanup() { }

基本上,您可以在ClassIntialize方法中使用SetEntity方法 希望对您有所帮助。