dat-science 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +36 -0
- data/dat-science.gemspec +1 -1
- data/lib/dat/science/experiment.rb +35 -1
- data/lib/dat/science/result.rb +12 -6
- data/test/dat_science_experiment_test.rb +35 -0
- metadata +2 -2
data/README.md
CHANGED
@@ -85,6 +85,19 @@ After creating a subclass, tell `Dat::Science` to instantiate it any time the
|
|
85
85
|
Dat::Science.experiment = MyApp::Experiment
|
86
86
|
```
|
87
87
|
|
88
|
+
### Controlling comparison
|
89
|
+
|
90
|
+
By default the results of the `candidate` and `control` blocks are compared
|
91
|
+
with `==`. Use `comparator` to do something more fancy:
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
science "loose-comparison" do |e|
|
95
|
+
e.control { "vmg" }
|
96
|
+
e.candidate { "VMG" }
|
97
|
+
e.comparator { |a, b| a.downcase == b.downcase }
|
98
|
+
end
|
99
|
+
```
|
100
|
+
|
88
101
|
### Ramping up experiments
|
89
102
|
|
90
103
|
By default the `candidate` block of an experiment will run 100% of the time.
|
@@ -173,9 +186,32 @@ end
|
|
173
186
|
`context` takes a Symbol-keyed Hash of additional information to publish and
|
174
187
|
merges it with the default payload.
|
175
188
|
|
189
|
+
#### Keeping it clean
|
190
|
+
|
191
|
+
Sometimes the things you're comparing can be huge, and there's no good way
|
192
|
+
to do science against something simpler. Use a `cleaner` to publish a
|
193
|
+
simple version of a big nasty object graph:
|
194
|
+
|
195
|
+
```ruby
|
196
|
+
science "huge-results" do |e|
|
197
|
+
e.control { OldAndBusted.huge_results_for query }
|
198
|
+
e.candidate { NewHotness.huge_results_for query }
|
199
|
+
e.cleaner { |result| result.count }
|
200
|
+
end
|
201
|
+
```
|
202
|
+
|
203
|
+
The results of the `control` and `candidate` blocks will be run through the
|
204
|
+
`cleaner` You could get the same behavior by calling `count` in the blocks,
|
205
|
+
but the `cleaner` makes it easier to keep things in sync. The original
|
206
|
+
`control` result is still returned.
|
207
|
+
|
176
208
|
## Hacking on science
|
177
209
|
|
178
210
|
Be on a Unixy box. Make sure a modern Bundler is available. `script/test` runs
|
179
211
|
the unit tests. All development dependencies will be installed automatically if
|
180
212
|
they're not available. Dat science happens primarily on Ruby 1.9.3 and 1.8.7,
|
181
213
|
but science should be universal.
|
214
|
+
|
215
|
+
## Maintainers
|
216
|
+
|
217
|
+
[@jbarnette](https://github.com/jbarnette) and [@rick](https://github.com/rick)
|
data/dat-science.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |gem|
|
2
2
|
gem.name = "dat-science"
|
3
|
-
gem.version = "1.
|
3
|
+
gem.version = "1.2.0"
|
4
4
|
gem.authors = ["John Barnette", "Rick Bradley"]
|
5
5
|
gem.email = ["jbarnette@github.com"]
|
6
6
|
gem.description = "Gradually test, measure, and track refactored code."
|
@@ -13,6 +13,8 @@ module Dat
|
|
13
13
|
# optional `block` if it's provided.
|
14
14
|
def initialize(name, &block)
|
15
15
|
@candidate = nil
|
16
|
+
@cleaner = lambda { |r| r }
|
17
|
+
@comparator = lambda { |a, b| a == b }
|
16
18
|
@context = { :experiment => name }
|
17
19
|
@control = nil
|
18
20
|
@name = name
|
@@ -27,6 +29,38 @@ module Dat
|
|
27
29
|
@candidate
|
28
30
|
end
|
29
31
|
|
32
|
+
# Internal: Run the cleaner on a value.
|
33
|
+
def clean(value)
|
34
|
+
cleaner.call value
|
35
|
+
end
|
36
|
+
|
37
|
+
# Public: Declare a cleaner `block` to scrub the result before it's
|
38
|
+
# published. `block` is called twice, once with the result of
|
39
|
+
# the control behavior and once with the result of the candidate.
|
40
|
+
# Exceptions during cleaning are treated as if they were raised
|
41
|
+
# in a candidate or control behavior block: They're reported as part
|
42
|
+
# of the result.
|
43
|
+
#
|
44
|
+
# Returns `block`.
|
45
|
+
def cleaner(&block)
|
46
|
+
@cleaner = block if block
|
47
|
+
@cleaner
|
48
|
+
end
|
49
|
+
|
50
|
+
# Internal: Run the comparator on two values.
|
51
|
+
def compare(a, b)
|
52
|
+
comparator.call a, b
|
53
|
+
end
|
54
|
+
|
55
|
+
# Public: Declare a comparator `block`. Results are compared with
|
56
|
+
# `==` by default.
|
57
|
+
#
|
58
|
+
# Returns `block`.
|
59
|
+
def comparator(&block)
|
60
|
+
@comparator = block if block
|
61
|
+
@comparator
|
62
|
+
end
|
63
|
+
|
30
64
|
# Public: Add a Hash of `payload` data to be included when events are
|
31
65
|
# published or returns the current context if `payload` is `nil`.
|
32
66
|
def context(payload = nil)
|
@@ -107,7 +141,7 @@ module Dat
|
|
107
141
|
end
|
108
142
|
|
109
143
|
duration = (Time.now - start) * 1000
|
110
|
-
Science::Result.new value, duration, raised
|
144
|
+
Science::Result.new self, value, duration, raised
|
111
145
|
end
|
112
146
|
|
113
147
|
|
data/lib/dat/science/result.rb
CHANGED
@@ -5,18 +5,20 @@ module Dat
|
|
5
5
|
class Result
|
6
6
|
attr_reader :duration
|
7
7
|
attr_reader :exception
|
8
|
+
attr_reader :experiment
|
8
9
|
attr_reader :value
|
9
10
|
|
10
|
-
def initialize(value, duration, exception)
|
11
|
-
@duration
|
12
|
-
@exception
|
13
|
-
@
|
11
|
+
def initialize(experiment, value, duration, exception)
|
12
|
+
@duration = duration
|
13
|
+
@exception = exception
|
14
|
+
@experiment = experiment
|
15
|
+
@value = value
|
14
16
|
end
|
15
17
|
|
16
18
|
def ==(other)
|
17
19
|
return false unless other.is_a? Dat::Science::Result
|
18
20
|
|
19
|
-
values_are_equal = other.value
|
21
|
+
values_are_equal = experiment.compare(other.value, value)
|
20
22
|
both_raised = other.raised? && raised?
|
21
23
|
neither_raised = !other.raised? && !raised?
|
22
24
|
|
@@ -33,7 +35,11 @@ module Dat
|
|
33
35
|
end
|
34
36
|
|
35
37
|
def payload
|
36
|
-
{
|
38
|
+
{
|
39
|
+
:duration => duration,
|
40
|
+
:exception => exception,
|
41
|
+
:value => experiment.clean(value)
|
42
|
+
}
|
37
43
|
end
|
38
44
|
|
39
45
|
def raised?
|
@@ -36,6 +36,41 @@ class DatScienceExperimentTest < MiniTest::Unit::TestCase
|
|
36
36
|
assert_same b, e.candidate
|
37
37
|
end
|
38
38
|
|
39
|
+
def test_cleaner
|
40
|
+
e = Experiment.new "foo"
|
41
|
+
e.control { "bar" }
|
42
|
+
e.candidate { "baz" }
|
43
|
+
e.cleaner { |value| value.upcase }
|
44
|
+
|
45
|
+
e.run
|
46
|
+
|
47
|
+
event, payload = Experiment.published.first
|
48
|
+
|
49
|
+
assert_equal "BAR", payload[:control][:value]
|
50
|
+
assert_equal "BAZ", payload[:candidate][:value]
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_cleaner_still_returns_unclean_result
|
54
|
+
e = Experiment.new "foo"
|
55
|
+
e.control { "bar" }
|
56
|
+
e.candidate { "baz" }
|
57
|
+
e.cleaner { |v| v.upcase }
|
58
|
+
|
59
|
+
assert_equal "bar", e.run
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_comparator
|
63
|
+
e = Experiment.new "foo"
|
64
|
+
e.control { "bar" }
|
65
|
+
e.candidate { "bar" }
|
66
|
+
e.comparator { |a, b| false }
|
67
|
+
|
68
|
+
e.run
|
69
|
+
|
70
|
+
event, payload = Experiment.published.first
|
71
|
+
assert_equal :mismatch, event
|
72
|
+
end
|
73
|
+
|
39
74
|
def test_context_default
|
40
75
|
e = Experiment.new "foo"
|
41
76
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dat-science
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2013-
|
13
|
+
date: 2013-03-14 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: minitest
|