MVC5 中的自动化测试

Automated Testing in MVC5

我正在启动一个新应用程序,并希望对我想要实现的大部分功能进行自动化测试。因此,我一直在阅读有关 TDD 的内容,以及您应该如何首先在应该首先失败的地方(当然)编写测试,然后编写代码使其通过。

  1. 现在,我还读到建议使用模拟框架来避免运行对数据库进行测试。为什么会这样呢?如果我 运行 针对测试数据库进行测试,这是否可以接受?

  2. 此外 - 在调用业务层方法的控制器级别编写测试是否可以,或者我应该将单元测试的覆盖范围集中在一种方法上吗?但是,如果该方法正在调用另一个方法怎么办?

  3. 可测试代码是否意味着我必须对接口进行依赖注入和调用方法并接受参数作为接口?

Now, I've also read that it is recommended to use mocking framework to avoid running your tests against a database. Why is that though? Is this acceptable if I run my tests against a test database?

编写单元测试(在您的情况下为 TDD)的全部意义在于,您希望专注于编写最少的代码以通过测试。如果您的 class 依赖于另一个 class 或数据库,则 class 和数据库不存在。因此,您编写了足够的代码来通过测试并模拟其他所有内容。考虑这个要求:"Given a string, take all the x characters from the string and then save the result into a datatabase"。您要做的第一件事是编写测试:

[TestMethod]
public void Extract_WhenCalledWithEmptyString_ShouldReturnEmpty()
{
    var extr = new XExtractor();
    var extracted = extr.Extract(string.Empty);
    Assert.IsTrue(extracted == string.Empty);
}

运行 您的测试和编译将失败,因为 XExtractor 不存在。所以我们需要通过它。我们继续写 class.

public class XExtractor
{
    public string Extract(string value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return string.Empty;
        }
    }
}

运行 测试,它有望通过。然后你的下一个测试。然后你最终编写了一个测试,如果一切正常并且找到字符 x(s),则 XExtractor 必须为特定字符串调用你的数据访问层 class 至少一次。因此,要通过测试,您需要为数据访问层编写最少的代码 class 才能通过测试,这意味着使用一种方法的简单接口。然后你模拟这个接口和 运行 你的测试并在模拟上做断言以确保它被调用一次并且它传递了你希望传递给它的字符串。在整个过程中,您都专注于测试 XExtractor 中的代码并确保它正在调用其他依赖项。在该依赖项中会发生什么,此时您不关心。以下是有帮助的:假设您不会编写数据访问层,但其他人会编写它,例如,我将在加拿大一直编写它。我将确保无论您传递给我什么字符串,我都会调用一些存储过程并将该字符串传递给它。我会做数据库连接等,我会 return 基于你我都同意的一些合同(接口)的结果。其他人将编写存储过程。然而,一旦我们都完成了,就会有人拿走你的 class、我的 class 和存储过程,并将它们相互插入,它应该就能正常工作了。这发生在 Composition Root.

完成测试后 XExtractor,您开始为数据访问层编写代码 class。但现在您需要专注于此 class 并假装您从未写过 XExtractor class。所以你所拥有的只是 class 的 public 接口。所以你现在需要编写数据访问层的所有测试,但是这个 class 需要数据库。没有数据库,所以你写一个接口,然后mock这个接口。

现在您知道您的 class 都可以工作了。您转到您的数据库,您可能需要在其中编写存储过程。您也编写单元测试以通过 sp。然后用真实的实现替换所有模拟,并将所有对象与真实对象连接起来。

Also - is it OK to write tests on the controller level that calls the methods from the business layer or should I keep the coverage of my unit tests focused on one method? But what if the method is calling another method?

当然你需要这样做。这就是模拟的目的。在上面的示例中,当您编写 XExtractor 时,如果它必须调用数据访问层,那么您将模拟数据访问层。然后 运行 对其进行断言以确保交互发生。

Does testable code means I have to do dependency injection

是也不是。是的,因为在你完成所有的模拟之后,你就可以要求 DI 容器将所有真正的依赖注入到彼此中。您甚至可以有一个配置遗嘱:一个用于您的测试依赖项,一个用于实际依赖项。编写一次配置,让容器为您完成所有管道工作。但是,如果您刚刚开始,那么请在没有依赖注入的情况下执行它们。一旦你经历了堵塞和管道的痛苦,然后转向依赖注入容器,因为那时你会看到它的真正价值。

and call methods against an interface and accept parameters as interface?

您会注意到界面将使您的工作变得更加轻松。很多测试框架可以很容易地模拟接口,这非常好。要模拟 classes,你需要有虚拟方法,否则一些模拟框架将无法模拟它们。但是仅仅因为你使用接口并不意味着你的 classes 不能使用继承和抽象。您仍然可以这样做,但只需确保它们实现了所需的接口。


再加几分

几个月后,您可能会收到不同的要求,因为另一个客户想要提取所有 y 个字符,但他们不想为 x 提取器付费(想象一下这非常复杂).在那种情况下,您编写另一个提取器,而我和编写 sp 的其他开发人员不必担心,因为我们只是从您那里获取一个字符串并保存它。因此,您编写 class、运行 测试,然后负责将它们整合在一起的人会将其出售给另一个客户。但是这个客户会得到YExtractor。你明白了...

最后确保您了解 mock 和 stub 之间的区别。在上面的示例中,当测试 XExtractor 时,您需要模拟数据访问层 class。为什么它是一个模拟?因为如果你的class不调用数据访问层class,测试就会失败。如果有什么东西可以让你的测试失败,那就是模拟。否则,它是一个存根并且是 jst 在那里支持你的测试。