Let's say you're working on this old Python project. It's ugly and
unpredictable, but you have no choice but to keep it alive. Luckily
you've heard about this new tool called Pythoscope, which can help
you cure the old guy.

You start by descending to the project directory::

  $ cd wild_pythons/

and initializing Pythoscope internal structures::

  $ pythoscope --init

This command creates **.pythoscope/** subdirectory, which will hold all
information related to Pythoscope. You look at the poor snake::

  # old_python.py
  class OldPython(object):
      def __init__(self, age):
          pass # more code...

      def hiss(self):
          pass # even more code...

and decide that it requires immediate attention. So you run Pythoscope
on it::

  $ pythoscope old_python.py

and see a test module generated in the **tests/** directory::

  # tests/test_old_python.py
  import unittest

  class TestOldPython(unittest.TestCase):
      def test___init__(self):
          # old_python = OldPython(age)
          assert False # TODO: implement your test here

      def test_hiss(self):
          # old_python = OldPython(age)
          # self.assertEqual(expected, old_python.hiss())
          assert False # TODO: implement your test here

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

That's a starting point for your testing struggle, but there's much more
Pythoscope can help you with. All you have to do is give it some more
information about your project.

Since Python is a very dynamic language it's no surprise that most
information about the application can be gathered during runtime. But legacy
applications can be tricky and dangerous, so Pythoscope won't run any code
at all unless you explicitly tell it to do so. You can specify which code
is safe to run through, so called, points of entry.

Point of entry is a plain Python module that executes some parts of your code.
You should keep each point of entry in a separate file in the
**.pythoscope/points-of-entry/** directory. Let's look closer at our old friend::

  # old_python.py
  class OldPython(object):
      def __init__(self, age):
          if age < 50:
              raise ValueError("%d isn't old" % age)
          self.age = age

      def hiss(self):
          if self.age < 60:
              return "sss sss"
          elif self.age < 70:
              return "SSss SSss"
          else:
              return "sss... *cough* *cough*"

Based on that definition we come up with the following point of entry::

  # .pythoscope/points-of-entry/123_years_old_python.py
  from old_python import OldPython
  OldPython(123).hiss()

Once we have that we may try to generate new test cases. Simply call pythoscope
on the old python again::

  $ pythoscope old_python.py

Pythoscope will execute our new point of entry, gathering as much dynamic
information as possible. If you look at your test module now you'll notice
that a new test case has been added::

  # tests/test_old_python.py
  import unittest
  from old_python import OldPython

  class TestOldPython(unittest.TestCase):
      def test___init__(self):
          # old_python = OldPython(age)
          assert False # TODO: implement your test here

      def test_hiss(self):
          # old_python = OldPython(age)
          # self.assertEqual(expected, old_python.hiss())
          assert False # TODO: implement your test here

      def test_hiss_returns_sss_cough_cough_after_creation_with_123(self):
          old_python = OldPython(123)
          self.assertEqual('sss... *cough* *cough*', old_python.hiss())

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

Pythoscope correctly captured creation of the **OldPython** object and call to
its **hiss()** method. Congratulations, you have a first working test case without
doing much work! But Pythoscope can generate more than just invdividual test
cases. It all depends on the points of entry you define. More high-level they
are, the more information Pythoscope will be able to gather, which directly
translates to the number of generated test cases.

So let's try writing another point of entry. But first look at another module
we have in our project::

  # old_nest.py
  from old_python import OldPython

  class OldNest(object):
      def __init__(self, ages):
          self.pythons = []
          for age in ages:
              try:
                  self.pythons.append(OldPython(age))
              except ValueError:
                  pass # Ignore the youngsters.
      def put_hand(self):
          return '\n'.join([python.hiss() for python in self.pythons])

This module seems a bit higher-level than **old_python.py**. Yet, writing a point
of entry for it is also straightforward::

  # .pythoscope/points-of-entry/old_nest_with_four_pythons.py
  from old_nest import OldNest
  OldNest([45, 55, 65, 75]).put_hand()

Don't hesitate and run Pythoscope right away. Note that you can provide many
modules as arguments - all of them will be handled at once::

  $ pythoscope old_python.py old_nest.py

This new point of entry not only allowed to create a test case for **OldNest**::

  # tests/test_old_nest.py
  import unittest
  from old_nest import OldNest

  class TestOldNest(unittest.TestCase):
      def test_put_hand_returns_sss_sss_SSss_SSss_sss_cough_cough_after_creation_with_list(self):
          old_nest = OldNest([45, 55, 65, 75])
          self.assertEqual('sss sss\nSSss SSss\nsss... *cough* *cough*', old_nest.put_hand())

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

but also added 4 new test cases for **OldPython**::

    def test_creation_with_45_raises_value_error(self):
        self.assertRaises(ValueError, lambda: OldPython(45))

    def test_hiss_returns_SSss_SSss_after_creation_with_65(self):
        old_python = OldPython(65)
        self.assertEqual('SSss SSss', old_python.hiss())

    def test_hiss_returns_sss_cough_cough_after_creation_with_75(self):
        old_python = OldPython(75)
        self.assertEqual('sss... *cough* *cough*', old_python.hiss())

    def test_hiss_returns_sss_sss_after_creation_with_55(self):
        old_python = OldPython(55)
        self.assertEqual('sss sss', old_python.hiss())

You got all of that for mere 2 additional lines of code. What's even better
is the fact that you can safely modify and extend test cases generated by
Pythoscope. Once you write another point of entry or add new behavior to your
system you can run Pythoscope again and it will only append new test cases
to existing test modules, preserving any modifications you could have made
to them.

That sums up this basic tutorial. If you have any questions, feel free to
ask them on the `pythoscope google group <http://groups.google.com/group/pythoscope>`_.
