11

I have a data source from which I can request a list of people that live in a (any) country, and a method which retrieves the people from that data source and sorts them by their name alphabetically. How should I write my unit test to make sure that the sorting part of my method works properly?

This is what my SUT looks like:

class PeopleStuff {

    public IData data;

    public List<Person> getSortedPeopleForCountry(String countryName) {
        List<Person> people = data.getPeopleForCountry(countryName);

        Comparator nameComparator = new PersonNameComparator();
        Collections.sort(people, nameComparator);

        return people;
    }

}

And this is what my unit test looks like:

@Test public void testGetPeopleSortsByPeopleName() {
    String COUNTRY = "Whatistan";

    // set up test (the 3 lines below are actually in a @Before setup method)
    PeopleStuff peopleStuff = new PeopleStuff();
    IData mockData = createNiceMock(IData.class);
    peopleStuff.data = mockData;

    // set up data
    List<PersonName> mockPeopleList = new ArrayList<PersonName>();
    mockPeopleList.add(new Person(COUNTRY, "A"));
    mockPeopleList.add(new Person(COUNTRY, "D"));
    mockPeopleList.add(new Person(COUNTRY, "B"));
    mockPeopleList.add(new Person(COUNTRY, "C"));

    when(mockData.getPeopleForCountry(COUNTRY)).thenReturn(mockPeopleList);

    // exercise
    List<String> result = peopleStuff.getSortedPeopleForCountry(COUNTRY);

    // assert
    assertEquals("A", result.get(0).name);
    assertEquals("B", result.get(1).name);
    assertEquals("C", result.get(2).name);
    assertEquals("D", result.get(3).name);
}

What I need to know is if the way I am stubbing the data, running the test and making the assertions is correct, or if there are better ways of doing this.

My application has a lot of methods to test and a lot of custom sorting algorithms; I implemented all tests to use some 4 values that I stub like that, in a "random" order which I choose when I write the test.


Should I just test if the comparators are called? That doesn't seem right to me, because I don't know if they're called for the right data or at the right time in the algorithm that's inside getSortedPeopleForCountry(). I want to detect situations like this:

public List<Person> getSortedPeopleForCountry(String countryName) {
    List<Person> people = data.getPeopleForCountry(countryName);

    Comparator nameComparator = new PersonNameComparator();
    List<Person> sortedPeople = new ArrayList<Person>(people)
    Collections.sort(sortedPeople, nameComparator);

    return people; // oops!
}

Should I leave it like this and add mock comparators which use the real comparators but also verify that they're being called?

Am I doing it right?

f.ardelian
  • 6,716
  • 8
  • 36
  • 53
  • I would sort the list and then start comparing its elements in pairs to make sure the *current* element has the same or a *bigger* country than the previous one. – Luiggi Mendoza Jul 16 '13 at 21:34
  • @LuiggiMendoza I already did that, but I changed my mind. That added a dependency on `PersonNameComparator` inside the test. And it took a `for` loop of 3 lines, which means an extra algorithm in the test which a reader would have to understand in order to understand the test. – f.ardelian Jul 16 '13 at 21:40
  • You make it look like 3 lines of code are pretty hard to understand... In fact, since I don't work with easymock, all the above code for me is way harder to understand than a simple `for` loop with an `if`. – Luiggi Mendoza Jul 16 '13 at 21:41
  • @LuiggiMendoza: Then prove a `@Test` method as an answer to the question. – f.ardelian Jul 16 '13 at 21:46
  • Refer to [this Q/A](http://stackoverflow.com/q/9244399/1065197) – Luiggi Mendoza Jul 16 '13 at 21:49

3 Answers3

4

I think your current test is very good - the tests are realistic, exercising all of the code, and you are mocking out the data source & using dependency injection to supply a mock data source. There is a lot of best practice going on in this test.

On the issue of whether you should look to mock the comparators (and therefore make the test on testGetPeopleSortsByPeopleName a pure unit test), you will definitely get two different opinions here:

  • A purist would argue that your test is technically an integration test, and that to have proper unit tests you need to adjust your test to use a mock comparator, and then test the comparator separately.
  • A pragmatist would argue that your test is already high quality, and that it doesn't matter that it isn't a unit test in the strictest sense. Furthermore, to split this into two separate unit tests may make the test less readable - which I imagine would be the case with the test above if you were to involve mock comparators.

My personal opinion is that you should leave it as it is, the fact that you have a high quality, readable test that exercises all the code and effectively asserts your requirements is far more important than worrying about having strictly pure unit tests.

The only way in which the test looks in need of improvement is the length of the test method - I think a little method extraction could help improve readability and make the test method more expressive. I would aim for something like this:

@Test public void testGetPeopleSortsByPeopleName() {

    peopleStuff.data = buildMockDataSource(COUNTRY, "A", "D", "B", "C")

    List<String> result = peopleStuff.getSortedPeopleForCountry(COUNTRY);

    assertPersonList(result, "A", "B", "C", "D")
}

private IData buildMockDataSource(String country, String ... names) {
    ...
}

private void assertPersonList(List<Person> people, String ... names) {
    ...
}
robjohncox
  • 3,639
  • 3
  • 25
  • 51
  • Okay, so I went with this and ended up with a ton of stub generation methods and list assertion methods but that turned out fine because I just put them all in a test helper file. The tests look very neat and easy to maintain. Thanks. – f.ardelian Jul 21 '13 at 10:32
2
ObjectA[] arr = objectAList.toArray(new ObjectA[objectAList.size()]);
for (int i = 0; i < objectAList.size() - 1; i++) {
        int j = i + 1;
        assertTrue(arr[i].getDate().compareTo(arr[j].getDate()) >= 0);
 } 

This code represents an example where ArrayList contaning ObjectA objects is sorted by field date in descending order. We are checking if the member of the list has smaller or equal date from his predecessor.

netod
  • 21
  • 4
0

Separate the sorting logic from returning the list. So I'd have getPeopleForCountry(String countryName) only return a list whereas a sorted list would be returned from getSortedPeopleForCountry(List ). That way you can test how it works before and after sorting. Also, you might want to override the Equals() method to compare names if that is what you want to go with, but then you'd later want to compare with some other property. That's your call.

Mukus
  • 4,870
  • 2
  • 43
  • 56
  • This is very vague and it completely ignores that I wrote that I have custom comparators for the Person objects. Also, this code is just a dumbed-down version of what I have in development. The question is about how to test that a function _looks like_ the example I wrote is sorting the given data. – f.ardelian Jul 16 '13 at 22:02
  • How does it ignore the comparator you wrote? Your comparator would be used inside the getSortedPeopleForCountry(List T)... from my answer. Anyways, that is my take on your code. If you had a test method like testGetPeopleForCountryWhenNotSorting(), you could test the other non sorting method would not test. – Mukus Jul 17 '13 at 11:24