Python+Ubuntu+TDD

19 december 2009

A couple of weeks ago I tried to record my desktop using gtk-recordmydesktop. It was simpler than I had imagined, so I went ahead and tried uploading the resulting .ogv file to Youtube. To my surprise Youtube swallowed the file without any fuzz – no need to resize or convert the video at all. Joy happy joy 🙂

I was a bit enthusiastic by then, so I thought ”can’t I solve some problem using my pyTDDmon tool and show it to others?”. So I picked a simple problem from the TDD-Problems site and went on.

Below you can watch the result. The first three parts were recorded the same night, with the comments added a few days later. I recorded and commented Part 4 tonight. It uses an updated version of the pyTDDmon tool, which has a little more user-friendly text-output in the console and pyTDDmon window.

Enjoy! I recommend watching the videos either in full-screen or zoomed-in. The smallish version directly below is kind of hard on my eyes at least.

Taggar: , , , , ,

Annonser

Grubblerier, fortsättning

01 augusti 2009

Jag skrev inlägget Grubblerier tidigare idag.

Jag har grubblat under dagen. Försökt analysera problemets ursprung.

Så kom jag på det: Jag oroar mig för att ändra på testkod. Det är ett välkänt ”TDD anti-pattern” – att man på något sätt känner sig känslomässigt fäst vid sina tester – så att man inte vågar eller vill röra vid dem.

Det är såklart en naiv inställning. Testerna liksom produktionskoden är under ständig utveckling i ett programvarusystem; betatanken – inget är någonsin färdigt, bara halvfärdigt. Och under ständig förändring/utveckling/evolution. [Betatanken kan för övrigt appliceras på allt: programvara, produkter, företag, människor, ekosystemen, samhällen, din chef, dig.]

Fortress Defender - en skärmdump

Så här ser Fortress Defender ut idag

Den naturliga vidareutvecklingen i mitt minispel är flytta ut ansvaret från Ball och Boat till en separat klass Entity eller kanske LinearEntity eftersom det handlar om linjär rörelse. Enhetstesterna för Entity kan utan skuldkänslor kopieras från t.ex. Ball och modiferas för att passa Entity. Entity själv kan utgå ifrån Ball.

Enhetstesterna för Ball och Boat som de står nu (som alltså testar linjärrörelse framför allt) är onödiga, då det inte är Ball eller Boats ansvar längre att utföra linjärrörelse. Det är Entity‘s ansvar. Ball/Boat testerna förpassas, när Entity är grön (alla tester OK), till papperskorgen.

Därefter är Ball och Boat enkla ”konfigurationer” av Entity. Vad behöver egentligen testas för dessa två? Frågan kan omformuleras till ”vad är Ball och Boats ansvar”. Och, utan att gå händelserna alltför mycket i förväg, känner jag redan nu att det som är Ball– och Boatspecifikt är punkterna A/B som Ball respektive Boat rör sig mellan. Så Ball och Boat kanske snarare ska vara ”Entititsskapande funktioner” än klasser.

Men förra stycket är ren spekulation. En sak i taget! Entity, here I come.

Taggar: , ,


Veckoutmaning, kort update

18 juli 2009

Ibland måste man vara lite praktiskt.

utvmiljö

Utvecklingsverktyg

Min utvecklingsmiljö är en WinXP-dator med Pythons inbyggda editor IDLE. Detta för att jag jobbar på min brorsas dator och inte vill skräpa ned alltför mycket med en massa program. Dessutom gillar jag att använda så få verktyg som möjligt, eftersom jag med åldern blivit lite ”anti-install/konfigg” rent allmänt. Denna hållning kommer egentligen från insikten ”ju fler steg tills miljön är uppsatt, desto fler felkällor att leta i när något fallerar”. (Förvisso ett mera verkligt problem först vid långtidsprojekt/många utvecklare!)

Nå denna miljö må uppfylla mina minimalism-krav, men den har också sina skavanker i form av att IDLE brukar ”hänga sig” ibland när man trycker F5 (kör). Det är något med sockets-connections hit och dit (IDLE verkar använda sockets för att kommunicera mellan texteditor och shell-fönster). Den praktiska lösning jag läst på nätet var att ta kol på alla python-processer mha. task manager, och sedan starta IDLE igen.

