You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 9373a75
Browse filesBrowse the repository at this point in the historyBrowse files
@@ -1218,6 +1218,8 @@ integer values for exactly that set of Roman numerals that
1218
1218
your requirements, this possibility may bother you; if so, write more
1219
1219
comprehensive test cases until it doesn't bother you.)
1220
1220
1221
+
.. note:: Comprehensive test coverage is a bit of a fantasy. YOu can make sure that every line of code you write is run at least once during the testing. But you can't make sure that every function is called with *every* possible type and value! So what we can do is anticipate what we think might break our code, and test for that. Some things *will* slip through the cracks -- but here's a hint: when a bug is discovered, the first thing you should do is write a test that exercises that bug -- a test that will fail due to the bug. Then fix it. Since all your other test still pass (they do, don't they?) -- you know the fix hasn't broken anything else. ANd since you have a test for it -- you know you won't accidentally reintroduce that bug.
1222
+
1221
1223
1222
1224
More Bad Input
1223
1225
--------------
@@ -1232,10 +1234,10 @@ regular expressions. (If you’re not familiar with regular expressions,
1232
1234
now would be a good time to read `the regular expressions
1233
1235
chapter <regular-expressions.html>`__.)
1234
1236
1235
-
As you saw in `Case Study: Roman
1236
-
Numerals <regular-expressions.html#romannumerals>`__, there are several
1237
-
simple rules for constructing a Roman numeral, using the letters ``M``,
1238
-
``D``, ``C``, ``L``, ``X``, ``V``, and ``I``. Let's review the rules:
1237
+
As we saw earlier, there are several simple rules for constructing a Roman numeral, using the letters ``M``,
1238
+
``D``, ``C``, ``L``, ``X``, ``V``, and ``I``.
1239
+
1240
+
Let's review the rules:
1239
1241
1240
1242
- Sometimes characters are additive. ``I`` is ``1``, ``II`` is ``2``,
1241
1243
and ``III`` is ``3``. ``VI`` is ``6`` (literally, “\ ``5`` and
@@ -1265,91 +1267,226 @@ simple rules for constructing a Roman numeral, using the letters ``M``,
1265
1267
it as ``XCIX``, “\ ``10`` less than ``100``, then ``1`` less than
1266
1268
``10``\ ”).
1267
1269
1268
-
Thus, one useful test would be to ensure that the ``from_roman()``
1270
+
Roman numerals can only use certain characters, so we should test to make sure there aren't any other characters in the input:
1271
+
1272
+
.. code-block:: python
1273
+
1274
+
deftest_invalid_character():
1275
+
"""
1276
+
Roman numerals can only use these characters:
1277
+
1278
+
M, D, C, L, X, V, I
1279
+
1280
+
This tests that other characters will cause a failure
1281
+
"""
1282
+
for s in ['Z', 'XXIIIQ', 'QXXIII', 'XXYIII']:
1283
+
with pytest.raises(ValueError):
1284
+
from_roman(s)
1285
+
1286
+
Another useful test would be to ensure that the ``from_roman()``
1269
1287
function should fail when you pass it a string with too many repeated
1270
1288
numerals. How many is “too many” depends on the numeral.
1271
1289
1272
1290
.. code-block:: python
1273
1291
1274
-
classFromRomanBadInput(unittest.TestCase):
1275
-
deftest_too_many_repeated_numerals(self):
1276
-
'''from_roman should fail with too many repeated numerals'''
1277
-
for s in ('MMMM', 'DD', 'CCCC', 'LL', 'XXXX', 'VV', 'IIII'):
'''from_roman should fail with malformed antecedents'''
1381
+
for s in ('IIMXCC', 'VX', 'DCM', 'CMM', 'IXIV',
1382
+
'MCMC', 'XCX', 'IVI', 'LM', 'LD', 'LC'):
1383
+
with pytest.raises(ValueError):
1384
+
> from_roman(s)
1385
+
E Failed: DID NOT RAISE <class 'ValueError'>
1386
+
1387
+
roman11.py:213: Failed
1388
+
========================== short test summary info ==========================
1389
+
FAILED roman11.py::test_invalid_character - Failed: DID NOT RAISE <class '...
1390
+
FAILED roman11.py::test_too_many_repeated_numerals - Failed: DID NOT RAISE...
1391
+
FAILED roman11.py::test_repeated_pairs - Failed: DID NOT RAISE <class 'Val...
1392
+
FAILED roman11.py::test_malformed_antecedents - Failed: DID NOT RAISE <cla...
1393
+
======================== 4 failed, 8 passed in 0.13s ========================
1394
+
1395
+
1396
+
Good deal -- yes, we *wanted* four tests to fail.
1397
+
1398
+
Now, "all" we need to do is write the code to check if the Roman numeral satisfies all the requirements.
1399
+
1400
+
So let's do that one requirement at a time:
1401
+
1402
+
.. note: This is actually a great use for "regular expressions". That is a topic all to itself, so we won't do that here. But if you are curious, you can read up on how to use regular expressions in Python to parse Roman Numerals in `Dive into Python 3 <https://diveintopython3.problemsolving.io/regular-expressions.html#romannumerals>`_
1403
+
1404
+
**Requirement:** you can only use the letters ``M``,
1405
+
``D``, ``C``, ``L``, ``X``, ``V``, and ``I``.
1406
+
1407
+
So let's try that:
1408
+
1409
+
.. code-block:: Python
1410
+
1411
+
defis_valid_roman_numeral(s):
1412
+
"""
1413
+
check if the input is a valid roman numeral
1414
+
1415
+
returns True if it is, False other wise
1416
+
"""
1417
+
1418
+
# does it use only valid characters?
1419
+
for c in s:
1420
+
if c notin"MDCLXVI":
1421
+
returnFalse
1422
+
1423
+
returnTrue
1424
+
1425
+
1426
+
This is the start of a function to test if a string is a valid Roman numeral. So far, it loops through all the characters in the string, and makes sure they are in the VALID_CHARS string. If not, then it returns False.
1427
+
1428
+
It is called in the ``from_roman`` function:
1429
+
1430
+
.. code-block:: Python
1431
+
1432
+
deffrom_roman(s):
1433
+
"""convert Roman numeral to integer"""
1434
+
ifnot is_valid_roman_numeral(s):
1435
+
raiseValueError(f"{s} is not a valid Roman numeral")
1436
+
...
1437
+
1438
+
1439
+
Now that we have that, let's run the tests again:
1440
+
1441
+
.. code-block:: ipython
1442
+
1443
+
In [63]: ! pytest roman12.py
1444
+
============================ test session starts ============================
1445
+
1446
+
...
1447
+
1448
+
========================== short test summary info ==========================
1449
+
FAILED roman12.py::test_too_many_repeated_numerals - Failed: DID NOT RAISE...
1450
+
FAILED roman12.py::test_repeated_pairs - Failed: DID NOT RAISE <class 'Val...
1451
+
FAILED roman12.py::test_malformed_antecedents - Failed: DID NOT RAISE <cla...
1452
+
======================== 3 failed, 9 passed in 0.14s ========================
1453
+
1454
+
Only three failures -- progess!
1455
+
1456
+
On to the next requirement: each character can only be repeated a certain number of times, so lets check to see if any appear to many times, but adding this to the ``is_valid_roman_numeral()`` function:
1457
+
1458
+
.. code-block:: python
1459
+
1460
+
# are any chars repeated too much?
1461
+
for c in ('MMMM', 'DD', 'CCCC', 'LL', 'XXXX', 'VV', 'IIII'):
1462
+
if c in s:
1463
+
returnFalse
1464
+
1465
+
And run the tests again:
1466
+
1467
+
.. code-block:: ipython
1468
+
1469
+
In [64]: ! pytest roman13.py
1470
+
============================ test session starts ============================
1471
+
1472
+
...
1473
+
1474
+
========================== short test summary info ==========================
1475
+
FAILED roman13.py::test_repeated_pairs - Failed: DID NOT RAISE <class 'Val...
1476
+
FAILED roman13.py::test_malformed_antecedents - Failed: DID NOT RAISE <cla...
1477
+
======================= 2 failed, 10 passed in 0.14s ========================
1478
+
1479
+
Two down, two to go!
1480
+
1481
+
Requirement: Sometimes characters are the opposite of additive.
1482
+
By putting certain characters before others, you subtract from the final value. But that pattern can't be repeated, so let's check for that:
0 commit comments