使用已经经过测试的方法进行单元测试方法。

huangapple go评论57阅读模式
英文:

Unit testing method that uses already tested method

问题

假设我们有一个名为Handler的类和一个名为Validator的类。Handler使用Validator来验证传入的请求。

Validator类已经进行了单元测试,以确保返回适当的错误等。

稍后,我们想为Handler创建单元测试。Validator已经有了单元测试,那么Handler的测试应该如何编写?

我们是否应该编写与Validator相同的测试,以验证Handler是否返回从Validator类获取的适当错误?这对我来说没有意义。

那么,在这种情况下,Handler类的单元测试应该如何编写?

英文:

Assume we have class Handler and class Validator. The Handler uses Validator to validate incoming requests.

The Validator class is unit tested, whether returns appropriate error etc.

Later, we want to create unit tests for Handler. The Validator has already unit tests, so how would the test for Handler look?

Will we write the same tests as for Validator whether Handler returns the appropriate error (retrieved from Validator class)? It doesn't make sense to me.

So how would the unit test for Handler class look in this case?

答案1

得分: 3

Mureinik提出的嘲笑解决方案确实是标准解决方案。这是地球上大多数人做的事情,也是大多数人认为正常的事情。在我看来,这也是非常误导人的,我不是唯一一个这样认为的人:

  • 在视频 Thoughtworks - TW Hangouts: Is TDD dead? (youtube) 中,21分10秒处,Kent Beck (Wikipedia) 说:“我的个人做法是几乎不使用模拟。”
  • 在同一视频中,23分56秒处,Martin Fowler (Wikipedia) 补充道:“我和Kent一样,几乎不使用模拟。”
  • 在他的书 xUnit Test Patterns: Refactoring Test Code (xunitpatterns.com) 的“Fragile Test”部分,作者Gerard Meszaros 表示“广泛使用模拟对象会导致测试过于耦合。”
  • 在他的演讲 TDD, where did it all go wrong? (InfoQ, YouTube) 中,49分32秒处,Ian Cooper 表示:“我强烈反对使用模拟,因为它们过于具体。”

如果您想了解更多关于为什么模拟是一个不好的主意的信息,请阅读我的博客文章:michael.gr - On Mock Objects and Mocking

处理这个问题的更好方法是我称之为增量集成测试的方法。这意味着永远不要模拟任何东西,始终在测试中集成实际的依赖关系(或者它们的伪装,但永远不要模拟),只需安排测试的执行顺序,以便首先测试最依赖的类,然后测试依赖于它们的类。这样,Handler 的测试可以使用 Validator 并假定它有效,因为 Validator 的测试已经运行并通过了。

不幸的是,测试框架几乎没有提供按特定顺序执行测试的支持。我编写了一个工具,用于为基于Maven的Java项目处理此问题,但您可能不使用Java,Maven,或者不愿意使用某个某人构建的奇怪工具。幸运的是,还有一种手动解决方法:测试框架通常按字母顺序执行测试,因此您仍然可以通过以使它们的字母顺序与应该执行的顺序一致的方式来命名它们来强制执行测试的执行顺序。例如,您可以将测试命名为 T01_ValidatorTestT02_HandlerTest 等,以便 Validator 的测试始终在 Handler 的测试之前运行。您可能还需要类似地命名您的包和/或命名空间。

有关增量集成测试的更多信息,请参阅我的博客:michael.gr - Incremental Integration Testing

英文:

The mocking solution that Mureinik presented in his answer is indeed the textbook solution. It is what most people on the planet do, and what most people consider normal. In my opinion, it is also deeply misguided, and I am not the only one who thinks this way:

  • In the video Thoughtworks - TW Hangouts: Is TDD dead? (youtube) at 21':10'' Kent Beck (Wikipedia) says "My personal practice is I mock almost nothing."
  • In the same video, at 23':56'' Martin Fowler (Wikipedia) adds "I'm with Kent, I hardly ever use mocks."
  • In the Fragile Test section of his book xUnit Test Patterns: Refactoring Test Code (xunitpatterns.com) author Gerard Meszaros states "extensive use of Mock Objects causes overcoupled tests."
  • In his presentation TDD, where did it all go wrong? (InfoQ, YouTube) at 49':32'' Ian Cooper says "I argue quite heavily against mocks because they are overspecified."

If you would like to read more about why mocks are a bad idea, see my blog post: michael.gr - On Mock Objects and Mocking

A better way to handle this is a method that I call Incremental Integration Testing. This means never mock anything, always integrate the actual dependencies in your tests, (or fakes thereof, but never mocks,) and simply arrange the order in which your tests are executed so that the most dependent-upon classes are tested first, and classes that depend on them are tested afterwards. This way, the test for the Handler can make use of the Validator and take it for granted that it works, because the test for the Validator has already run, and it has passed.

Unfortunately, testing frameworks offer very little, if any, support for executing tests in a particular order. I have written a tool that will take care of this for maven-based java projects, but you might not be using java, or maven, or you might not be willing to use some weird tool that some guy built. Luckily, there is a manual workaround: Testing frameworks tend to execute tests in alphabetic order, so you can still enforce the order of execution of your tests by naming them in such a way that their alphabetic order coincides with the order in which they should be executed. For example, you can name your tests T01_ValidatorTest, T02_HandlerTest, etc. so that the test for Validator always runs before the test for Handler. You might also have to similarly name your packages and/or namespaces.

For more information about Incremental Integration Testing see my blog: michael.gr - Incremental Integration Testing

答案2

得分: 2

你不应该在Handler单元测试中重新测试Validator的逻辑。

单元测试的概念是隔离要测试的最小单元的逻辑(通常是一个类)。

解决这个问题的经典方法是模拟Validator,然后测试Handler是否正确地响应Validator可能具有的不同行为。

你没有标记这个问题所涉及的编程语言,但是任何半可用的语言应该都有一些库允许模拟。例如,这是我在[tag:java]中使用[tag:mockito]和[tag:JUnit]来实现的方式:

// 省略了导入部分
@ExtendWith(MockitoExtension.class)
public class HandlerTest {
    // 创建一个模拟的Validator
    @Mock
    private Validator validator;

    private Handler handler;

    // 使用上述Validator设置要测试的handler
    @BeforeEach
    public void setUp() {
        handler = new Handler(validator);
    }

    @Test
    void handlePassedValidation() {
        // 使用“通过”的验证来模拟Validator
        when(validator.isValid()).thenReturn(true);
      
        handler.handle();

        // 断言当验证通过时handler是否按预期工作
    }

    @Test
    void handleFailedValidation() {
        // 使用“失败”的验证来模拟Validator
        when(validator.isValid()).thenReturn(false);
      
        handler.handle();

        // 断言当验证失败时handler是否按预期工作
    }
}
英文:

You shouldn't re-test the Validator's logic in the Handler's unit test.

The idea of unit (as opposed to component, integration, or end-to-end tests) is to isolate the logic of the minimal unit (usually a class) you're testing.

The textbook solution for this would be to mock the Validator and then test that the Handler properly reacts to different behaviors the Validator may have.

You didn't tag the question with any programming language, but any half-decent language should have some library to allow mocking. E.g., here's how I'd do it in [tag:java] using [tag:mockito] and [tag:JUnit]:

// Imports snipped for brevity's sake
@ExtendWith(MockitoExtension.class)
public class HandlerTest {
    // Create a mock Validator
    @Mock
    private Validator validator;

    private Handler handler;

    // Set up the handler being tested with said validator
    @BeforeEach
    public void setUp() {
        handler = new Handler(validator);
    }

    @Test
    void handlePassedValidation() {
        // Mock the validator with a "passing" validation
        when(validator.isValid()).thenReturn(true);
      
        handler.handle();

        // Assert the handler did what it should when the validation passed
    }

    @Test
    void handleFailedValidation() {
        // Mock the validator with a "failing" validation
        when(validator.isValid()).thenReturn(false);
      
        handler.handle();

        // Assert the handler did what it should when the validation failed
    }
}

</details>



huangapple
  • 本文由 发表于 2023年5月14日 18:36:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/76247001.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定