[Tutor] Long post: Request comments on starting code and test code on chess rating project.

boB Stepp robertvstepp at gmail.com
Sun Aug 13 19:04:50 EDT 2017


On Sun, Aug 13, 2017 at 3:52 AM, Peter Otten <__peter__ at web.de> wrote:
> boB Stepp wrote:

>> I am only at the very beginning of coding this class.  I have not
>> added any methods yet.  Currently I am adding class constants that
>> will be used in this class' methods.  These constants are for internal
>> class use only and should not be altered from outside the class.
>
> Still, a good unit test might be to initialise a RatingCalcultator with
> different "highest possible ratings" and then verify for each instance that
> actual ratings never exceed the specified value.

I just had not gotten around to doing this yet.

> =================================================================================
>> #!/usr/bin/env python3
>>
>> """Module to process chess ratings."""
>>
>> class RatingCalculator:
>>     """This class contains methods to validate and calculate chess
>>     ratings."""
>>
>>     # Overestimated extremes for possible chess rating values.  Values
>>     # outside this range should not be achievable.
>
> If there are no hard limits, why give a limit at all?

My thinking was to put in a sanity check.  The ratings should fall
into sensible limits.

>>
>>     _LOWEST_POSSIBLE_RATING = 0
>
> Again, this is just a number. Is it really worthwile explicit checking in a
> unit test? The most relevant tests address behaviour.

Shouldn't I check for inadvertent changing of what are constants by
later modifications of the code?

>>     _HIGHEST_POSSIBLE_RATING = 3000
>>
>>     # Fundamental constants for use in the chess rating formula.  The keys
>>     # to these values are tuples giving the rating ranges for which these
>>     # K-factors are valid.
>>
>>     _K_MULTIPLIERS = {
>>             (_LOWEST_POSSIBLE_RATING, 2099): 0.04,
>
> The Python way uses half-open intervals. This has the advantage that there
> is a well-defined k-multiplier for a rating of 2099.5. Note that if you
> apply that modification you no longer need any upper limits. Use bisect (for
> only threee values linear search is OK, too) to find the interval from a
> list like [0, 2100, 2400], then look up the multiplier in a second list
> [0.04, 0.03, 0.02])

Note:  Valid ratings can only be positive integers.  No floats allowed
in the final rating result or in the initial input of an established
rating that is to be changed.

So you are suggesting doing something like:

-----------------------------------------------------------------------------------
py3: from bisect import bisect
py3: def get_k_factors(rating):
...     RATING_BREAKPOINTS = [0, 2100, 2400]
...     K_ADDERS = [16, 12, 8]
...     K_MULTIPLIERS = [0.04, 0.03, 0.02]
...     index = bisect(RATING_BREAKPOINTS, rating) - 1
...     k_adder = K_ADDERS[index]
...     k_multiplier = K_MULTIPLIERS[index]
...     return k_adder, k_multiplier
...
py3: get_k_factors(1900)
(16, 0.04)
py3: get_k_factors(2100)
(12, 0.03)
py3: get_k_factors(2399)
(12, 0.03)
py3: get_k_factors(2400)
(8, 0.02)
-----------------------------------------------------------------------------------

I like it!  It will save me from having to use a bunch of conditionals.


>>             (2100, 2399): 0.03,
>>             (2400, _HIGHEST_POSSIBLE_RATING): 0.02}
>>
>>     _K_ADDERS = {
>>             (_LOWEST_POSSIBLE_RATING, 2099): 16,
>>             (2100, 2399): 12,
>>             (2400, _HIGHEST_POSSIBLE_RATING): 8}
>>
> =================================================================================
>>
>> The test code in test_main.py is:
>>
>>
> =================================================================================
>> #!/usr/bin/env python3
>>
>> """Module to test all functions and methods of main.py."""
>>
>> import unittest
>> import rating_calculator.main as main
>>
>> class TestRatingCalculatorConstants(unittest.TestCase):
>>     # Check that the constants in RatingCalculator have the proper values.
>>
>>     def setUp(self):
>>         # Create instance of RatingCalculator for use in the following
>>         # tests. Create tuple of test-value pairs to interate over in
>>         # subtests.
>>
>>         self.rating_calculator = main.RatingCalculator()
>>         self.test_value_pairs = (
>>                 (self.rating_calculator._LOWEST_POSSIBLE_RATING, 0),
>
> With the _ you are making these constants implementation details. Isn't one
> goal of unit tests to *allow* for changes in the implementation while
> keeping the public interface?

I don't think I am understanding your thoughts.  Would you please
provide an illustrative example?  I am concerned that later program
development or maintenance might inadvertently alter values that are
expected to be constants.

>>                 (self.rating_calculator._HIGHEST_POSSIBLE_RATING, 3000),
>>                 (self.rating_calculator._K_MULTIPLIERS[(
>>                     self.rating_calculator._LOWEST_POSSIBLE_RATING,
>>                     2099)], 0.04),
>>                 (self.rating_calculator._K_MULTIPLIERS[(2100, 2399)],
>>                 0.03), (self.rating_calculator._K_MULTIPLIERS[(2400,
>>                     self.rating_calculator._HIGHEST_POSSIBLE_RATING)],
>>                     0.02),
>>                 (self.rating_calculator._K_ADDERS[(
>>                     self.rating_calculator._LOWEST_POSSIBLE_RATING,
>>                     2099)], 16),
>>                 (self.rating_calculator._K_ADDERS[(2100, 2399)], 12),
>>                 (self.rating_calculator._K_ADDERS[(2400,
>>                     self.rating_calculator._HIGHEST_POSSIBLE_RATING)], 8))
>>
>>
>>     def test_class_constants(self):
>>         # Check that all class constants have the correct values.
>>
>>         for tested_item, expected_value in self.test_value_pairs:
>>             with self.subTest():
>>                 self.assertEqual(tested_item, expected_value)
>>
>>
>> if __name__ == '__main__':
>>     unittest.main()
>>
> =================================================================================
>>
>> Instead of doc strings in the test code, I have used comments based on
>> something I read online.  The point of doing this was so the doc
>> strings would _not_ show up in the test run output.  Is this
>> worthwhile advice to follow?
>
> No. Why would you think it is?

Because some article online (I don't recall the source now.) convinced
me that having doc strings showing up in unittest output contributed
unnecessary clutter for no benefit.

>> I'm looking forwards to everyone's thoughts.
>
> Values are the most volatile and least important part of a program.
> Start coding with the algorithms and tests covering those.

I was just looking for the lowest hanging fruit to get an easy start
on this project, and use it as a check to see if I was going off in
inadvisable directions.


-- 
boB


More information about the Tutor mailing list