TDD for Parser
I am documenting here for Parser
instead of doing in the main page, because,
this is considered as an extra supplement.
Writing failing tests for the spec on the top
In the file spec/parser_spec.rb
pick the top most example and write expectation
in using RSpec DSL.
See the example below. First create the class Parser
in /lib/subjuster/parser.rb
. Require that file in lib/subjuster.rb
as well.
# subjuster/parser.rb
module Subjuster
class Parser
end
end
# spec/parser_spec.rb
it 'Should take `user_input` as params' do
inputs = Subjuster::UserInput.new(source: 'somefilename')
expect(Subjuster::Parser.new(inputs: inputs).inputs).to equal(inputs)
end
then you run example, you see following error
ArgumentError:
wrong number of arguments (given 1, expected 0)
# ./spec/parser_spec.rb:6:in `initialize'
# ./spec/parser_spec.rb:6:in `new'
# ./spec/parser_spec.rb:6:in `block (2 levels) in <top (required)>'
then you define constructor
and params
, also set attr_reader
. You are doing
in straight forward fashion, because, you have already been through this before.
module Subjuster
class Parser
attr_reader :inputs
def initialize(inputs:)
@inputs = inputs
end
end
end
Now, we target for next example which is rather complex
to test.
it 'Should able to parse the valid `srt` file'
Now important thing is, to test this module we need test data also called fixture data. So, you need to prepare the test data before your test-example runs.
def srt_content
<<-STR
1
00:00:57,918 --> 00:01:02,514
"In order to affect a timely halt
to deteriorating conditions
2
00:01:02,589 --> 00:01:05,183
and to ensure the common good,
3
00:01:05,259 --> 00:01:08,626
a state of emergency is declared
for these territories
STR
end
Since we are practicing Unit Testing
strategy, we cannot let our test-suite use FileSystem API
to read/write to file. Therefore we created a method to return file contents.
But Why?
Because, of the following reasons:-
- Firstly, the content of the file will already be known to you; so no need to pretend to read back from FileSystem.
- File IO is very slow relative to Ruby execution.
- Also, the philosophy of UnitTesting is to test the system in 100% isolation, if it touches File IO then the rules is violated.
What would be the solution then?
You will have to assume that File.read
always works, so you create a stub
of the File.read
method and mock in your example. We wrote the failing test like,
it 'Should able to parse the valid `srt` file' do
# Mocking any request goes to `File.read`
str_content = fixture_data
allow(File).to receive(:read){str_content}
inputs = Subjuster::UserInput.new(source: 'somefilename')
expect(Subjuster::Parser.new(inputs: inputs).parse.first).to be_a(Hash)
end
In the example above, we mocked the behavior of File.read
to return obvious data. This is called Monkey Patching.
When we run this example, we see the following error
NoMethodError:
undefined method `parse' for #<Subjuster::Parser:0x00562f71227670>
We will add the method Parser#parse
and add production code and try to make it GREEN.
Now, we have completed the code for parse to pass the test. see the file here
First, we solved the problem in any way we could, then we refactored the code to make it readable, comprehensible and if possible performant.
** Optimized code **
module Subjuster
class Parser
attr_reader :inputs
def initialize(inputs:)
@inputs = inputs
end
def parse
items = []
file_content_array = File.read(inputs.source_filepath).split("\n")
count = file_content_array.count
index = 0
while index < count do
line = file_content_array[index]
if line =~ /\A[-+]?[0-9]+\z/
splitted_line = file_content_array[index+1].split(' --> ')
dialog, index = find_dialog_from(list: file_content_array, index: index + 2)
items << { id: line, start_time: splitted_line.first, end_time: splitted_line.last, dialog: dialog }
end
end
items
end
private
# This will find next `dialog number` embedded line and
# return lines joined upto that line, along with the index of line of the `dialog num`
def find_dialog_from(list:, index:)
buffer = []
count = list.count
while !(list[index] =~ /\A[-+]?[0-9]+\z/) && index < count do
buffer << list[index]
index += 1
end
[buffer.join("\n"), index]
end
end
end
Now, the tests passes.
Then we pick Subjuster::Adjuster
.