1

This is (again) a question of methodology...

Suppose we are testing a service that returns Articles given ids, i.e. List<Article> getArticles(List<Integer> ids);. In addition, the corresponding location will be null if the id is invalid.

Firstly fill the database by the SQL:

INSERT INTO articles VALUES (2001, "aaa"), (2002, "bbbb"), (2003, "cc"), (2004, "ddddd");

Then test by the following. Which way shall I do it (see details in the comments below)?

void testGetArticle() {
    // NOTE: WHICH WAY?

    // way1: one existing id, one nonexisting id.
    var ids = Arrays.newArrayList(2002, 9999);

    // way2: many existing ids, many nonexisting ids.
    var ids = Arrays.newArrayList(1000, 1001, 2002, 2003, 2004, 9997, 9998, 9999);

    List<Article> a = xx.getArticle(ids);
    assertNull(a.get(0));    assertEquals(a.get(1), ...);  ...etc... // assert each of the results
}

Thanks for any suggestions!

2 Answers 2

3

For every assertion, ask yourself: does this improve confidence in the code? If two of the assertions test the exact same code and the only difference is what the actual values are in the test then it is very unlikely that both of them add value which the other one doesn't.

Some corollaries to that:

  1. This is why red/green/refactor TDD can be so powerful: you're supposed to think of the smallest step forward, and write a test for each such step. If done properly that means every new test forces the implementation to change, and so every test adds value.
  2. Variable names can help to show how two similar values differ in testing. For example:

    void setup() {
        var existingId = idGenerator.next();
        var existingAddress = "foo";
        [insert existingId with existingAddress into DB]
        var nonExistingId = idGenerator.next();
    }
    
    void shouldReturnAddressForExistingId() {
        assertEqual(existingAddress, a.getAddress(existingId))
    }
    
    void shouldReturnNullForNonExistingId() {
        assertEqual(null, a.getAddress(nonExistingId))
    }
    
  3. The "should" pattern above is intentional. I've found it's much better than the default "test" pattern at making you think about what each test is meant to prove (the "why") rather than which piece of code is being tested (the "what").
  4. Additional setup and assertions take time. When writing unit tests that isn't too costly, but writing DB tests like you're doing this could easily balloon into a huge problem, as it often does, where the test suite take hours to run.
  5. Superfluous tests will reduce the confidence of anyone reading the tests later, because they will know that
    1. you did not use TDD and
    2. you were unable to write the fastest test to verify every branch of the code.
4
  • Thanks very much for the insightful reply!
    – ch271828n
    Mar 6, 2020 at 3:11
  • By the way, shall I insert data in which way: (a) repository.save(new MyModel(existingId, existingAddress)). (b) run a sql file with content INSERT INTO mytable VALUES (123, 'hello') (say existingAddress=='hello'). Personally, I think (a) is better since in (b) I have to hardcode 'hello' like a magic number. However, I see people running a startup SQL file (like data.sql in Spring)... Thanks!
    – ch271828n
    Mar 6, 2020 at 3:14
  • 1
    A startup SQL file can be appropriate for a production system, but in a test it's extremely important that the test can be understood by itself, with minimal reference to outside state. It is also super important that the test is easy to change when the requirements change, as they inevitably do, and having the test data in a separate file makes that harder.
    – l0b0
    Mar 6, 2020 at 4:13
  • Sorry for disturbing, I would appreciate it if you could have a look at a "similar" question, thanks! --> softwareengineering.stackexchange.com/questions/406178/…
    – ch271828n
    Mar 7, 2020 at 0:20
0

You must embrace all the possible use cases, including "on bound" cases, that is the representative case.There is no a "representative" case, which misses even one possible use case.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.