【问题标题】:Prevent writing to database on unit tests - .NET MVC / Entity Framework防止在单元测试中写入数据库 - .NET MVC / Entity Framework
【发布时间】:2018-08-13 19:51:53
【问题描述】:

我正在尝试为使用实体框架的现有 .NET MVC 4.5 Web 应用程序编写单元测试。我想测试bool 方法是否按预期返回true/false;但是被测试的方法会写入数据库日志。

很遗憾,这些测试抛出以下异常:

System.InvalidOperationException:在应用程序配置文件中找不到名为“xxxDbContext”的连接字符串。

我不希望修改数据库,并且我正在努力寻找如何在此示例中模拟 `DbContext 的示例。

如何在不实际写入数据库的情况下测试布尔结果?在下面的示例中,Logger.Log() 向数据库添加了一行,我不希望在单元测试期间修改实际的数据库。

以下是代码的简化示例:

测试方法

[TestMethod]
public void LocalFileCompare_File2IsNull_ReturnsFalse()
{
    var t = new tManager(new tSet());
    FileInfo file1 = new FileInfo(@"C:\Temp\TempFile.txt");
    FileInfo file2 = null;

    var result = transferSet.LocalFileCompare(file1, file2);
    Assert.IsFalse(result);
}

待测方法

public bool LocalFileCompare(FileInfo file1, FileInfo file2)
{
    if (file1 == null || file2 == null)
    {
        Logger.Log("LocalFileCompare: One or both files are null.");
        return false;
    }
    if (file1.FullName != file2.FullName)
    {
        Logger.Log($"LocalFileCompare - file names don't match.");
        return false;
    }
    if (file1.Length != file2.Length)
    {
        Logger.Log($"LocalFileCompare - file sizes don't match.");
        return false;
    }

    return true;
}

谢谢!

【问题讨论】:

  • 什么是Logger
  • 注入标记的记录器或使用编译器宏为记录器编写不同的方法
  • @Steve 我在回答中解释了“注入”方法。
  • 你可以使用 Moq 来模拟你的数据库 - 它非常好 -> spin.atomicobject.com/2017/08/07/intro-mocking-moq
  • @DanielLoudon 模拟数据库是为了集成测试。单元测试应该全部在内存中运行,没有外部依赖。

标签: c# entity-framework unit-testing moq


【解决方案1】:

您的 LocalFileCompare() 方法依赖于 Logger 类(也就是“紧密耦合”)。因为Logger的实现会写入数据库,所以调用LocalFileCompare()时无法避免写入数据库,除非去掉依赖。为此,您可以改为让方法依赖于接口,让Logger 类实现该接口,然后将其“注入”到LocalFileCompare() 方法中。

这将允许您通过简单地实现接口并且不将实现写入数据库来轻松创建模拟记录器类。

第 1 步:创建ILogger 接口

第 2 步:更改您的 Logger 类以实现 ILogger

第 3 步:更改 LocalFileCompare() 方法的签名以包含 ILogger 参数

第 4 步:将 Logger 实例添加到对 LocalFileCompare() 方法的所有调用中

第 5 步:使用模拟的 ILogger 实现更新您的测试

// Step 1
public interface ILogger
{
    void Log(string message);
}

// Step 2
public class Logger : ILogger
{
    void Log(string message)
    {
        // Implementation here
    }
}

// Step 3
public bool LocalFileCompare(FileInfo file1, FileInfo file2, ILogger logger)
{
    if (file1 == null || file2 == null)
    {
        logger.Log("LocalFileCompare: One or both files are null.");
        return false;
    }
    if (file1.FullName != file2.FullName)
    {
        logger.Log($"LocalFileCompare - file names don't match.");
        return false;
    }
    if (file1.Length != file2.Length)
    {
        logger.Log($"LocalFileCompare - file sizes don't match.");
        return false;
    }

    return true;
}

// Step 4
// Update your calls to LocalFileCompare()

// Step 5
// Put this somewhere in the Test project
public class MockLogger : ILogger
{
    void Log(string message)
    {
        Console.WriteLine(message);
    }
}

// In your test class
ILogger mockLogger;

// In your Test Setup method
mockLogger = new MockLogger();

[TestMethod]
public void LocalFileCompare_File2IsNull_ReturnsFalse()
{
    var t = new tManager(new tSet());
    FileInfo file1 = new FileInfo(@"C:\Temp\TempFile.txt");
    FileInfo file2 = null;

    var result = transferSet.LocalFileCompare(file1, file2, mockLogger);
    Assert.IsFalse(result);
}

【讨论】:

    【解决方案2】:

    如果你只想要一个快速而肮脏的解决方案而不必使用依赖注入,你可以使用Preprocessor

    类似

    public void Log()
    {
    #if UnitTest
    //Log to file
    #else
    //Log to db
    #endif 
    }
    

    然后添加一个新的配置名称UnitTest。切换到该配置并转到项目属性 -> 构建 -> 条件编译符号 -> 添加UnitTest in

    【讨论】:

    • 是的,那样做,你迟早会过得很糟糕的:)
    • @Bartosz 这就是为什么它是一个快速而肮脏的解决方案。如果您只是在小范围内进行,则非常理想。
    • 好吧,我并不是说你没有警告 OP :) 这是对正确设计原则的一种肮脏的破解,虽然它应该是非法的:)
    猜你喜欢
    • 2020-05-28
    • 1970-01-01
    • 1970-01-01
    • 2021-09-25
    • 1970-01-01
    • 2011-10-24
    • 2014-10-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多