Denna morgon hängde sig IDLE osedvanligt många gånger, vilket ledde till frustration. Så jag bestämde mig för att lösa problemet en gång för alla. Jag kom på att om dödandet av processerna var ett dubbelklick istället för harangen — Ctrl+Alt+Delete — sortera om namnlist — döda en process i taget mha musklick — så hade gräset varit grönare.

Sagt och gjort, jag fixade en .bat-fil som gjorde jobbet åt mig: killpythonw.bat. Här är dess innehåll:

TASKKILL /F /IM "pythonw.exe"

Så om du råkar ha samma problem som jag nån gång vet du att det finns en smidig lösning!

Taggar: , ,


Veckoutmaning: Litet spel med Pygame (6/7)

17 juli 2009

Läs tidigare delar: Ett Två Tre&Fyra Fem

En liten uppdatering. Nu har jag fått rätt på muspekare och ”sikte”. Siktet är helt enkelt en liten röd halvgenomskinlig kanonkula:

screenshot

Nu börjar det röra sig lite på skärmen äntligen!

Det som kvarstår innan det ska vara någorlunda spelbart är egentligen bara fartygen. Fast för att få ett komplett spel behövs såklart smått&gott som ”Press to begin” och snyggare grafik&ljud (just nu har jag bara en sampling när man ”släpper” en kanonkula, en sampling av något slags fjäder som släpps upp!). Och så explosioner/vattenkaskader när man missar!

Taggar: , , ,


Refactoring a Python module

02 mars 2009

Inspired by the P.I.T.S. presentation at the recent Software Craftsmanship 2009 conference in London, I decided to perform a refactoring of some open source code.

I browsed around for awhile and found the Pychess project, and inside that just took a file at random. It happened to be protoload.py:

import urllib, os

def splitUri (uri):
    uri = urllib.url2pathname(uri) # escape special chars
    uri = uri.strip('\r\n\x00') # remove \r\n and NULL
    return uri.split("://")

def protoopen (uri):
    """ Function for opening many things """

    try:
        return urllib.urlopen(uri)
    except (IOError, OSError):
        pass

    try:
        return open(uri)
    except (IOError, OSError):
        pass

    raise IOError, "Protocol isn't supported by pychess"

def protosave (uri, append=False):
    """ Function for saving many things """

    splitted = splitUri(uri)

    if splitted[0] == "file":
        if append:
            return file(splitted[1], "a")
        return file(splitted[1], "w")
    elif len(splitted) == 1:
        if append:
            return file(splitted[0], "a")
        return file(splitted[0], "w")

    raise IOError, "PyChess doesn't support writing to protocol"

def isWriteable (uri):
    """ true if protoopen can open uri to in write mode """

    splitted = splitUri(uri)

    if splitted[0] == "file":
        return os.access (splitted[1], os.W_OK)
    elif len(splitted) == 1:
        return os.access (splitted[0], os.W_OK)

    return False

It is small enough for a blog, yet contains something to work with.

The first thing I do is look at the method- and class names:

  • splitUrl
  • protoopen
  • protosave
  • isWriteable

By the way – I know methods are called functions in Python – I’m just a little ”C# wounded” so you will see me using both words 🙂

Since the file is called ”protoopen” I guess the protopen is the most important function, so I’ll check that out first.

The comment clearly spells out ”Function for opening many things”, and, looking through the code, it seems it is indeed returning pipe objects either from the urllib or the basic open() method of python (file system open). Here I see room for improvement: there is duplication in this method. The two try-catches are very similar, only the specific method used to open is different. So I’ll try out a loop over those two methods, and see if it is more readable.

    methods = [urllib.urlopen, open]
    for method in methods:
    	try:
    		return method(uri)
	    except (IOError, OSError):
            pass

It is quite easy to follow, in my mind easier than previously, so I’ll keep it. But, I’m not quite fond with the variable names ‘method’ and ‘methods’, a more descriptive naming would be more readable. When I’m looking for good names for things, I try to think of how the names are used. ‘method’ is used to open something, with one specific technique. ‘openers’ and ‘openup’ comes to mind and I try it out:

def protoopen (uri):
    """ Function for opening many things """

    openers = [urllib.urlopen, open]
    for openup in openers:
    	try:
    		return openup(uri)
        except (IOError, OSError):
            pass

    raise IOError, "Protocol isn't supported by pychess"

