Friday, October 23, 2020

2.4 TDD Example

 Assume that you work for a service provider, and must develop a script that extracts customer data from a router configuration. The script should parse customer data, such as customer name, customer VLAN, and customer IP address, and return the data in the JavaScript Object Notation (JSON) format.

To simplify the task, the router configuration should already be in the config.txt file, located in the same folder as the script.

As a testing framework, you should use unittest.

By observing the configuration in the config.txt file, you verify that the required customer data is available in the configuration.

ip vrf CUSTOMER_A
  rd 65000:100 
!
ip vrf CUSTOMER_B
  rd 65000:101 
!
interface GigabitEthernet0/0.100
  encapsulation dot1Q 100
  ip vrf forwarding CUSTOMER_A 
  ip address 10.10.100.1 255.255.255.0
  no ip redirects 
  ip nat outside 
  ip virtual-reassembly in 
!
interface GigabitEthernet0/0.101
  encapsulation dot1Q 101
  ip vrf forwarding CUSTOMER_B 
  ip address 10.10.101.1 255.255.255.0
  no ip redirects 
  ip nat outside 
  ip virtual-reassembly in 
!
Interface                  IP-Address
Embedded-Service-Engine0/0 unassigned
GigabitEthernet0/0         192.168.254.2
GigabitEthernet0/0.100     10.10.100.1
GigabitEthernet0/0.101     10.10.101.1

Development Procedure

Before actual programming, you decide to build a script in multiple iterations. Each iteration will already fulfill part of the requirements, but the last iteration will group everything.

Iterations:

  • Iteration 1: Parse Customer Names

  • Iteration 2: Parse Customer IP Addresses

  • Iteration 3: Parse Customer VLANs

  • Iteration 4: Group All Together

Steps used for a specific iteration:

  1. Write tests

  2. Run tests (should fail)

  3. Write code

  4. Run tests (should pass)

  5. Refactor code

Parse Customer Names

Step 1: Write tests.


During the first iteration, you decide to parse customer names from the configuration.

To follow the rules of the TDD, first create tests for parsing customer names.

You decide that the class responsible for configuration parsing will be called ConfigurationParser. This class will contain all methods required for parsing customer data. Therefore, you can already instantiate the class in the test. You can also create this class, but without any functionality.

cp = ConfigurationParser()

Next, you decide that parsed customer names should be returned in a Python list.

expected_names = ['CUSTOMER_A', 'CUSTOMER_B']

The method that parses customer names is called parseCustomerNames().

parsed_names = cp.parseCustomerNames()

At the end of the test, assertions are being made to check the method output based on the input.

self.assertEqual(list, type(parsed_name))
self.assertEqual(expected_names, parsed_names)

Step 2: Run tests (should fail).

$ python3.7 -m unittest test_parser.py
E
======================================================================
ERROR: test_parse_cust_names (test_parser.TestParse)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_parser.py," line 8, in test_parse_cust_names
    parsed_names = cp.parseCustomerNames()
AttributeError: 'ConfigurationParser' object has no attribute 'parseCustomerNames'
----------------------------------------------------------------------
FAILED (errors=1)

No actual functionality exists, so running the test will fail.

Step 3: Write code.



Once tests are created, you should implement the actual functionality.

As specified in the requirements, the router configuration should already be present in the config.txt file.

You decide that the customer names will be parsed from the VRF configuration.

ip vrf CUSTOMER_A

To parse a certain substring from a configuration line, use regular expressions.

customerNamePattern = r'ip vrf ([a-zA-Z_]+)\n'
customerNames = re.findall(customerNamePattern, deviceConfig)

The result of the re.findall method is a list, which is what you need to return.

return customerNames

Step 4: Run tests (should pass).

$ python3.7 –m unittest test_parser.py
.
----------------------------------------------------------------------
Ran 1 test in 0.003s
OK

If the functionality is implemented correctly, tests should pass now.

Step 5: Refactor code.



The last step of the iteration is to refactor the code if needed. Because you will implement additional methods to parse customer VLANs and customer IP addresses, you decide to put the deviceConfig variable out of the method so that other methods can use it, too.

The TDD iteration is now complete, and you can go on to the next iteration.

Parse Customer VLANs

Repeat Steps 1 to 5.



In the second iteration, you decide to parse the customer VLAN. Similar to the previous iteration, first you create tests where you define the following:

  • The method that will be used to parse the customer VLAN: parseCustomerVLAN()

    1. Parameters: customer_name

  • The return type of the method (integer): expected_vlan = 100

At the end of the test, check that the actual parsed data are the same as the expected data.

Repeat Steps 1 to 5.



You decide that the customer VLAN will be parsed from the interface configuration.

interface GigabitEthernet0/0.100

To parse a certain substring from a configuration line, use regular expressions.

intPattern = (
    r"interface GigabitEthernet0\/0\.([0-9]+)\n encapsulation "
    r"dot1Q [0-9]+\n ip vrf forwarding %s"
    % (customerName)
)
allCustomerSubInterfaces = re.search(intPattern, self.deviceConfig)

As expected in the test, you return the integer value of the VLAN.

return int(allCustomerSubInterfaces.group(1))

Parse Customer IP Addresses

Repeat Steps 1 to 5.


In the third iteration, you decide to parse the customer IP addresses. Similar to the previous iterations, first create tests where you define the following:

  • The method that will be used to parse the customer IP address: parseCustomerIPAddress()

    1. Parameters: vlan

  • The return type of the method (string): expected_ip = "10.10.100.1"

At the end of the test, check that the actual parsed data are the same as the expected data.

Repeat Steps 1 to 5.

You decide that the customer IP addresses will be parsed from the interface summary configuration.

GigabitEthernet0/0.100     10.10.100.1

To parse a certain substring from a configuration line, you use regular expressions.

customerIpPattern = r'GigabitEthernet0/0.%s[ ]+([0-9\.]+)' % (vlan)
customerIpAddress = re.search(customerIpPattern, self.deviceConfig)

As expected in the test, you return the string value of the IP address.

return customerIpAddress.group(1)

Combine All Together

Repeat steps 1 to 5 (continued).


For the last iteration, you decide to combine all previously developed methods together. Similar to the previous iterations, first create tests where you define the following:

  • The method that will be used to parse customer data: parseCustomerData()

  • The return type of the method (dictionary): expected_data = { "CUSTOMER_A": [100, "10.10.100.1"] }

At the end of the test, check that the actual parsed data are the same as the expected data.

Repeat steps 1 to 5 (continued).


The parseCustomerData() method combines all the previously developed methods and returns customer data inside a Python dictionary, which can be very easily converted to JSON format later if needed.

Content Review Question

What is the correct order of steps in a TDD iteration?

No comments:

Post a Comment