mercredi 19 juin 2019

What are best practices for unit testing multiple functions that have very similar test scenarios?

Let's say I have two pure functions, pureFn1 and pureFn2. These functions must pass similar tests.

describe("generic function", () => {
  it("should fulfill condition 1", testCondition1);

  it("should fulfill condition 2", testCondition2);

  it("should fulfill condition 3", testCondition3);

  it("should fulfill condition 4", testCondition4);
});

How would the similar tests be written for these functions? Should the test suite be reusable?

For example, if we reused the test suite, it will end up looking like this:

const reusableTestSuite = (codeUnderTest) => {
  describe(`${codeUnderTest.name}`, () => {
    it("should fulfill condition 1", testCondition1(codeUnderTest));

    it("should fulfill condition 2", testCondition2(codeUnderTest));

    it("should fulfill condition 3", testCondition3(codeUnderTest));

    it("should fulfill condition 4", testCondition4(codeUnderTest));
  });
};

reusableTestSuite(pureFn1);
reusableTestSuite(pureFn2);

This is pretty DRY, but the problem is there is an abstraction which might make the tests harder to read and modify, and thus possibly discourage devs from writing more tests in the name of speed and ease. This is discussed in point 8 of this article. When these abstracted tests break, the next developer might hate to read it.

The other option is to copy-paste the tests:

describe("pureFn1", () => {
  it("should fulfill condition 1", testCondition1Fn1);

  it("should fulfill condition 2", testCondition2Fn1);

  it("should fulfill condition 3", testCondition3Fn1);

  it("should fulfill condition 4", testCondition4Fn1);
});

describe("pureFn2", () => {
  it("should fulfill condition 1", testCondition1Fn2);

  it("should fulfill condition 2", testCondition2Fn2);

  it("should fulfill condition 3", testCondition3Fn2);

  it("should fulfill condition 4", testCondition4Fn2);
});

This is full-on code duplication. There is no abstraction. If we had two functions with 20 common properties between them, we would have to copy and paste 20 tests and remember to update all the similar tests when one changes, forever.

So between these approaches, which is the better method? Or is there a third method, a best practice in writing unit tests in a way that we get the benefits of abstraction while not making it hard for the next developer to immediately learn what went wrong, and thus not discourage them from writing more of these tests?

Aucun commentaire:

Enregistrer un commentaire