I compare this with what we had to begin with to see if it is easier to read:

def protoopen (uri):
    """ Function for opening many things """

    try:
        return urllib.urlopen(uri)
    except (IOError, OSError):
        pass

    try:
        return open(uri)
    except (IOError, OSError):
        pass

    raise IOError, "Protocol isn't supported by pychess"

I think it is easier now than what it was to begin with. A minor improvement to readibility is that in Python, you really don’t have to specify excactly which exception you want to catch, so I remove that:

	openers = [urllib.urlopen, open]
	for openup in openers:
		try:
			return openup(uri)
		except:
			pass

I try removing the openers variable altogether – since it is a concept that ‘burdens’ the method (the less concepts, the easier to grasp):

def protoopen (uri):
	""" Function for opening many things """ 

	for openup in [urllib.urlopen, open]:
		try:
			return openup(uri)
		except:
			pass 

	raise IOError, "Protocol isn't supported by pychess"

I think this is slightly better than before, and keep it. Now in Python it is etiquette to put statements following try: and except: on separate lines, but since there is only one statement in each I try out campacting the loop:

def protoopen (uri):
	""" Function for opening many things """ 

	for openup in [urllib.urlopen, open]:
		try:    return openup(uri)
		except: pass 

	raise IOError, "Protocol isn't supported by pychess"

I think it is easier to read, and our function is getting really slim, and doing just as much as it did to begin with.

One slight improvement is left, and that is the comment. ‘Many things’ is vague and we can improve that:

def protoopen (uri):
	""" Function for opening URLs and files """ 

	for openup in [urllib.urlopen, open]:
		try: 	return openup(uri)
		except:	pass 

	raise IOError, 'Protocol isn't supported by pychess'

I’m quite happy with protoopen now, and continue looking for ways to refactor this file.

The first method in protoload.py is splitUrl. Actually it seems it does more than split a URL: it also cleans it up. But I cannot see a simple way to simplify this right now, so I turn my eyes on the protosave method.

def protosave (uri, append=False):
	""" Function for saving many things """ 

	splitted = splitUri(uri) 

	if splitted[0] == "file":
		if append:
			return file(splitted[1], "a")
		return file(splitted[1], "w")
	elif len(splitted) == 1:
		if append:
			return file(splitted[0], "a")
		return file(splitted[0], "w") 

	raise IOError, 'PyChess does not support writing to protocol'

The first thing I notice is the duplication of the inner if’s: there is the append check, and the two return file()-calls, with ”a” och ”w” used. Only thing that differs is the number in the [], it is 1 in the top if, and 0 in the elif. So I refactor this a little, using an index variable I call ix:

	ix = -1
	if splitted[0] == "file":
		ix = 0
	elif len(splitted) == 1:
		ix = 1 

	if ix == -1:
		raise IOError, "PyChess doesn't support writing to protocol" 

	if append:
		return file(splitted[ix], "a")
	return file(splitted[ix], "w")

Arguably we have removed some duplication. But is it really more readable? I don’t know. I’ll give it a little more love by observing that the elif really could be an ordinary if:

	ix = -1
	if splitted[0] == "file":
		ix = 0
	if len(splitted) == 1:
		ix = 1
	if ix == -1:
		raise IOError, "PyChess doesn't support writing to protocol"
	if append:
 		return file(splitted[ix], "a")
	return file(splitted[ix], "w")

Now it just looks like a long list of if’s. It is not very intention revealing and it is harder to read IMO.

I think I’ve put myself in a dead end, so I go back to the original and try once more.

def protosave (uri, append=False):
	""" Function for saving many things """ 

	splitted = splitUri(uri) 

	if splitted[0] == "file":
		if append:
			return file(splitted[1], "a")
		return file(splitted[1], "w")
	elif len(splitted) == 1:
		if append:
			return file(splitted[0], "a")
		return file(splitted[0], "w") 

	raise IOError, 'PyChess does not support writing to protocol'

One thing I really like with Python is the ability to define functions really close to where they are used. Here we may define a funtion to do the inner work of the if’s, instead of breaking it out as in my previous attempt:

