Browse Source

Add basic README and a "live" example

Alois Mahdal 9 years ago
parent
commit
271244f796
4 changed files with 329 additions and 2 deletions
  1. 169
    2
      README.md
  2. 28
    0
      doc/examples/calc.cgi
  3. 31
    0
      doc/examples/calc.sh
  4. 101
    0
      doc/examples/example_test.py

+ 169
- 2
README.md View File

@@ -1,4 +1,171 @@
1 1
 sznqalibs
2
-======
2
+=========
3
+
4
+Collection of python libs developed for testing purposes.
5
+
6
+
7
+hoover
8
+------
9
+
10
+hoover is a testing framework built with following principles
11
+in mind:
12
+
13
+ *  data-driven testing,
14
+ *  easy test data definition (even with huge sets),
15
+ *  helpful reporting.
16
+
17
+Typical use case is that you have a tested system, another
18
+"reference" system and knowledge about testing input.  You then
19
+create drivers for both systems that will parse and prepare
20
+output in a way that it can be compared to eaach other.
21
+
22
+
23
+### Examples ###
24
+
25
+An example is worth 1000 words:
26
+
27
+    from sznqalibs import hoover
28
+
29
+
30
+    class BaloonDriver(hoover.TestDriver):
31
+        """
32
+        Object enclosing SUT or one of its typical use patterns
33
+        """
34
+
35
+        _get_data(self):
36
+            # now do something to obtain results from the SUT
37
+            # using self._argset dictionary
38
+            self.data['sentence'] = subprocess.check_output(
39
+                ['sut', self.args['count'], self.args['color']]
40
+            )
41
+
42
+    class OracleDriver(hoover.TestDriver):
43
+        """
44
+        Object providing Oracle (expected output) for test arguments
45
+        """
46
+
47
+        _get_data(self):
48
+            # obtain expected results, for example by asking
49
+            # reference implementation (or by reimplementing
50
+            # fraction of the SUT, e.g. only for the expected
51
+            # data)
52
+            self.data['sentence'] = ("%(count)s %(color)s baloons"
53
+                                     % self._args)
54
+
55
+    class MyTest(unittest.TestCase):
56
+
57
+        def test_valid(self):
58
+            # as alternative to defining each _args separately,
59
+            # Cartman lets you define just the ranges
60
+            argsrc = hoover.Cartman({
61
+                # for each parameter define iterator with
62
+                # values you want to combine in this test
63
+                'count': xrange(100),
64
+                'color': ['red', 'blue']
65
+            })
66
+            # regression_test will call both drivers once with
67
+            # each argument set, compare results and store results
68
+            # along with some statistics
69
+            tracker = hoover.regression_test(
70
+                argsrc=argsrc,
71
+                tests=[(operator.eq, OracleDriver, BaloonDriver)]
72
+            )
73
+            if tracker.errors_found():
74
+                print tracker.format_report()
75
+
76
+But that's just to get the idea.  For a (hopefully) working
77
+example, look at doc/examples subfolder, there's a "calculator"
78
+implemented in Bash and Perl/CGI, and a *hoover* test that
79
+compares these two implementations to a Python implementation
80
+defined inside the test.
81
+
82
+
83
+### pFAQ (Potentially FAQ) ###
84
+
85
+The truth is that nobody asked any questions so far, so I can't
86
+honestly write FAQ (or even AQ, for that matter) ;).  So at
87
+least I'll try to answer what I feel like people would ask:
88
+
89
+ *  **Q:** What do you mean by implementing "reference", or
90
+    "oracle" driver?  Am I supposed to re-implement the system?
91
+    Are you serious?
92
+
93
+    **A:** Yes, I am serious.  But consider this:
94
+
95
+    First, not all systems are necessarily complicated.  Take
96
+    GNU *cat*.  All  it does is print data.  Open, write,
97
+    close.  The added value is that it's insanely good at it.
98
+    However, your oracle driver does not need to be *that*
99
+    good.  Even if it only was able to check the length or
100
+    MD5 of data, it would be more than nothing.
101
+
102
+    Also if you are creative enough, you can select the data
103
+    in a clever way so that you can develop tricks that can
104
+    help your driver on the way.   For example you could
105
+    "inject" the data with hints on how the result should be.
106
+
107
+    Next, you don't need to actually re-implement the driver.
108
+    As
109
+    the most "brute" strategy, instead of using hoover to
110
+    generate the data, you might want to just go and generate
111
+    the data somehow manually (as you might have done it so
112
+    far), verify it and feed it to your drivers. including
113
+    expected results.  This might not be the most viable option
114
+    for a huge set, but at least what *hoover* will give you is
115
+    the running and reporting engine.
116
+
117
+    Then there might be cases when the system actually *is*
118
+    trivial and you *can* re-implement it, but for some reason
119
+    you don't have a testing framework on the native platform.
120
+    For example, embedded system, or a library that needs to
121
+    be in specific language like bash.  In case it has trivial
122
+    parts, you can test them in *hoover* and save yourself
123
+    some headache with maintenance.
124
+
125
+    Last, but not least--this was actually the story behind
126
+    *hoover* being born--there are cases whan you already
127
+    *have* reference implementation and new implementation, you
128
+    just need to verify that behavior is the same.  So you just
129
+    wrap both systems in drivers, tweak them so that they can
130
+    return the same data (if they already don't) or at least
131
+    data you can write a comparison function for, squeeze them
132
+    all into `hoover.regression_test` and hit the big button.
133
+    Note that you can even have 1 reference driver and N SUT
134
+    drivers, which can save you kajillions of machine seconds
135
+    if your old library is slow or resource-hungry but you have
136
+    more ports of the new one.
137
+
138
+    As a bonus, note that *hoover* can also provide you with
139
+    some performance stats.  Well, there's absolutely no intent
140
+    to say that this is a proper performance measurement tool
141
+    (it's actually been designed to assess performance of the
142
+    drivers), but on the other hand, it comes with the package,
143
+    so it might be useful for you.
144
+
145
+ *  **Q:** Is it mature?
146
+
147
+    **A:** No and a tiny yes.
148
+
149
+    Yes, because it has already been used in real environment
150
+    and it succeeded.  But then again, it has been deployed by
151
+    author, and he has no idea if that's actually doable for
152
+    any sane person.  You are more than welcome to try it and
153
+    provide me with feedback and I can't provide any kind of
154
+    guarranteees whatsoever.
155
+
156
+    No, because there are parts that are still far from being
157
+    polished, easy to use  or even possible to understand.
158
+    (Heck, at this moment even I don't understand what `RuleOp`
159
+    is or was for :D).  And there are probably limitations that
160
+    could be removed.
161
+
162
+    That said, the code is not a complete utter holy mess,
163
+    though.
164
+
165
+    But the API **will** change.  Things will be re-designed
166
+    and some even removed or split to other modules.
167
+
168
+    My current "strategy", however, is to do this on the run,
169
+    probably based on real  experience when trying to use it in
170
+    real testing scenarios.
3 171
 
4
-collection of python libs developed for testing purposes

+ 28
- 0
doc/examples/calc.cgi View File

@@ -0,0 +1,28 @@
1
+#!/usr/bin/perl
2
+
3
+use strict;
4
+use warnings;
5
+
6
+use CGI;
7
+
8
+my $q = CGI -> new();
9
+
10
+$q->import_names;
11
+my $op = $Q::op;
12
+my $a = $Q::a;
13
+my $b = $Q::b;
14
+
15
+my $ops = {
16
+    add => sub { $_[0] + $_[1] },
17
+    sub => sub { $_[0] - $_[1] },
18
+    mul => sub { $_[0] * $_[1] },
19
+    div => sub { $_[0] / $_[1] },
20
+};
21
+
22
+if (defined $ops->{$op}) {
23
+    print "Content-type: text/plain\n\n";
24
+    print $ops->{$op}->($a, $b);
25
+    print "\n";
26
+} else {
27
+    warn "unsupported operator: $op\n";
28
+}

+ 31
- 0
doc/examples/calc.sh View File

@@ -0,0 +1,31 @@
1
+#!/bin/sh
2
+
3
+op=$1
4
+a=$2
5
+b=$3
6
+result=
7
+
8
+usage() {
9
+    echo "usage: $(basename $0) operator a b"
10
+    exit 1
11
+}
12
+
13
+case $op in
14
+    add)
15
+        result=$(( a + b ))
16
+        ;;
17
+    sub)
18
+        result=$(( a - b ))
19
+        ;;
20
+    mul)
21
+        result=$(( a * b ))
22
+        ;;
23
+    div)
24
+        result=$(( a / b ))
25
+        ;;
26
+    *)
27
+        usage
28
+        ;;
29
+esac
30
+
31
+echo $result

