Duet Programming

Duet programming is a protocol for collaborative programming that provides structure and organization for computer science learners. The purpose is to define roles that allow both students to actively engage in the design and implementation process at all times, albeit on different aspects of the same program.

This is accomplished through division between core implementation and test code. By taking a test-driven development approach, the design and implementation of test cases is given equal weight to the design and implementation of the core functionality of the project. Students are expected to trade off between testing and core implementation often for large projects in order to make sure that they both engage in both parts.

This protocol has been created to address a disadvantage of Pair Programming, the dominant protocol for collaborative computer science learning. In that protocol, only one person actively develops at a time (the driver), with the second member assisting when necessary (the navigator). This protocol can make it difficult for the navigator to stay engaged.

There are two main aspects to collaborative work. The first is how to communicate with each other respectfully and constructively in order to provide a supportive learning environment. The second is to follow specific protocols that provide phases of working independently and working together in order to build skills towards completing software development entirely independently.

Maintaining a supportive learning environment

Do:

Don't:

Duet Programming Protocol

Duet Programming is divided into three phases. Each phases begins with independent working and ends with a discussion. The second two phases may be repeated, with the two partners switching roles each iteration. From a high level, partner A will be the functionality developer, and partner B will be the test developer.

Phase 1: Problem Clarification, test design

Student A: Determine exact interfaces for functions. Implement the "skeleton" files - main, .h, and .c files. These files must compile and include all of the functions you will implement. For all functions, they will contain a single line that returns an item of the right type (if they are supposed to return something).

Student B: Identify input ranges, (valid inputs and non-valid inputs with borders), identify which inputs should be test cases and their expected results. Categorize test cases as "normal," "boundary," or "error." These tests are called black box tests because you know only the interfaces, not the actual code, when you design them.

Discussion: Both students inspect the others' work. They ask any questions when they are unsure, suggest more test cases if applicable. Divide the functions into groups and order them in the order in which you will implement them. Execute phase 2 and 3 separately for each group of functions, trading off between roles.

Phase 2: Implementation

Student A: Implement the first group of functions (in the .c file).

Student B: Implement the tests for the first group of functions. Do not forget to use good modularity when designing your test code.

Discussion part 1: Look input ranges from the black box tests. Is there separate code to handle each case? If not, are the different ranges equivalent? Also, verify that the boundaries in the input ranges match the boundaries present in the code.
Discussion part 2: Looking at student A's code, jointly develop a set of white box tests that exercise all paths in the code. If you developed more tests than the black box tests, discuss whether that code is necessary, or whether the initial tests were insufficient.

Phase 3: Compilation and testing

This phase is performed together. First remove any compilation errors. Then execute the test cases. Discuss and jointly correct any bugs.
Once you have completed phases 2 and 3 for a group of functions, switch roles and repeat!