3

I am attempting to build the header row of a month in HTML. I have a test case, and when I visually inspect either the comparison or the portion generated by my code, I get an assertion error (eg. my case did not pass). The weird thing is that when I visually inspect, the outputs SEEM to be identical.

I have done some fiddling, and narrowed the scope of the problem. Please see code below.

Here is my testcase:

class xyz(unittest.TestCase):

    def test__render_table_header(self):
        self.maxDiff = None
        testy = self.testcal1
        htmltest = testy._render_table_header(date(2014, 8, 1))
        htmlcase = """<table>
            <th colspan='7'>
                <div class="headercontainer">
                    <div class="montheader">{}</div>
                    <div class="yearheader">{}</div>
                </div>
            </th>
            <tr>
                <td class='dayheader'>Sun</td>
                <td class='dayheader'>Mon</td>
                <td class='dayheader'>Tues</td>
                <td class='dayheader'>Wed</td>
                <td class='dayheader'>Thurs</td>
                <td class='dayheader'>Fri</td>
                <td class='dayheader'>Sat</td>
            </tr>
            <tr>""".format('August', '2014')
        self.assertEqual(htmlcase, htmltest)

Here is my function:

def _render_table_header(self, dateobj):

    TOP_OF_TABLE = """<table>
        <th colspan='7'>
            <div class="headercontainer">
                <div class="montheader">{}</div>
                <div class="yearheader">{}</div>
            </div>
        </th>
        <tr>
            <td class='dayheader'>Sun</td>
            <td class='dayheader'>Mon</td>
            <td class='dayheader'>Tues</td>
            <td class='dayheader'>Wed</td>
            <td class='dayheader'>Thurs</td>
            <td class='dayheader'>Fri</td>
            <td class='dayheader'>Sat</td>
        </tr>
        <tr>"""
    month = dateobj.strftime('%B')
    year = dateobj.strftime('%Y')
    return TOP_OF_TABLE.format(month, year)

Here is the error and diff I get:

FAIL: test__render_table_header 

(__main__.test_enhanced_cal_helper_functions)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "court_app_timeline.py", line 430, in test__render_table_header
    self.assertEqual(htmlcase, htmltest)
AssertionError: '<table>\n            <th colspan=\'7\'>\n                <div [585 chars]<tr>' != '<table>\n                <th colspan=\'7\'>\n                 [649 chars]<tr>'
  <table>
-             <th colspan='7'>
+                 <th colspan='7'>
? ++++
-                 <div class="headercontainer">
+                     <div class="headercontainer">
? ++++
-                     <div class="montheader">August</div>
+                         <div class="montheader">August</div>
? ++++
-                     <div class="yearheader">2014</div>
+                         <div class="yearheader">2014</div>
? ++++
-                 </div>
+                     </div>
? ++++
-             </th>
+                 </th>
? ++++
-             <tr>
+                 <tr>
? ++++
-                 <td class='dayheader'>Sun</td>
+                     <td class='dayheader'>Sun</td>
? ++++
-                 <td class='dayheader'>Mon</td>
+                     <td class='dayheader'>Mon</td>
? ++++
-                 <td class='dayheader'>Tues</td>
+                     <td class='dayheader'>Tues</td>
? ++++
-                 <td class='dayheader'>Wed</td>
+                     <td class='dayheader'>Wed</td>
? ++++
-                 <td class='dayheader'>Thurs</td>
+                     <td class='dayheader'>Thurs</td>
? ++++
-                 <td class='dayheader'>Fri</td>
+                     <td class='dayheader'>Fri</td>
? ++++
-                 <td class='dayheader'>Sat</td>
+                     <td class='dayheader'>Sat</td>
? ++++
-             </tr>
+                 </tr>
? ++++
-             <tr>+                 <tr>? ++++


----------------------------------------------------------------------

I will admit to you fine folks that I'm not the most talented programmer. In fact, my entire programming career hearkens back to only about 5 months ago. Along with that comes a certain ineptitude at reading diffs. It seems to me that the major difference between the outputs has to do with leading whitespace. How can I fix this?

PS - any tips, comments, pointers, etc. are much appreciated.

