简体   繁体   中英

How do I build a multi-layer test object?

I have some classes I want to test, but I find the underlying entities very complex from a testing perspective since entites can have many layers of dependencies.

For example, I have a class called Building , which has a Parking , which has Car s, which have Engine s, and then a function which takes a building as a parameter and checks if there are more diesel cars than petrol. That means that in my tests I need to create a Building , then attach a Parking to it, to which I have to attach a Car and so on.

Is there any standard way of dealing with deeply nested entities?

If you're testing a function that requires a multiple of some type of object, ie a List, to verify the counting, there's a few ways to go about it.

One way is just mocking a bunch of objects to return the value. You should be mocking at each individual level, so at the Building level, you'd be mocking Parking , and at the Parking level, you'd be mocking Car s.

when(car.getEngineType()).thenReturn(EngineType.DIESEL);

However, you can also isolate for a unique case by using seed value creation if you want a less mock-everything approach.

public class CarTest {

    private EngineType DIESEL = EngineType.DIESEL;

    @Test
    public void testCreate() {
        Car car = createWithSeedValue();
        Assert.equals(DIESEL, car.getEngine().getEngineType());
    }

    public Car createWithSeedValue() {
        return create(EngineTest.createWithSeedValue(DIESEL));
    }

    public Car create(Engine engine) {
        Car car = new Car(engine);
        return car;
    }
}

In this case, instead of creating a bunch of mock Car s, we can fill a garage with a bunch of real Car POJOs of our own definition, providing only the seed values we need for the test to work with actual objects. The benefit of this is that you can more easily instantiate Lists with actual POJOs than have to mock individual objects being passed into the list.

The "standard way" of creating unit tests (ie if you only want to test the Building 's functionality and not the whole lot together - that would be integration tests ) for objects with many / deep dependencies is using mocks

Mocks are dummy objects that have limited functionality (just do what's needed for the given test). Usually each method of a mock object knows what inputs to expect and you provide a hand-written mapping between these inputs and what the output should be. You then inject these into your tested object instead of the actual dependencies.

There are many frameworks that will help you create mock objects.

Depends if what you want to test if perviewed by you as one 'unit'. If all those classes mentioned are very strongly coupled, you could consider they are one unit and test them together.

You can also consider calling Builder class one unit, Parking class another unit, etc. In such a case, you could introduce the mockito framework.

You pass a mocked object of Parking into your Building class (which is the class you are testing).

I prefer to keep my 'units' small, usually existing of only 1 class, or sometimes multiple tiny classes. If you keep your units small, often times you are going to need a mocking framework to keep your test focussing on those small units.

You can use mocking frameworks like Mockito. Then you can mock immediate dependencies of your class and ask the dependencies to return results which your class can use.

An example related to your scenario would be something like

class Building {
    Building(Parking parking) {
    //init
    }

    boolean hasMoreDieselCars() {
        return parking.getDieselCarCount() > parking.getPetrolCarCount();
    }
}

With a mock, you can write something like this.

when(parking.getDieselCarCount()).thenReturn(14);
when(parking.getPetrolCarCount()).thenReturn(11);

Similarly, for unit testing Parking , which has a dependency on a Car list, you can mock Car like this.

when(car.getEngine()).thenReturn(new DieselEngine());

Hope this helps.

Factories are a good way to handle this kind of situations (mocking is certainly an overkill here):

class ObjectFactory {

    public static Building building() {
        return new Building(parking());
    }

    public static Parking parking() {
        Parking parking = new Parking();
        parking.addCar(car());
        return parking;
    }

    public static Car car() {
        return new Car(engine());
    }

    public static Engine engine() {
        return new Engine();
    }     
}

Combined with data randomization you can create different (but valid) objects each time (which can increase the coverage). Eg

public static Parking parking() {
    Parking parking = new Parking();
    for(int i = 0; i < integer(1, 10); i++) parking.addCar(car());
    return parking;
}

public static Car car() {
    Car car = new Car(engine());
    car.setModel(alphanumeric(1, 10));
    return car;
}

I sometimes even add these methods to the entity class itself. Yeah, yeah - it's a test-only code in the production code, but it looks really readable and the randomization also describes the business rules which can simplify the understanding of the business logic. Eventually the code will look like: Parking parking = Parking.random() .

If you need different flavours of objects filled there are couple of options:

  • If in test you need a specific value for some field (eg for name) - create a random object and override that only value.
  • If you need a whole different object hierarchy (special for some case) then create multiple methods: random() , randomNoAssociations() , etc.
  • If a very special case is needed and it doesn't make sense to create a general-purpose method for that you can add a private method in your test class (assuming it's only used for tests in that class).

Sometimes you need those persisted in DB - then create a separate factory (PersistedObjectFactory) that uses the original factory to create objects and then it would store them.

PS: in the examples before for randomization I was using Qala Datagen (disclaimer - I'm the author).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM