Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add property based tests on diff-sequences #9072

Merged
merged 8 commits into from
Oct 29, 2019

Conversation

dubzzz
Copy link
Contributor

@dubzzz dubzzz commented Oct 21, 2019

Summary

When I added the first property based tests in Jest codebase - see #8012 - @pedrottimark asked me:

Your thoughts also welcome about tests for diff-sequences package in Jest repo.

This PR adds property based tests to check diff-sequences.

I think we should find better namings for some of the tests I added 🤔

Test plan

PR only adds tests.

const allData = flatten(data); // [...data[0], ...data[1], ...data[2], ...data[3], ...]
const partialData = flatten(data.filter((_, i) => i % 2 === 1)) // [...data[1], ...data[3], ...]
const commonItems = findCommonItems(allData, partialData)
expect(commonItems.length).toBeGreaterThanOrEqual(partialData.length)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test also passes with toEqual which I think makes it similar to the preceding test. What do you think?

Exploring what it would look like to use the preceding test alone to assert the property:

it('should find subsequence', () => {
  fc.assert(fc.property(
    fc.array(fc.char()), fc.array(fc.char()),
    (a, b) => {
      const commonItems = findCommonItems(a, b)

      expect(findCommonItems(a, commonItems)).toEqual(commonItems)
      expect(findCommonItems(commonItems, a)).toEqual(commonItems)
      expect(findCommonItems(b, commonItems)).toEqual(commonItems)
      expect(findCommonItems(commonItems, b)).toEqual(commonItems)
    }
  ))
});

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I am right your suggestion for should find subsequence is close to should be no-op when passing common items: it adds the b-based cases.

💭 Let's suppose for a minute that we remove the test called should find at the least the maximal known subset...

In that case if we use findCommonItems = (a, b) => [] all the properties will pass. None of them are checking that there are at least some items - because they actually cannot do so given the few assumptions they have on the inputs.

Actually this last test is the only test covering the we expect at least N entries in the list of common items

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update: should be reflexive would be error in the scenario described above

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think if the assertion becomes: expect(commonItems).toEqual(partialData)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're totally right as we have both:

  • expect(commonItems.length).toBeGreaterThanOrEqual(partialData.length)
  • expect(commonItems.length).toBeLessThanOrEqual(partialData.length) // rule we use for 'should have at most the length of its inputs'

We have: expect(commonItems).toEqual(partialData)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes this it much more useful as it produces sets of entries for which we know what are the exact outputs.

You can ignore my comment:

It might even replace my hacky test: should find at the least the maximal known subset

#9072 (comment)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, in the last test, let‘s assert the items themselves instead of the length.

If you agree in the second to last test, let‘s include assertions for both a and b and replace “no-op” in name with something more descriptive

import diff from '../';
import fc from 'fast-check'

const findCommonItems = (a: string[], b: string[]): string[] => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lint-and-typecheck failed on CI with Array type using 'string[]' is forbidden. Use 'Array<string>' instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you. It looks like a few more lint errors. Does yarn lint --fix locally display them?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought I did, I'll retry

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pedrottimark This time I did it for sure ^^ I might not be at head. Maybe I could just merge onto master on branch to get the latest changes

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohhh, now it’s about the Facebook comment at the beginning of the file. I always forget it.

You can copy and paste from index.test.ts file

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it: Facebook copyright header check failed for the following files

@pedrottimark
Copy link
Contributor

pedrottimark commented Oct 22, 2019

Nicolas, thank you for this practical tutorial on property-based testing!

Beyond the scope of this pull request, is there a generic name for a test that compares the package to a short but slow implementation of diff for random inputs?

@dubzzz
Copy link
Contributor Author

dubzzz commented Oct 22, 2019

Beyond the scope of this pull request, is there a generic name for a test that compares the package to a short but slow implementation of diff for random inputs?

Such test would be pretty useful - testing the optimal implementation against a dummy one that just does the job. It might even replace my hacky test: should find at the least the maximal known subset.

I have no generic naming for it. But we can name it something like:
should produce the same output as a non optimal reference *reference or implementation

@pedrottimark
Copy link
Contributor

pedrottimark commented Oct 22, 2019

Super, I will find my quadratic implementation in JavaScript and convert to TypeScript

Do you recommend (in future pull request) that I add it to this test file or add a separate test file?

@dubzzz
Copy link
Contributor Author

dubzzz commented Oct 22, 2019

Do you recommend that I add it to this test file or separate test file in future pull request?

As you like ;) But really good idea to add this one ;)

If we want to go further we can even go further by adding yet another test:

  • generate a and b fully random arrays of chars
  • compute common items
  • count the number of each char in common, same for a (and b)
  • assert that for any character in common the number of times this character appears in common is less or equal to the number of times it appears in a

Can be seen as a variant of should have at most the length of its inputs

@dubzzz
Copy link
Contributor Author

dubzzz commented Oct 22, 2019

I added the test I was talking about in my previous comment here: 5c2e725

@pedrottimark
Copy link
Contributor

After some time in my mental slow cooker, I remembered some pieces that might interest you:

  • index.test.ts has an alternative algorithm as countDifferences because, as you commented in the symmetric test, you can test for the number but not the items themselves
  • for randomized sequences, you can test that common items are subsequence of a and b
const isSubsequenceOf = (
  subsequence: Array<string>,
  sequence: Array<string>,
): boolean => {
  let iSub = 0;
  let iSeq = 0;

  while (iSub !== subsequence.length && iSeq !== sequence.length) {
    if (subsequence[iSub] === sequence[iSeq]) {
      iSub += 1;
    }
    iSeq += 1;
  }

  return iSub === subsequence.length;
};

Would you like to add a test of random sequences which asserts

  • common items have expected length computed by alternative algorithm
  • common items are indeed a subsequence of both sequences

Because the last test constructs a subsequence of a sequence, it can replace assertion about number of items with assertion about the items themselves:

expect(commonItems).toEqual(partialData);

@codecov-io
Copy link

Codecov Report

Merging #9072 into master will decrease coverage by 0.02%.
The diff coverage is n/a.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #9072      +/-   ##
==========================================
- Coverage   64.07%   64.05%   -0.03%     
==========================================
  Files         277      277              
  Lines       11681    11676       -5     
  Branches     2863     2862       -1     
==========================================
- Hits         7485     7479       -6     
  Misses       3572     3572              
- Partials      624      625       +1
Impacted Files Coverage Δ
packages/jest-reporters/src/get_result_header.ts 33.33% <0%> (-14.29%) ⬇️
packages/expect/src/utils.ts 94.87% <0%> (-0.07%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update bd48ccc...03d6b64. Read the comment docs.

Copy link
Contributor

@pedrottimark pedrottimark left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks again! This looks ready to merge if you are happy with it.

@dubzzz
Copy link
Contributor Author

dubzzz commented Oct 29, 2019

I'm happy with it, we can go ahead

@pedrottimark pedrottimark merged commit bf61095 into jestjs:master Oct 29, 2019
@dubzzz dubzzz deleted the pbt-diff-sequences branch October 30, 2019 22:43
@github-actions
Copy link

This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
Please note this issue tracker is not a help forum. We recommend using StackOverflow or our discord channel for questions.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 11, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants