Implementation
Now that we have a basic design and test strategy, let's start writing some code.
First make our python module:
mkdir flash/
touch flash/__init__.py
touch flash/load.py
This creates our python module and get's us started with some empty files.
Let's first implement SPC-format.question
as an object in flash/load.py
:
#!/usr/bin/python2
'''
csv loading module
'''
import csv
class Question(object):
''' represents a question and can be asked
partof: #SPC-format.question
'''
def __init__(self, question, answer):
self.question = question.strip()
self.answer = answer.strip().lower()
def __eq__(self, other):
if not isinstance(other, Question):
return False
return self.question == other.question and self.answer == other.answer
def __neq__(self, other):
return not self == other
Note that we do not have the ask()
method yet though (we will do that later).
Now let's implement SPC-format.validate
:
def validate_questions(questions):
''' Given a list of questions, validate them according to spec
partof: #SPC-format.validate
'''
# check for duplicates
all_qs = [q.question for q in questions]
seen = set()
duplicates = []
for q in all_qs:
if q in seen:
duplicates.append(q)
seen.add(q)
if duplicates:
raise ValueError("duplicate questions found: {}".format(duplicates))
Finally, let's implement SPC-format
itself -- the loading of the file.
def load_io(f):
''' load questions from a file '''
reader = csv.reader(f)
questions = []
for row in reader:
if len(row) == 0 or (len(row) == 1 and not row[0].strip()):
# skip if the row contains nothing but whitespace
continue
if len(row) != 2:
raise ValueError("row is invalid length of {}: {}".format(
len(row), row))
questions.append(Question(*row))
return questions
def load_path(path):
''' given a path, load a list of validated questions
partof: #SPC-format
'''
with open(path, 'rb') as f:
return load_io(f)
When we've finished with all of that, type art ls
... and nothing is
implemented. This is because we still need to tell Artifact where we have
implemented stuff. Edit .art/settings.toml
and add "flash/"
to
the code_paths
list.
code_paths = ["flash/"]
Now try:
$ art ls ~/tmp/learn-art
spc% tst% | name | parts
20.0 0.0 | REQ-purpose | SPC-cli, SPC-format, SPC-report
0.0 0.0 | SPC-cli |
60.0 0.0 | SPC-format |
0.0 0.0 | SPC-report |
And notice that SPC-format
is partially impelmented!
Implementing Unit Tests
In order for SPC-format to be completely implemented and tested we need to impelemnt
it's tst-
subarts
Note:
tst-
subarts contribute to both tst% and spc%, the idea being that implementing your unit tests are necessary to being actually done.
Let's implement some tests. First create our test files:
mkdir flash/tests
touch flash/tests/__init__.py
touch flash/tests/test_load.py
Then write our tests in flash/tests/test_load.py
:
import os
import unittest
from StringIO import StringIO
from .. import load
script_dir = os.path.split(__file__)[0]
class TestLoadIo(unittest.TestCase):
def test_basic(self):
"""#SPC-format.tst-basic"""
text = '''\
one,1
two,2
three,3'''
result = load.load_io(StringIO(text))
expected = [
load.Question("one", "1"),
load.Question("two", "2"),
load.Question("three", "3"),
]
self.assertEqual(result, expected)
def test_csv(self):
"""#SPC-format.tst-load"""
path = os.path.join(script_dir, 'example.csv')
result = load.load_path(path)
expected = [
load.Question("foo", "bar"),
load.Question("forest", "ham"),
load.Question("I", "love"),
load.Question("you", "too"),
]
self.assertEqual(result, expected)
def test_invalid_columns(self):
"""#SPC-format.tst-invalid_cols"""
# extra ',' after 1
text = '''\
one,1,
two,2
three,3'''
with self.assertRaises(ValueError):
load.load_io(StringIO(text))
def test_duplicate(self):
"""#SPC-format.tst-duplicates"""
# note: extra ',' after 1
text = '''\
one,1,
two,2
three,3
two,2'''
with self.assertRaises(ValueError):
load.load_io(StringIO(text))
def test_valid_line_ending(self):
"""The last line should be able to end with '\n'."""
text = '''\
one,1
two,2
three,3
'''
result = load.load_io(StringIO(text))
expected = [
load.Question("one", "1"),
load.Question("two", "2"),
load.Question("three", "3"),
]
self.assertEqual(result, expected)
Notice that we also have an extra unit test. That's okay, not every test needs a coresponding spec in the real world either!
Summary
We have successfully implemented and tested one artifact (SPC-format
), along
with all of its subarts. We did this by implementing it in source code.
View the current status using art serve
.
You can also see the example here.
Notice that SPC-format
is considered both spc%
and tst%
complete and so
is green. Furthermore, when you look at it you can see the lines where it is
implemented.