def protosave (uri, append=False):
	def dofile(path, append):
		if append:
			return file(path, "a")
		return file(path, "w") 

	splitted = splitUri(uri) 

	if splitted[0] == "file":
		return dofile(splitted[1], append)
	if len(splitted) == 1:
		return dofile(splitted[0], append) 

	raise IOError, 'PyChess does not support writing to protocol'

This already feels a lot better. Now there is a slight duplication left inside the dofile method: the file(path start of both return lines. If we decide wich letter to use beforehand, maybe we can simplify even more:

def protosave (uri, append=False):
	def dofile(path, mode):
		return file(path, mode) 

	mode = "w"
	if append: mode = "a" 

	splitted = splitUri(uri) 

	if splitted[0] == "file":
		return dofile(splitted[1], mode)
	if len(splitted) == 1:
		return dofile(splitted[0], mode) 

	raise IOError, 'PyChess does not support writing to protocol'

And what do you know — the dofile method just delegates it’s arguments to the file method, effectively deprecating itself!

def protosave (uri, append=False):
	""" Function for saving to file or URL """ 

	mode = "w"
	if append: mode = "a" 

	splitted = splitUri(uri) 

	if splitted[0] == "file":
		return file(splitted[1], mode)
	if len(splitted) == 1:
		return file(splitted[0], mode) 

	raise IOError, 'PyChess does not support writing to protocol'

Let’s compare this with the original protosave function:

def protosave (uri, append=False):
	""" Function for saving many things """ 

	splitted = splitUri(uri) 

	if splitted[0] == "file":
		if append:
			return file(splitted[1], "a")
		return file(splitted[1], "w")
	elif len(splitted) == 1:
		if append:
			return file(splitted[0], "a")
		return file(splitted[0], "w") 

	raise IOError, "PyChess doesn't support writing to protocol"

The new version looks a little less messy, since there are less nested if’s. I’ll stop there, and turn my attention to the isWriteable function.

The first thing I notice about isWriteable is that contains some similarity to protosave. They both contain the logic of the splitted uri. How can we remove that duplication?

One lesson I’ve learned is that facing these kind of things, it is often easiest to just start with the small things. The larger things will just solve themselves by themselves then. Let’s see if that idea holds in this case!

I begin with creating two new functions for the tests in the if’s. I’ll just call them caseOne- Two for now since I cannot come up with more descriptive names.

def caseOne(splitted):
	return splitted[0] == "file" 

def caseTwo(splitted):
	return len(splitted)==1 

def protosave (uri, append=False):
	""" Function for saving to file or URL """ 

	mode = "w"
	if append: mode = "a" 

	splitted = splitUri(uri) 

	if caseOne(splitted):
		return file(splitted[1], mode)
	if caseTwo(splitted):
		return file(splitted[0], mode) 

	raise IOError, 'PyChess does not support writing to protocol' 

def isWriteable (uri):
	""" Returns true if protoopen can open a write pipe to the uri """ 

	splitted = splitUri(uri) 

	if caseOne(splitted):
		return os.access (splitted[1], os.W_OK)
	if caseTwo(splitted):
		return os.access (splitted[0], os.W_OK) 

	return False

It already seems a little less duplicated. Now we’d like to break out the split,if,if structure into a separate function, so we don’t have to repeat ourselves in isWriteable, with the same logic as protosave. I’ll call it uriLogic, and to begin with it is just imaginary, that is I’m not so sure it will work at all.

def uriLogic(uri, ret1, ret2, second):
	splitted = splitUri(uri)
	if caseOne(splitted):
		return ret1(splitted[1], second)
	if caseTwo(splitted):
		return ret2(splitted[0], second)
	return None

Let’s see how it would be used in protosave:

def protosave (uri, append=False):
	""" Function for saving to file or URL """ 

	mode = "w"
	if append: mode = "a" 

	result = uriLogic(uri, file, mode) 

	if result == None:
		raise IOError, 'PyChess does not support writing to protocol'

It’s extremely short at least. Readable? Could be better. Let’s see how isWriteable treates uriLogic:

def isWriteable(uri):
	""" Returns true if protoopen can open a write pipe to the uri """ 

	result = uriLogic(uri, os.access, os.W_OK) 

	if result == None:
		return False
	else:
		return result

So we’ve really simplified isWriteable and protosave, but we’ve also added three more functions.

Since caseOne- and Two are only used inside of the uriLogic function, I’ll inline them again:

def uriLogic(uri, ret1, ret2, second):
	splitted = splitUri(uri)
	if splitted[0] == "file":
		return ret1(splitted[1], second)
	if len(splitted) == 1:
		return ret2(splitted[0], second)
	return None

.. and we’ve removed two of the three functions. I think it’s time to look at the whole file as it looks now, and compare with the original:

def splitUri (uri):
	uri = urllib.url2pathname(uri) # escape special chars
	uri = uri.strip('\r\n\x00') # remove \r\n and NULL
	return uri.split("://") 

def protoopen (uri):
	""" Function for opening URLs and files """ 

	for openup in [urllib.urlopen, open]:
		try: 	return openup(uri)
		except:	pass 

	raise IOError, "Protocol isn't supported by pychess" 

def uriLogic(uri, ret1, ret2, second):
	splitted = splitUri(uri)
	if splitted[0] == "file":
		return ret1(splitted[1], second)
	if len(splitted) == 1:
		return ret2(splitted[0], second)
	return None 

def protosave (uri, append=False):
	""" Function for saving to file or URL """ 

	mode = "w"
	if append: mode = "a" 

	result = uriLogic(uri, file, mode) 

	if result == None:
		raise IOError, 'PyChess does not support writing to protocol' 

def isWriteable(uri):
	""" Returns true if protoopen can open a write pipe to the uri """ 

	result = uriLogic(uri, os.access, os.W_OK) 

	if result == None:
		return False
	else:
		return result

There is a lot less duplication in the code. But there is one tiny little trick we could do to bring down the noise level even more: instead of returning None in the uriLogic function, we could return False:

def splitUri (uri):
	uri = urllib.url2pathname(uri) # escape special chars
	uri = uri.strip('\r\n\x00') # remove \r\n and NULL
	return uri.split("://") 

def protoopen (uri):
	""" Function for opening URLs and files """ 

	for openup in [urllib.urlopen, open]:
		try: 	return openup(uri)
		except:	pass 

	raise IOError, "Protocol isn't supported by pychess" def uriLogic(uri, ret1, ret2, second):

def uriLogic(uri, ret1, ret2, second):
	splitted = splitUri(uri)
	if splitted[0] == "file":
		return ret1(splitted[1], second)
	if len(splitted) == 1:
		return ret2(splitted[0], second)
	return None 

def protosave (uri, append=False):
	""" Function for saving to file or URL """ 

	mode = "w"
	if append: mode = "a" 

	result = uriLogic(uri, file, mode)
	if result == False:
		raise IOError, 'PyChess does not support writing to protocol'
	else:
		return result 

def isWriteable(uri):
	""" Returns true if protoopen can open a write pipe to the uri """
	return uriLogic(uri, os.access, os.W_OK)

That looks a bit slicker! Suddenly isWriteable is a single line method!

Taggar: , , , ,


Kontinuerlig testkörning i Python

15 december 2008

Satt och lekte med ett ”autotestscript” i helgen. Det är mycket simpelt uppbyggt:

  1. Kör Test.py som innehåller enhetstest för projektet
  2. Vänta i fem sekunder
  3. Upprepa från 1

Jag har ”snott” idén från en kommentar angående utvecklingen av Google Chrome – tydligen kör Google ibland enhetstesterna kontinuerligt i bakgrunden. På så sätt får man ett ”larm” när man bryter mot något enhetstest!

Rent konkret kör jag scriptet, som jag kallar autotests.py, i ett terminalfönster. Invid detta fönster har jag igång två editorfönster jag skriver kod i, dels enhetstesterna, dels projektkoden. Jag har alltså tre filer i projektet som utvecklas:

  • Main.py – själva programkoden
  • Tests.py – enhetstester för Main.py
  • autotest.py – kontinuerlig körning av Tests.py

Känslan hittills är go! Det blir ett mjukt men ändå högt tempo på kodandet. Lägga till ett test, se det larma, lägga till kod för att få testet att gå igenom. Detta utan att behöva kompilera, vänta på bygget + enhetstestkörning. Bara trycka Ctrl+S när jag skrivit nått nytt.

autotest.py ser ut så här:

import os, time
while True:
  for i in range(0,50):
    print ""
  os.system("python Tests.py")
  time.sleep(5)
autotest

Autotest in action

Taggar: , , , ,


%d bloggare gillar detta: