Why are mocking test frameworks helpful?

2019-08-03 03:36发布

问题:

It seems like all of the Mockito examples I have looked at, "fake" the behavior of the object they are testing.

If I have an object that has a the method:

public int add(int a, int b) {return a+b}

I would simply use JUnit to assert whether two integers passed in would result in the correct output.

With all of the examples I've seen with Mockito, people are doing things like when.Object.add(2,3).thenReturn(5). What's the point of using this testing framework, if all you're doing is telling the object how to act on the test side, rather than the object side?

回答1:

Mocking frameworks are good for testing a system by mocking that system's dependencies; you wouldn't use a mocking framework to mock or stub add if you are testing add. Let's break this out a bit further:

Testing add

A mocking framework is not good for testing your add method above. There are no dependencies other than the very stable and extremely-well-tested JVM and JRE.

public int add(int a, int b) {return a+b}

However, it might be good for testing your add method if it were to interact with another object like this:

public int add(int a, int b, AdditionLogger additionLogger) {
    int total = a + b;
    additionLogger.log(a, b, total);
    return total;
}

If AdditionLogger isn't written yet, or if it's written to communicate with a real server or other external process, then a mocking framework would absolutely be useful: it would help you come up with a fake implementation of AdditionLogger so you could test your real method's interactions with it.

@Test public void yourTest() {
    assertEquals(5, yourObject.add(2, 3, mockAdditionLogger));
    verify(mockAdditionLogger).log(2, 3, 5);
}

Testing add's consumers

Coincidentally, a mocking framework is also unlikely to be good for testing consumers of your method above. After all, there is nothing particularly dangerous about a call to add, so assuming it exists you can probably call the real one in an external test. 2 + 3 will always equal 5, and there are no side effects from your calculation, so there's very little to be gained by mocking or verifying.

However, let's give your object another method that adds two numbers with a little bit of random noise:

public int addWithNoise(int a, int b) {
    int offset = new Random().nextInt(11) - 5;  // range: [-5, 5]
    int total = a + b + offset;
    return total;
}

With this, it may be very hard for you to write a robust assert-style test against this method; after all, the result is going to be somewhat random! Instead, to make an assert-style test easier, maybe we can stub out addWithNoise to make some of this more predictable.

@Test public void yourTest() {
    when(yourObjectMock.addWithNoise(2, 3)).thenReturn(6);
    // You're not asserting/verifying the action you stub, you're making the dependency
    // *fast and reliable* so you can check the logic of *the real method you're testing*.
    assertEquals(600, systemUnderTestThatConsumesYourObject.doThing(yourObjectMock));
}

In summary

It can be easier to explain mocking and mock syntax when interacting with well-known operations like add or well-known interfaces like List, but those examples are not usually realistic cases where mocks are needed. Remember that mocking is only really useful for simulating the dependencies around your system-under-test when you can't use real ones.



回答2:

Goal of unit-testing is to test the functionality without connecting to any external systems. If you are connecting to any external system, that is considered integration testing.

While performing unit-testing, a system may need some data that could have been retrieved from external systems as database, web/rest services, API etc during Systems/Integration testing. In such scenarios, we need to supply mock/fake data to test some business rules, or any other form of logic.

With above, unit-tests ensure that a particular unit of code works with given set of fake/mocked data, and should behave in similar manner in integrated environment.