NotAnAmbiTurner
  • 2,553
  • 2
  • 21
  • 44
  • First thing that comes to mind is difference between tabs and spaces, which wouldn't always show up correctly on the diff (unless you can set it to display tabs differently? not sure). – dwanderson Nov 11 '15 at 20:25
  • Second thing: there ARE different amounts of space, since your test string is indented once for the class, and again for the function. If you were to do:` htmlcase = (""" etc """)` things might work. The issue is that multiline string *respects* your indentation, and you've put a bunch of it (which is fine), but differing amounts between the real code and test code (not fine).
    – dwanderson Nov 11 '15 at 20:28

3 Answers3

3

Don't define the HTML code in two different places. Define it in one place and use it where needed.

One common way to do this is to define it as a global variable in a module, and then import it from that module. For example, you could have this in a module named, say, html_examples.py:

TABLE_CODE = """
    <table>
        <th colspan='7'>
         blah blah...
"""

Then when you need access to that text in another module, you can just say this:

from html_examples import TABLE_CODE
John Gordon
  • 29,573
  • 7
  • 33
  • 58
  • This is the way to go: you don't want to test if you've made a typo in your html code, so there's no reason to re-type it all again. – dwanderson Nov 11 '15 at 20:30
  • I would agree with that, except he's trying to make sure what he renders matches his expectation, and it's not clear how to do that without having the template AND an expected output for a given set of test inputs – Foon Nov 11 '15 at 20:33
  • @Foon I would argue that that's confusing unittests with some even lower-level testing. Making sure a string is what you think it is isn't really Python's job; in this case the "rendering" is slapping in the month and date, which should be verified for sure. But not by making sure the entirety of an HTML table is character-for-character the same. I'm not sure how best to do this either though; so I don't mean to disagree without proposing solutions, it jsut leaves a weird taste in my mouth, as far as proper testing goes. – dwanderson Nov 11 '15 at 22:29
  • @dwanderson interesting... maybe it's just the newbie in me, but I just felt way more comfortable testing the entire block of HTML. I'm definitely picking up what you're putting down though -- it would have the added benefits of the tests not breaking if I decided to change the template I'm using. It's probable that as I become more advanced, I'll be less trepidatious about doing such things. I'm also more than a little behind in my knowledge of importing modules a la what comes with an import, what doesn't, etc. If I define a GLOBAL in a module, then import one class, is that GLOBAL imported? – NotAnAmbiTurner Nov 12 '15 at 04:17
3

First off: multiline strings don't respect indentions. This means that if your test case is inside a class and your generator is inside a function, that's going to be an issue.

More usefully, whitespace in general are essentially ignorable in HTML... I'd suggest at a minimum doing something like:

def strip_white_space(str):
   return str.replace(" ", "").replace("\t", "").replace("\n", "")

self.assertEqual(strip_white_space(htmlcase), strip_white_space(htmltest))

A better approach which I don't know how to do off the top of my head would be to caniconalize the two strings. Clean Up HTML in Python has some suggestions, e.g.:

from BeautifulSoup import BeautifulSoup
htmlcase = BeautifulSoup(htmlcase).prettify()
htmltest = BeautifulSoup(htmltest).prettify()

(Though I don't know for certain that will always remove whitespace consistently)

Nitin Nain
  • 5,113
  • 1
  • 37
  • 51
Foon
  • 6,148
  • 11
  • 40
  • 42
  • Thanks! Using beautiful soup for that is an awesome idea! I couldn't really figure how to get this working. I would be curious if there will be a python interpreter at some point that will automatically drop leading whitespace in indented multiline strings - that's the behaviour I had expected initially. – NotAnAmbiTurner Nov 12 '15 at 04:21
0

One potential solution is to simply match the indents on both the string that forms the testcase, and the string that is used in the function. This has the effect of allowing the test to pass.

However, it seems sort of stupid (is in not scalable, and really doesn't go to the heart of the function). It would be much better to simply ignore leading whitespace. Perhaps another commenter can be of assistance with that. While we're at it, I would be interested to hear people's comments re: generating HTML in python and what the best way is to ensure that proper nesting of tags is maintained (for visual appeal, if nothing else) on output.

NotAnAmbiTurner
  • 2,553
  • 2
  • 21
  • 44