[ISBN Verifier] Error in tests that check if the `X` character is only the verification digit

Hi,
During a review, the person ask me if his code check if the X character is only acceptable at the end of the ISBN. So I look at the test and I see this test:

    def test_x_is_only_valid_as_a_check_digit(self):
        self.assertIs(is_valid("3-598-2X507-9"), False)

I think this is the test that test it. But here is the problem I have.
The sum % 11 is not equal to 0.

Here is his solution:

def is_valid(isbn):
    
    isbn = isbn.replace('-', '')
    
    if len(isbn) != 10:
        return False
        
    sum = 0
    for index, digit in enumerate(list(isbn)):
        if digit.isdecimal():
            sum += int(digit) * (10 - index)
        elif digit == 'X':
            sum += 10
        else:
            return False
            
    return sum % 11 == 0 

Here the operation his code is doing:

3 * 10 + 5 * 9 + 9 * 8 + 8 * 7 + 2 * 6 + 10 + 5 * 4 + 0 * 3 + 7 * 2 + 9 * 1 = 268
268 % 11 = 4

As you can see the 10 (replacing the X value) is not multiplied (this is how he write his solution). Because of this his test pass but not for the good reason. It pass because the sum % 11 != 0 and not because X is valid only as a check character.

I think we should add the test case where the ISBN is "3-598-2X507-5". This is a case where the sum is 264 (and 264 % 11 == 0). Doing so will handle that sum % 11 == 0 and the X character is not at the end.

1 Like

See also ISBN tests doesn't catch incorrect handling of X in some cases - #12 by MatthijsBlom

I can see the value in adding a test case like that. If two other maintainers agree, you could then PR it.

1 Like

I agree, and I think there should be 3 test cases to handle ‘X’:

  • one that doesn’t pass if ‘X’ is substituted for 10 and multiplied (I think the current one satisfies this case).
  • one that doesn’t pass if ‘X’ is substituted for 10 and not multiplied, which is what the OP is proposing.
  • one that doesn’t pass if ‘X’ is ignored altogether.

It is possible that either the current test or the proposed test satisfies this third case, didn’t check.

Agreement.

Would you like to make a PR to add this test to the problem specs repo?

That should be covered by “too short” tests.

1 Like

Given that this was raised more than once, we have a reproducible wrong implementation that passes the tests and a test suite that indicates it should not pass (but accidentally does), I am in favour of adding a or multiple tests to fix this issue.

Looks like several maintainers approve so feel free to PR the proposed test, @Keftcha.

1 Like

Hi, I have created the PR, it’s available here.

I commented this in the PR, but to get more eyeballs on it here, do we need to keep the current test if it doesn’t catch all edge cases and its superseded by the OP’s suggestion?

Only one of those cases causes issues and was approved by three maintainers. I’m not convinced the other two are needed.

1 Like

If a new test makes a prior test unhelpful, we can definitely deprecate it.

The proposed test fails to check when X is substituted for 10 and multiplied (by 5, according to the position where it is located in the string). The sum would be:

3 * 10 + 5 * 9 + 9 * 8 + 8 * 7 + 2 * 6 + 10 * 5 + 5 * 4 + 0 * 3 + 7 * 2 + 5 * 1 = 304
304 % 11 = 7

Which would make the result false for the wrong reason, exactly as the current test does.

I think we should either have 2 tests, one that fails when X is substituted for 10 and not multiplied, and another that fails when it is substituted for 10 and multiplied; or we should find a convenient arrangement of digits that fails in both cases.

EDIT: This means I’m in favor of adding the proposed test and also of keeping the current one (which asserts the case where X is substituted for 10 and multiplied). I don’t think one deprecates the other, they are complementary.

2 Likes

Yes I agree with this assessment.

We want to catch other potential issues with solutions not shown here so covering both paths makes sense!

2 Likes

In the PR I have implemented the three cases @oxe-b present in this message.
It seems to me that the three cases are needed.

Here is the operations for the three cases:

3-598-2X507-9
3*10 + 5*9 + 9*8 + 8*7 + 2*6 + 10*5 + 5*4 + 0*3 + 7*2 + 9*1 = 308
308 % 11 == 0

3-598-2X507-5
3*10 + 5*9 + 9*8 + 8*7 + 2*6 + 10 + 5*4 + 0*3 + 7*2 + 5*1 = 264
264 % 11 == 0

3-598-2X517-1
3*10 + 5*9 + 9*8 + 8*7 + 2*6 + 5*4 + 1*3 + 7*2 + 1*1 = 253
253 % 11 == 0

All theses cases, depending on how the X value is handled, we got a sum that can be divided by 11.

2 Likes

The purpose of the tests isn’t to be exhaustive. The purpose of the tests is to guide students towards a correct implementation. We add tests when it’s reasonable to assume that students may land on an incorrect implementation without a test to help highlight a specific bug.

Do people think this test case is needed? What implementation would lead to this? Is it likely that a student would unintentionally have a bug in their code that would lead to this result? Are we solving for a potential bug with this test or trying to write comprehensive tests?

If the student filters out X, the tests that depend on a valid X being substituted for 10 would fail.

And there are tests for the number of digits, like @glennj said above. So, if the student chooses to substitute only a X in the last position and ignore the others, those tests would fail.

So, I don’t think this third test is needed after all, just the other two (the current one and the proposed one).

Students can write code which can have all sort of bugs which would require exhaustive testing to capture each and every case.

If we wanted tests which would catch every solution students could write, we’d be writing exhaustive tests. This is explicitly not the goal of the tests.

The question isn’t if it is possible for a student to write buggy code that would be caught by the test but if it is probable. The question is about how likely that is to occur and how reasonable said code would look.

Yes, that’s what I said. The third test isn’t needed because the “natural” incorrections it catches are mostly covered by other tests. Only the other two (one of them being the current test already present) add something substantial.

So, I think adding the proposed test without deprecating the current one is probably enough.

2 Likes

I’ve seen the issue described here multiple times over multiple tracks but all in mentoring. Most people don’t opt for mentoring.

I don’t feel this is such an edge case either. The mistake makes sense and is easy to make ánd is fundamentally incorrect.

Agree with you on the overal message of your post.

3 Likes