export zord {
assemble(parts: ZordPart[]): Megazord;
dissemble(megazord: Megazord): ZordPart[];
}
<API client>
import * from "zord"
zordParts = ["T-Rex", "Mammoth", ... "Sabertooth"]
const megazord = zord.assemble(zordParts)
import * from "zord"
zordParts = ["T-Rex", "Mammoth", ... "Sabertooth"]
const megazord = zord.assemble(zordParts)
import * from "zord"
zordParts = ["T-Rex", "Cannons", ... "Sabertooth"]
const megazord = zord.assemble(zordParts)
export zord {
assemble(parts: ZordPart[],
assemblingOrder: number[]): Megazord;
}
export zord {
assemble(parts: ZordPart[],
assemblingOrder: number[]): Megazord;
}
exposes implementation details
export zord {
assemble(parts: ZordPart[],
name: string,
specialMove: string): Megazord;
}
export zord {
assemble(parts: ZordPart[],
name: string,
specialMove: string): Megazord;
}
1. misplaceable
2. hardly extensible
1. Throughout this tutorial, we will design and refine the autobot API
Activity #1: Write a client for our API
Activity #2: Define an interface for our API
Activity #3: Define types for our API
Activity #4: Think about extensibility
1. Under the Github organization find the tutorial2 repo
2. Clone the repo
3. Open Autobot.spec.ts
4.Don't look at anything else or you will lose all the fun
https://github.ugrad.cs.ubc.ca/CPSC310-2017W-T2/tutorial2.git
1. It should be used by course instructors and TAs
2. It should setup virtual environments to run tests against students' answers
3. Autobot contains courses and deliverables
4. Tests from a given deliverable must run against a given commit
< As designing APIs is hard. My API is far from perfect >
1. In Autobot.spec.ts there are some variables
2. Using only features from your IDE, write a test that uses the autobot API to test "d0"
3. Don't look at the source code of Autobot!
4. I'll provide all method signatures of the API in the next minute
5. At the 5 mins mark, the signatures will be improved with some documentation
Autobot {
test(csID: string, githubRepo: string, commitSHA: string, deliverable: string): Promise<number>
getCourseID(githubRepo: string): string
getCourses(): string[]
exist(courseID: string): Promise<boolean>
register(courseID: string, instructor: string, tas: string[], students: string[]): Promise<boolean>
clone(githubRepo: string, commitSHA: string): Promise<boolean>
add(courseID: string, deliverable: string, testSuite: ITestSuite): Promise<boolean>
build(studentRepo: any): Promise<boolean>
testSuite(studentRepo: any, testSuite: any): Promise<number>
setupPermission(instructor: string, tas: string[]): Promise<boolean>
}
Autobot {
/** Test one commit from a user against a given deliverable.
Returns a grade */
test(csID: string, githubRepo: string, commitSHA: string, deliverable: string): Promise<number>
/** Obtains unique ID of a course based on a Github Repo.
Returns the course ID for that repo */
getCourseID(githubRepo: string): string
/** List courses using Autobot */
getCourses(): string[]
/** Check if a course exists
Returns true if course exists. false otherwise. */
exist(courseID: string): Promise<boolean>
/** Register a new course
Returns true if course was successfully registered. false otherwise */
register(courseID: string, instructor: string, tas: string[], students: string[]): Promise<boolean>
/** Clone a Github repo
Returns true if repo was successfully cloned. false otherwise */
clone(githubRepo: string, commitSHA: string): Promise<boolean>
/** Add a deliverable to a course
Returns true if deliverable was successfully added. false otherwise */
add(courseID: string, deliverable: string, testSuite: ITestSuite): Promise<boolean>
/** Build the student Github repo
Returns true if repo was successfully built. false otherwise */
build(studentRepo: any): Promise<boolean>
/** Run the test suite
Returns a grade */
testSuite(studentRepo: any, testSuite: any): Promise<number>
/** Setup permissions for course instructor and TAs */
setupPermission(instructor: string, tas: string[]): Promise<boolean>
}
describe("Autobot", () => {
let autobot: Autobot;
before(async () => {
Log.info("before: setup a course project and its deliverables");
autobot = new Autobot();
const courseID = "cpsc310-2017w2";
const instructor = "Reid Holmes";
const tas = ["8d8", "4lom", "a290b"];
const students = ["t1t0b", "r2d2", "c3p0", "t5t0b", "bb8", "t5t0b", "aat9", "a5f5t", "g2l5m"];
await autobot.register(courseID, instructor, tas, students);
await autobot.add(courseID, "d0", TestSuites.get("d0"));
await autobot.add(courseID, "d1", TestSuites.get("d1"));
});
it("Should run all test for d0", async () => {
Log.info("\n----------------------------------");
const grade = await autobot.test(
"t5t0b",
"cpsc310-2017w2_team66",
"37debc2b427003dcc4d12aea11b32ef0d6d9aa42",
"d0");
expect(grade).to.be.closeTo(97.00, 0.003);
});
});
< If you used more than that,
the logic of the API is leaking to the client >
1. Take a look at the source code of Autobot.ts
2. Consider which methods should be exposed and define them in IAutobot.ts
3. Change everythin else to private
export interface IAutobot {
addCourse(courseID: string, instructor: string, tas: string[], students: string[]): Promise<boolean>;
addDeliverable(courseID: string, deliverable: string, testSuite: ITestSuite): Promise<boolean>;
testDeliverable(csID: string, githubRepo: string, commitSHA: string, deliverable: string): Promise<number>;
}
< Favor private classes, fields & methods >
1. Imagine how you can encapsulate the parameters of test
2. Define types for the input/output of test
export interface IAutobot {
addCourse(course: ICourse): Promise<IAutobotResponse>;
addDeliverable(deliverable: IDeliverable): Promise<IAutobotResponse>;
testDeliverable(testRequest: IAutobotTestRequest): Promise<IAutobotResponse>;
}
export interface IAutobotTestRequest {
userID: string;
githubRepo: string;
commitSHA: string;
deliverable: string;
}
/** Similar to IInsightFacade */
export interface IAutobotResponse {
code: number;
body: IAutobotSuccessBody | IAutobotErrorBody;
}
< Extensible. You can easily add new fields to IAutobotTestRequest>
You would probably favor a
smaller name, e.g. ITestRequest
<API owners>
OK. As a client, now I want to run on a datacenter.
Give me yet another version of Autobot!
<API owners>
OK. As a client, now I want to run on a datacenter.
Give me yet another version of Autobot!
<Clients>
OK. As a client, now I have to know about all these details!
I'll find a better Autobot
export interface IRunner {
testDeliverable(testRequest: IAutobotTestRequest): Promise<IAutobotTestBody>;
cloneRepo(githubRepo: string, commitSHA: string): Promise<boolean>;
buildRepo(studentRepo: any): Promise<boolean>;
testSuite(studentRepo: any, testSuite: ITestSuite): Promise<IAutobotTestBody>;
}
< Extensible. Yet challenging.
Now I've a compromise not only with IAutobot but also IRunner>
export default class Autobot implements IAutobot {
private runner: IRunner;
constructor(runner: IRunner = new DesktopRunner()) {
this.courses = {};
this.runner = runner;
}
...
}
export default class Autobot implements IAutobot {
private runner: IRunner;
constructor(runner: IRunner = new DesktopRunner()) {
this.courses = {};
this.runner = runner;
}
...
}
we are enforcing backwards compatibility with the default value
< Always strive for backwards compatibility>