Search This Blog

Showing posts with label documentation. Show all posts
Showing posts with label documentation. Show all posts

10 May 2015

TDD: Test Driven Documentation

What's Test Driven Documentation ?

Well the idea is pretty simple, while you write specification in Gherkin you describe the way you want the application to behave.
Writing the documentation is explaining how the application behaves so as to learn how to use it.
What if from good requirements we could infer part of the documentation ?

If we run these tests with behave and run a selenium driver we can leverage to take a few screenshots and add information about what's happening. In short we want to go from

Feature: Devpi server list our personal packages

  As a    Developer
  I want  to access the in house package repository
  So that I can develop the latest and greatest software

  Scenario: see my personal index
    Given I access the main devpi page as "nlaurance"
    When I click My personal repository
    Then I have permission to upload packages
    And I want a "overlined" screenshot saved as "index.png"
    And I document in the "se" as "bubble_list.png" with
       """
       Make sure the upload permission
       is set for you
       """


to


let's have a peek at what's in devpi.py, inside the steps directory


def dom_element_size(dom_element):
    x, y = dom_element.location['x'], dom_element.location['y']
    width, height = dom_element.size['width'], dom_element.size['height']
    return x, y, width, height


@then(u'I have permission to upload packages')
def step_impl(context):
    permissions = context.browser.find_element_by_class_name('permissions')
    assert_that(permissions.text, contains_string(context.user))

    my_permission = permissions.find_element_by_xpath("descendant::span[contains(text(), '{0}')]".format(context.devpi_user))

    outline_elements = getattr(context, 'outline_elements', [])
    outline_elements.append(dom_element_size(my_permission))
    context.outline_elements = outline_elements

at this stage there is a hamcrest assertion, to make sure we comply to the requirement. Then we keep track of the desired element(s) position and size.

@then(u'I document in the "{hotspot}" as "{filename}" with')
def step_impl(context, hotspot, filename):
    outline_elements = getattr(context, 'outline_elements', [])

    screenshot = getattr(context, 'current_screenshot',
                         StringIO(context.browser.get_screenshot_as_png()))
    commented = paste_bubble(screenshot, outline_elements, hotspot, context.text, filename)
    context.current_screenshot = commented



We can take a screenshot at any time, to allow chaining we pass the current screenshot to the context.

so what is paste_bubble doing ?

it uses the ninepatch library to format image ressources to our need. In fact a fork of it to simplify the code.

def paste_bubble(screenshot, coords, hotspot, text, save_as='screenshot.png'):
    image = screenshot if isinstance(screenshot, Image.Image) else Image.open(screenshot)
    for coord in coords:
        x, y, width, height = coord

    # se
    paste_x = int(x + width)
    paste_y = int(y + height)

    bubble_img = bubble(text, hotspot)
    image.paste(bubble_img, (paste_x, paste_y), bubble_img)
    image.save(save_as)
    return image

Yes, this is still at the embryo stage, and the hotspot (position) is not really implemented yet.

def bubble(text, hotspot="se"):
    ninepatch = Ninepatch('./bubbles/{0}.9.png'.format(hotspot))
    text_as_image = text2img(text)
    scaled_bubble = ninepatch.render_with_content(text_as_image)
    if DEBUG:
        scaled_bubble.save('bubble.png')
    return scaled_bubble

Now some play with inkscape and nine patch editor, to create some outlines. In here we have a se.9.png file in the bubbles subdirectory.

This is a work in progress, but that first result is rewarding enough.
I'll see if this deserves a packaging of its own.

02 March 2015

Python Puzzle #2

Here is the challenge #2. This one deserves more explanations.
Your team has to maintain a legacy project, and for various historical reasons, the project has not enough documentation. Features delivery is always a higher priority than the documentation update.
So, you decide it's time that every team member commits to read and improve the documentation.
The plan is to spend an hour every Monday, Wednesday and Friday to this task, each team member in turn.

you want a function, that given a start date and a number of weeks, will assign the team member to this maintenance.


import unittest
import datetime


team_members = [
    "Dilbert",
    "Wally",
    "Alice",
    "Pointy haired Boss",
    "Dogbert",
    "Asok",
    "Ted",
    "Loud Howard",
    "Carol",
    "Ratbert",
    "Catbert",
    "Dilmom",
    "Phil, prince of insufficient light",
    "Bob the dinosaur",
    "Garbageman",
    "Tina",
    "Accountig Troll",
    "Elbonian guy",
    "Mordac",
    "Topper",
    "Liz",
]


def build_rota(start_date, weeks=4):
    pass


