EXERCISE INSTRUCTIONS
1.  Open the project for Ex3.1 in Eclipse, in the same way as you have for previous exercises.

2.  Your task for this exercise is to create some examples of feature files for testing part of
    the courier management system, to have cucumber convert them into Java bindings, and to insert
    code into these bindings so that the code executes the steps of the Gherkin scenarios.
    First, in project explorer, right-click on src/test/java and in the context menu select New|Package.
    
3.  Name the new package com.kpl.CourierMgmt.FeatureTests, and dismiss the dialog to create the new
    package.
    
4.  If not already added, you need to add the Cucumber libraries to your maven project file. Double-click
    on the pom.xml file in project explorer to open its configuration editor.
    
5.  Select the dependencies tab, then click on Add next to the list of dependencies.

6.  In the dialog that appears, set the group-id to: io.cucumber, the artifact-id to: cucumber-java,
    and the version number to: 2.3.1. Select 'test' from the scope drop-down, then click OK.
    
7. Click on add again, and this time add the JUnit support libraries for cucumber using 
    group-id: io.cucumber, artifact-id: cucumber-junit, and version: 2.3.1.
    
8.  Right-click on com.kpl.CourierMgmt.FeatureTests, and select New|Other..., then from the list,
    expand general, then select File from the list of options.

9.  In the ensuing dialog, set the name of the new file to CourierBooking.feature, then click OK. A new
    file will be added to your test project, that will have been populated with some examples of
    Gherkin syntax for you to use as a guide.
    
10. Change the Feature: title to 'Book a Courier', and the description beneath it to something like 
    'As a customer I want to book a courier so that my package can be collected and delivered'.

11. Change the Scenario: title to 'Book a single package delivery'.

12.  Replace the Given .. When .. Then statement under the scenario to the following (copy and paste):
        
        Given I am using the default courier manager
		And I have a 1.0 kg package to go from "Fleet Street London" to "Ludgate Hill London"
		When I book a courier
		Then the booking is successful
		And the distance the courier will carry is 5.0 km

13. Delete the whole of the Scenario Outline below this scenario, and save your feature file. 

14. Back in project explorer, right-click on com.kpl.CourierMgmt.FeatureTests and select New | Other...
    to  bring up the file type selector wizard. Expand Cucumber, and select Step definition class before
    clicking Next.
    
15. Set the filename to CourierBookingSteps.java, check each of the boxes at the bottom of the dialog
    for Given, When, Then, And, But, then click Finish to create a sample  step file. 
    
16. Open the CourierBookingSteps.java file for editing if it is not already open. Remove everything inside
    the class opening and closing curly braces.
    
17. Insert some extra import statements beneath the others, to gain access to other key cucumber
    classes:

        import cucumber.api.PendingException;
    
18. Back in the project explorer window, right click on the feature file, select Run as | Cucumber feature. 
    This will generate the java step source code in the console window.
    
19. Copy the lines of code from the console window and paste them into the class body in the 
    CourierBookingSteps.java file. Save all files.
    
20. To make your tests run from JUnit test runner, you need to add a new empty test runnner class.
    Right click on the FeatureTests package line in project explorer, and select New | class.
    
21. Name the new class FeatureTestRunner, and click finish to create the class.

22. You need to place annotations onto the test class you just created. Add the following two 
    import statements at the top of the source file:
    
    import cucumber.api.junit.Cucumber;
    import org.junit.runner.RunWith;
    
23. Now decorate the CourierBookingSteps class declaration with a RunWith annotation:

    @RunWith(Cucumber.class)
    public class FeatureTestRunner {
        . . .
        
24. Save all files, then run all the JUnit tests. You should see that the number of tests has risen by one,
    but that one test is now skipped. Look in the console window, and you should see that the skipped test
    was skipped because it encountered a PendingException.

25. Our next task is to implement the bodies of these binding step functions. We need a Hashtable to pass
    values from one step function to the next when a test is running. At the top of the CourierBookingSteps
    class, add a private hash table field as follows:

    Hashtable<String, Object> context = new Hashtable<String, Object>();
    
    You will probably need an import java.util.Hashtable; statement at the top of the file.

26. Find the 'Given("^I am using the default CourierManager$"' function, and replace its throw of a PendingException()
    with the following two lines of code:

	Scheduler scheduler = (new SchedulerFactory()).CreateScheduler();
        context.put("scheduler", scheduler);

    --- Add any missing import statement to make the Scheduler visible to this test.
    
	[HINT] The Scheduler class in your CourierManager project is the class that schedules a courier
	to execute your requested delivery. We shall be testing its MakeBooking method in this test. Note
	also that the context object is a hash table store in which we can place objects between the
	execution of the step functions.

27. Cucumber for Java is poor at parsing double floating point arguments at the moment. Add a helper function just
    below the hash table declaration that converts two integers into a floating point number:
    
    private double fromInts(int exp, int mant) {
        double frac = mant;
        while(frac >= 1.0)
            frac /= 10;
        return exp + frac;
    }
    
16. Replace the throw in the 'Given(""^I have a (\\d+)\\.(\\d+) kg package...' function with the following code:

            double weight = fromInts(arg1,  arg2);
            context.put("weight", weight);
            context.put("origin", arg3);
            context.put("destination", arg4);

17. The code in the 'WhenIBookACourier' function should be changed to:

            
            Scheduler scheduler = (Scheduler)context.get("scheduler");
            CourierBooking booking = scheduler.MakeBooking(
                "Test shipment", 
                (String)context.get("origin"), 
                (String)context.get("destination"), 
                (double)context.get("weight"));
            context.put("booking", booking);

18. Add an import to the top of the source file to give your functions access to the
    JUnit Assertions class:
    
    		import static org.junit.jupiter.api.Assertions.*;
    
19. The code in the 'ThenTheBookingIsSuccessful' function should become:

    		assertNotNull(context.get("booking"));


20. The code in the 'ThenTheDistanceTheCourierWillCarryIsKm' function should become:

            CourierBooking booking = (CourierBooking)context.get("booking");
        	assertEquals(fromInts(arg1, arg2), booking.GetDistance(), 0.01);

	[HINT] the third parameter to the assertEquals function gives the error margin between the
	values of the first and second parameters.

21. Run all the JUnit tests and make sure your new Gherkin test is passing.

END GOAL:
    By this point, you have enhanced your project to support ATDD testing using Cucumber. You
	have created a Gherkin scenario, generated executable bindings for the scenario, and run
	the tests generated by the scenario.

BONUS ACTIVITY
22. You are going to add a new scenario that uses Gherkin scenario outlines. Add an extra scenario
    to the bottom of your feature file as follows:

	Scenario Outline: Book a sequence of single package deliveries
    Given I am using the default courier manager
	And I have a <Weight>.<WeightFrac> kg package to go from '<Origin>' to '<Destination>'
	When I book a courier
	Then the booking is successful
	And the distance the courier will carry is <Distance>.<DistanceFrac> km
	
	Examples:
	| Weight | WeightFrac | Origin              | Destination         | Distance | DistanceFrac |
	| 1      | 0          | Fleet Street London | Ludgate Hill London | 5        | 0            |
	| 2      | 0          | Petty France London | Fleet Street London | 5        | 0            |
	| 0      | 5          | Petty France London | Ludgate Hill London | 5        | 0            |
 
	[HINT] This outline deliberately reuses the existing binding functions from the previous
	single scenario. You don't need to write any new binding step functions.

23. Re-run all the tests. You should see three additional passing tests in the test report.

OPTIONAL EXTRA BONUS

24. You are going to use tables as part of a single test, so that we can set up several
    deliveries at once. Add the following scenario to your feature file:

	Scenario: Book a set of concurrent deliveries
    Given I am using the default courier manager
	And I have the following deliveries to schedule
		| Weight   | Origin              | Destination         | 
		| 1.0    | Fleet Street London | Ludgate Hill London |
		| 2.0    | Petty France London | Fleet Street London |
		| 0.5    | Petty France London | Ludgate Hill London |
	When I make these bookings
	Then the couriers, distances and prices are
		| Courier         | Distance | Price |
		| Rapid Roger     | 5.0     | 1500  |
		| Lightning Larry | 5.0     | 1500  |
		| Agile Agatha    | 5.0     | 1500  |
		
    Three of the lines in this test should be highlighted to indicate that they have not
    yet been implemented as test functions.

25. Save the file, and right-click on the CourierBooking.feature file in project explorer. From
    the context menu, select Run as | Cucumber feature. If this doesn't work, try right clicking
    on the FeatureTest package line and use Run as | JUnit tests. ONe or other of these will
    give you the empty cucumber functions in the console window.

26. Copy the three new test functions into the clipboard and paste them into the
    CourierBookingSteps.java source file.

27. Notice that the test steps that contain tables now have a DataTable argument passed to them.
    What's more, the DataTable argument is marked as an error. You need an import at the top
    of the source file for the DataTable. It is contained in the cucumber.api package.

28. Navigate to the 'GivenIHaveTheFollowingDeliveriesToSchedule' method and replace its
    call to the Pending() method with the following code to remember the table passed into
	the test step:

            context.put("deliveries", arg1);
 
29. Add a new class to your CourierManagerFeatureTests package, naming it 'Delivery'.

30. Make the class public, and give it three properties as follows:

    public class Delivery
    {
        public double Weight;
        public String Origin;
        public String Destination;
    }

31. Return to your CourierBookingSteps.java file.

32. Replace the code in the 'WhenIMakeTheseBookings' function with the following:

		DataTable table = (DataTable)context.get("deliveries");
		List<Map<String, String>> rows = table.asMaps(String.class, String.class);
		List<Delivery> deliveries = new ArrayList<Delivery>();
		for(Map<String, String> m : rows) {
			Delivery d = new Delivery();
			d.Weight = Double.parseDouble(m.get("Weight"));
			d.Origin = m.get("Origin");
			d.Destination = m.get("Destination");
			deliveries.add(d);
		}
        List<CourierBooking> bookings = new ArrayList<CourierBooking>();
        Scheduler scheduler = (Scheduler)context.get("scheduler");
        for(Delivery d : deliveries)
            bookings.add(scheduler.MakeBooking
                ("A shipment", d.Origin, d.Destination, d.Weight));
        context.put("bookings", bookings);

33. Replace the code in the 'ThenTheCouriersDistancesAndPricesAre' function with:

		Scheduler scheduler = (Scheduler)context.get("scheduler");
        List<CourierBooking> bookings = (List<CourierBooking>)(context.get("bookings"));
        List<List<String>> rawTable = arg1.raw();
        for(int i = 1; i < rawTable.size(); i++) 
        {
            CourierBooking b = bookings.get(i-1);
            List<String> row = rawTable.get(i);
            assertEquals(row.get(0), scheduler.DetailsFromBooking(b).Courier.Name);
            assertEquals(Double.parseDouble(row.get(1)), b.GetDistance(), 0.01);
            assertEquals(Integer.parseInt(row.get(2)), b.GetFare());
        }

34. Run all the tests and ensure these new table tests are passing.

ADVANCED OPTIONAL BONUS ACTIVITY!
35. Open ended activity. The steps above have hand-held you through each of the scenario types
    available to you in Cucumber, using .raw() and .asMaps().. Now we'd like you
	to devise a scenario of your own, that validates some untested aspect of bookings, such as
	illegal input values, prices beyond 15 euros, or maybe even hazardous packages.

36. Be prepared to demonstrate your working solution to the rest of the class!

