Unittest in according to TDD on Palindrome

99 Views Asked by At

I'm student at QA and now I try to write a palindrome code in Python based on TDD and unittest.

I tried to write the code and it is functional but it doesn't work in according to assertTrue, also I checked assertEqual.

Python Code:

def PalindromTest(string):
    return string == string[::-1]

while True:
    stringul = input ("Enter a string or number:\n")
    string = stringul.replace(" ", "").lower()
    Test = PalindromTest(string)

    if Test == True:
        print (f'"{stringul} value is a palindrom"')
    else:
        print (f'"{stringul} value is not a palindrom"')

Test:

import unittest;
from Asignment_TDD import PalindromTest;

class MyPalindrome (unittest.TestCase):
    def test_palindrome (self):
        self.assertTrue (PalindromTest("civic"), "civic")

if __name__=='__main__':
    unittest.main()

Help me please to write correct version of Unittest

2

There are 2 best solutions below

0
Robert On

Well, that code was clearly not written in TDD style :-)

A unit test needs to be automated, so neither the test nor the code under test should ask for input. (If the code under tests is poorly behaved and you cannot prevent it from asking, try to automatically feed it input. But this is probably too advanced yet.)

With that, I'd remove the while True: loop asking for input, or move it elsewhere, or guard it with an if __name__=='__main__': check, like you have in the test case. That way it doesn't run just because some other program imports the code under test. You should probably always have that if __name__=='__main__': unless you're writing a script that's not going to be reused elsewhere. Once you do that, your test runs and passes. I'd now add another test for a text that's not a palindrome and assert that the code correctly returns False.

def PalindromTest(string):
    return string == string[::-1]

if __name__=='__main__':
    while True:
        stringul = input ("Enter a string or number:\n")
        string = stringul.replace(" ", "").lower()
        Test = PalindromTest(string)

        if Test == True:
            print (f'"{stringul} value is a palindrom"')
        else:
            print (f'"

Running:

$ python3 test.py 
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

Speaking of what I'd do: I'd also stick with Python naming conventions. It will make your code much easier to understand.

0
Mark Seemann On

If you want to follow test-driven development (TDD), you first write a test.

You could, for example, write this test:

def test_civic (self):
    self.assertTrue (PalindromTest("civic"), "civic should be a palindrome")

Note that the second parameter to assertTrue isn't any expected value, but a helpful error message you can write for your own (or others') benefit.

Following the red-green-refactor checklist, you now write the simplest code you can think of in the System Under Test (SUT):

def PalindromTest(string):
    return False

Since you first need to see the test fail, you can return False. Then run the test to verify that the test is failing:

$ py test_palindrome.py
F
======================================================================
FAIL: test_palindrome (__main__.MyPalindrome.test_palindrome)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "\test_palindrome.py", line 6, in test_palindrome
    self.assertTrue (PalindromTest("civic"), "civic should be a palindrome")
AssertionError: False is not true : civic should be a palindrome

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (failures=1)

This is an important checkpoint that verifies that you have, indeed, written the correct test. Only then do you make the simplest possible edit to pass the test:

def PalindromTest(string):
    return True

This passes the test:

$ py test_palindrome.py
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

Since return True clearly isn't the full implementation, this tells you that you need more tests. You could, for example, add this test:

def test_civil (self):
    self.assertFalse (PalindromTest("civil"), "civil should not be a palindrome")

When you now run the test suite, the new test fails.

In order to pass the entire test suite, what's the simplest thing that could possibly work?

This, perhaps?

def PalindromTest(string):
    return string.endswith('c')

I'm here employing the Devil's Advocate technique by going out of my way to pass all tests with a deliberately incorrect implementation. At least, all tests pass.

You could argue that you'll now use the Refactor phase of red-green-refactor to 'simplify' the code:

def PalindromTest(string):
    return string == string[::-1]

An argument could be made that this is better. It certainly looks more correct, and since this is a toy example, we may indeed reach that conclusion.

In more complex situations, however, we tend to rely on the test suite to tell us whether the implementation is correct, because typically the implementation code will be complex enough that bugs might go unnoticed.