+ 101
- 0
doc/examples/example_test.py View File

@@ -0,0 +1,101 @@
1
+#!/usr/bin/python
2
+
3
+import httplib
4
+import operator
5
+import subprocess
6
+import unittest
7
+import urlparse
8
+
9
+from sznqalibs import hoover
10
+
11
+
12
+class BaseCalcDriver(hoover.BaseTestDriver):
13
+
14
+    def __init__(self):
15
+        super(BaseCalcDriver, self).__init__()
16
+        self._mandatory_args = ['op', 'a', 'b']
17
+
18
+        def bailout_on_zerodiv(argset):
19
+            if argset['op'] == 'div':
20
+                return argset['b'] == 0
21
+            return False
22
+
23
+        self.bailouts = [bailout_on_zerodiv]
24
+
25
+
26
+class PyCalcDriver(BaseCalcDriver):
27
+
28
+    def _get_data(self):
29
+        ops = {
30
+            'add': lambda a, b: float(a) + b,
31
+            'sub': lambda a, b: float(a) - b,
32
+            'mul': lambda a, b: float(a) * b,
33
+            'div': lambda a, b: float(a) / b,
34
+        }
35
+        a = self._args['a']
36
+        b = self._args['b']
37
+        op = self._args['op']
38
+        self.data['result'] = ops[op](a, b)
39
+
40
+
41
+class CgiCalcDriver(BaseCalcDriver):
42
+
43
+    def __init__(self):
44
+        super(CgiCalcDriver, self).__init__()
45
+        self._mandatory_settings = ['uri']
46
+
47
+    def _get_data(self):
48
+        pq = "op=%(op)s&a=%(a)s&b=%(b)s" % self._args
49
+        parsed_url = urlparse.urlparse(self._settings['uri'])
50
+        conn = httplib.HTTPConnection(parsed_url.hostname)
51
+        conn.request("GET", "%s?%s" % (parsed_url.path, pq))
52
+        resp = conn.getresponse()
53
+        assert resp.status == 200
54
+        self.data['result'] = float(resp.read().strip())
55
+
56
+
57
+class CliCalcDriver(BaseCalcDriver):
58
+
59
+    def __init__(self):
60
+        super(CliCalcDriver, self).__init__()
61
+        self._mandatory_settings = ['cmd']
62
+
63
+    def _get_data(self):
64
+        cmd = [
65
+            self._settings['cmd'],
66
+            self._args['op'],
67
+            str(self._args['a']),
68
+            str(self._args['b']),
69
+        ]
70
+        out = subprocess.check_output(cmd)
71
+        self.data['result'] = float(out.strip())
72
+
73
+
74
+class TestCase(unittest.TestCase):
75
+
76
+    def setUp(self):
77
+        self.driver_settings = {
78
+            'uri': "http://myserver/cgi-bin/calc.cgi",
79
+            'cmd': "./calc.sh"
80
+        }
81
+        self.scheme = dict.fromkeys(['op', 'a', 'b'], hoover.Cartman.Iterable)
82
+
83
+    def test_using_rt(self):
84
+        argsrc = hoover.Cartman({
85
+            'op': ['add', 'sub', 'mul', 'div'],
86
+            'a': [-10, -1, 1, 10, 1000],
87
+            'b': [-10, -1, 1, 10, 1000],
88
+            }, self.scheme
89
+        )
90
+        tests = [
91
+            (operator.eq, PyCalcDriver, CliCalcDriver),
92
+            (operator.eq, PyCalcDriver, CliCalcDriver),
93
+        ]
94
+        tracker = hoover.regression_test(argsrc, tests, self.driver_settings)
95
+        print hoover.jsDump(tracker.getstats())
96
+        if tracker.errors_found():
97
+            self.fail(tracker.format_report())
98
+
99
+
100
+if __name__ == '__main__':
101
+    unittest.main()