TinyMail is a project I started a while ago. It was inspired by
discussion around the Letters
project: to build an open-source email client for MacOS, lighter and
better and more flexible than Apple's Mail.app.
In the initial
implementation, the backend code (tinymail.maildata) was warped
and tangled, a real mess. So I rewrote all of it, this time with tests
(thank you Mock!) and
clean separation of responsibilities between classes. The async code is more
readable because of Monocle
– no more callback functions – and a simple IMAP worker that lives in its own
thread.
Now it's time for the front-end code to be reorganized and, hopefully,
unit tested. I still want to use Nose, and a lot of code needs to run on
the main thread (because it talks to Cocoa UI classes). At some point it
will surely need to do async stuff, and Nose will need to run on a
different thread (so it can block while the main thread event loop keeps
running), so I hacked together this slightly
crazy Nose plugin that "teleports" every test on the main thread and
runs it over there. So far, all the tests can happily run synchronously,
but it's good to be able to inject a "sleep for 5 seconds" in the middle
of a test, and manually interact with the UI.
Also of note: Monocle plays nicely with the Cocoa event loop.
callAfter and callLater from PyObjCTools.AppHelper will
schedule callables to run on the main thread and they will happily take a
monocle Callback argument.
There is a Python library called mock (by Michael Foord) that you should know
about. It's small, powerful, and you need it when writing unit tests.
The library is built around the Mock class. This is an object that
can take any shape you need. Its purpose is to trick the code-being-tested that
it's running in a production environment, calling real functions on real
objects and getting real results back. As part of the unit test's set-up, you
configure a number of mocks; then you call the method that's being tested, and
afterwards make assertions on the mocks, to make sure the tested method worked
correctly.
When you're testing a bit of functionality, you want to keep things simple.
Don't check anything else in that one unit test. Mock helps you
isolate the bit of code being tested, so that it doesn't go calling on some
other code, that you're not interested in checking right now.
If the code is well-factored, you will only need to mock a very few number
of things in each test. Heavy mocking, and complex test set-up, is a good
indicator of tightly coupled code, so doing test-driven development with mocks
tends to encourage simple, straightforward interfaces between objects.
After each test there is some cleanup. Ideally, the only cleanup you need is
performed automatically by the runtime: objects go out of scope and are
deallocated. Sometimes you need to mock a bit of global functionality (like
datetime.now()) and you want it restored; there is a handy decorator named
patch, part of mock, that does this. If you find yourself doing
anything more complex, something is wrong.
So I highly recommend writing unit tests with Mock. It makes
testing easier and helps you spot poorly organised code.
Say you regularly use more than one computer. Home and office, heavy duty
desktop and light portable, doesn't matter. You probably want some measure of
synchronization between them, like an external hard disk that you plug into
whichever computer you're working on.
But external disks are slow and fragile and cumbersome and take up extra
space, a software solution would be better. Perhaps by now you're thinking
"sure, use DropBox", and if you're a
normal human being, that's perfectly good: DropBox is an amazing service,
really well polished, I heard from lots of happy users. But if you're like me,
DropBox seems expensive (like, you actually have to pay for any serious use),
dangerous (my files on their server? never!) and not fun (can't hack on it).
So, go opensource!
There's rsync, a gem of a program,
excellent for mirroring a hierarchy of files. But if you actively change files
in two locations and try to synch back and forth, it will, at the very least,
recreate any file you remove. Not the right tool for the job then. There's also
Unison, which seems to
fit the bill, but it's big and weird and written in OCaml (what is
that?!), I'd rather have something simpler. Some well-respected opensource
backup tools (rdiff-backup, duplicity) might seem useful, but they are just
that, backup tools, not good for keeping folders in synch.
So, then, I decided to build my own. There was a misguided attempt at a git-based FUSE filesystem,
which was incredibly fun but hopelessly slow. But the second attempt, MagicFolder, I'm using
quite happily to synchronize my music and photos and random files between home
and work. It uses checksums to identify files, keeps a version history to
figure out what to keep and what's safe to remove, and defers conflict
resolution to the user by renaming one of the conflicting files. It needs to be
run by hand, and weird things may happen if it gets interrupted during a synch,
but with a bit of work those issues will be fixed.