minitest 5.16.0 → 5.16.3
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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/History.rdoc +24 -0
- data/README.rdoc +2 -1
- data/lib/minitest/mock.rb +45 -13
- data/lib/minitest/test.rb +11 -4
- data/lib/minitest/test_task.rb +1 -1
- data/lib/minitest.rb +1 -1
- data/test/minitest/test_minitest_mock.rb +98 -1
- data/test/minitest/test_minitest_test.rb +90 -0
- data.tar.gz.sig +2 -1
- metadata +4 -4
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9c5a4db9f495b34c86f2c39eef6b6d424b22d8d9e6a86038bf0ff84c1c20c975
|
4
|
+
data.tar.gz: b8a1b6cb226d14c6972460bec05ec610d05acdfa0edcffe19bf51e8220486206
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6505a8f386da89263a005ba8eef3f81053e7e666a54ec03abd8f71e06c290ebf318df4f7728e420e3f40fab55ffe1a57824ec435286c89864ea830d330d6b028
|
7
|
+
data.tar.gz: 5b8bbba7d8565026e64da1d3af31a5aa342c3d063acd75de9661bc09fb142f4a469e9febf44daf2764d4fb33021d993a27bd936200efded501158b23c299989e
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/History.rdoc
CHANGED
@@ -1,3 +1,27 @@
|
|
1
|
+
=== 5.16.3 / 2022-08-17
|
2
|
+
|
3
|
+
* 2 bug fixes:
|
4
|
+
|
5
|
+
* Fixed exception sanitization by removing TypeError restriction on rescue.
|
6
|
+
* Use A instead of deprecated TESTOPTS in rake test:slow. (davidstosik)
|
7
|
+
|
8
|
+
=== 5.16.2 / 2022-07-03
|
9
|
+
|
10
|
+
* 4 bug fixes:
|
11
|
+
|
12
|
+
* Added MT_KWARGS_HACK kludge for stub to deal with ruby 2.7 kwargs nastiness. (tsugimoto)
|
13
|
+
* In #expect, pop Hash class from args if $MT_KWARGS_HACK. (casperisfine)
|
14
|
+
* In above scenario, set expected kwargs (as Objects) based on actual kwargs.
|
15
|
+
* Nuke ivars if exception fails to marshal twice (eg better_errors). (irphilli)
|
16
|
+
|
17
|
+
=== 5.16.1 / 2022-06-20
|
18
|
+
|
19
|
+
* 2 bug fixes:
|
20
|
+
|
21
|
+
* Apparently adding real kwarg support to mocks/stubs broke some code. Fixed.
|
22
|
+
* Use `MT_KWARGS_HACK=1` to activate the kludgy kwargs support w/ caveats.
|
23
|
+
* Clarified some doco wrt the block on #stub.
|
24
|
+
|
1
25
|
=== 5.16.0 / 2022-06-14
|
2
26
|
|
3
27
|
* 2 major enhancements:
|
data/README.rdoc
CHANGED
@@ -641,6 +641,7 @@ minitest-capistrano :: Assertions and expectations for testing
|
|
641
641
|
Capistrano recipes.
|
642
642
|
minitest-capybara :: Capybara matchers support for minitest unit and
|
643
643
|
spec.
|
644
|
+
minitest-cc :: It provides minimal information about code coverage.
|
644
645
|
minitest-chef-handler :: Run Minitest suites as Chef report handlers
|
645
646
|
minitest-ci :: CI reporter plugin for Minitest.
|
646
647
|
minitest-context :: Defines contexts for code reuse in Minitest
|
@@ -739,7 +740,7 @@ minitest-stub-const :: Stub constants for the duration of a block.
|
|
739
740
|
minitest-tags :: Add tags for minitest.
|
740
741
|
minitest-unordered :: Adds a new assertion to minitest for checking the
|
741
742
|
contents of a collection, ignoring element order.
|
742
|
-
minitest-vcr :: Automatic cassette
|
743
|
+
minitest-vcr :: Automatic cassette management with Minitest::Spec
|
743
744
|
and VCR.
|
744
745
|
minitest_log :: Adds structured logging, data explication, and verdicts.
|
745
746
|
minitest_owrapper :: Get tests results as a TestResult object.
|
data/lib/minitest/mock.rb
CHANGED
@@ -51,6 +51,8 @@ module Minitest # :nodoc:
|
|
51
51
|
@actual_calls = Hash.new { |calls, name| calls[name] = [] }
|
52
52
|
end
|
53
53
|
|
54
|
+
@@KW_WARNED = false # :nodoc:
|
55
|
+
|
54
56
|
##
|
55
57
|
# Expect that method +name+ is called, optionally with +args+ (and
|
56
58
|
# +kwargs+ or a +blk+, and returns +retval+.
|
@@ -91,10 +93,26 @@ module Minitest # :nodoc:
|
|
91
93
|
|
92
94
|
if block_given?
|
93
95
|
raise ArgumentError, "args ignored when block given" unless args.empty?
|
96
|
+
raise ArgumentError, "kwargs ignored when block given" unless kwargs.empty?
|
94
97
|
@expected_calls[name] << { :retval => retval, :block => blk }
|
95
98
|
else
|
96
99
|
raise ArgumentError, "args must be an array" unless Array === args
|
97
|
-
|
100
|
+
|
101
|
+
if ENV["MT_KWARGS_HAC\K"] && (Hash === args.last ||
|
102
|
+
Hash == args.last) then
|
103
|
+
if kwargs.empty? then
|
104
|
+
kwargs = args.pop
|
105
|
+
else
|
106
|
+
unless @@KW_WARNED then
|
107
|
+
from = caller.first
|
108
|
+
warn "Using MT_KWARGS_HAC\K yet passing kwargs. From #{from}"
|
109
|
+
@@KW_WARNED = true
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
@expected_calls[name] <<
|
115
|
+
{ :retval => retval, :args => args, :kwargs => kwargs }
|
98
116
|
end
|
99
117
|
self
|
100
118
|
end
|
@@ -150,6 +168,9 @@ module Minitest # :nodoc:
|
|
150
168
|
expected_args, expected_kwargs, retval, val_block =
|
151
169
|
expected_call.values_at(:args, :kwargs, :retval, :block)
|
152
170
|
|
171
|
+
expected_kwargs = kwargs.map { |ak, av| [ak, Object] }.to_h if
|
172
|
+
Hash == expected_kwargs
|
173
|
+
|
153
174
|
if val_block then
|
154
175
|
# keep "verify" happy
|
155
176
|
@actual_calls[sym] << expected_call
|
@@ -250,7 +271,7 @@ class Object
|
|
250
271
|
# NOTE: keyword args in callables are NOT checked for correctness
|
251
272
|
# against the existing method. Too many edge cases to be worth it.
|
252
273
|
|
253
|
-
def stub name, val_or_callable, *block_args, **block_kwargs
|
274
|
+
def stub name, val_or_callable, *block_args, **block_kwargs, &block
|
254
275
|
new_name = "__minitest_stub__#{name}"
|
255
276
|
|
256
277
|
metaclass = class << self; self; end
|
@@ -263,26 +284,37 @@ class Object
|
|
263
284
|
|
264
285
|
metaclass.send :alias_method, new_name, name
|
265
286
|
|
266
|
-
|
267
|
-
|
268
|
-
if
|
287
|
+
if ENV["MT_KWARGS_HAC\K"] then
|
288
|
+
metaclass.send :define_method, name do |*args, &blk|
|
289
|
+
if val_or_callable.respond_to? :call then
|
269
290
|
val_or_callable.call(*args, &blk)
|
270
291
|
else
|
271
|
-
|
292
|
+
blk.call(*block_args, **block_kwargs) if blk
|
293
|
+
val_or_callable
|
272
294
|
end
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
295
|
+
end
|
296
|
+
else
|
297
|
+
metaclass.send :define_method, name do |*args, **kwargs, &blk|
|
298
|
+
if val_or_callable.respond_to? :call then
|
299
|
+
if kwargs.empty? then # FIX: drop this after 2.7 dead
|
300
|
+
val_or_callable.call(*args, &blk)
|
277
301
|
else
|
278
|
-
|
302
|
+
val_or_callable.call(*args, **kwargs, &blk)
|
303
|
+
end
|
304
|
+
else
|
305
|
+
if blk then
|
306
|
+
if block_kwargs.empty? then # FIX: drop this after 2.7 dead
|
307
|
+
blk.call(*block_args)
|
308
|
+
else
|
309
|
+
blk.call(*block_args, **block_kwargs)
|
310
|
+
end
|
279
311
|
end
|
312
|
+
val_or_callable
|
280
313
|
end
|
281
|
-
val_or_callable
|
282
314
|
end
|
283
315
|
end
|
284
316
|
|
285
|
-
|
317
|
+
block[self]
|
286
318
|
ensure
|
287
319
|
metaclass.send :undef_method, name
|
288
320
|
metaclass.send :alias_method, name, new_name
|
data/lib/minitest/test.rb
CHANGED
@@ -204,7 +204,7 @@ module Minitest
|
|
204
204
|
def sanitize_exception e # :nodoc:
|
205
205
|
Marshal.dump e
|
206
206
|
e # good: use as-is
|
207
|
-
rescue
|
207
|
+
rescue
|
208
208
|
neuter_exception e
|
209
209
|
end
|
210
210
|
|
@@ -213,15 +213,22 @@ module Minitest
|
|
213
213
|
msg = e.message.dup
|
214
214
|
|
215
215
|
new_exception e.class, msg, bt # e.class can be a problem...
|
216
|
-
rescue
|
216
|
+
rescue
|
217
217
|
msg.prepend "Neutered Exception #{e.class}: "
|
218
218
|
|
219
|
-
new_exception RuntimeError, msg, bt
|
219
|
+
new_exception RuntimeError, msg, bt, true # but if this raises, we die
|
220
220
|
end
|
221
221
|
|
222
|
-
def new_exception klass, msg, bt
|
222
|
+
def new_exception klass, msg, bt, kill = false
|
223
223
|
ne = klass.new msg
|
224
224
|
ne.set_backtrace bt
|
225
|
+
|
226
|
+
if kill then
|
227
|
+
ne.instance_variables.each do |v|
|
228
|
+
ne.remove_instance_variable v
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
225
232
|
Marshal.dump ne # can raise TypeError
|
226
233
|
ne
|
227
234
|
end
|
data/lib/minitest/test_task.rb
CHANGED
data/lib/minitest.rb
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
require "minitest/autorun"
|
2
2
|
|
3
|
+
def with_kwargs_env
|
4
|
+
ENV["MT_KWARGS_HAC\K"] = "1"
|
5
|
+
|
6
|
+
yield
|
7
|
+
ensure
|
8
|
+
ENV.delete "MT_KWARGS_HAC\K"
|
9
|
+
end
|
10
|
+
|
3
11
|
class TestMinitestMock < Minitest::Test
|
4
12
|
parallelize_me!
|
5
13
|
|
@@ -363,6 +371,61 @@ class TestMinitestMock < Minitest::Test
|
|
363
371
|
assert_mock mock
|
364
372
|
end
|
365
373
|
|
374
|
+
def test_mock_allow_all_kwargs__old_style_env
|
375
|
+
with_kwargs_env do
|
376
|
+
mock = Minitest::Mock.new
|
377
|
+
mock.expect :foo, true, [Hash]
|
378
|
+
assert_equal true, mock.foo(bar: 42)
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
def test_mock_allow_all_kwargs__old_style_env__rewrite
|
383
|
+
with_kwargs_env do
|
384
|
+
mock = Minitest::Mock.new
|
385
|
+
mock.expect :foo, true, [], bar: Integer
|
386
|
+
assert_equal true, mock.foo(bar: 42)
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
def test_mock_block_is_passed_keyword_args__args__old_style_bad
|
391
|
+
arg1, arg2, arg3 = :bar, [1, 2, 3], { :a => "a" }
|
392
|
+
mock = Minitest::Mock.new
|
393
|
+
mock.expect :foo, nil, [{k1: arg1, k2: arg2, k3: arg3}]
|
394
|
+
|
395
|
+
e = assert_raises ArgumentError do
|
396
|
+
mock.foo(k1: arg1, k2: arg2, k3: arg3)
|
397
|
+
end
|
398
|
+
|
399
|
+
assert_equal "mocked method :foo expects 1 arguments, got []", e.message
|
400
|
+
end
|
401
|
+
|
402
|
+
def test_mock_block_is_passed_keyword_args__args__old_style_env
|
403
|
+
with_kwargs_env do
|
404
|
+
arg1, arg2, arg3 = :bar, [1, 2, 3], { :a => "a" }
|
405
|
+
mock = Minitest::Mock.new
|
406
|
+
mock.expect :foo, nil, [{k1: arg1, k2: arg2, k3: arg3}]
|
407
|
+
|
408
|
+
mock.foo(k1: arg1, k2: arg2, k3: arg3)
|
409
|
+
|
410
|
+
assert_mock mock
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
def test_mock_block_is_passed_keyword_args__args__old_style_both
|
415
|
+
with_kwargs_env do
|
416
|
+
arg1, arg2, arg3 = :bar, [1, 2, 3], { :a => "a" }
|
417
|
+
mock = Minitest::Mock.new
|
418
|
+
|
419
|
+
assert_output nil, /Using MT_KWARGS_HAC. yet passing kwargs/ do
|
420
|
+
mock.expect :foo, nil, [{}], k1: arg1, k2: arg2, k3: arg3
|
421
|
+
end
|
422
|
+
|
423
|
+
mock.foo({}, k1: arg1, k2: arg2, k3: arg3)
|
424
|
+
|
425
|
+
assert_mock mock
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
366
429
|
def test_mock_block_is_passed_keyword_args__args_bad_missing
|
367
430
|
arg1, arg2, arg3 = :bar, [1, 2, 3], { :a => "a" }
|
368
431
|
mock = Minitest::Mock.new
|
@@ -442,7 +505,7 @@ class TestMinitestMock < Minitest::Test
|
|
442
505
|
assert_equal exp, e.message
|
443
506
|
end
|
444
507
|
|
445
|
-
def
|
508
|
+
def test_mock_block_raises_if_args_passed
|
446
509
|
mock = Minitest::Mock.new
|
447
510
|
|
448
511
|
e = assert_raises(ArgumentError) do
|
@@ -456,6 +519,20 @@ class TestMinitestMock < Minitest::Test
|
|
456
519
|
assert_match exp, e.message
|
457
520
|
end
|
458
521
|
|
522
|
+
def test_mock_block_raises_if_kwargs_passed
|
523
|
+
mock = Minitest::Mock.new
|
524
|
+
|
525
|
+
e = assert_raises(ArgumentError) do
|
526
|
+
mock.expect :foo, nil, kwargs:1 do
|
527
|
+
true
|
528
|
+
end
|
529
|
+
end
|
530
|
+
|
531
|
+
exp = "kwargs ignored when block given"
|
532
|
+
|
533
|
+
assert_match exp, e.message
|
534
|
+
end
|
535
|
+
|
459
536
|
def test_mock_returns_retval_when_called_with_block
|
460
537
|
mock = Minitest::Mock.new
|
461
538
|
mock.expect(:foo, 32) do
|
@@ -761,6 +838,26 @@ class TestMinitestStub < Minitest::Test
|
|
761
838
|
end
|
762
839
|
end
|
763
840
|
|
841
|
+
def test_stub__hash_as_last_real_arg
|
842
|
+
with_kwargs_env do
|
843
|
+
token = Object.new
|
844
|
+
def token.create_with_retry u, p; raise "shouldn't see this"; end
|
845
|
+
|
846
|
+
controller = Object.new
|
847
|
+
controller.define_singleton_method :create do |u, p|
|
848
|
+
token.create_with_retry u, p
|
849
|
+
end
|
850
|
+
|
851
|
+
params = Object.new
|
852
|
+
def params.to_hash; raise "nah"; end
|
853
|
+
|
854
|
+
token.stub(:create_with_retry, ->(u, p) { 42 }) do
|
855
|
+
act = controller.create :u, params
|
856
|
+
@tc.assert_equal 42, act
|
857
|
+
end
|
858
|
+
end
|
859
|
+
end
|
860
|
+
|
764
861
|
def test_stub_callable_block_5 # from tenderlove
|
765
862
|
@assertion_count += 1
|
766
863
|
Foo.stub5 :blocking, Bar.new do
|
@@ -786,6 +786,13 @@ class TestMinitestUnitOrder < MetaMetaMetaTestCase
|
|
786
786
|
end
|
787
787
|
end
|
788
788
|
|
789
|
+
class BetterError < RuntimeError # like better_error w/o infecting RuntimeError
|
790
|
+
def set_backtrace bt
|
791
|
+
super
|
792
|
+
@bad_ivar = binding
|
793
|
+
end
|
794
|
+
end
|
795
|
+
|
789
796
|
class TestMinitestRunnable < Minitest::Test
|
790
797
|
def setup_marshal klass
|
791
798
|
tc = klass.new "whatever"
|
@@ -895,6 +902,89 @@ class TestMinitestRunnable < Minitest::Test
|
|
895
902
|
assert_equal @tc.failures, over_the_wire.failures
|
896
903
|
assert_equal @tc.klass, over_the_wire.klass
|
897
904
|
end
|
905
|
+
|
906
|
+
def with_runtime_error klass
|
907
|
+
old_runtime = RuntimeError
|
908
|
+
Object.send :remove_const, :RuntimeError
|
909
|
+
Object.const_set :RuntimeError, klass
|
910
|
+
yield
|
911
|
+
ensure
|
912
|
+
Object.send :remove_const, :RuntimeError
|
913
|
+
Object.const_set :RuntimeError, old_runtime
|
914
|
+
end
|
915
|
+
|
916
|
+
def test_spec_marshal_with_exception__better_error_typeerror
|
917
|
+
klass = describe("whatever") {
|
918
|
+
it("raises with binding") {
|
919
|
+
raise BetterError, "boom"
|
920
|
+
}
|
921
|
+
}
|
922
|
+
|
923
|
+
rm = klass.runnable_methods.first
|
924
|
+
|
925
|
+
# Run the test
|
926
|
+
@tc = with_runtime_error BetterError do
|
927
|
+
klass.new(rm).run
|
928
|
+
end
|
929
|
+
|
930
|
+
assert_kind_of Minitest::Result, @tc
|
931
|
+
assert_instance_of Minitest::UnexpectedError, @tc.failure
|
932
|
+
|
933
|
+
msg = @tc.failure.error.message
|
934
|
+
assert_equal "Neutered Exception BetterError: boom", msg
|
935
|
+
|
936
|
+
# Pass it over the wire
|
937
|
+
over_the_wire = Marshal.load Marshal.dump @tc
|
938
|
+
|
939
|
+
assert_equal @tc.time, over_the_wire.time
|
940
|
+
assert_equal @tc.name, over_the_wire.name
|
941
|
+
assert_equal @tc.assertions, over_the_wire.assertions
|
942
|
+
assert_equal @tc.failures, over_the_wire.failures
|
943
|
+
assert_equal @tc.klass, over_the_wire.klass
|
944
|
+
end
|
945
|
+
|
946
|
+
def test_spec_marshal_with_exception__worse_error_typeerror
|
947
|
+
worse_error_klass = Class.new(StandardError) do
|
948
|
+
# problem #1: anonymous subclass can'tmarshal, fails sanitize_exception
|
949
|
+
def initialize(record = nil)
|
950
|
+
|
951
|
+
super(record.first)
|
952
|
+
end
|
953
|
+
end
|
954
|
+
|
955
|
+
klass = describe("whatever") {
|
956
|
+
it("raises with NoMethodError") {
|
957
|
+
# problem #2: instantiated with a NON-string argument
|
958
|
+
#
|
959
|
+
# problem #3: arg responds to #first, but it becomes message
|
960
|
+
# which gets passed back in via new_exception
|
961
|
+
# that passes a string to worse_error_klass#initialize
|
962
|
+
# which calls first on it, which raises NoMethodError
|
963
|
+
raise worse_error_klass.new(["boom"])
|
964
|
+
}
|
965
|
+
}
|
966
|
+
|
967
|
+
rm = klass.runnable_methods.first
|
968
|
+
|
969
|
+
# Run the test
|
970
|
+
@tc = klass.new(rm).run
|
971
|
+
|
972
|
+
assert_kind_of Minitest::Result, @tc
|
973
|
+
assert_instance_of Minitest::UnexpectedError, @tc.failure
|
974
|
+
|
975
|
+
msg = @tc.failure.error.message.gsub(/0x[A-Fa-f0-9]+/, "0xXXX")
|
976
|
+
|
977
|
+
assert_equal "Neutered Exception #<Class:0xXXX>: boom", msg
|
978
|
+
|
979
|
+
# Pass it over the wire
|
980
|
+
over_the_wire = Marshal.load Marshal.dump @tc
|
981
|
+
|
982
|
+
assert_equal @tc.time, over_the_wire.time
|
983
|
+
assert_equal @tc.name, over_the_wire.name
|
984
|
+
assert_equal @tc.assertions, over_the_wire.assertions
|
985
|
+
assert_equal @tc.failures, over_the_wire.failures
|
986
|
+
assert_equal @tc.klass, over_the_wire.klass
|
987
|
+
end
|
898
988
|
end
|
899
989
|
|
900
990
|
class TestMinitestTest < TestMinitestRunnable
|
data.tar.gz.sig
CHANGED
@@ -1 +1,2 @@
|
|
1
|
-
|
1
|
+
��#3)�~<Lp1���=�ܞb���qG���,]�x,_�.f��� ��|Wqn�g�:����ۊ���-5
|
2
|
+
�3WP�fG&�����TU�5���tj�Ͷ�AoRvn�ܿ����h��������V�ڌ�Xh3�Gp��g���|\%d�L�Y#��D�e�=p�={����t�,I���L�����i���2-��$�~�z����]6F�M{t/֦yϭ
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: minitest
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.16.
|
4
|
+
version: 5.16.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Davis
|
@@ -29,7 +29,7 @@ cert_chain:
|
|
29
29
|
JFmxn4h9YO/pVdB962BdBNNDia0kgIjI3ENnkLq0dKpYU3+F3KhEuTksLO0L6X/V
|
30
30
|
YsuyUzsMz6GQA4khyaMgKNSD
|
31
31
|
-----END CERTIFICATE-----
|
32
|
-
date: 2022-
|
32
|
+
date: 2022-08-17 00:00:00.000000000 Z
|
33
33
|
dependencies:
|
34
34
|
- !ruby/object:Gem::Dependency
|
35
35
|
name: rdoc
|
@@ -57,14 +57,14 @@ dependencies:
|
|
57
57
|
requirements:
|
58
58
|
- - "~>"
|
59
59
|
- !ruby/object:Gem::Version
|
60
|
-
version: '3.
|
60
|
+
version: '3.24'
|
61
61
|
type: :development
|
62
62
|
prerelease: false
|
63
63
|
version_requirements: !ruby/object:Gem::Requirement
|
64
64
|
requirements:
|
65
65
|
- - "~>"
|
66
66
|
- !ruby/object:Gem::Version
|
67
|
-
version: '3.
|
67
|
+
version: '3.24'
|
68
68
|
description: |-
|
69
69
|
minitest provides a complete suite of testing facilities supporting
|
70
70
|
TDD, BDD, mocking, and benchmarking.
|
metadata.gz.sig
CHANGED
Binary file
|