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

Peter Otten __peter__ at web.de
Tue Aug 15 03:10:39 EDT 2017


boB Stepp wrote:

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

>> 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.

>> 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?

In what scenario would such an inadvertent change occur?

I prefer the indirect check sketched out above. Assuming there is a 
check_rating() method you can make tests like

rc = RatingCalculator(lowest_rating=0, highest_rating=100)
for r in [0, 50, 100]:
    rc.check_rating(r)
for r in [-1, 101]:
    with self.assertRaises(FailedSanityCheck):
        rc.check_rating(101)

that ensure that a method works as expected rather than verifying a certain 
constant value. (You have to do the above for at least two ranges to make 
sure that the arguments are used for the check.)


> 
>>>     _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.

Then you need a test for that ;)
 
> So you are suggesting doing something like:

> py3: from bisect import bisect
> py3: def get_k_factors(rating):

Yes, but I would pass the lists as arguments to allow for easier testing.

> ...     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
> ...

>> 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?  

When you are sending a parcel to a friend you don't care about the path it 
takes or that the delivery service obeys the speed limit. You only demand 
that it arrives at the specified place within the specified time interval.
That way the service is free to choose the best (quickest, shortest, or 
cheapest) route and the best (fastest, cheapest) means of transportation.

A class is a service provider, too. If you test private methods and states 
you are either stuck with one implementation or have to change unit tests 
and implementation in lockstep -- and these unit test modifications may 
introduce bugs, too.

If instead you manage to cover the code with tests that only use the public 
interface you can replace one implementation of the tested class with a 
completely different one -- and when your unit tests continue to succeed you 
are pretty sure that the replacement is correct, too. 

Thus the hurdle to trying out improvements ("refactoring") is lower and you 
are more likely to end up with clean and efficient code.

> I am concerned that later program
> development or maintenance might inadvertently alter values that are
> expected to be constants.

Unlike 0 the value of 3000 is in no way special. Your script will be more 
robust if it continues to work with an upper bound of 2900 or 4000 -- or 
even floats in the range 0 <= rating < 1.

>> 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.

I think that is mostly a distraction. 

Unfortunately there are many ways to burn time dealing with the irrelevant 
aspects of a program. I know some of them -- from hearsay, of course ;)



More information about the Tutor mailing list