class TestDottedToDict(unittest.TestCase):
    def setUp(self):
        self.expected = [(datetime.date(2014, 12, 26), 'Dilbert'),
                         (datetime.date(2014, 12, 29), 'Wally'),
                         (datetime.date(2014, 12, 31), 'Alice'),
                         (datetime.date(2015, 1, 2), 'Pointy haired Boss'),
                         (datetime.date(2015, 1, 5), 'Dogbert'),
                         (datetime.date(2015, 1, 7), 'Asok'),
                         (datetime.date(2015, 1, 9), 'Ted'),
                         (datetime.date(2015, 1, 12), 'Loud Howard'),
                         (datetime.date(2015, 1, 14), 'Carol'),
                         (datetime.date(2015, 1, 16), 'Ratbert'),
                         (datetime.date(2015, 1, 19), 'Catbert'),
                         (datetime.date(2015, 1, 21), 'Dilmom'),
                         (datetime.date(2015, 1, 23), 'Phil, prince of insufficient light'),
                         (datetime.date(2015, 1, 26), 'Bob the dinosaur'),
                         (datetime.date(2015, 1, 28), 'Garbageman'),
                         (datetime.date(2015, 1, 30), 'Tina'),
                         (datetime.date(2015, 2, 2), 'Accountig Troll'),
                         (datetime.date(2015, 2, 4), 'Elbonian guy'),
                         (datetime.date(2015, 2, 6), 'Mordac'),
                         (datetime.date(2015, 2, 9), 'Topper'),
                         (datetime.date(2015, 2, 11), 'Liz'),
                         (datetime.date(2015, 2, 13), 'Dilbert'),
                         (datetime.date(2015, 2, 16), 'Wally'),
                         (datetime.date(2015, 2, 18), 'Alice'),
                         (datetime.date(2015, 2, 20), 'Pointy haired Boss'),
                         (datetime.date(2015, 2, 23), 'Dogbert'),
                         (datetime.date(2015, 2, 25), 'Asok'),
                         (datetime.date(2015, 2, 27), 'Ted'),
                         (datetime.date(2015, 3, 2), 'Loud Howard'),
                         (datetime.date(2015, 3, 4), 'Carol'),
                         (datetime.date(2015, 3, 6), 'Ratbert'),
                         (datetime.date(2015, 3, 9), 'Catbert'),
                         (datetime.date(2015, 3, 11), 'Dilmom'),
                         (datetime.date(2015, 3, 13), 'Phil, prince of insufficient light'),
                         (datetime.date(2015, 3, 16), 'Bob the dinosaur'),
                         (datetime.date(2015, 3, 18), 'Garbageman'),
                         (datetime.date(2015, 3, 20), 'Tina'),
                         (datetime.date(2015, 3, 23), 'Accountig Troll'),
                         (datetime.date(2015, 3, 25), 'Elbonian guy'),
                         (datetime.date(2015, 3, 27), 'Mordac'),
                         (datetime.date(2015, 3, 30), 'Topper'),
                         (datetime.date(2015, 4, 1), 'Liz'),
                         (datetime.date(2015, 4, 3), 'Dilbert'),
                         (datetime.date(2015, 4, 6), 'Wally'),
                         (datetime.date(2015, 4, 8), 'Alice')]

    def test_rota(self):
        got = list(build_rota(datetime.date(2014, 12, 25), weeks=15))
        self.assertListEqual(got, self.expected)

Have fun!

18 November 2010

Documenting web services with restructured text

Let's say you're programming a web service with a REST interface.

You might return different HTTP codes that you want to explain in your documentation.
Of course you will use ReST (restructured Text) to document.

Wouldn't it be helpfull that you could link to a full explanation of the code ?

here is a file that you can import from your ReST files like so

.. include:: http_errors.txt

http_errors.txt being :

.. _`100 Continue`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.1.1
.. _`101 Switching Protocols`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.1.2

.. _`200 OK`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.1
.. _`201 Created`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.2
.. _`202 Accepted`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.3
.. _`203 Non-Authoritative Information`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.4
.. _`204 No Content`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.5
.. _`205 Reset Content`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.6
.. _`206 Partial Content`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.7

.. _`300 Multiple Choices`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.1
.. _`301 Moved Permanently`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.2
.. _`302 Found`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.3
.. _`303 See Other`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.4
.. _`304 Not Modified`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
.. _`305 Use Proxy`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.6
.. _`307 Temporary Redirect`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.8

.. _`401 Unauthorized`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2
.. _`402 Payment Required`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.3
.. _`403 Forbidden`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.4
.. _`404 Not Found`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.5
.. _`405 Method Not Allowed`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.6
.. _`406 Not Acceptable`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.7
.. _`407 Proxy Authentication Required`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.8
.. _`408 Request Timeout`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.9
.. _`409 Conflict`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.10
.. _`410 Gone`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.11
.. _`411 Length Required`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.12
.. _`412 Precondition failed`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.13
.. _`413 Request Entity Too Large`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.14
.. _`414 Request-URI Too Long`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.15
.. _`415 Unsupported Media Type`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.16
.. _`416 Requested Range Not Satisfiable`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.17
.. _`417 Expectation Failed`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.18

.. _`500 Internal Server Error`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.1
.. _`501 Not Implemented`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.2
.. _`502 Bad Gateway`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.3
.. _`503 Service Unavailable`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.4
.. _`504 Gateway Timeout`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.5
.. _`505 HTTP Version Not Supported`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.6