Upload
others
View
18
Download
0
Embed Size (px)
Citation preview
CS350Introduction to Software Engineering
May 1, 2020
CS350 OutlineSpring 2020
1 Introduction 01/12/2020 - 01/18/2020Overview
This module introduces you to the course organization, policies, and mechanics. We’ll review the structure of the course website and give you an opportunity toget set up for the semester to come.
We’ll take a brief look at the major themes and areas of emphasis that you can expect to hear more about through the coming semester.
Objectives
1. Identify the requirements, protocols, and policies for the online course
2. Use the selected technology tools for communication and collaboration
3. Discuss the course themes and their relation to software development.
Relevance
An understanding of the tools used in an online course is fundamental in becoming a successful online learner. It is also important to identify the expectationsfor participation, assignment submission, and the time management skills required in the online format.
You can expect that professional software development will be quite different from typical academic programming assignments. This course will emphasize thetools and techniques that developers use on a daily basis, with an emphasis on automating best practices of software engineering.
1. Read the syllabus.2. Read the communications policy.
3. Course Structure and Policies
4. Account Setup
5. What Makes Software Development Difficult?
In your recitation section
1. Recitations will not meet this week. However, the recitation next week requires some preparation in advance of your scheduled session. You should readthrough the expectations this week.
2 The Software Development Process 01/19/2020 - 01/25/2020Overview
Every development organization settles into a process that they use to produce new software. Although the component activities are largely the same, differentprocesses place different levels of emphasis on the components and arrange those components differently.
We’ll look at the more common and the more influential models for software development processes and will examine the interaction between the developmentmodel and the organization and composition of development teams.
Objectives
1. Discuss the phases and component activities of software development
2. Assess the likely impact of popular software process development models on a project
3. Discuss common team organizations and roles in software development
Relevance
A development model establishes the context in which we can consider more specific best practices. At the same time, the more modern and trendydevelopment models take for granted that team members will have a high level of familiarity with common best practices.
Process Models
1. Tsui, Ch.42. Software Development Process Models
3. Lab: Secure Shell Keys
4. See website for assignment.
Staffing
1. Team Organization
Design
1. High-Level Design: A Quick Overview
In your recitation section
1. Network Conferencing: Google Meet 01/21/2020
3 Requirements 01/26/2020 - 02/01/2020Overview
We will look at the processes of eliciting and analyzing requirements, and at common forms of documents for recording them. We will look at how thesedocuments vary depending on whether we are doing requirements analysis “up front” or incrementally. We will discuss the characteristics that contribute to thequality of requirements statements.
Objectives
1. Recognize common forms of requirements documents2. Discuss quality characteristics for requirements3. Write a requirements document in an industry-standard format.
Relevance
All software development projects begin with a statement of requirements. All subsequent software construction must refer back to those requirements. It istherefore essential that all developers be able to read and understand requirements documents, even if they were not actively engaged in writing thosedocuments.
Eliciting and Writing Requirements
1. Wiegers, ch 6, 7, 10, 112. Eliciting Requirements3. Writing Requirements
In your recitation section
At the end of the week
1. Project Phase 1: Software Requirements Specification Start work on phase 1 of the project. Note: Due dates for project phases are collected in a separate section near the bottom of this outline.
4 Software Construction 02/02/2020 - 02/08/2020Overview
Software construction is the term used for the development activities that follow requirements and lead up to the final system and acceptance tests. In thismodule, we will take a high-level look at the component activities of software construction and will discuss some of the critical decisions that need to be madeat the start of construction.
Students will begin the process of building their personal development environment by installing and practicing with a modern IDE (Integrated DevelopmentEnvironment).
Objectives
1. Identify and discuss the constituent activities of software construction2. Discuss issues associated with these activities3. Review the contributions and features of IDEs in construction activities
Relevance
This module sets the stage for the exploration of more detailed construction topics to follow in the remainder of the course.
Clean Coding
1. Martin, ch 2, 3, 6, 7, 102. Clean Coding3. CS382, Sections 1-3 (Read lecture notes and do the labs.)4. Integrated Development Environments5. See website for assignment.
Organizing Incremental Construction
1. Cohn, ch 1-3, 72. User Stories3. Shore, ch. 8, Stories
In Your Recitation Section
1. . Recitations do not meet again until explicitly noted in the Project schedule below.
5 Unit Testing 02/09/2020 - 02/22/2020Overview
In this section we will review the basic principles of unit testing, and then we will look at the problem of automating the testing oracle, the procedure ofdetermining when our code has passed or failed each test.
This will lead us to the world of modern Unit test frameworks, which seek to make running tests so effortless that there is no longer any excuse to defer testing.
On the cutting edge of testing practice, we will look at mock objects as a means of further automating our tests.
Objectives
1. Discuss the position of testing in an overall verification and validation context.2. Discuss the varying scopes and goals of testing.3. Discuss the activities comprising testing and the role of automation in supporting those activities.4. Apply *Unit frameworks.5. Apply the mutator/accessor strategy for ADT testing.6. Discuss the use of mock objects in unit testing.
Relevance
Unit testing has always been seen as a critical part of software construction, but modern best practices place more emphasis on it than ever. “Test first” is amantra in modern software development, and common practice integrates testing so tightly into the build process that it’s more trouble to avoid tests than to re-run them at each step.
V&V
1. Verification and Validation2. Lab - Eclipse3. Testing4. Why Do We Test?5. Choosing Tests6. Testing the F16 Fighter
Self-Checking Test Drivers
1. Good Unit Tests2. Automating the Testing Oracle3. JUnit Tutorial
4. (optional) Google Test Primer
5. Lab: Unit Testing6. Testing ADTs
7. See website for assignment.
Test-Driven Development
1. Martin, ch 9
2. Test-Driven Development
Stubbing and Mocking
1. Stubs and Mocking2. Mocks Aren’t Stubs3. Google C++ Mocking Framework for Dummies
6 Version Control 02/23/2020 - 03/01/2020Overview
Version control is concerned with managing the history of changes made to the software by the development team. A good version control system offers a teamcontrol over the history, exploration, and collaboration on a project. We’ll look at the issues and approaches to local, centralized, and distributed version control,and explore how to work with a distributed version control system from an IDE.
Objectives
1. Discuss the issues and problems involved in collaborative development of software
2. Discuss the major types of version control (local, centralized, and distributed) and how team conflicts are resolved in each
3. Discuss the concept of branches and their impact on software development strategy
4. Apply a distributed or centralized version control system
5. Manage a repository within a forge environment
Relevance
Version control has rescued many a project from utter disaster of having lost or destroyed critical code. Probably the only practice that has done so more oftenwould be regular backups. But version control is also central to managing collaboration among team members, allowing confidence that developers workingindependently need not fear overwriting or interfering with one another’s work.
Version Control
1. Version Control2. Masters & Blum, Chapter 43. Local Version Control (sccs, rcs)4. Centralized Version Control (CVS, Subversion)5. Understanding Git Conceptually6. Distributed Version Control7. Breaking the Build
8. Lab: Version Control with Git
9. Forges
10. See website for assignment.
Exam
1. Midterm exam 03/01/2020 - 03/03/2020
7 Build Management 03/01/2020 - 03/17/2020Overview
A build manager has the task of performing any automated steps required to rebuild a software project after programmers have made changes. We will look atthe primary models for build management, file dependencies and task dependencies, and the most commonly used managers for each model. We’ll also look athow to replace an IDE’s built-in builder with a more flexible manager.
Objectives
1. Discuss the role of build managers in supporting a project2. Discuss the limitations of the default build managers provided with IDEs3. Discuss and assess the impact of the major forms of dependency management in builds4. Apply a common file dependency manager (make) and task dependency manager (ant/maven/gradle)
Relevance
Modern projects now rely on build managers for much more than just the basic operations of compiling and linking. Build managers are also called upon to runtests, to prepare software packages for deployment, to deploy them, and to prepare project reports and post those reports to project web sites. These demands gofar beyond the capabilities of the simple manager included in your IDE. Used properly, a build manger can save a team a lot of tedious work.
1. Build Managers
2. See website for assignment.
3. File Dependencies: make
4. Task Dependencies: ant5. Managing Builds with Maven
6. Gradle guide: Creating a New Gradle Build
7. Gradle guide: Building Java Applications8. Gradle guide: Building C++ Executables9. Task Dependencies: Gradle
10. Lab: Building with Gradle11. See website for assignment.
(Optional) Maven & Ant In Detail
1. Ant Tutorial2. Maven in 5 Minutes
8 Configuration Mgmt 03/18/2020 - 03/28/2020Overview
Software Configuration Management (SCM) addresses a wide variety of issues in the development of software. These include version control, studied earlier,but also the problems of coping with portability to multiple target platforms and the incorporation of externally developed code libraries into a project. We’llexplore these new issues of SCM and will look at how configuration management tools can aid in keeping all the components of a project compatible with oneanother.
Objectives
1. Discuss issues in supporting multiple target platforms2. Discuss issues involved when incorporating 3rd party libraries as software components3. Discuss the role of configuration management tools4. Apply a common configuration management tool (maven/tvy)
5. Discuss the role of repositories
Relevance
With an increasingly rich universe of open-source and commercial libraries available, development teams are increasingly encouraged to avoid wasting effortby resolving problems already solved by someone else. Discovery of useful libraries can be difficult, however, and the possibility that a useful library may itselfincorporate still other libraries raises the very real problem of inconsistency among a project’s components.
Configuration Management
1. Software Configuration Management2. Appleton, Streamed Lines through "Using the Branching Patterns3. Managing Third-Party Libraries4. Managing Code Variants
9 Documentation 03/29/2020 - 04/04/2020Overview
In this module, we will review some of the basic lessons on source code documentation that you may have learned as beginning programmers and will considerhow well they translate to more professional practice. In accord with current best practice, we will look more closely at API documentation and the tools forbuilding and maintaining that documentation. We will also look at some common project reports and how they might be posted to a project website.
Objectives
1. Discuss forms and roles of code documentation2. Discuss common API documentation tools3. Apply a common API documentation generator (Javadoc/Doxygen)
Relevance
Modern practice places far less emphasis on source code documentation than beginning programmers are often led to believe. At the same time, developers arecommonly expected to conform to a higher standard in preparing API documentation and project reports.
1. Martin, ch 42. McConnell, Ch. 323. Documentation and Documentation Generators4. Project Reports & Websites
10 Analysis Tools 04/04/2020 - 04/11/2020Overview
This module will examine some of the validation tools that lie outside the realm of testing, including analysis for dead code, for overly complex code, and forviolation of coding standards and practices.
Also, with our now content-rich, automated builds, we will examine the practice of continuous integration as a means of keeping project status information upto date.
Objectives
1. Discuss the differences between dynamic and static analysis2. Apply code coverage tools3. Apply static code analysis tools and interpret their results4. Apply a continuous integration framework to manage report generation.
Relevance
Many clients require the use of code analysis tools on delivered code, treating the reports from these tools as part of the acceptance test for the system.Developers need to understand both the abilities and limitations of the analysis performed by these tools. With so many reports being produced by projects,automating not just the build but the launching of new builds is an increasingly common practice.
Static & Dynamic Analysis
1. Program Analysis Tools2. Continuous Integration3. Continuous Integration Lab
11 System and Regression Testing 04/12/2020 - 04/18/2020Overview
System testing often involves inputs that are hard to supply or outputs that are hard to capture (e.g., graphics on a screen). Some regression tests can face thesame problem. We’ll examine the possibilities of automating tests at this level to a degree similar to what we achieved earlier with unit testing.
Objectives
1. Discuss problems introduced when testing at the system level
2. Discuss tools for testing GUI code3. Discuss the role of bug/issue tracking in a development process4. Apply an issue tracking system in a team project
Relevance
The rise of GUI interfaces as the most common way for users to interact with programs created a huge problem for system testing that, decades later, is still asource of difficulty for development teams. Developers need to know what can be done automatically about this and how they can design or plan around theproblems when automated solutions are unworkable.
1. System Testing2. Regression Testing3. Issue Tracking
12 Agile Methods 04/19/2020 - 04/27/2020Overview
Agile development is a set of practices centered on an incremental development model. Agile methods are threatening to topple the Waterfall as the mostcommonly used development process model.
We will explore the principles and practices that comprise agile development. We will see how many of the practices studied in the earlier modules lie at theheart of agile practice. We will look at some of the primary development models within the agile movement.
Objectives
1. Discuss the conflict between incremental and “up front” models2. Assess the impact of incremental models on a development project3. Discuss the philosophy and components of agile models4. Discuss the impact of agile models and requirements elicitation
Relevance
Agile development is not only a set of development practices and process models, but also a social/political movement in the world of software development.
The Agile Manifesto calls for non-technical managers to properly respect software developers by adopting a hands-off attitude to technical decisions. This callwould be unforgivably arrogant if the Manifesto did not also call for developers to demonstrate their professionalism by knowing and adopting the profession’sbest practices, independent of any mandates from management requiring them to do so.
1. The Agile Manifesto & Twelve principles
2. Agile Methods3. Beck, Ch 74. Extreme Programming (XP)5. Scrum Guide6. Scrum7. Is Agile for Amateurs?8. Trends in Agile
13 ProjectPhase 1
1. Project Phase 1, Requirements Specification 02/02/2020 - 02/13/2020
Phase 2
1. Sign up for project teams 02/12/2020 - 02/13/20202. Project Phase 2, stories 02/14/2020 - 02/24/2020
Phase 3
1. Project Phase 3 02/25/2020 - 03/29/20202. Project: Design Brainstorming 02/25/2020 - 02/25/20203. Optional phase 3 progress meetings (in your recitation section) 03/24/20204. Project phase 3 review meetings (in your recitation section) 03/31/2020
Phase 4
1. Project Phase 4 03/30/2020 - 04/19/20202. Project phase 4 review meetings 04/21/2020
14 Special Events and Dates1. MLK Day 01/20/2020 - 01/20/20202. Midterm exam 03/01/2020 - 03/03/20203. Spring Break 03/09/2020 - 03/14/20204. Final exam 05/02/2020 - 05/04/2020
15 PreambleGeneral Information
Below are the modules that comprise the course content.
Each module consists of a series of activities.
Not every assigned activity requires you to submit something for grading. Nonetheless, you are expected to do them all.
Do not skip or delay doing the labs just because they are ungraded. Most of them introduce skills (and sometimes set up specific data) used in asubsequent assignment or in the project.
If no due date is specified, you are supposed to complete the assigned activity by the end of the final day allotted for that entire module.
Where a due date is given with no time, you have the entire day (until 11:59:59PM ET of the due date).
15 PostscriptAll times in this schedule are given in Eastern Time.
Symbol KeyLecture:Slides :Event or important dateReadDo lab:Assignment:Take theDo:In your recitation section:Under construction:
16 PresentationTopics Lectures Readings Assignments & Other Activities
Topics Lectures Readings Assignments & Other Activitiestopics lecture slides event exam video project construct text exam asst selfassess exam activity lab recitationDocument Kind Prefixlecture Read lecture notes:eventexam Take the exam:lab Do:asst Do assignment:reading Read (optional):© 2015-2020, Old Dominion Univ.
CS 350 Introduction to Software Engineering (Spring 2020)Steven J. Zeil
Last modified: Jan 7, 2020
Contents:1 Course Description
1.1 Instructor1.2 Course Pre-requisites1.3 Meeting Times and Delivery Method
2 Basic Course Information2.1 Required Text2.2 Resources for Getting Started2.3 Computer Accounts2.4 Hardware & Software Requirements
3 Course Policies3.1 Recitation Attendance3.2 Due Dates and Late Submissions3.3 Academic Honesty3.4 General University Policies3.5 Grading
4 Assignment Grading5 Exams6 Topics
6.1 Objectives6.2 Expectations
7 Educational Accessibility
Website: ../../
1 Course DescriptionThis course explores the software development process. It will discuss the major activities common to software development processes, and some of the ways inwhich those activities are organized and managed.
Heavy emphasis will be placed on the day-to-day skills required during software construction. The course will explore lessons and tools offered by the majorsuccessful open-source software efforts.
The course requires each student to participate as a member of a team in a significant team project. Each student will be required to demonstrate proficiency inseveral software development tools.
1.1 Instructor
Steven Zeil E&CS 3208(757) 683-4928 [email protected]
Please make sure to include the course name “CS350” in the subject line of any email related to this course.
1.1.1 Office Hours
Office hours are posted online at http://www.cs.odu.edu/~zeil/officehours/
General questions about course content and reports of website problems should normally be asked in the Forums on Blackboard or via email.
Questions about grades, how to solve assignments and other graded activities must be sent to [email protected], not posted in Forums.
For more discussion on course communications, please refer to the Communications policy.
1.2 Course Pre-requisitesCS 252 (Introduction to Unix for Programmers)CS 330 (Object-Oriented Programming and Design), orCS 361 (Advanced Data Structures and Algorithms)
Students who have not taken CS 330 are encouraged to take CS 382 (Introduction to Java) as a pre-requisite or, at the very least, to work throughthat courses’s website during the first few weeks of the semester.
1.3 Meeting Times and Delivery MethodStudents must register for both a lecture section and a recitation section.
% else
* The primary purpose of the recitation is to allow project teams to meet with the instructor for periodic evaluation of their progress.
* For the purpose of these meetings, it will be important that students be seated at or connected to their chosen development machine and prepared to demonstrate aspects of the project under review. * There will also be a small number of earlier recitations devoted to learning the conferencing tools that will be used in the later meetings. Recitation sections will not meet every week, but the scheduled meetings are mandatory. During weeks when recitations are not scheduled, project teams may wish to use that time for their own meetings. As necessary, the instructor may announce open "help" sessions during the recitation time periods to provide aid on assignments and labs. Attendance at these sessions will be optional.
2 Basic Course Information2.1 Required TextReadings from the Internet will be assigned from the course website.
2.2 Resources for Getting StartedODU Online Student Orientation
2.3 Computer AccountsStudents will need two network accounts to participate in this class:
An ODU ITS account. This is the account associated with your @odu.edu email. It will allow you to log into the course’s Blackboard site. All ODUstudents automatically receive this account, though you may need to activate yours, if you are new to ODU.
An account on the CS Dept. network. This will be used for access to the CS dept computing resources, and for accessing and submitting assignments. Youmay have a CS account already if you were registered for a CS class last semester. If not, the account setup can be initiated at http://www.cs.odu.edu/ byclicking on “Account Creation” under “Online Services”.
2.4 Hardware & Software Requirements
Students will need frequent access to a PC capable of hosting software development activities or of connecting remotely to CS Dept servers where suchactivities can be performed.
Students will be attending network conferences requiring the use of a microphone. Webcams are optional.
For both remote access to CS Dept servers and for network conferencing, a good-quality internet connection is important.
The course will introduce students to a wide variety of software packages. All of these are open-source, free software, but students will need to install some ofthese on their chosen development machine (whether their own PC or in their account on the CS network).
3 Course Policies3.1 Recitation AttendanceProject review sessions will be scheduled for selected weeks during the recitation periods. Attendance at these is mandatory. Failure to attend will result insubstantial grade penalties for that portion of the project.
Attendance at other scheduled recitation sessions, as announced in the course outline, is also mandatory. Failure to attend may result in grade penalties.
3.2 Due Dates and Late SubmissionsLate assignments and make-up exams will not normally be permitted.
Exceptions to this and other grading policies will be made only in situations of unusual and unforeseeable circumstances beyond the student’s control.Arrangements must be made prior to the due date in any situations where the conflict is foreseeable.
“I’ve fallen behind and can’t catch up”, “I’m having a busier semester than I expected”, or “I registered for too many classes this semester” are not grounds foran extension.
3.3 Academic HonestyEverything turned in for grading in this course must be your own work, or, for team projects, the work of your own team. Opportunities for teamwork will beclearly identified as such.
Students are expected to conform to academic standards in avoiding plagiarism.
Among other things, this means that if you use ideas found on the internet (outside of the course website) in your answers to an assignment or examquestion (including when coding!), you must cite your sources appropriately.
If you use text directly taken from such sources you must appropriately designate the quoted material as such.
The instructor reserves the right to question a student orally or in writing and to use his evaluation of the student’s understanding of the assignment and of thesubmitted solution as evidence of cheating.
Students who contribute to violations by sharing their code/designs with others may be subject to the same penalties. Students are expected to use standard Unixprotection mechanisms (chmod) to keep their assignments from being read by their classmates. Failure to do so will result in grade penalties, at the very least.
This policy is not intended to prevent students from providing legitimate assistance to one another. Students are encouraged to seek/provide one another aid inlearning to use the operating system, in issues pertaining to the programming language, or to general issues relating to the course subject matter.
Student discussions should avoid, however, explicit discussion of approaches to solving a particular programming assignment, and under nocircumstances should students show one another their code for an ongoing assignment, nor discuss such code in detail.
Violations of this policy will be reported to the Office of Student Conduct and Academic Integrity for consideration for punitive action.
3.4 General University PoliciesThe ODU Catalog lays out a wide variety of University policies that are binding upon both students and faculty. All students are required to abide by these.
3.5 Grading
Assignments 15%Semester Project 45%
Midterm Exam 15%Final Exam 25%
Individual Assignments (15% in total) and individual Semester Project Phases (50% in total) will not necessarily be equally weighted.
Grading is normalized.
4 Assignment GradingIndividual assignments will be turned in through the CS submission system, rather than through Blackboard–more information is available here. Most of theassignments will be graded by an automatic grader. The results will be sent to your email account. Unless the assignment explicitly states otherwise, you may
submit a total of three times; the instructor will take the last of the marks, although you may request that your score be “rolled back” to an earlier one. You mayNOT submit after viewing the sample solution.
5 ExamsThe Midterm Exam and Final Exam will be administered online via Blackboard.
6 TopicsTopics will include:
Software development processes, including the waterfall, unified OO, and extreme programming modelsRevision management: local, centralized, & distributed approachesConfiguration Management: project configuration, managing external librariesDocumentation toolsBuild ManagementTest-driven developmentUnit & Integration Testing: coverage, self-checking, mocking, designing for testabilityDebugging: local & remote, monitors, reverse debuggingRegression TestingIssue trackingSoftware Forges & Repositories
6.1 ObjectivesStudents completing this course should be able to:
Demonstrate an understanding of the overall strategy of software development:
Discuss the phases and component activities of software development
Assess the likely impact of popular software process development models on a project.
Discuss common team organizations and roles in software development.
Work with software requirements documents
Read common forms of requirements documents
Write at least one standard form of requirements document
Apply requirements to guide the subsequent construction of software
Apply best practices in collaborative software construction
Discuss the issues and problems involved in collaborative development of software.
Evaluate the suitability of alternative best practices for a software construction project.
Support common best practices of via a modern IDE and associated tools
Apply a variety of software measurement and estimation techniques.
6.2 ExpectationsStudents will engage in team projects in this course. Students are expected to actively participate in and contribute to their teams, and this engagement with theteam will be part of the grade.
7 Educational AccessibilityOld Dominion University is committed to ensuring equal access to all qualified students with disabilities in accordance with the Americans with DisabilitiesAct. The Office of Educational Accessibility (OEA) is the campus office that works with students who have disabilities to provide and/or arrange reasonableaccommodations.
If you experience a disability which will impact your ability to access any aspect of my class, please present me with an accommodation letter from OEAso that we can work together to ensure that appropriate accommodations are available to you.
If you feel that you will experience barriers to your ability to learn and/or testing in my class but do not have an accommodation letter, please considerscheduling an appointment with OEA to determine if academic accommodations are necessary.
The Office of Educational Accessibility is located at 1021 Student Success Center and their phone number is (757) 683-4655. Additional information isavailable at the OEA website http://www.odu.edu/educationalaccessibility/
CommunicationsSteven Zeil
Last modified: Dec 21, 2019
Contents:1 So many options2 General Rules for On-line Communications
2.1 Public and Private Communications2.2 Etiquette in Email and Other Written Communications
3 Asking Good Questions3.1 Identification3.2 You Have to Give Information to Get Information3.3 Thou Shalt Not Paraphrase3.4 “Copy and Paste” is Your Friend!3.5 If I Ask You a Question, Answer It
1 So many optionsCommunication is a major concern in any course. Web courses make this a little trickier by reducing the options for face-to-face discussion.
Options for communications in this course include:
to instructor
I generally try to answer all course-related email within 24 hours, sometimes dropping to 48 on weekends and holidays. I generally averagebetter than that.You can increase your odds of a speedy reply by making sure that “CS350” appears somewhere in the subject line of your email.
on group assignments, to members of your group
Email to the entire class should generally be avoided.
Discussion Board
Posts to Discussions will be visible to the instructor and the entire class.
Announcements
Only the instructor can send announcements.
Network Conferencing
Early in the semester, you’ll be learning how to use Google Meet as a network conferencing system. You can use this to meet the instructor during officehours or to set up meetings with team members during group assignments.
Office Hours
Yes, given that this is a Distance Course, many of you can’t come to campus to see me face-to-face. But I also offer options of meeting by telephone orvia network conferencing (Google Meet).
You can find my office hours here.
2 General Rules for On-line Communications2.1 Public and Private CommunicationsChoose a communications option that is appropriate to the nature of the discussion.
Some of the communications options that you have will open your discussion up to the entire class. Others will limit your discussion to the instructor or to yourteam on a group assignment.
In general, any conversation in which you discuss all or part of your solution to an individual assignment, even if you are only speculating onpossible solutions, should be limited to you and the instructor. Sharing such information with other students is a violation of the course’s policy onAcademic Honesty.
Use email or office hours for those kinds of questions.
Similarly, questions about your own grade on an assignment should be limited to a private communication with the instructor. In fact, if you were to try toask such a question in the public Discussion area, ODU privacy policies would prohibit the instructor from replying.
For group assignments, of course, you can consider opening up the conversation to include your team members. But you should not discuss specifics ofsolutions to group assignments with other teams.
On the other hand, questions about the course subject matter or purely clarification questions about an assignment may be useful subjects for theentire class. These are good subjects for the Discussions area.
The instructor may, if he feels it is appropriate, copy an e-mailed question to the Discussions area so that the answer becomes available to everyone.
2.2 Etiquette in Email and Other Written CommunicationsStudents posting in the Discussions area or sending email to the instructor or to classmates are expected to conform to the norms for civility and respectfor ones’ classmates and instructors that are common to all on-campus speech and writing.
Students are also expected to conform to the norms of “netiquette”, for example, RFC 1855: Netiquette Guidelines . In particular:
Emotions are often hard to convey and easy to misunderstand in written text. Smileys and other emoticons can help (but don’t assume that attachinga :-) to an insult will make everything OK with the people reading your post.).
DON’T WRITE IN ALL CAPITALS or in all bold or, even worse, IN ALL BOLD CAPITALS. This is considered to be shouting, and mostpeople don’t like to be shouted at, whether in real life or on-line.
“Shooting the messenger” is seldom a good idea. In general, assume that people who take the time to reply to your posts are honestly trying to help.Getting mad at them and “flaming” back is counter-productive if you really want people to help you.
Replies to posts will often be short and to the point simply because the responder has limited time. Don’t mistake terseness for rudeness.
Many people who post questions and requests for help may have made very basic mistakes. If you omit the details of everything you thought of andchecked before making your post, don’t be insulted if someone replies with a very basic suggestion or a link to something that you have alreadyread.
Don’t “hijack” existing Discussions (threads) to talk about a topic different from the original poster’s topic. Start your own thread instead.
In an ordinary conversation, no one appreciates the person who barges in and insists on changing the topic of discussion. And if two groups ofpeople actually insist on trying to simultaneously carry on a discussion on two distinct topics within the same conversation, the result is usuallyconfusing to everyone.
3 Asking Good QuestionsWhether posted in Discussions or sent via email, a question is the beginning of a dialog. A well-prepared question will get you an informative answer quickly.A poorly-prepared one may get you irrelevant answers or may require several rounds of back-and-forth dialog, delaying your eventual answer by many hours oreven days.
So it’s in your own self-interest to ask your question in a way that gets you the answer you need as quickly as possible.
3.1 IdentificationWho are you? : If you are sending me email, make sure your course login name or your real name appears somewhere in the message. I hate getting mail [email protected] saying “Why did I get such a low grade on question 5?” when I have no idea who this person is!
What course is this? : Again, if you are sending me a question via email, please remember to state which course you are asking about. I teach multiple coursesmost semesters.
For email, please put the course number (CS350) in the subject line. I configure my email reader to flag such messages for priority handling, giving you lesschance of being lost amid the daily flood of spam.
Use a clear and precise subject header.: In Discussions, your subject header helps people decide if your post is worth reading. It also helps people find priordiscussions that may have been relevant to later posts.
In e-mail, the subject line shares much the same purpose. Empty and short subject lines are also more likely to get tossed by autoamtic spam filters.
3.2 You Have to Give Information to Get InformationWhen you ask a question, you usually want an appropriate answer that will let you get past whatever difficulty you are having. So that answer needs to berelevant to your particular difficulty and tailored to your understanding of the course material.
I’m not psychic.
If you send me an email consisting of nothing more than “I’m stuck on this assignment.”, there’s really no way I can give you the kind of answer you are hopingfor. You need to tell me:
What part of the assignment are you stuck on?
What have you tried so far? (Be specific. If you’ve tried lots of things, tell me about your best attempt so far.)
What happened when you tried it?
If you don’t tell me those things, I’m just going to have to ask for that information, delaying the process of getting you the answer you want.
If you have an issue with a webpage, \emphasis{tell me the URL}. If a link is broken, \emphasis{tell me the URL of the page containing the link} and describethe location of the link. Don’t just tell me “the link to assignment 2 is broken”. I probably have a dozen different links to that assignment in different pages.
To copy text from PuTTY or from a Linux or OS/X terminal, just left-click anddrag your mouse across text to select it. Your selected text is automaticallycopied to the clipboard, and you can paste it into an e-mail message or Forumpost.
3.3 Thou Shalt Not ParaphraseThere’s nothing more frustrating than getting a question like
“When I try to compile my solution to the first assignment, I get an error message. What’s wrong?”
Grrr. What was the (exact) text of the error message? Was this on a Linux or Windows machine? What compiler were you using? What compiler options didyou set? What did the code look like that was flagged by the message?
No, I’m not kidding. I get messages like this all the time. And it wastes my time as a question answerer to have to prompt for all the necessary information. Italso means a significant delay to the student in getting an answer, because we have to go through multiple exchanges of messages before I even understand thequestion.
The single most important thing you can do to speed answers to your questions is to be specific. I’m not psychic. I can only respond to the information youprovide to me.
Never, ever paraphrase an error message (“I got a message that said something about a bad type.”)Never, ever paraphrase a command that you typed in that gave unexpected results (“I tried lots of different compilation options but none ofthem worked.”, or, my personal favorite, “I tried everything.”)Never, ever paraphrase your source code (“I tried adding a loop, but it didn’t help.”)Never, ever paraphrase your test data (“My program works perfectly when I run it.”)
All of the above are real quotes. And they are not at all rare.
The problem with all of these is that they omit the details that would let me diagnose the problem.
3.4 “Copy and Paste” is Your Friend!
And it’s not all that hard to provide those details. Error messages can becopied and pasted into your message. The commands you typed and theresponses you received can be copied-and-pasted from your ssh/xtermsession into your message. Your source code can be copied-and-pasted orattached to the message.
Note that this information is almost always plain text. Unless you reallyneed to show me graphics, please don’t send me screen shots. They are often hard to read and often do not allow me to make the fine distinctions I need to tellwhat is going on. Keep in mind that raster graphics formats (gif, jpg, png, etc.) often look very different when rendered on screens with different resolutions.
3.5 If I Ask You a Question, Answer ItI often respond to a student’s question with further questions of my own.
Teachers since Socrates have always done this, and students have always been annoyed at it. But who are we to argue with history?
Sometimes I do this to get more info I need, sometimes to guide the student towards an answer I think they should be able to find for themselves.
It’s surprising how often students ignore my questions and either never respond at all, respond as if my questions were rhetorical, or, if I have asked 2 or 3questions, pick the one that’s easiest to answer and ignore the rest.
This pretty much guarantees that the dialog will grind to a halt as I wind up repeating myself, asking the same questions as before, and some students go righton ignoring my questions, …
Course Structure and PoliciesSteven J. Zeil
Last modified: Dec 21, 2019
Contents:1 Syllabus2 Course Structure
2.1 Sessions2.2 Assignments2.3 Semester Project2.4 Exams
3 Course Pre-requisites4 Communications
4.1 Office Hours4.2 Course Forums
5 Important Policies5.1 Late Submissions5.2 Academic Honesty5.3 Grading
6 Course Themes6.1 Goals6.2 Areas of Emphasis
1 SyllabusAll students are responsible for reading the course syllabus and abiding by the policies described there.
2 Course Structure2.1 Sessions
Lectures: MWF 2:00-2:50PM, Dragas 1117Recitations: M 7:10-9:10PM, Tu 3:00-5:00PM (selected weeks)
Five phases:
Recitations
Recitations will be used for special topics and for meetings with teams once the semester project is underway.
They will not meet every week.Meetings will be on-line.
You will attend from your “development machine”.
2.2 AssignmentsIndividual assignments (3 attempts each). Assignments will include:
1. Command line exercises (e.g., SSH Keys)2. Tool Set up (e.g., Installing compilers and Eclipse)3. Programming in Java (e.g., Unit Testing)4. Version Control (e.g., Git)
This a non-exhaustive list.
2.3 Semester ProjectA moderately large program on which you will work in teams of 4-6 people.
Five phases:
1. Writing Requirements2. Planning for construction: writing user stories3. Early construction: build management, version control, story tracking, project website4. Middle construction: configuration management, documentation management, continuous integration5. Later construction: integration testing, analysis tools
In general, you will be evaluated upon process as much as upon you ability to produce working code.
The idea is to see if you have established a team process such that, if you had enough time, you would have eventually implemented the entire process.
2.3.1 Project Teams
I will assign you to a team, at random, for the first phase (Phase 1).
1. Writing Requirements2. Planning for construction: writing user stories3. Early construction: build management, version control, story tracking,
project website4. Middle construction: configuration management, documentation
management, continuous integration5. Later construction: integration testing, analysis tools
Five phases:
1. Writing Requirements2. Planning for construction: writing user stories3. Early construction: build management, version control, story tracking,
project website4. Middle construction: configuration management, documentation
management, continuous integration5. Later construction: integration testing, analysis tools
You will choose your own teams for the remaining phases
Must be members of your recitation section.
2.3.2 Project and Recitations
The final three phases will be evaluated in part via a team meeting with theinstructor.
Held during recitation periodInstructor will ask questions about team’s overall progress.The instructor may direct questions to specific team members to besure that everyone shares an understanding of the project andrequired skills.
General rule is that if one member of a team does not knowsomething, that’s a mark against that person. If two peopledon’t know something, that’s a mark against the entire team.
At end of some meetings, you may receive a short test/assignment, which each team member must complete individually within a limited time period (aday or less).
2.4 ExamsMidterm & Final
Administered on-lineDates & times on the course calendar
Final exam is cumulative.
3 Course Pre-requisitesCS 252 (Introduction to Unix for Programmers)
and
CS 330 (Object-Oriented Programming and Design), orCS 361 (Advanced Data Structures and Algorithms)
Students who have not taken CS 330 are encouraged to take CS 382 (Introduction to Java) as a pre-requisite or, at the very least, to workthrough that course’s website during the first few weeks of the semester.
4 CommunicationsContact Info
Steven Zeil E&CS 3208(757) 683-4928 [email protected]
Important: The course name “CS350” should appear in the subject line of all course-related email.
Forums are also available on Blackboard for general discussions.
4.1 Office Hoursif _kennedy
My general office hours are available at https://www.cs.odu.edu/~tkennedy/. Instructions for scheduling a formal appointment are listed here. Note that myoffice hours include both live and web-conference based appointments.
Office hours are posted online
Available from http://www.cs.odu.edu/~zeil/officehours/
Meeting modes:
face to face in my officeNetwork conferencing (Google Meet)Telephone
4.2 Course Forumsthe Hallway
for general discussion of course topics
no discussion of assignment solutions!the Janitor’s Closet
reports of site issues, broken links, missing or incorrect web pages, etc.
5 Important Policies5.1 Late Submissions… are not normally accepted. Exceptions may be made in cases of
documented emergenciesarranged prior to the due date when possible.
Extensions to due dates will not be granted due to
difficulties “porting” from one system to anothertransient system crashessystem overloaded
5.2 Academic HonestyODU is governed by a student honor code.
Everything you turn in for grading must be your own work.Students are expected to conform to academic standards in avoiding plagiarism.Detailed policy statement is in the syllabus.
Academic Honesty (cont)
Aiding a fellow student to copy someone else’s work (including your own) places you equally in violation.Includes leaving work world-readable on the computer system!
Failure to report observed violations of the honor code is also a violation.Aiding a fellow student to copy someone else’s work (including your own) places you equally in violation.
Includes leaving work world-readable on the computer system!Failure to report observed violations of the honor code is also a violation.
5.3 Grading
Assignments: 15%Semester project: 50%
Midterm exam: 15%Final exam: 20%
6 Course ThemesQuestions
What is “Software Engineering”?
What is “Engineering”?
Is there “engineering” in software development?
6.1 GoalsLook at best practices from the open source world.What are the tools and techniques that developers use on a daily basis?Automate best practices.
Make it more trouble and more time consuming to do things wrong than to do them right.
6.2 Areas of EmphasisTeamworkTest-Driven Development (JUnit)
Build Management (Gradle)
Version Control (Git)Configuration Management (Gradle)Documentation Management (Javadoc)
6.2.1 Teamwork
Reaching and recording agreementCollaborating without conflict.Visibility & communication
6.2.2 Test-Driven Development (TDD)
Exemplified by the philosophy of “write the tests first, then design and write the code.”
This is easily justified when fixing bugs. You need to have a test handy that shows the bug causing the program to fail, so that you can run it (again and again)while you try to fix the bug. How else will you know that you have it fixed?
But test-driven development is really about how to do the initial design. Programmers are often lazy. If they write the code first, they often skimp on the testingbecause that seems like too much work for code that they are “sure” is correct. (Remember, all programmers are optimists – they always believe that their codeis going to work “as soon as I fix this one bug”. The combination of unjustified optimism and laziness can be deadly!)
But if the tests are already there, and if it’s easy to run them (or, even better, hard not to run them – I’ll talk about that in just a moment), then programmers willactually do the testing on a regular basis.
And thinking about tests for special/boundary cases, etc., often helps you remember those cases when later doing the design and coding.
6.2.3 Build Management
Making sure that you and others can build the system easily.
A good build manager will not only compile and link the source code…
It will also run the tests
making it more difficult to not test after every change
and update the documentation and reports
again, actually requiring the programmer to work harder to let these things get out of sync than to keep them up to date
6.2.4 Version Control
The ability to track changes in the software.
Avoid accidentsresolve conflicting changesExplore ideas, then discard ones that turn out to be undesirable
6.2.5 Configuration Management
How do we cope with importing third-party libraries that are themselves changing and have version depencies among themselves?How do we cope with dependencies of our own software upon the underlying platform?
6.2.6 Documentation Management
Integrating documentation into the development process,minimizing the “chore”.
Account Setup
Author: Date: Mar 25, 2020 TOC: yes
1 Do you have an active CS Network account?Important: The CS Dept maintains its own network of Windows PCs and Unix machines. These are separate from the general University PCs maintained bythe ITS (the general university computing services).
In this course, you need a CS network account, which is entirely separate from the general University account you get from ITS.
If you had a CS account in the past (if you took other CS courses, you probably did), it should be reactivated. Follow the steps [Confirming Your Account][] tobe sure.
2 If You Have Never Had A CS AccountBrowse to http://www.cs.odu.edu and click on “Account Creation”.
After following the account creation procedure, use the browser to view your ODU email and wait for the authentication message.
After authenticating, you will probably need to finish the steps below on another day.
3 Confirming Your AccountIf/when You Have A CS account…
3.1 The CS Windows NetworkLog in on one of the Windows PCs.
If you are on-campus, the CS Dept has labs in Dragas and E&CS.
If you are off-campus, you can do this via the Virtual PC Lab. Open a browser and go to the CS Dept home page. Look for the link to the “VirtualComputing Lab”. Follow the instructions given there under “Connecting” to open a Remote Desktop Connection to a virtual lab machine.
If you have a CS account login, but have forgotten your password, go to the CS Dept home page and click on “Password Reset”.
If your account is not working for some other reason, contact the system staff.
3.2 The CS Unix NetworkUsing an ssh client, log in to one of our Linux servers.
If you are able to log in, give the command “pwd”. It should respond “/home/yourLoginName”
If you see anything else (most likely, just “/”), then there was a problem with your account setup. Contact the system staff.
If all looks OK, then give the command “exit” to log out.
4 If You Have Problems4.1 Account Problems: Contacting the CS Systems StaffImportant: the CS Dept.’s computing facilities are separate from the general University facilities run by the ITS. ITS staff at the Technical Support Center inWebb Center cannot, in most cases, help you with account problems on the CS Dept. network.
To contact the CS Dept systems staff, send email to [email protected]. As with any emailed communication, you will get the most satisfactory results if youdescribe your problem clearly and fully.
4.2 Problems with Website RegistrationIf you encounter problems when trying to register with either the CS350 website, it’s probably not something the CS systems staff can help with, because if youhave done the prior steps, we already know you have a working account.
Be aware that my own lists of students enrolled in the course and of newly-issued login names for students who have created accounts is updatedovernight.
If you have enrolled in the course within a day or two, it takes up to a day for ODU to provide that info to us, and then up to a day for my website to pickit up.
If you have just created a CS network account, my website will not know it until the next morning.
Contact the course instructor ([email protected]) if that does not resolve the problem.
What Makes Software Development Difficult?Steven J Zeil
Last modified: Dec 21, 2019
Abstract
Not surprisingly, the title question is of much interest to developers and their managers. There have been quite a few studies devoted to that subject.
One place where identifying complicating factors comes into play is in trying to estimate the cost of a project before it can begin.
A number of different cost models have been proposed.We’ll look at one of them to get a sense of the community consensus on what the factors might be.
1 Opening DiscussionHow do you expect professional software to differ from “academic” software development?
What are some of the factors that make some software development projects especially difficult?
2 Lessons from a Cost ModelOne of the best known software development cost models is
COCOMO (COnstructive COst MOdel) by B. Boehm, 1981
The Intermediate Cocomo formula estimates effort ( ) in person-months:
KLoC stands for “Kilo (thousands) of Lines of Code”, and represents an estimate of how large a propsoed project is going to be.
Coming up with that estimate is a separate and non-trivial taskThe rest of the effort formula is an attempt to figure out how hard it will be to develop that many lines of code.
E
E = (KLoC ⋅ EAFai )bi
The constants and describe the characteristics of the development team.
These are obtained by fitting the model to the costs observed in past projects developed by the same team.
2.1 ExampleSuppose that in previous projects of various sizes, our company’s performance had fitted to , .
If we are looking at doing a 10 KLOC project that is very similar to past projects, the estimated effort would be
(The EAF will be 1.0 if this project is very similar to past ones.)
If we are looking at doing a 20 KLOC project that is very similar to past projects, the estimated effort would be
2.2 EAFThe most interesting part of the COCOMO formula is the EAF, a multiplier computed from a checklist of project attributes.
Each attribute describes some characteristic that affects how hard it is to develop software.The team managers assign each attribute an estimated rating ranging from “Very Low” to “Extra High”
This yields a multiplier value for that characteristic,1.0 is “average”values > 1.0 mean that more effort is requiredvalues < 1.0 mean that less effort is required
2.2.1 Cost factors
Ratings
ai bi
a = 3.0 b = 1.12
E = (KLoC ⋅ EAFai )bi
= 3.0 ⋅ ⋅ 1.0101.12
= 40person-months
E = (KLoC ⋅ EAFai )bi
= 3.0 ⋅ ⋅ 1.0201.12
= 86person-months
Cost Drivers Very Low Low Nominal High Very High Extra HighRatings
Cost Drivers Very Low Low Nominal High Very High Extra HighProduct attributesRequired software reliability 0.75 0.88 1.00 1.15 1.40Size of application database 0.94 1.00 1.08 1.16Complexity of the product 0.70 0.85 1.00 1.15 1.30 1.65Hardware attributesRun-time performance constraints 1.00 1.11 1.30 1.66Memory constraints 1.00 1.06 1.21 1.56Volatility of the target environment 0.87 1.00 1.15 1.30Required turnabout time 0.87 1.00 1.07 1.15Personnel attributesAnalyst capability 1.46 1.19 1.00 0.86 0.71Applications experience 1.29 1.13 1.00 0.91 0.82Software engineer capability 1.42 1.17 1.00 0.86 0.70Target environment experience 1.21 1.10 1.00 0.90Programming language experience 1.14 1.07 1.00 0.95Project attributesApplication of software engineering methods 1.24 1.10 1.00 0.91 0.82Use of software tools 1.24 1.10 1.00 0.91 0.83Required development schedule 1.23 1.08 1.00 1.04 1.10
2.3 ExampleSuppose that in previous projects of various sizes, our company’s performance had fitted to , .
If we are looking at doing a 10 KLOC project that is very similar to past projects, the estimated effort would be
But suppose that we have decided to write this project in a programming language that is new to our team, and for which our preferred debugging tools areunavailable.
a = 3.0 b = 1.12
E = (KLoC ⋅ EAFai )bi
= 3.0 ⋅ ⋅ 1.0101.12
= 40 person-months
The EAF table says
RatingsCost Drivers Very Low Low Nominal High Very High Extra High
Programming language experience 1.14 1.07 1.00 0.95Use of software tools 1.24 1.10 1.00 0.91 0.83
So we might guess that our EAF = 1.254, so those two factors add an additional 25% to the effort estimate:
2.4 Trends
RatingsCost Drivers Very Low Low Nominal High Very High Extra High
Product attributesRequired software reliability 0.75 0.88 1.00 1.15 1.40Size of application database 0.94 1.00 1.08 1.16Complexity of the product 0.70 0.85 1.00 1.15 1.30 1.65Hardware attributesRun-time performance constraints 1.00 1.11 1.30 1.66Memory constraints 1.00 1.06 1.21 1.56Volatility of the target environment 0.87 1.00 1.15 1.30Required turnabout time 0.87 1.00 1.07 1.15Personnel attributesAnalyst capability 1.46 1.19 1.00 0.86 0.71Applications experience 1.29 1.13 1.00 0.91 0.82Software engineer capability 1.42 1.17 1.00 0.86 0.70Target environment experience 1.21 1.10 1.00 0.90Programming language experience 1.14 1.07 1.00 0.95Project attributesApplication of software engineering methods 1.24 1.10 1.00 0.91 0.82Use of software tools 1.24 1.10 1.00 0.91 0.83
= 1.14 ∗ 1.10
E = (KLoC ⋅ EAFai )bi
= 3.0 ⋅ ⋅ 1.25101.12
= 50 person-months
RatingsCost Drivers Very Low Low Nominal High Very High Extra High
Required development schedule 1.23 1.08 1.00 1.04 1.10
Some of the trends are obvious.
As required reliability and product complexity increase, so does the effort requiredAs the team’s level of experience with the application area, the target environment, and the programming language increase, the expected effortgoes down.
Some of the trends are not monotonic.
As the required development schedule becomes more rigorous, the effort initially decreases (concrete deadlines may motivate performance) andthen increases (stress and the need for compromises to meet deadlines).
3 Things to think aboutConsider a project with the following characteristics:
Large development teams withhigh turnovervastly different levels of experience / ability
Minimal opportunity for face-to-face communication
What do you think the EAC would be for this project?
Can you think of a situation where these project characteristics are common? +
Consider a typical open source project on SourceForge or similar sites. They can be staffed largely by volunteers, working remotely. The developers may noteven live and work in the same timezone, so face-to-face meetings are unlikely and most communication will be asynchronous (e.g., e-mail).
The participants may range from seasoned veterans to near-novices.
By conventional thinking, it’s a wonder that such projects ever succeed!
Software Development Process ModelsSteven J Zeil
Last modified: Dec 21, 2019
Contents:1 Software Development Process Models
1.1 Common Activities2 Major Software Development Models3 The Waterfall Model
3.1 Verification & Validation3.2 Testing throughout the Waterfall3.3 Advantages of Waterfall3.4 Disadvantages of Waterfall
4 Iterative/Incremental Development4.1 Advantages4.2 Disadvantages
5 The Spiral Model5.1 Advantages of Spiral Model5.2 Disadvantages of Spiral Model
6 Rational Unified Process6.1 Unified Model Phases6.2 Unified Model Phases Continued6.3 Key Concepts of the RUP6.4 Advantages of RUP6.5 Disadvantages of RUP
7 Agile Methods
Abstract
Each development organization has its own working process that it evolves for how it gets software development done. In this lesson, we look at the constituentsteps that make up typical instances of these processes, and will survey some of the more common arrangments of those steps into a software developmentprocess model:
the Waterfall ModelIterative and Incremental ModelsThe Spiral ModelThe Rational Unified Process
Agile models
1 Software Development Process ModelsAbstract
Each development organization has its own working process that it evolves for how it gets software development done.
Although that might sound chaotic, in practice there is pretty broad consensus on what the constituent steps are that make up the entire process. So it becomes amatter of arranging those steps, of tweaking the details, and especially of settling on the relative emphasis and level of detail in these steps.
The arrangements that companies arrive at are seldom entirely innovative. Instead, they fall into a few standard patterns, which we will survey in this lesson.
A software development process model (SDPM), a.k.a., a software life-cycle model, is the process by which an organization develops software.
Projects typically broken into phasesThere are varying criteria for entering or exiting each phase
1.1 Common ActivitiesAlthough there are many models (in theory, one per development team), there is pretty broad agreement on what needs to go on during this process:
1. Recognition of problem / need / opportunity2. Feasibility study3. Analysis of requirements4. Design of system5. Implementation6. Testing7. Deployment8. Maintenance
Different SDPMs will divide these activities among phases in different ways.
Let’s talk about a few of these in more detail.
1.1.1 Analysis of Requirements
Examine existing system.
How does it work?
What are its shortcomings?
Propose a new system
Enumerate precisely what the new system will do
“System” here is used in its generic sense: a collection of people, organizations, machines, and procedures for getting things done. There’s almost always anexisting system, even if it is totally un-automated.
If you are a follower of Object-Oriented (OO) approaches, you have a deep conviction that studying and, ultimately, simulating an existing system is afundamental principle of software development. OO developers never ask the question “Is it possible to build a system that does X?”. That’s because the existingsystem serves as an existence proof — they’re already doing X, so we start by understanding and then simulating what they are doing now.
Common Documents from Requirements Analysis
Feasibility reportSoftware Requirements Specification (SRS)
Detailed statement of problemFunctional requirementsConstraints on the system
We’ll look at requirements in more detail in a later section.
1.1.2 Design
“Design” means deriving
a solution which satisfies the software requirements.
Commonly recognized subproblems include
architectural design,
the collection of decisions that need to be common to all components.
Examples of architectural design decisions would be
Will the system run on a single machine or be distributed over multiple CPUs?Will outputs be stored in a file or in a database?How will run-{}time errors be handled?
high-level design,
dividing the system into components, and
low-level design
choosing the data structures and algorithms for a single component.
A possible breakdown of design activities
You are probably pretty familiar already with procedures for doing high-level and low-level design. Architectural design, on the other hand, is something that isseldom worth worrying about in the scale of projects addressed within an academic semester.
The breakdown shown in this picture is probably more elaborate than you would have attempted, though the component ideas should, considered separately, beclear enough.
The diagram here suggests a fairly document-heavy process typical of Waterfall, our first process model.
1.1.3 Maintenance
Maintenance is another practice that seldom arises in academic projects. Normally, when you do an assignment for a course, you’re completely done with at theend of the semester. Keeping it working, adding new functionality, etc., is not a concern.
But you’ve certainly seen how operating systems, application programs, games, and many other software products are subject to an ongoing process of bugfixes, enhancements, and new releases.
Maintenance can have a number of forms:
Corrective
fixing problems
Adaptivechanges in environment
Perfectiveadding featuresimproving performance
Preventativerefactoring to improve maintainability
Refactoring is a change made that preserves the current behavior of the system.
2 Major Software Development ModelsThe Waterfall ModelIterative and Incremental ModelsThe Spiral ModelThe Rational Unified ProcessAgile Models
3 The Waterfall Model
The best known process model
dates from 1970’sthough widely derided, it remains widely used
and its terminology forms the basis for almost all discussions of other SDPMs
Defining characteristic: movement from phase to phase is always forward (downhill),irreversible
Milestones are set at each phase transitionschedule deadlinesrequired reportsrequired approval to move on
The waterfall model gets its name from the fact that the first diagrams of this process illustrated it as a series of terraces over which a stream flowed, cascadingdown from one level to another. The point of this portrayal was that water always flows downhill — it can’t reverse itself. Similarly, the defining characteristicof the waterfall model is the irreversible forward progress from phase to phase.
Waterfall is often criticized as inflexible, in large part because of that irreversible forward motion. Many organizations, in practice, will do a kind of “waterfallwith appeals”, allowing developers to revisit and revise decisions and documents from earlier phases after jumping through a number of deliberately restrictivehoops.
3.1 Verification & ValidationMost of the activities in Waterfall are familiar, with the possible exception of requirements analysis, which we will be looking at in more detail in a later lesson.
For now, I want to look at Verification and Validation (V&V).
Verification & Validation: assuring that a software system meets the users’ needs.
The principle objectives are:
The discovery of defects in a systemThe assessment of whether or not the system is usable in an operational situation.
3.1.1 What’s the Difference?
Verification:
“Are we building the product right” (Boehm)The software should conform to its (most recent) specification
Validation:
“Are we building the right product”The software should do what the user really requires
Verification is essentially looking for mistakes in our most recent bit of work by comparing what we have now to the most recent “official” document definingour system.
Validation is a return to first principles, comparing what we have now to what we (or our customers) originally wanted.
You might think that, in a process divided into steps, if we do each step “correctly”, then the entire sequence must be “correct”. In practice, though, theaccumulation of small errors can lead to massive alterations over time. (That’s not just a matter for programmers.)
Most V&V activities mix verification and validation together to different degrees.
Testing
Testing is the act of executing a program with selected data to uncover bugs.As opposed to debugging, which is the process of finding the faulty code responsible for failed tests.
Testing is the most common, but not the only form of V&V
Industry figures of 1-3 faults per 100 statements are quite common.
Is testing verification or validation? A great deal depends on how we decide whether the test output is correct. If we do this by viewing the data ourselves andlooking for things that jump out to our eyes as “wrong”, then we are doing mainly validation. On the other hand, if part of our design process was to set up a setof tests with files of their expected outputs, and we are simply comparing the actual output files to the expected output files, then we are doing moreverification.
3.2 Testing throughout the Waterfall
3.2.1 Testing stages
Unit Test: Tests of individual subroutines and modules,
usually conducted by the programmer.
Integration Test: Tests of “subtrees” of the total project hierarchy chart (groups of subroutines calling each other).generally a team responsibility.
System Test: Test of the entire system,supervised by team leaders or by V&V specialists.Many companies have independent teams for this purpose.
Regression Test: Unit/Integration/System tests that are repeated after a change has been made to the code.Acceptance Test: A test conducted by the customers or their representatives to decide whether to purchase/accept a developed system.
3.2.2 Not just in one phase
Although the waterfall model shows V&V as a separate phase near the end, we know that some forms of V&V occur much earlier.
Requirements are validated in consultation with the customers.Unit testing occurs during Implementation, etc.
So this phase of the waterfall model really describes system and acceptance testing.
A Still-broader View
Even the “V&V V” does not capture the full context of V&V:
Requirements must be validatedDesigns may be validated & verifiedMaintenance changes are tested
3.3 Advantages of WaterfallLinear structure is easy to understandDevelopment progress is easily estimated and explicitly documentedWidely knownScales well
3.4 Disadvantages of WaterfallInflexible: corrections limited to current phaseIn some projects, requirements are not known or understood early in the life-cycleWorking version of system is only available near the endOften becomes a documentation mill
4 Iterative/Incremental Development
A variety of related approachesa counter-reaction to what many believe to be an overly rigid, management-focused,waterfall model
emphasize quick cycles of development, usually with earlier and more user-orientedvalidationRequirements specification, design and implementation are interleaved.Each version adds a small amount of additional functionality.
As a counter-reaction to what many believe to be an overly rigid waterfall model, there are a varietyof incremental approaches that emphasize quick cycles of development, usually with earlier and more user-oriented validation.
There is a greater emphasis on producing intermediate versions, each adding a small amount of additional functionality. Some of these are releases, eitherexternal (released outside the team) or internal (seen only by the team), which may have been planned earlier.
What’s the difference between iterative and incremental?
“Iterative” means that we can re-visit decisions, design, and code produced in earlier iterative steps.
“Incremental” means that each iteration produces just a small unit of additional functional behavior. We don’t try to build major subsystems of the projectin a single pass.
This often requires a more “vertical” view in which we implement a bit of high level control code and pieces of related low-level code.
As opposed to the “horizontal” approach of working “bottom up” and implementing the low-level ADTS, then the code that calls, upon them, then…, ending with the top-level interface ot the whole program.
Or the “horizontal” approach of working “top down” and implementing the most abstract code (the GUI or command-line interfaces), thenfunctions that they call, then the … ending with the lowest-level ADTS that don’t call on anything else.
Iterative versus Incremental Models
Iterative – we do some set of process steps repeatedly.
To use a programming analogy, this is iterative:
while (!done) { ⋮
}
Incremental – we accumulate value in small steps.
To use a programming analogy, this is incremental:
total += x;
Incremental development is almost always iterative, but you can be iterative without being incremental.
Variations
Some projects employ throw-away prototyping, versions whose code is only used to demonstrate and evaluate possibilities.
This can lead to insight into poorly understood requirements.
Evolutionary prototyping keeps the prototypes, gradually evolving them into the final deliverable
Some waterfall projects may employ incremental schemes for parts of large systems (e.g., the user interface).
4.1 AdvantagesAbility to explore poorly understood requirementsFlexibilityWorking implementation is available early.
4.2 DisadvantagesPoor process visibility (e.g., are we on schedule?),
Continual small drifts from the main architecture leading to poorly structured systems.
Dead-ends (the local optimization problem)
5 The Spiral Model
1986, Boehm
An iterative approach with a focus on risk management
Each iteration builds on the earlier ones
risk: an uncertain outcome with a potential for loss
Examples:
team inexperienceinability to meet scheduleuncertainty in requirements
Spiral Phases
1. Determine objectives, alternatives and constraints:Define requirementsAlternatives (including, e.g., 3rd-party code) identifiedConstraints defined
2. Identify and resolve risks, evaluate alternatives:Evaluate identified alternativesIdentify risksResolve risksProduce prototype
3. Develop and testAnalyze performance of prototypeCreate & review design, code, test
4. Plan next iterationOften includes customer evaluation of prototype or current project iteration
5.1 Advantages of Spiral ModelFlexible – emphasis on alleviating risks as they are identifiedConsiderable opportunity for validationScales wellGood process visibilityWorking releases/prototypes produced early
5.2 Disadvantages of Spiral ModelCan be costly (slow?)Risk analysis is a specialized skill, but critical to project success
6 Rational Unified Process1997, Jacobsen, Booch, and Rumbaugh,
Best Practices
Develop iterativelyManage requirementsUse componentsModel visuallyVerify qualityControl changes
These three were already some of the biggest names in OOA&D before they decided to collaborate on a unified version of their previously distinctiveapproaches.
Their collaboration coincided with their being hired by Rational Corp., a major vendor of software development tools. Hence the “Rational” in RUP refers tothe name of the company. It’s not bragging. They aren’t saying that this is a uniquely intellectual approach or that Waterfall, Spiral, et. al., are “irrational”.
6.1 Unified Model Phases
Inception: initial concept
Pitching the project conceptUsually informal, low details.“Perhaps we should build a … ”
Elaboration: exploring requirements
Adding detail to our understanding of what the system should do.Produces
Domain modelAnalysis modelRequirements documentRelease plan
6.2 Unified Model Phases ContinuedConstruction: building the software
Design & implementation
Transition: final packaging
Activities that can’t be done incrementally during construction, e.g.,performance tuninguser training
Releases
One task during Elaboration is to plan releases:
Major phases are divided into increments, each of which ends with a release.
A release is some kind of product that implements some part of the required functionality
Its existence and/or acceptance by management shows that we are ready to move on.
The release plan records decisions about
How many releases there will beWhat functionality will be added with each releaseWhen the releases will be madeWhich releases are internal (i.e., only the development team sees them) and which are external
The term “increments” gets used a lot in different models. Sometimes it refers, as it does here, to the time period during which the next release of the software isdeveloped. In other cases it refers to the next version of the software. In other cases it refers to the software release itself.
6.3 Key Concepts of the RUP
6.3.1 Common Workflows
Although waterfall and other SDPMs treat analysis, design, etc., as one-timephasesCareful study shows that developers do analysis, design, etc., activitiescontinuously.
Analysis: what do we need the (currently considered part of the)system to do?
Design: how do we get it to do that?
Implementation: write out that series of design decisions in anappropriate notation (e.g., code, diagrams, requirements statements)Validation: Is our implementation correct?
For example, deep in the implementation phase of a Waterfall project, aprogrammer is assigned a function to implement.
That programmer will
think carefully about what the function is supposed to do (analysis)choose an algorithm that will accomplish that (design)code the function (implementation)unit-test that function (validation).
But we aren’t in the analysis, design, or validation phases.
The diagram on the right is supposed to illustrate that, although the percentage of time devoted to the activities of analysis, design, implementation, andvalidation, none of those activites ever entirely go away and are, once and for all, done.
A process model may still use some of these same terms as the name for major phases, but that’s really a different sense of the terms. For example, the “Design”phase of the Waterfall is when the language in which we “implement” is the collection of notations and diagrams that we use for system design. But we stillanalyze, design, implement, and validate our Design decisions.
ADIV
In the RUP, all progress is made as continual ADIV cycles
ADIV: Analysis, Design, Implementation, Validation
6.3.2 An Evolution of Models
RUP supports development via a series of models.
The most important of these are
Domain Model
A model of the application domain as it currently exists, before we began our new development project.Ensures that the development team understands the world that the system will work in.
Analysis Model
A model of how the world will interact with the software system that we envision.Expresses what the system will do when it is working.
Design Model
Describes how we can get the system to do the things the analysis model says it should do.
Models Evolved
RUP embraces the
Object-Oriented philosophy
Every program is a simulationThe quality of a program’s design is proportional to how faithfully the objects and interactions in the program reflect those in the real world
Domain, analysis, and design models all focus on how classes of objects interact with one another
Most of the classes in the design are presumed to have already been described as part of the analysis model,
Most of the classes in the analysis model are presumed to have already been described as part of the domain model,
6.4 Advantages of RUPProcess details are expressed in general terms, allowing local customizationHeavy emphasis on documentation (UML)Can embrace incremental releasesEvolutionary approach can lead to clean implementations
6.5 Disadvantages of RUPProcess details are expressed in general terms, providing minimal guidance and requiring local customizationComplexHeavy documentation can be expensive
7 Agile MethodsA modern variant of incremental development.
Agile development is
A reaction against heavily-managed, documentation-heavy processesA social movement within the software development profession
Introduced in the Agile Manifesto (2001)
Emphasis Areas
Emphasis is on
Iterative & incremental developmentFrequent communication with customer representatives
Work is organized via “user stories”Short “time-boxed” development cyclesFocus on quality as a matter of professional prideAdoption of professional best-practices
We’ll look at Agile in more detail later in the semester, after we have learned more about these “best practices” that lie at the heart of the process.
Lab: Secure Shell KeysSteven J Zeil
Last modified: Mar 25, 2020
Contents:1 ssh Isn’t Just for Terminal Sessions2 Replacing Passwords with ssh Keys
2.1 Generate a Key Pair2.2 Try It Out2.3 Key Agents2.4 Creating Special-Purpose Key Pairs2.5 Playing with Fire: Password-Free Key Pairs
This is a self-assessment activity to give you practice in working with the git version control system. Feel free to share your problems, experiences, andchoices in the Forum.
1 ssh Isn’t Just for Terminal SessionsYou should already be familiar with ssh even if you are used to invoking it through PuTTY.
In this lab, you will be working with ssh from a command line. You will be working with
a server machine, which will be one of the CS Dept Linux servers. I will assume in the rest of this document that you are using atria.cs.odu.edu asyour server.
a client machine, at which you can issue ssh commands. This will need to be a different machine from the server.
Ideally, use a PC of your own.
If your PC runs Linux or OS/X, you should be fine.If your PC runs runs Windows 10, good options are Bash on Windows. or the OpenSSH command-line tool in Powershell. (You may need tomake some changes to the paths referenced in these commands if you are working in Powershell.)
Other good options for Windows 10 or for older versions of Windows are CygWin or setting up a Linux virtual machine.
You can use two different CS Linux servers as your client and server (e.g., atria.cs.odu.edu and sirius.cs.odu.edu) but it’s not ideal. Somemistakes that you might make will be disguised by the fact that you are already logged in to the network via the client machine, so it will be hard totell if your commands are working because you did the lab correctly or because you are already logged in.
Let’s start with just the basics of using ssh from the command line.
1. Try opening a remote session on your server machine by issuing the following command on your client machine:
ssh -l yourCSLoginName atria.cs.odu.edu
You can omit the “-l yourCSLoginName” if your current terminal session is under a user name identical to your CS Dept login name. You can alsocombine it with the machine name, separated by an “@”:
This opens up what should be a familiar text-mode command session on the remote machine. Issue a few commands to verify that everything is familiar,and then log out of the remote machine.
2. Now give the same command, but append a command string to the end:
ssh -l yourCSLoginName atria.cs.odu.edu ls -l
ssh is useful for issuing all sorts of commands to a remote machine. The “default” is to issue the command to open a login shell, but you can issue anycommand you want.
ssh has other tricks to offer as well.
ssh servers also, by default, provide file copying services via scp and sftp..
The ssh protocol can act as a “tunnel” for other common network protocols (e.g., email). This includes protocols that normally are limited to localnetwork connections or that, for other reasons, have trouble getting through firewalls and routers. For example, all of the techniques covered in CS252 forconnecting via X Windows have actually relied upon an ssh tunnel to carry the X or NX protocol messages between the two machines.
We’ll be making heavy use of both of these features of ssh in the coming semester. However, these will call for a more sophisticated approach to identifyingourselves than explicitly typing in our login names and passwords for every connection to a network service.
2 Replacing Passwords with ssh Keysssh keys provide a way of identifying yourself that is generally more secure than simple passwords. Based on one-way cryptography, an ssh key has two parts: apublic key and a private key. You can distribute the public key to a variety of server systems that you like to log in to. You keep the private key on clientmachines that you log in from. Often these client machines are ones you have a certain amount of physical control over — a home computer or a laptop that you
own. That physical security is coupled with a lengthy passphrase needed to activate the private key. Once activated, the private key can be kept active through awork session, allowing you to repeatedly log in to clients that have your public key.
It’s important to keep this straight: * The public key goes onto the remote server that you are connecting to. * The private key stays on your (local)client that you are connecting from.
2.1 Generate a Key PairThe ssh-keygen program is most commonly used to generate public/private key pairs.
Most Linux systems will have this already installed.
On a Windows CygWin system, you can get it as part of the openssh package.
Another possibility is Pageant, part of the PuTTY ssh suite for Windows.
Also, you can generate keys from within Eclipse (Window Preferences General Network Connections SSH2 Key Management,but the key length is limited to 1024 bits, which is considered a bit low these days.
1. To generate a key pair, give the commands:
mkdir ~/.ssh # if you don't already have this directory chmod 700 ~/.ssh ssh-keygen -b 2048 -t rsa
You can do this step on your client machine or on the remote Linux server.
You can change the name of the generated files if you like. (I keep different key pairs for different client machines and name them accordingly, e.g.,“officePC”, “homePC”, etc.). Do keep it in your ~/.ssh directory, however.
You will be prompted for a passphrase. This is used to protect your private key in case someone gains access to the machine/account where you have itstored. Do choose one. Even though the command prompt says it’s optional, you don’t want to have an unprotected private key around. Most people usemuch longer passphrases than a typical password, but generally place less emphasis on odd character substitutions that make the phrase harder to type.
2. Now look in your ~/.ssh directory. You should see your new keys. One file has the extension “.pub”. that’s the public part of the key.
3. You can do a quick test of your key pair by giving the command
⇒ ⇒ ⇒ ⇒ ⇒
This is, by the way, an excellent way to check tosee if a pair of files really do belong together as apublic/private key pair, or to check if you haveforgotten your passphrase.
ssh-keygen -y -f path/to/your/private-key
This command will prompt you for your passphrase and, if you are successful in providing it, willthen print the corresponding public key.
Compare the output of that to
cat path/to/your/public-key
They should match except for the little “usr@machine” comment at the end indicating the machine on which you created the key.
Remember, the idea is that you want the public key to be on the remote server and the private key on your local client.
Since you have just created both keys on one machine, one of them is currently out of place.
4. If you created your key pair on your client machine, transfer the public key into your ~/.ssh directory on the remote server. (If you don’t have a ~/.sshdirectory there, create one. Give is the same permissions as shown in step 1, above.)
If, on the other hand, you created your key pair on the remote server, transfer the private key to a convenient directory on your local client, and delete theoriginal private key from the remote server.
2.2 Try It OutAt this point, you should have
A copy of the private key on the local client.A copy of the public key on the remote server.
But just having the public key on the remote server isn’t enough. We have to authorize that key before it will do anything for us.
1. Let’s authorize this key as one that you can use to log in to your CS Linux account.
On Linux servers, you do this by adding the public key to ~/.ssh/authorized_keys. authorized_keys is simply a text file that contains a list of publickeys.
There’s at least three ways to do this.
You can simply attach it to the end of the authorized_keys file:
cd ~/.ssh touch authorized_keys cp -a authorized_keys authorized_keys.bak cat path/to/your/publicKeyFile >> authorized_keys
This works the first time you install a key. But if you need to replace or modify a key, it’s not as useful, because you would wind up with multiplecopies of the same key in your authorized_keys file, leading to unpredictable results.
Alternatively, log in to the server, open ~/.ssh/authorized_keys in your favorite text editor and add the public key to the end. (This is also howyou can clean up this file later if you want to remove a key, either because the key doesn’t work properly or to clean up after this assignment isdone and graded.)
If you are replacing a key, be sure to delete the old entry for that key. If you have entries in there for keys you aren’t using, remove those aswell.
You might read on the Internet about a command ssh-copy-id that combines the file transfer of a public key from a client and the authorizationinto a single step.
Since we are going to be modifying the authorized keys shortly, however, I don’t recommend this. People who rely on this command often wind upwith messy authorized_keys files that are hard to work with.
2. Now, back on your client machine, try connecting to the server:
ssh -i path/to/your/PrivateKey -o "IdentitiesOnly=yes" [email protected]
You should be prompted for the passphrase for your new key.
Troubleshooting
If you run into problems with the final step or with the ones that follow, …
1. Review your steps. Make sure that you have the correct files on the correct machines.
Remember, the public key goes on the remote server inside the authorized_keys file.
The private key goes on your local client machine.
That means that when you are giving a path to your private key in an ssh command, it must be a path on your local machine.
2. Check your permissions on the server very carefully. You should have the equivalent of:
What is an “agent”? An agent is a program that runs, usuallyin the background, and performs some action on your behalfwhen the right conditions present themselves, without requiringyour attention or intervention.
chmod 711 ~
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
3. You may also need to check the permissions on your private key on your local client. Some versions of ssh will refuse to use a private keythat is readable by anyone other than you (the owner of the file).
In particular, if you get a message complaining that your private key permissions are too open, change the permissions on that key!
chmod 600 path-to-your-private-key
If you are using Bash/Ubuntu on Windows, you may find that chmod commands like the one above are ignored. (Do an “ls -l …” tocheck.)
You may be able to make chmod work by using an editor:
sudo nano /etc/wsl.conf
and adding the lines
[automount]
options="metadata"
Then close all open bash instances, restart bash, and see if your chmod commands now work.
2.3 Key AgentsWait, did we just make your life harder?
Now, you may wonder what good that was. Every time you try to log in to a CS Linux machine, you will be prompted for that passphrase, which is probablymuch longer than your old password.
But usually, we don’t activate the private key for a one-shot login. Instead, we run a keyagent on our client machine. We tell it to activate our private key (giving it the passphraseto prove that we are its owner). It then watches for subsequent ssh connection attempts andoffers up the activated private key.
Take note that the ssh-agent command is inside backticks, not apostrophes.
An example of a typical agent is the process that runsperiodically when you have your email program open, thatchecks every few minutes to see if new mail has arrived.
What is as “ssh key agent”? This is an agent that watches forincoming attempts by a remote server to validate an ssh key andautomatically confirms that you have already unlocked that keywith the appropriate passphrase.
1. On your client machine:
eval `ssh-agent` ssh-add ~/.ssh/yourPrivateKeyFilename
This launches a new key agent and tells it to watch for incomingrequests to validate that private key.
2. Then try giving the following commands from your client
ssh [email protected] date ssh [email protected] pwd ssh [email protected] ls ssh -A [email protected]
All of these should work with your being prompted at most once for your passphrase. The last one leaves you logged in to atria. From that session onatria, try logging in to sirius:
ssh sirius.cs.odu.edu
Again, you should find that you are able to do this without being prompted for your password or passphrase. The -A option in the earlier ssh commandcaused your agent’s credentials to be forwarded into your session on atria.
If you like this, you should look into the package keychain, which is a way to set up agents and activate your keys upon logging in to your client. If you don’tcare for it, restore your authorized_keys file in your CS account to its previous state (the .bak file). But keep those keys. We’ll use them later.
2.4 Creating Special-Purpose Key PairsSuppose that you wanted to allow someone else to try out a program that you had written and that is sitting somewhere in your account area. Let’s also supposethat you don’t want to make this program available to the entire world. For example, perhaps you are working on a programming assignment in one of mycourses and you want me to take a look at your running program.
You could, of course, tell me your login name and password, but that would be a bad idea unless you really, really trust me. (And why should you?)
Based on what we have just seen, you could create an ssh key pair, add the public key to your authorized_keys file, give me the private key and itspassphrase. Then, after I had tried your program out, you could simply remove that key from ~/.ssh/authorized_keys to lock me back out.
But that’s still way too trusting of you. During the time I had access to your program, I would have access to everything in your account. I would, evenusing the ssh keys, be logged in as you.
But we can actually adapt that second approach by limiting the key pair to running only a specific command or program when anyone uses it to log in.
1. On the remote server, edit the public key that you created in the earlier steps.
The key is typically written as a single long line ending in _yourLoginName@machineName_, reflecting where you created the key. To the front of thatline, add
command="/usr/bin/env",no-port-forwarding
and a blank space, right in front of the “ssh-rsa”.
(If you aren’t familiar with the env command in Linux, run it to see what it does.)
2. Add that edited public key to your authorized keys list on the server as you did earlier. (Remove the old copy of that key.)
3. You should still have a key agent running on your client. If not, restart it and add your private key.
Now try logging in via that key by giving the following commands on the client:
ssh [email protected] ssh [email protected] pwd
Notice that, whether you give a specific command for ssh to execute on the remote server or omit it, trying for a normal login session, what actuallyhappens is that you are logged in to your account, the env command is run, and then you are logged out.
So, to return to our earlier example, if you were working on a programming assignment in one of my courses and you want me to take a look at your runningprogram, you could create a special-purpose key pair to run that program (and do nothing else), and then give me the private key and passphrase, knowing that Iwould be limited to using your account for that single purpose.
2.5 Playing with Fire: Password-Free Key PairsNow we are going to do something a bit dangerous.
1. Shut down the key agent on your client machine with the command
ssh-agent -k
2. Create a new key pair, giving them a different name from the ones you set up earlier.
This time, however, when prompted for a pass phrase, just hit Enter to create a key pair without a pass phrase.
3. Test your keys via the commands
ssh-keygen -y -fpath/to/your/privateKey cat path/to/your/publicKey
Again, the printed public keys should match except for the trailing comment. This time, however, you should not be prompted for a passphrase.
4. If you created these keys on the remote server, transfer the private key to your local client machine and delete the original.
If you created these keys on your local client, transfer the public key to your ~/.ssh/ directory on the remote server.
5. Now, adding that key to your authorized key list would be risky, because anyone who got a copy of the private key would be able to log into your accountusing no passphrase at all.
So, to keep this safe, immediately edit that public key. This time add
command="pwd",no-port-forwarding,no-agent-forwarding
to the beginning of the public key.
6. Add that edited public key to your authorized key list on the server.
7. On the client, give the command
ssh -i path/To/Your/New/Private/Key [email protected]
You should see that the command is executed without your being prompted for a password or pass phrase.
If you are still prompted for a password, there may be remnants of your ssh-agent interfering. Try
ssh -o IdentitiesOnly=yes -F /dev/null -i path/To/Your/New/Private/Key [email protected]
When you have succeeded in completing these steps, remove the keys you have added to your ~/.ssh/authorized_keys file on the server.
Team OrganizationSteven J Zeil:
Last modified: Dec 21, 2019
Contents:1 Issues
1.1 Communications2 General Organizations
2.1 Hierarchical Team Organization2.2 Matrix Organization
3 Software Team Organizations3.1 Chief Programmer Teams3.2 SWAT Teams3.3 Agile Teams3.4 Distributed Teams
Abstract
Development teams are organized to
promote communication among the team members and between th team and interested external partiesmatch required skills against needs of the projectenhance overall productivity of the team
In this lesson, we look at some of these issues and at some common organizations that have been considered to deal with these.
1 IssuesMajor issues in organizing a development team are
Communication
maximize communication within the team
establish responsive communication points with stakeholders
Staffing
does the team contain all the skill sets reuqired to complete the work?
Productivity
are we making effective use of our team members’ skills?
1.1 CommunicationsThere are two key aspects here
internal communications within the teamexternal communications with interested parties
1.1.1 Internal Communications
Brook’s law is well-known truism of software development:
"Adding manpower to a late software project makes it later. – Fred Brooks, _The Mythical Man-Month, 1975
Why?
Brooks argues that
Complex projects cannot be decomposed into subprojects that can be solved independently without communication.
He suggests that partly, this is an attribute of the problem being solved.
Essential versus accidental complexity
He suggests that sometimes we add complexity by our choices of process or design, but that some problems are just plain complicated. As a consequence,he suggests that there is an
irreducible number of errors
that we will always commit along the way, and that this is independent of the number of people we have working.
But he also argues that
More personnel implies more communications paths
staff have potential communications paths.
Reducing Internal Communications Costs
Our options are limited
Keep teams small, orStructure the teams in ways that facilitate the most critical communications paths
1.1.2 External Communications
We can divide this into
communications with the development team’s own managementcommunications with the eventual users of the software
Historically, the former has been emphasized
visibility of the process is a major factor in Waterfall & Spiral
Over time, more and more emphasis has shifted to communications with the users.
Users, Customers, and Stakeholders
“Users” is an incredibly vague, catch-all term that serves to hide as much as it reveals.
Avoid it when possible.
Customers are the people who have the authority to accept or reject the system, to pay for it or not.
“Pay” is not necessarily a simple monetary transaction.(e.g., internal projects)
Stakeholders are the people with an interest in the structure and behavior of the system.
Example:
A company’s upper-level management approves budget to develop a system. They are the customers.
nn(n−1)
2
The output of the system is a series of reports used on a monthly basis by the company’s middle and low-level managers. They are stakeholders, becausethey have an interest in the content and format of those reports.
Much of the input of the system will be supplied by the company’s clerical staff, sales people, and technicians. They are also stakeholders, because thesystem will directly impact their daily jobs.
We look to customers for general guidelines on what will make the valuable and acceptable to them.
We look to stakeholders for much of the insight into how the system needs to work.
2 General OrganizationsThere are a few “obvious” ways to organize developers into a team.
One might be the basic democracy (or anarchy) of everyone working as peers.
You probably don’t need very large teams before this gets unwieldy.
2.1 Hierarchical Team OrganizationOne team per major subsystemTeams report to managersManagers report to one or more levels of higher managers
Dangers of Hierarchical Teams
Distance between teams (where real work is done) and higher mgmt may hide problems.
I like van Vliet’s example:
"The following scenario is not entirely fictitious:
bottom: we have severe troubles in implementing module X;
level 1: there are some problems with module X;
level 2: progress is steady, I do not foresee any real problems;
top: everything proceeds according to our plan."
Level in hierarchy often equates with rank / rewards
Engineers either cannot rise in level or are forced to abandon their primary skills to be managers
2.2 Matrix OrganizationStaff are assigned specialized roles based on skill sets
e.g., architects, designers, analysts, testersTeam managers address general ability of team to perform role
Subprojects identify required role playersTeams with commonly required roles move among multiple subprojectsProject managers oversees specific project
Roles/TeamsSub-systems Coding Architecture Analysis Testing GUI
A Y Y Y YB Y Y Y YC Y Y YD Y Y
Clearly this has limited benefit if an organization is so small that they have just one architect, one tester, …
In this model, team members with critical skills can be swapped from project to project depending on where each project is within the SDPM. For example, agood architectural designer may leave projects once they enter the high-level design phase.
Dangers of Matrix Organization
Restricted inter-role communicationManagement clashes
project managers may clash with the higher managers that assign people via the matrix.
3 Software Team OrganizationsAnarchy, hierarchy, and matrix are all pretty generic forms.
Next we look at some specific organizations that have been attempted.
3.1 Chief Programmer TeamsMills (1970)
Motivated in part by studies showing vast differences in productivity between different individuals within an organization
Programming productivity studies have found that the “best” people in many organizations were orders of magnitude more productive than the average.(Not than the worst, but than the average.)
Some of this correlates with varying degrees of experience, both in general and with the specific problem domain and development environment at hand.But some of this seems to indicate that some people just are better at this than others.
Aims to allow the potentially most productive people to work unhindered
Organization of the Chief Programmer Team
Chief programmer is team leaderresponsible for design and critical parts of the implementation
Assistant programmer assists chief and does rest of the implementationLibrarian handles documentation, deployment, code management, etc.A few specialists can augment the team
Observations
A very coding-centric viewHigh stress on chiefLow level of satisfaction for remainder of teamDoes not scale well to large projects.
3.2 SWAT TeamsSWAT == Skilled With Advanced Tools
A team organization for iterative process models
Small team, sharing a common workspaceHeavy tool supportTeam leader is “first among equals”
3.3 Agile TeamsSmall teams, often sharing a workspace
Typically, one team manager
Often includes customers/stakeholders or their designated representative as a team member.Team sets their own best practices
selects toolsEmphasis on self-management, self-motivation
Professional pride
3.4 Distributed TeamsCommon in open-source development
Small core team responsible for project “vision”Associated developers submit patches, changes, etc.
Must be approved by core team to be merged into official projectA pull request is a request for others to check out a proposed change.
Users submit bug reports and feature requestsMay be prioritized by core team.But an associated developer is not prohibited from working on a low-priority request.
Total team can be huge and diverse.
Closing Thoughts
If you were formed into groups, this week, to work on a project, how would you organize yourselves?
Would it match any of the organizations shown here?
Does it make a difference to your answer whether this is a distance course?
High-Level Design: A Quick OverviewSteven J Zeil
Last modified: Jan 29, 2020
Contents:1 Levels of Design2 Modeling Classes
2.1 Attributes2.2 Operations2.3 Relations
3 Classification3.1 How Do We Discover Classes?3.2 How Do We Discover Relationships Among Classes?3.3 Keep It Real!
Abstract
High-level design is the process of breaking a large system into smaller manageable pieces that can work together to solve a larger problem.
This lesson presents a quick overview of classification, the dominant approach to high-level design.
1 Levels of DesignSoftware design is largely deivided into
Architectural Designglobal decisions that affect nearly all components of a system.
High-Level Designthe division of a system into modular components
Low-Level Designthe selection of data structures and algorithms to implement an individual component
CS361 emphasizes this level of design
CS330 emphasizes high-level design
if you have taken CS330, most of this lecture will be review for you.
CS361 emphasizes low-level design
In current practice, the primary type of “component” that we seek is the class.
The process of discovering such classes is classification, and that is what this lesson will concentrate on.
2 Modeling ClassesWe model classes by focusing on
attributesthe data components that conceptually make up or are “contained” within a class
methodsa.k.a. operations, the things we can to do to object of that class
relationshow does this class interact with the other classes in our design?
2.1 AttributesAn attribute of a class is a data property that is conceptually a part of that class.
For example, we might say that an duration or elapsed time can be broken down into hours, minutes, and seconds. Shown on the right is a UML diagram for aclass with those three attributes.
2.1.1 Attributes Data Members
One possible realization of this class would be to indeed represent attributes as data members:
class Duration { public: int hours; int minutes; int seconds; }
Most class designers would balk at this however, preferring
≠
class Duration { private: int hours; int minutes; int seconds; public: ⋮ int getHours() const {return hours;} void setHours (int h) { hours = h; } int getMinutes() const {return minutes;} void setMinutes (int m) {minutes = m; } int getSeconds() const {return seconds;} void setSeconds (int s) { seconds = s; } }
Both of these are perfectly reasonable realizations of the idea that “a duration of time has attributes of hours, minutes, and seconds”.
This is another perfectly reasonable realization of that idea:
class Duration { private: int secondsSinceMidnight; public: ⋮ int getHours() const {return secondSinceMidnight / 3600;} void setHours (int h); int getMinutes() const {return (secondSinceMidnight % 3600) / 60;} void setMinutes (int m); int getSeconds() const {return secondSinceMidnight % 60;} void setSeconds (int s); }
This implementation might be favored if we expect to do a lot of calculations on durations (e.g., adding times together).
This demonstrates that we can have the logical idea of an attribute that does not reflect the eventual data members. All of these possible implementations arestill represented by the same UML diagram.
2.2 OperationsOperations are things we do to/with an object that are conceptually more complex than simple storage and retrieval.
It makes sense, for example, to add one duration to another. E.g., if I listen to a music track that takes 4 minutes, then another that takes 3 minutes and 30seconds, then I have spent a total of 7 minutes, 30 seconds listening to music. Similarly if I say that one track takes 4 minutes and another takes 30 seconds lessthan the first, I should be able to compute the acutal duration of the second track by subtraction. I might also want to allow multiplication by an integer so thatwe could work with concepts like “twice as long as”.
In programming terms, operations will map onto public function members. (It is possible to model private members in UML, but that is generally reserved forthe very late states of design.)
It’s possible to have classes that have the same attributes but different operations (and vice-versa).
Another idea of “time” is that of the time of day – a particular instant in time rather than a duration. The attributes are the same, but the operations would bedifferent. It does not make any sense to add one time of day to another – you can’t add 12:30PM to 4:00AM and expect that to actually mean anything. But youmight add a duration of 30 minutes to 12:30PM to figure out what time it is if you arrive for an appointment at 12:30PM and have to wait for 30 minutes.
2.3 RelationsSo far we have talked about what is “inside” a single class. Relations describe important properties between pairs of classes.
2.3.1 Associations
The most basic form of relation is the association, which is simply any named relationship that we wish to discuss or focus on. We read these connections asmeaning
there is a TimeOfDay associated with an Event that tells us when the event starts, andthere is a Duration associated with an Event that tells us how long the event takes".
Associations are very general, so much so that, without the label identifying what we mean by them, they would be too vague to be useful.
But when it’s time to write the labels for an association, there are a few of labels that occur so often in practice that they merit their own UML visual signal:
2.3.2 Aggregation
This new relation is aggregation, and can be read as “an Agenda is part of an Event” or “an Event has a(n) Agenda”.
As a general rule, aggregation relationships could be rewritten as attributes:
This…
…and this…
…mean pretty much the same thing.
Not all attributes, however, can be rewritten as aggregation.
Sometimes we prefer to use the aggregation arrow to make the relationship stand out.
If we think it’s important to our discussion, we may want to make the relationship more visible.
Aggregation is somewhat stronger than an attribute.
For example, I might be OK with this notion of a Student being an attribute of an ID card.
But I have a real problem with the idea that a Student is “part of” an ID card. We don’t press students flat and laminate them onto a piece of plastic!
Now, late in the design phase, when we are clearly talking about programming language classes rather than real-world constructs, I might toleratethis form of aggregation.
2.3.3 Inheritance / Generalization / Specialization
In many real-world scenarios, we encounter pairs of classes where one class is a specialized form of the other. In programming languages, this is referred to asan inheritance relationship, and could be denoted as an association labeled as “specializes” or (in the other direction) “generalizes”, or, more simply “is a”.
For example, we might say that a Duration is a kind of “time”, and a TimeOfDay is another kind of time.
This diagram, for example, captures the ideas that
All variations on the idea of “Time” will have same attributes – hours, minutes, and seconds.Duration and TimeOfDay are specialized forms of Time.Duration and TimeOfDay each have operations that are not common to one another not common to the general idea of Time.
3 ClassificationThe Object-Oriented design philosophy states that
Every program is a simulation, and the quality of a program’s design is directly proportional to how faithfully it mimics the objects in the worldbeing simulated and the way in which those objects interact.
Object-oriented design is all about designing software whose structure mimics the real world.
3.1 How Do We Discover Classes?We do not invent classes,We discover them by examination of the world in which the software will reside.
Remember, first and foremost, that classes are groups of objects and objects are things.
3.1.1 Look for “Things”
Look through the available documentation on the problem you are trying to solve. Talk to people who work in that world.
Look for the kinds of things (nouns or noun phrases) that get mentioned over and over in discussion the problem area.
If you can’t have a meaningful discussion about the problem without mentioning a thing of some kind, that “kind of thing” should be one of your classes.
3.2 How Do We Discover Relationships Among Classes?Look through the available documentation on the problem you are trying to solve. Talk to people who work in that world.
Look for descriptions of how things interact.
Verbs and verb phrases that occur repeatedly in any discussion about the problem are suggestive of operations, particularly when they describe somethingthat is happening or changing.
“Next we schedule the meeting at the agreed-upon time.”
“We compute the total time of all the tracks onthe album.”
Verbs and verb phrases that describe how things are rather than howthey are changing may be suggestive of relations and/or attributes.
“The meeting starts at a specified time and lasts for a predetermined amount of time.”
“The duration of a meeting can be expressed in terms of hours, minutes, and seconds.”
3.3 Keep It Real!Example: We have been tasked with automating the means by which public library patrons find books in the library.
We “know” that we will be using a database to permit searches for books.
Nonetheless, it is a mistake to design a LibraryDatabase class.
Because no one walks into a library, sees a large box labeled “Database”, and says “Ooo, let me look at that.”
Instead we would design a Catalog class.
Because many generations of library patrons have walked in to a library, moved to the “card catalog”, and searched though it for relevant books.
We discover this class in the real world, and model it in our design.
It is entirely possible that the data structure and algorithms used for low-level design of the Catalog will be that same database, but that’s a hidden,private decision.
From examination of the physical catalog, we learn that traditional searches have been supported by author, by title, and by subject keyword.
This informs our interface design for the Catalog class in a way that might not have been obvious if we jumped directly in with a Database.
Network Conferencing: Google MeetSteven J Zeil
Last modified: Jan 20, 2020
Contents:1 Between Four and Seven Days Before Your Recitation Section2 Between 72 and 48 Hours Before Your Recitation Section
2.1 Possible Assignments3 Between 1 and 24 Hours Before Your Recitation Section4 At Least 15 Minutes Before Your Scheduled Meeting5 During Your Meeting6 After the Instructor Leaves
6.1 Screen Sharing6.2 Document Sharing6.3 Diagram Sharing
In this course you will be working in teams, and your team members might not be able to meet with you in person. Although email, forums, and wikis can servefor much communication, there will be times when you need to talk “synchronously”
This is a mandatory exercise exploring options in network conferencing. It will count as an assignment in the course grades.
You will need a PC with a good internet connection, a microphone, and headphones for this exercise.
Headphones, not speakers! Even if it’s just a pair of earbuds you normally use for listening to music.
When you use speakers, there is a significant chance of the sound from your speakers being picked up by your microphone and sent out to everyone in theconference. At best, this creates an unpleasant echo effect. At worst, it creates feedback making the audio useless for everyone.
1 Between Four and Seven Days Before Your Recitation Section1. Navigate to your recitation section (not the lecture section)in Blackboard. In the “My Groups” area, you should see that you have been added to a
“WebConference…” group. Enter your group’s Discussion Board.
If you are the first person here, create a new thread titled “Counting Off”. Post your @odu.edu email address.
Each subsequent person should reply to that post, adding their ODU email address.
2. Check the system reqts for the machine you expect to use for conference meetings in this course.
Note in particular that you will need to use the Chrome or Firefox browsers. In particular, Internet Explorer, Edge, and Safari are not supported.
2 Between 72 and 48 Hours Before Your Recitation Section1. Return to your group’s discussion board in Blackboard.
The instructor will have replied to that thread, giving each person there an assignment. Carry out your assignment.
2.1 Possible Assignments
2.1.1 Meeting
If your assignment is “Meeting” followed by a time, your task is to set up a meeting and invite your team and the instructor to it.
1. Navigate to calendar.google.com and log in with your ODU account.
2. Navigate to the day of your recitation. Click on the time slot closest to the start of your recitation, dragging the box down to the scheduled end time ofyour recitation.
A “New event” box will pop up. Click the “More Options” button to get to the detailed page.
3. Complete the meeting setup by supplying the requested information.
A. Enter a meaningful name for the Event (e.g., CS 350: Group 1 Meeting).
B. Edit the start and end times, if necessary to match your assigned time and the end of the recitation period.
C. If you see an “Add Conferencing” button in the place of the “Hangouts Meet” indicator shown here, click that to add the video conference.
D. In the “Add guests” area, enter the ODU email addresses for each of your teammates ([email protected]) (get these from the group discussionboard) and click add.
A. You may find that Calendar offers to auto-complete some of these for you.
B. Also add the instructor’s email, [email protected], to the invitation list.
It’s important that youinvite the instructor (andyour teammates) via [email protected] email address,not an @cs.odu.edu orpersonal GMail account.
4. Click “Save” when you are done.Invitations will be sent to all of thegroup. These will include a linkallowing direct access to theconference.
2.1.2 Document
If your assignment was “document”, then
1. Go to Google Drive, which you can select from the Google Apps button . Make sure that you are logged in with your @odu.edu credentials.
2. Create a new Google Doc document. Copy and paste a few paragraphs from this web page into that document.
3. Click in the title area and give your document/drawing a title. Include “CS350 document” and your group name/number from Blackboard in the title.
4. Exit the document and return to the Drive directory page.
5. Right-click on your new document/drawing and select “Share…”. Enter the email addresses of your group members with “Can Edit” permission.
Also add the instructor’s email, [email protected], to the shared list.
It’s important that you share via an @odu.edu email address, not an @cs.odu.edu or personal GMail account.
2.1.3 Diagram
If your assignment was “diagram”, then
1. Go to Google Drive, which you can select from the Google Apps button . Make sure that you are logged in with your @odu.edu credentials.
2. Create a new Google Doc document.
3. Click in the title area and give your document/drawing a title. Include “CS350 diagram” and your group name/number from Blackboard in the title.
4. In the “Add-Ons” menu, select “Get add-ons”. Search for “PlantUML Gizmo” and install it.
5. In the “Add-Ons” menu, select “Plant-UML Gizmo”, “Start”.
6. Add a few empty lines to your document, then position the cursor in the middle of them.
In the Gizmo editor pane, select “Observer class structure”, then click Insert to place a diagram into your document.
7. Exit the document and return to the Drive directory page.
8. Right-click on your new document/drawing and select “Share…”. Enter the email addresses of your group members with “Can Edit” permission.
Also add the instructor’s email, [email protected], to the shared list.
It’s important that you share via an @odu.edu email address, not an @cs.odu.edu or personal GMail account.
3 Between 1 and 24 Hours Before Your Recitation Section1. Check your email. You should have received at least one invitation to participate in a Google Meet session. One will be from the instructor. Others may
be from your teammates for this exercise.
2. As an ODU student, you have a Google account for your @odu.edu email. This is what you must use to participate in all class-related meetings. Log intoyour ODU email to be sure that your browser is logged in under your ODU account rather than any personal GMail/Google account that you might alsohave.
You may want to explicitly logout of your personal GMail account before logging into your ODU Account
3. In a separate tab/window, check your Google calendar. You should see the event listed there.
4. Links in the email and calendar entry will allow you to indicate whether you plan to attend. Click on one to indicate that you plan to attend.
5. Check the system reqts for the machine you expect to use.
Note in particular that you will need to use the Chrome or Firefox browsers. In particular, Internet Explorer, Edge, and Safari are not supported.
6. Go to Google Drive, which you can select from the Google Apps button . Make sure that you are logged in with your @odu.edu credentials.
You should see at least one “CS350 document” and at leat one “CS350 diagram” has been shared with you.
7. Open one of the diagrams.
8. Install the PlantUML Gizmo: In the “Add-Ons” menu, you may already see an option to install it. If not, select “Get add-ons”. Search for “PlantUMLGizmo” and install it.
4 At Least 15 Minutes Before Your Scheduled Meeting1. Again, log into your ODU email to be sure that your browser is logged in under your ODU account rather than any personal GMail/Google account that
you might also have.
2. Enter the meeting by using the “Join Hangouts Meet” link in the invitation email or in the calendar entry.
3. If this is your first time using Meet video conferencing, you may be prompted to load a browser plugin. If not, but all that you see is a large green “speechbubble” with quotations marks inside it, click on it and you may be prompted to install the browser plugin.
4. Wait for your group members to arrive.
5. (Optional) If you have time before the meeting starts, you may start the After the Instructor Leaves portion of this exercise.
5 During Your MeetingWhile you wait for the instructor,
1. Note that each person has control over which person occupies the large central area of the call window on their own screen. Simply click on one of thethumbnail images at the bottom of the call to bring up that person. Try this a few times.
2. At the bottom center of your screen, you should see your main meeting controls.
These allow you to
A. Mute or activate your microphoneB. Leave the callC. Turn your camera on and off
Try the microphone and camera controls out. Note that etiquette in network conferences is to mute your microphonewhen you are not speaking. This reduces the amount of background noise that everyone must deal with. It also reduces the chances of echoing andfeedback.
In very large conferences (more than a half dozen or so people), it’s also common to switch one’s video feed off when not speaking or actively engaged inthe conversation. Turning your video back on is then a signal for attention, rather like raising your hand in class.
3. In the upper right corner, you should see a small control area. (You may need to click on the control area to expand it)
Clicking on the chat box at the top right will drop down a text chat window, useful is someone’s microphone is not working. It’salso useful for doing things like sending a URL to other people so they can copy-and-paste it into their browser.
4. Near the bottom right of your screen, you can find another important control.
The “Present Now” button allows you to do “screen sharing”, presenting your entire screen or a specific window to the otherparticipants. You’ll play with that more in just a bit.
5. Your instructor will join you briefly near the beginning of your scheduled session.
Once the instructor joins, he may ask you all to leave and rejoin in a meeting session scheduled by one of the team members instead of the one created bythe instructor. If so, meet him there.
6 After the Instructor Leaves6.1 Screen SharingPractice screen sharing and get yourselves set up for future recitation discussions:
One by one, take turns doing the following:
1. Use the “Present Now” control to turn on screen sharing.
You will be given a choice of what to share: your entire screen or just selected windows.
If you are the first person to take a turn, share your entire screen.
If you are the second person to take a turn, share your web browser.
After the first two people have tried, the remainder may share either their entire screen or their web browser.
2. Ask your team-mates to describe to you what they see from your PC. In particular, can they read the text in your window(s)?
If you are sharing just your web browser window, try resizing the window at full-screen and to just half the screen height and width. Ask your teammateshow this affects what they see.
3. Right-click on a line in your web-browser to bring up a pop-up menu. Tell your team-mates what you have done and ask them to again describe to youwhat they see from your PC.
4. Click on a menu in your web-browser to drop down a list of menu entries. Tell your team-mates what you have done and ask them to again describe toyou what they see from your PC.
6.2 Document Sharing1. You should have at least one document shared with you in Google drive by a team member. Go to Google Drive and open that document (in a separate
tab/window). Click somewhere in the document.
As everyone opens the document, you should see “cursors” for each person. Everyone should add their name to the document. (Don’t wait to take turns –just move your cursor to a clear spot and type your name.)
Once all the names are typed out, arrange them into a bulleted list near the top of the document.
Notice how updates can be seen simultaneously at different locations.
You can play around a bit with other edits, but leave the list of names intact.
(You can do the same kind of thing with Google Drive shared documents outside of video calls as well. But the ability to edit simultaneously is aninteresting and unusual feature.)
6.3 Diagram Sharing
The PlantUML Gizmo allows you to work together on design diagrams.
Collaborating is not quite as smooth as in the basic Google Doc you just practiced with. The key elements to remember are:
Selecting an image and then clicking Edit selected gives you a private copy of the text that defined the diagram.
You can edit that to your heart’s content, watching what happens in the Preview area.
No one else sees your Preview area.
Clicking Insert replaces the diagram that everyone sees in the document.
If you have do not have a diagram selected in the main document when you do this, it insets a new diagram at your cursor.
Collaborating on single large document in this way would be awkward. But a large document with many small diagrams is quite manageable.
Working together, convert the initial diagram into this:
1. Identify a single change to be made to get closer to your goal. Let one person announce that they are going to do it.
2. That person should select the diagram, click Edit selected, carry out the change, and then click Insert to post the change to everyone else.
3. Repeat the above two steps as necessary, with different people taking turns carrying out the changes.
Some notes on the required changes:
Class diagrams in PlantUML typically start with a description of the classes involved, using a C++/Javaish notation.
That is followed by descriptions of one or more connecting arrows.
An arrow has the name of two classes on either side of “–” for a solid line, “..” for a dotted line.You can add various characters to one or both ends of the “–” or “..” to set the type of arrow head.
For our purposes, try ‘o’, ‘<’ or ‘>’, ‘<|’, and ‘|>’. E.g., “o–>”.
You can add a direction inside the arrow assembly, e.g., “<-left-”, to indicate the direction you want the line to go. This actually rearranges the classboxes to accommodate your selection.
You can attach a label to an arrow by following the entire classnames-and-arrow assembly with a colon, a space, and then the desired label.
You can follow the label with a blank and then either ‘<’ or ‘>’ to add a direction marker.
Here’s a quick guide to the the PlantUML notation for UML class diagrams.
Delete the line “hide empty members”. (It produces more compact, but technically non-standard UML.)
I prefer “Observable” to “Subject”, both because it is more descriptive and because the classes Observer and Observableare part of the standardJava API. ButSubject` is the original term popularized in Design Patterns, the groundbreaking book by Gamma et al.
CS330 folks should recognize the components of these UML class diagrams. For the rest of you, we’ll have a quick look at what these actually mean later in thesemester.
1. Optional: if you have the time and the inclination, try this tool for doing UML diagrams collaboratively within Google drive.
Draw.io handles collaboration more smoothly than the PlantUML, but there’s a lot of very basic UML stuff that is impossible or, at least, tricky to drawwith it.
Leave your shared documents and diagrams in place until a grade has been posted for your participation in this recitation.
Eliciting RequirementsSteven J Zeil:
Last modified: Dec 21, 2019
Contents:1 Basic Principles of Requirements Analysis2 Eliciting Requirements3 Obtaining Requirements Information
3.1 Interviews3.2 Prototyping
4 Organizing Requirements Information4.1 Viewpoint Analysis4.2 Use Cases4.3 Object-Oriented Analysis
Abstract
Eliciting requirements is the process of determining what the customers actually need a proposed system to do and of documenting that information in a waythat will allow us to write a more formal requirements document later.
In this lecture we survey some key concepts, problems, and actitivies associated with requirements elicitation, and then look at use cases, one of the mostpopular ways of documenting our understanding of those requirements.
1 Basic Principles of Requirements AnalysisUnderstanding the customer’s requirements for a software system
Developers working with customers to find out about the application domain, the services that the system should provide and the system’s operationalconstraints
May involve end-users, managers, engineers involved in maintenance, domain experts, trade unions, etc. These are called stakeholders
Problems of requirements analysis
Stakeholders don’t know what they really want
(Is this a real problem, or snobbery on the part of software developers? For as often as I see this “problem” listed, I’m never really sure.)
Stakeholders express requirements in their own terms
Different stakeholders may have conflicting requirementsOrganizational and political factors may influence the system requirementsThe requirements may change during the analysis process.New stakeholders may emerge
2 Eliciting RequirementsThe process of eliciting requirements is, fundamentally, an exercise in communication between developers and stakeholders.
Problem 1: Obtaining informationProblem 2: Organizing the information obtained
3 Obtaining Requirements InformationCommon sources are
Reading available documentation
Interviewing the stakeholders…
Facilitated meetings
(i.e., gather the stakeholders around a table for a formal discussion).
Prototyping…
Direct observation (a member of the development team)
A member of the development team physically observes the current process in operation.
3.1 InterviewsInterviews may focus on general questions, e.g.,
“What do you do? ”What information do you need to do your job?“, ”What problems are you encountering with the current system?"
Or may seek to elicit scenarios
“Walk me through the process you follow when X happens.”
These interviews can be as much about learning how the current system works as about what is desired from the new system.
That’s consistent with object-oriented approaches where a domain model is constructed of how the system works now, and that model is graduallyevolved into an analysis model of how we would like it to work in the future,
including the possible automation of processes currently done manually.OO practice assumes that the structure of the new system will mirror the structure of the old one to a significant degree, so effort spent modelingthe current world is also effort towards modeling the new system.
3.2 PrototypingBuilding a quick mock-up of the eventual system
May ranges from paper mockups of screens to beta-test codeProviding or displaying the prototype to the stakeholders
It can be an effective method to
Validate our understanding of the requirementsElicit discussion on things that we mis-understood or failed to consider at all.
4 Organizing Requirements Information4.1 Viewpoint Analysis
Stakeholders represent different ways of looking at a problem or problem viewpoints.This multi-perspective analysis is important as there may be no single correct way to analyze system requirements.
By approaching requirements from multiple viewpoints, we can identify conflicting requirements among the stakeholders.
4.1.1 Types of viewpoint
Viewpoints are perspectives on the system, not “people who view it”.
Common viewpoints include
Data sources or sinks
Viewpoints responsible for producing or consuming data.
Representation frameworks:
Viewpoints representing particular types of system model.
May be compared to discover requirements that would be missed using a single representation.
Receivers of services:
Viewpoints external to the system that receive services from it.
Particularly useful for interactive systems
4.1.2 The VORD method
VORD: Viewpoint-Oriented Requirements Definition
Viewpoint identificationDiscover viewpoints which receive system services and identify the services provided to each viewpoint
Viewpoint structuringGroup related viewpoints into a hierarchy. Common services are provided at higher-levels in the hierarchy
Viewpoint documentationRefine the description of the identified viewpoints and services
Viewpoint-system mappingTransform the analysis to an object-oriented design
Example: Checkbook balancing
Consider the design of software to balance a checkbook:
should accept transactions including
checksdepositswithdrawalsinterest accruedservice charges
Allow transactions to be confirmed from bank statement
should show current and confirmed balances
Viewpoint Identification
Plausible viewpoints are:
AccountBankAccount HolderClearing House
Services Provided
Account
apply transactions
Bank
create/close accountsreceive transactionsinitiate transactions (charges)
Account holder
receive account statementinitiate transactionsexamine register entriesget statement balanceget confirmed & unconfirmed register balancesconfirm transactions from statement
Clearing House
receive checks
Viewpoint Documentation
For each viewpoint, we document the data and services that are visible form that viewpoint.
Reference: Bank
Attributes: name, location, accounts list
Events: end-of-month, transaction arrival
Services:
create/close accountsreceive transactionsinitiate transactions (charges)
Sub-VPs:
And we document the services
Reference: receive transaction (account debit)
Rationale: electronic funds transfer, may be signal from clearing house of a check received
Specification: The account balance is checked to determine if adequate. If so, confirmation of the transaction is sent, the account balance is reducedby the transaction amount, and the transaction noted in the account log. If not, the account kind is consulted for overdraft policy, and the policyroutine found there is invoked.
Viewpoints: Bank, Clearing House
NF Reqts: Response of confirmation, rejection, or unable-to-process must be made within 30 seconds, with a mean response less than 0.1 seconds.
Viewpoints: Bank
4.2 Use CasesUse cases have become popular as a means of organizing requirements.
Often includes in requirements documentsSpecific requirements are often linked back to a use case that explain or justifies them.
A use case is a collection of scenarios.
It depicts a functionality of the system,typically beginning with some external input or action.
It provides a step-by-step description of a main (success) scenario path.It includes extensions / alternative paths.
4.2.1 Writing Use-Cases
1. Start with a collection of scenarios2. Group by common user goals3. Define the actors4. Give brief descriptions of use cases5. Give a detailed description of the basic path6. Add alternative paths
Example: grading tests
Grading an Assessment
Actors: Scorer
Main Path
1. The scorer begins with an assessment and a collection of response documents.
2. For each item in the assessment, the scorer obtains the item’s rubric. Then for each response document, the scorer goes to the item responsefor that same item, grades the response using that rubric, and adds the resulting score and (if provided) feedback to the result document forthat response document.
3. When all items have been graded, then the scorer computes a total score for each results document.
4. The scorer add the score from each result document to the grade book.
Alternative: Candidate by Candidate Scoring
2: For each candidate, the scorer goes through each of the items. For each item, the scorer obtains the item’s rubric, grades the item response usingthat rubric, adds the resulting score and (if provided) feedback to the result document for that response document.
Example:
Get Paid for Car Accident
Actors: Claimant, Accident victim making claim, Insurance Company, Company insuring Claimant, Agent, Insurance Company representative>processing claim
Main Success Scenario
1. Claimant submits claim with substantiating data.2. Insurance Company verifies that Claimant owns a valid policy.3. Insurance Company assigns Agent to examine case.4. Agent verifies that all details are within policy guidelines.5. Insurance Company pays Claimant.
Extensions
1a. Submitted data is incomplete:
1a1. Insurance Company requests missing information. 1a2. Claimant supplies missing information.
2a. Claimant does not own a valid policy:
2a1. Insurance Company declines claim, notifies Claimant, records all this, and terminates proceedings.
3a. No Agents are available at this time:
3a1. (What does the Insurance Company do here?)
4a. Accident violates basic policy guidelines:
4a1. Insurance Company declines claim, notifies Claimant, records all this, and terminates proceedings.
4b. Accident violates some minor policy guidelines :
4b1. Insurance Company begins negotiation with Claimant as to degree of payment to be made.
(Adolph)
4.2.2 Components of a use case
Lots of local variations:
1. Title
2. Overview – natural language description
3. Actors – list of external entities participating in the use case
4. Preconditions – list of conditions that must be true before this use case can be invoked.
5. Scenario(s): specified as a main path and a collection of alternative paths
6. Postconditions – list of conditions that are expected to be true upon completion of the use case
7. Exceptions – List of failure conditions associated with the scenarios, and the appropriate system responses
Of these, #1 and #5 are essential and universal.
#4, #6, & #7 are often empty
Pre-conditions are generally omitted if trivial (always true)Exceptions can often be re-written as alternative pathsPost-conditions are often omitted because he desired non-trivial conditions may only hold if the main path succeeeds.
Example
Attempt Assessment
Overview: A candidate attempts to take an assessment. The candidate might or might not finish the assessment within a single session.
Actors: candidate, proctor
Preconditions:
1. Assessment has been created2. Assessment has been provided to the proctor
Main Path:
1: A candidate indicates to a proctor their desire to take an assessment. (This may involve simply showing up at a previously-schedule time andplace when the assessment is going to be available.)
2: The proctor provides the candidate with a copy of the assessment and an empty response document.
3: The proctor authorizes the start of the assessment session.
4: The candidate reads the assessment, and repeatedly selects items from it. For each selected item, the candidate creates a response, adding it to theresponse document.
5: The proctor ends the assessment session after time has expired.
6: The candidate returns the response document to the proctor.
— Alternatives
Unavailable
2: The proctor determines that the candidate is not eligible to take the assessment at this time (the assessment is not available or the candidate hasnot fulfilled assessment requirements such as being enrolled in the course).
The use case terminates immediately.
Variant Assessments
2A: Proctor randomly selects one of multiple available variants of the assessment.
2B: The proctor gives that selected assessment and an empty response document to the candidate.
In-line answers
2: The response document and the assessment copy are a single document.
Candidate provided response document
2: The candidate brings blank sheets of paper or blue books to serve as the response document.
Candidate finishes early
5: The candidate indicates that he/she is finished prior to the end of the allotted time. The proctor ends the assessment session.
Suspended session
5: The candidate asks to suspend the session, with the intention of completing the assessment at a later date. The proctor determines from theassessment properties that this is acceptable.
6: The proctor collects the candidate’s response document and copy of the assessment.
4.2.3 Quality Use Cases
Good use cases are
Written in customer languageValidatableFocused on a single goalExpress requirements, not design
A poor use case
Register for Course
1. Display a blank schedule.2. Display a list of all classes in the following way: The left window lists all the courses in the system in alphabetical order. The lower window
displays the times the highlighted course is available. The third window shows all the courses currently in the schedule.3. Do4. Student clicks on a course.5. Update the lower window to show the times the course is available.6. Student clicks on a course time and then clicks on the “Add Course” button.7. Check if the Student has the necessary prerequisites and that the course offering is open.8. If the course is open and the Student has the necessary prerequisites, add the Student to the course. Display the updated schedule showing the
new course. If no, put up a message, “You are missing the prerequisites. Choose another course.”9. Mark the course offering as “enrolled” in the schedule.
10. End do when the Student clicks on “Save Schedule.”11. Save the schedule and return to the main selection screen.
Problems:
Too much user interface detail (design)
Too low level.
Written more from system’s perspective than from customer’sUnclear pseudo-code-ish structure
Poor grammar.
Improved Version
Register for Course
1. Student requests a new schedule.2. The system prepares a blank schedule form and pulls in a list of open and available courses from the Course Catalog System.3. Student selects primary and alternate courses from the available offerings.4. For each course, the system verifies that the Student has the necessary prerequisites and adds the Student to the course, marking the Student
as “enrolled” in that course in the schedule.5. When the Student indicates the schedule is complete, the system saves the schedule.
4.2.4 Use Cases and Requirements
We will shortly see that common requirements documents are
Reqts Definitions, andReqts Specifications,
the latter being more detailed and formal.
Use cases fall somewhere between the two, and are often used as a mechanism for gathering the additional details needed to write the specifications.
4.3 Object-Oriented AnalysisThe Object-Oriented philosophy can be summarized as:
Every program is really a simulation.
The quality of a program’s design is proportional to the faithfulness with which the structures and interactions in the program mirror those inthe real world.
In pre-OO, Waterfall and similar processes, at the end of each phase, we set aside the prior set of documentation and sit with blank sheets of paper to begin thenext phase “fresh”.
The records of elicited requirements information serve as references when we start writing the requirements documents.
The requirements documents serve as references when we start writing the design documents.
The design documents serve as references when we start writing the code.
But each new step is, in essence, a fresh start.
There’s a certain wisdom in this. There’s a real danger in making design decisions when writing early requirements, or in having people reading therequirements think that you made a design decision when choosing notations or writing explanations as part of the requirements.
A fresh start helps remind designers that they have the freedom, if not the obligation, to consider alternatives.
Evolution
But the OO view is very different. If we buy into the OO philosophy, then we take a more evolutionary process.
OOA is largely viewed as a model-building activity:
Start with a model of how the world works now.Make just enough changes to that model to explain what we want to change (e.g., via new automation).Make just enough additional changes to account for necessary design “compromises”.
The result is, eventually, a design that had clear ties to the original model of the world before we started our project.
Model Building
The primary models of Object Oriented Analysis and Design are:
domain model
analysis model
design model
4.3.1 Domain Models
A domain model is a model of the application domain as it currently exists, before we began our new development project. The point of the domain model is tobe sure the development team understands the world that the system will work in.
The domain model describes the world in terms of objects interacting with one another via messages.
Not every project needs a domain model
e.g., If the team has done several projects already in this application domain, they may already share a common domain model.
(Fortunately, most companies work in a small number of application domains.If they hired you last month to develop software for analyzing seismic data forpetroleum engineering, they are unlikely to ask you to develop a compiler or aword processor next month.)
In that case, the team may already have a good understanding of the domain.
Even if a document describing the domain model is desired, domain modelstend to be highly reusable since the world around our software systems usuallychanges fairly slowly.
Interacting Objects
All OOA models are based on the concept of objects interacting with one another by the exchange of messages (function calls).
Hence, even if we are modeling a physical object or a human being, we write our models in terms of classes with an interface of attributes and operations.
E.g.,
Transactiondate number relatedAccount amountapply(toBalance)
CheckbookAccount
Balance list of Transactions add(Transaction) cancel(Transaction) balanceAgainst(Statement)
4.3.2 Analysis Models
An analysis model is a model of how the world will interact with the software system that we envision. As such, it is our statement of just what the system willdo when it is working.
There is a real temptation to simply assume that the automated system will simply squat in the middle of the world, interacting with all the real world objects,sort of like this:
Or if you prefer,…
It’s Magic!
Poof! We have an analysis model!
Not wrong, per se, but it’s certainly not helpful.
Such an approach is fundamentally at odds with the OO philosophy, though.
We should look to the real world to suggest how to decompose our system.
In this case, we have not decomposed it at all – just wrapped it up into a single black box.
All the hard decisions still need to be made.
And we have thrown out any insight we gained from building our domain model.
In essence we have not done any analysis at all here. This “model” isn’t wrong, per se, but it’s certainly not helpful. We’ve basically thrown away everythingwe’ve learned in the domain model about how objects really interact. We’re treating the new program as a simple box, with no knowledge of its internalstructure, Essentially, we’ve just deferred all the hard questions to the upcoming design.
Evolving the Analysis Model
What we really hope for is an evolution from our domain model to our analysis model. The OO philosophy tells us that the classes and interactions of ourdomain model …
… should carry over into our analysis.
In essence, we hope to retain these classes, add more detail to our understanding of them, and to establish a boundary that tells us which of these classes andbehaviors will be automated, which will remain entirely unautomated, and which will have some portion automated while other parts remain external.
The Boundary
… establish a boundary that tells us which of these classes and behaviors will
be automated
remain external
be a mixture of the two
The system, then, remains a collection of interacting objects rather than an unstructured black box.
We may need to modify our class interfaces to reflect our new understanding, but we don’t need to discard them and start from scratch.
There’s a definite overlap in the purpose of a requirements document and of an analysis model. Some will regard the analysis model as a kind of requirementsspecification. In some projects, though, a requirements document will still be required as something for customers or management to sign off on. But theanalysis model is the basis from which the eventual requirements document is derived.
The evolutionary approach will carry forward, in an OO-style project, fromrequirements into design. When building the design model, we will start withthe classes (and their APIs/interfaces) and make merely the changes we deemnecessary for an effective design.
(OOA is covered in more detail in CS330.)
Could not parse reqts...skipping
Project Phase 1: Software Requirements SpecificationCS350
Last modified: Jan 21, 2020
Contents:1 Overview2 Phase 1
2.1 Software Requirements Specification2.2 Peer Evaluation
3 Mechanics3.1 Teams
4 Submission4.1 SRS Document4.2 Evaluation
1 OverviewThe project is divided into 5 phases
1. Software Requirements Specification
2. User stories
3. Version control, ADT documentation, build manager, unit tests, website/wiki
4. configuration management, continuous integration
5. Final implementation, code analysis, integration & system test
All five phases are team activities. The teams for this first phase will be randomly assigned. For the remaining phases, you will select your own teams.
One purpose of allowing teams is to distribute the workload. One purpose of requiring teams is to be sure that students can employ the relevant tools andtechniques taught in this course in a collaborative environment.
Individual team members should not feel they can safely sit back and let everyone else do the work. Nor should one gung-ho team member, however talented,grab all the work and do it all. As a team, you must take responsibility for making sure that all team members understand and practice the relevant tools and
techniques.
2 Phase 12.1 Software Requirements SpecificationPrepare a Software Requirements Specification (SRS) document for this requirements definition.
Your SRS must follow the format of the IEEE 830 standard, available on the course Library page. For Section 3, you may use any of the alternativeorganizations from the Appendix A of that standard.
You must, however, in section 1.5 Overview, explicitly state which of those organizations you have selected.Make your choice wisely. If you are unfamiliar with Object-Oriented Analysis, for example, do not choose to organize your requirements by object/class.Pay attention to what is important and what is secondary in a requirements specification. The real heart of a reqts spec. is in Section 3. The other sectionsare supporting text whose purpose is to make Section 3 easier to understand.
Even within Section 3, there is the “core” of the actual functional requirements (typically, “The system shall …” statements) and there is supporting textto help introduce and explain those requirements.
I do not care how nice your supporting text is — if you never actually state the functional requirements, you will not score well!
Talk to each other! Don’t just assign everyone a section of the outline to write in isolation and assume they can all be stitched together the day theassignment is due.
Given the critical importance of section 3, I recommend that you divide responsibilities up in a fashion such that each team member makes acontribution (even if only proof-reading) to parts of that section.
2.2 Peer EvaluationAfter your work has been submitted, each team member must individually take the peer evaluation survey. The purpose of this survey is to assess the relativecontributions made by individual team members.
3 Mechanics3.1 Teams
This is a team assignment. Teams have been assigned randomly. You can view your team (group) assignment as one of the SRS… groups in the Groups area onyour recitation Blackboard site.
These are not the same groups that you had for the Web Conferencing recitations!
Blackboard will allow you to email your team members for coordination purposes. Your team has also been provided with a Discussion Area, which you mayuse to facilitate your work. Both will be visible only to your team members and to me.
Keep in mind that you have access to Google Drive via your ODU email account. You might chose to work collaboratively via a shared Google Docs documenton that cloud drive.
4 Submission4.1 SRS DocumentPrepare your SRS as a PDF document, following these guidelines.
When you are ready to electronically submit your assignment, use the button below:
Submit this assignment
Only one team member must submit on behalf of your team. If you submit more than once, (e.g., you discover a mistake after submitting), only your LASTsubmission before the due date will be considered.
4.2 EvaluationEvaluations will be a combination of what the group has accomplished and how much each individual contributed to that group’s effort.
This phase will be graded as:
score = doc. quality * indiv
where:
doc. quality is an assessment of the completeness, correctness, and quality of the document submitted by the team.
indiv is a scaling factor for an individual’s effort. A normal effort level is 1.0. This factor will be raised or lowered by a combination of
score on peer evaluation surveyswhether the individual contributed to the survey
Clean CodingSteven J Zeil
Last modified: Dec 18, 2019
Contents:1 What is Clean Coding?
1.1 Perspectives2 Principles
2.1 Code must Express its Intent2.2 Tests should always pass.2.3 Keep it small, keep it simple
3 Practices3.1 Meaningful names3.2 Limit your Scope3.3 Functions should do One Thing3.4 Error Handling is “One Thing”3.5 Avoid Duplication3.6 Distinguish between data structures and “true” objects3.7 The Law of Demeter3.8 Error Handling3.9 Classes should be small!
Abstract
Clean coding is a set of principles and practices aimed at producing code that is readable, trusted, and maintainable.
1 What is Clean Coding?In the end, we write code.
There are certain best practices that “good” programmers follow, maybe without even thinking about them, or maybe because they have learned, to their sorrow,that doing otherwise will cost them, even if only in time and effort.
Good code is
Readable
Trusted
Has it been tested?
How well?Does it pass?
Does it smell?
A code smell is a section of code that exhibits a practice that the programming community considers ill-advised or even dangerous.
Maintainable
Does the code throw barriers in the way of people trying to fix it or change it? e.g.,
Is the code highly coupled so that changes in one place directly affect many others?
Is code duplicated, so that fixes/changes need to be applied simultaneously to many different places?
1.1 Perspectives(from Martin, ch 1)
Grady Booch
Clean code is simple and direct. Clean code reads like well-written prose. Clean code never obscures the designer’s intent but rather is full of crispabstractions and straightforward lines of control.
Dave Thomas (OTI)
Clean code can be read, and enhanced by a developer other than its original author. It has unit and acceptance tests. It has meaningful names. Itprovides one way rather than many ways for doing one thing. It has minimal dependencies, which are explicitly defined, and provides a clear andminimal API. Code should be literate since depending on the language, not all necessary information can be expressed clearly in code alone.
.
Ron Jeffries
In priority order, simple code:
Runs all the tests;Contains no duplication;Expresses all the design ideas that are in the system;Minimizes the number of entities such as classes, methods, functions, and the like.
Ward Cunningham
You know you are working on clean code when each routine you read turns out to be pretty much what you expected. You can call it beautiful codewhen the code also makes it look like the language was made for the problem.
2 PrinciplesTests should always pass.Code should be expressive.
maximize cohesionKeep classes and methods small.Work at one level of abstraction.
Keep it small, keep it simpleMinimize coupling
Law of DemeterAvoid duplication.Do not create or maintain unnecessary code.
Closely related: SOLID design in OOP
SOLID is an acronym for a collection of design principles related to good object-oriented design.
Single ResponsibilityOpen-Closed PrincipleLiskov SubstitutionInterface SegregationDependency Inversion
The overall theme of SOLID is to control the ways in which different classes depend on each other. SOLID design tries to find class interfaces that are robusteven in the face of change.
We won’t delve into SOLID in this course, because it requires a certain grasp of object-oriented programming that is covered in CS330, and not everyone in thiscourse will have taken CS330.
But, if you are interested:
1. Martin: Agile Principles, Patterns, and Practices in C# chapters 8–12
2. SOLID Design (from CS330)
2.1 Code must Express its IntentMartin relates a story about replaying an editing session via an editor that captures the entire history of edits.
reveal +
Bob enters the module. He scrolls down to the function needing change. He pauses, considering his options. Oh, he’s scrolling up to the top of the module to check the initialization of a variable. Now he scrolls back down and begins to type. Ooops, he’s erasing what he typed! He types it again. He erases it again! He types half of something else but then erases that! He scrolls down to another function that calls the function he’s changing to see how it is called. He pops up another window and looks at a subclass. Is that function overridden?
Notice how much time is spent reading rather than writing.How much worse would this be if Bob had not touched this code in weeks, or months?
Or if Bob was not the original author of the code?
Code should be expressive of its intent.
2.1.1 Refactor to Improve
Refactoring is the process of making changes to code that make it more expressive, but that should not alter its behavior in any way.
Common examples of refactoring include:
renaming entitiesreplacing literal constants by named constant variablesreformatting codeencapsulating public data membersextracting blocks of code into separate functions.
Many IDEs (including Eclipse) have build-in support for these and other refactoring activities.
For example, renaming a function from the Refactor menu not only changes the name of the function in the declaration, but also of all calls to thatfunction in all source code files where it is used.
2.2 Tests should always pass.We do not write large amounts of code and then get around to testing it (or claiming we had no time to test it).
We do not even write large amounts of code without pausing to rerun our tests every few minutes.
We’ll pass on the details for now, as we will be discussing automated unit testing and test driven development in great detail later.
2.3 Keep it small, keep it simpleMinimize the number of classes and membersAvoid duplicationEach function should “do one thing”.
3 Practices3.1 Meaningful names
int nCstCnt; // number of customers int numberOfCustomers;
There was a time when programmers could be forgiven for writing the code on the left.
Early programming languages limited variable names to a 5, 6, or 8 characters.FORTRAN used the first character to declare the type of a variable:
Names beginning with “I..N” were integers.Everything else was floating point.
The mechanics of punched cards encouraged the use of statements no more than 72 characters long.Keypunches and early text editors lacked support for automatic completion of partial variable names.
None of that is true any more!
So please stop writing code like I had to back in the late 1970’s – S. Zeil
3.1.1 Good Names should…
Be expressive of the intention.
If you need a comment to explain what this thing is, you need a better name.
Be pronounceable
Draw meaningful distinctions.
Avoid tacking meaningless noise words like “Info”, “Data”, “Object”, …
What’s the difference between classes named Book and BookData?
Doesn’t every class actually provide data about something?
Avoid encoding type information.
Avoid variable names like songList.
Better to describe the intent as album, playlist, or discography.
Use appropriate parts of speech
Class, variable and constant names should be noun phrases.Method/function names should be verb phrases.
Avoid “nounifying” verbs.
generateSummaryReport is a perfectly good function name.It’s a terrible name for a variable or class.
And SummaryReportGenerator is hardly better.Better:
class SummaryReport { ⋮ void generateAndPrint(OutputDevice);
3.2 Limit your ScopeThe scope of a declared name is the range of code in which it can be legally referenced.
Place your declarations in as limited a scope as possible.1
This prevents accidental re-uses of the same variable name, including many uses that may interfere with one another.
3.2.1 Example (conditionals)
Don’t do this:
String firstName; String lastName; int pos = fullName.find(','); if (pos >= 0) { lastName = fullName.substr(0, pos); firstName = fullName.substr(pos+1); println ("Hello " + firstName + ' ' + lastName); } else { println ("Hello " + fullName); }
The scopes of firstName and lastName continue to run through the rest of this code block, till the end of the enclosing { }.
Do this instead:
int pos = fullName.find(','); if (pos >= 0) { String lastName = fullName.substr(0, pos); String firstName = fullName.substr(pos+1); println ("Hello " + firstName + ' ' + lastName); } else { println ("Hello " + fullName); }
Not only is there less chance of an accidental re-use of the names later, but there is also never a period of time in which the variables are uninitialized.
3.2.2 Example (for loops)
Don’t do this:
vector<Author>::iterator iter = authorList.begin(); while (iter != authorList.end()) { doSomethingWith(*iter);
++iter; }
The scope of iter continues to run through the rest of this code block, till the end of the enclosing { }.
Do this instead:
for (vector<Author>::iterator iter = authorList.begin(); iter != authorList.end(); ++iter) { doSomethingWith(*iter); }
The scope of iter runs only to the end of the loop body.
Of course, since C++ 2014 you can do even better:
for (Author& author: authorList) { doSomethingWith(author); }
but the same rule applies whenever you are attempted to use a while loop with some kind of counter or external position marker.
3.3 Functions should do One ThingThis helps to keep them small, and understandable.
Bad: Suppose a Library class had this:
expand +
public void addBookToCollection (Book book) { List<Author> authors = book.getAuthors(); Title title = book.getTitle(); List<SubjectKeyword> subjects = new List<>(); try { subjects = book.getSubjects(); } catch (SubjectsUnassigned ex) { requestReviewOfSubjects(book); } try { DecimalClassification dewey = oclc.getClassificationCode(book.getISBN()); ShelfLocation intendedLocation = getShelfFor(dewey); book.setLocation (intendedLocation); } catch (OCLCServiceUnavailable ex) { log.error("Error contacting OCLC about " + book.toString(), ex); } try { for (Author author: authors) { authorIndex.add(author, book); } titleIndex.add (title, book); for (SubjectKeyword subject: subjects) { subjectIndex.add(subject, book); } } catch (CatalogingError ex) { log.error ("Error cataloging " + book.toString(), ex); } }
It’s long. You might not think this is particularly long, but it’s too long to be expressive.It spans multiple levels of abstraction. Embedded in here are decisions about library structure
how many catalogs exist and what their indexing scheme iswhat classification scheme we use (DeweyDecimal) and who provides that classification service (OCLC).
and about the ADT interfaces used to manipulate the individual catalogs and classification service.
Cleaner:
expand +
public void addBookToCollection (Book book) { addBookToCatalogs(book); moveBookToIntendedLocation(book); } private void addBookToCatalogs(book) { addBookToAuthorCatalog(book); addBookToTitleCatalog(book); addBookToSubjectCatalog(book); } private void addBookToAuthorCatalog(Book book) { List<Author> authors = book.getAuthors(); try { for (Author author: authors) { authorIndex.add(author, book); } } catch (CatalogingError ex) { log.error ("Error cataloging " + book.toString(), ex); } } private void addBookToTitleCatalog(Book book) { Title title = book.getTitle(); try { titleIndex.add (title, book); } catch (CatalogingError ex) { log.error ("Error cataloging " + book.toString(), ex); } } private void addBookToSubjectCatalog (Book book) { try { List<SubjectKeyword> subjects = book.getSubjects(); for (SubjectKeyword subject: subjects) { subjectIndex.add(subject, book); } } catch (CatalogingError ex) { log.error ("Error cataloging " + book.toString(), ex); } } catch (SubjectsUnassigned ex) { requestReviewOfSubjects(book); } } private void moveBookToIntendedLocation (Book book) { try {
ShelfLocation intendedLocation = getIntendedLocation(book); book.setLocation (intendedLocation); } catch (OCLCServiceUnavailable ex) { log.error("Error contacting OCLC about " + book.toString(), ex); } } private ShelfLocation getIntendedLocation (Book book) throws OCLCServiceUnavailable { DecimalClassification dewey = oclc.getClassificationCode(book.getISBN()); return getShelfFor(dewey); }
Look, in particular, at the top two functions. * Aren’t they clearly more expressive of what needs to happen? * Is there anything in there that took you out of theLibrary-level of abstraction and forced you to simultaneously think about how more detailed components of the library were designed to work?
3.4 Error Handling is “One Thing”Many of those functions were complicated by the pattern of
1. Try to do something2. Handle the errors can arise if the attempt fails
But those are two different “things”…
expand +
public void addBookToCollection (Book book) { addBookToCatalogs(book); attemptToMoveBookToIntendedLocation(book); } private void addBookToCatalogs(book) { attemptToAddBookToAuthorCatalog(book); attemptToAddBookToTitleCatalog(book); attemptToAddBookToSubjectCatalog(book); } private void attemptToAddBookToAuthorCatalog(Book book) { try { addBookToAuthorCatalog(book); } catch (CatalogingError ex) { log.error ("Error cataloging " + book.toString(), ex); } } private void addBookToAuthorCatalog(Book book) { List<Author> authors = book.getAuthors(); for (Author author: authors) { authorIndex.add(author, book); } } private void attemptToAddBookToTitleCatalog(Book book) { try { addBookToTitleCatalog (book); } catch (CatalogingError ex) { log.error ("Error cataloging " + book.toString(), ex); } } private void addBookToTitleCatalog(Book book) { Title title = book.getTitle(); titleIndex.add (title, book); } private void attemptToAddBookToSubjectCatalog(Book book) { try { addBookToSubjectCatalog (book); } catch (CatalogingError ex) { log.error ("Error cataloging " + book.toString(), ex); } }
private void addBookToSubjectCatalog(Book book) throws CatalogingError { List<SubjectKeyword> subjects = getSubjectListOrRequestReview(book); for (SubjectKeyword subject: subjects) { subjectIndex.add(subject, book); } } private List<SubjectKeyword> getSubjectListOrRequestReview(Book book) { try { return book.getSubjects(); } catch (SubjectsUnassigned ex) { requestReviewOfSubjects(book); return new Collections<SubjectKeyWord>.emptyList(); } } private void moveBookToIntendedLocation (Book book) { try { ShelfLocation intendedLocation = getIntendedLocation(book); book.setLocation (intendedLocation); } catch (OCLCServiceUnavailable ex) { log.error("Error contacting OCLC about " + book.toString(), ex); } } private ShelfLocation getIntendedLocation (Book book) throws OCLCServiceUnavailable { DecimalClassification dewey = oclc.getClassificationCode(book.getISBN()); return getShelfFor(dewey); }
We have a lot more functions now, but each one is almost trival to read.
3.5 Avoid DuplicationDuplicated code is code that needs to be fixed/changed multiple times when fixing one bug or adding one feature.
Implicitly couples blocks of code in a way not visible from the interfaces.
Makes it hard to hide (“information hiding”) a design decision.
In our library example, the decision on how to handle cataloging errors has encoded three times:
private void addBookToCatalogs(book) { attemptToAddBookToAuthorCatalog(book); attemptToAddBookToTitleCatalog(book); attemptToAddBookToSubjectCatalog(book); } private void attemptToAddBookToAuthorCatalog(Book book) { try { addBookToAuthorCatalog(book); } catch (CatalogingError ex) { log.error ("Error cataloging " + book.toString(), ex); } } private void attemptToAddBookToTitleCatalog(Book book) { try { addBookToTitleCatalog (book); } catch (CatalogingError ex) { log.error ("Error cataloging " + book.toString(), ex); } } private void attemptToAddBookToSubjectCatalog(Book book) { try { addBookToSubjectCatalog (book); } catch (CatalogingError ex) { log.error ("Error cataloging " + book.toString(), ex); } }
Better is to collect that common decision either like this:
private void addBookToCatalogs(book) { attemptToAddBookToAuthorCatalog(book); attemptToAddBookToTitleCatalog(book); attemptToAddBookToSubjectCatalog(book); } private void handleCatalogingError (Book book, CatalogingError ex) { log.error ("Error cataloging " + book.toString(), ex); } private void attemptToAddBookToAuthorCatalog(Book book) { try { addBookToAuthorCatalog(book); } catch (CatalogingError ex) { handleCatalogingError (book, ex);
} } private void attemptToAddBookToTitleCatalog(Book book) { try { addBookToTitleCatalog (book); } catch (CatalogingError ex) { handleCatalogingError (book, ex); } } private void attemptToAddBookToSubjectCatalog(Book book) { try { addBookToSubjectCatalog (book); } catch (CatalogingError ex) { handleCatalogingError (book, ex); } }
or like this:
private void attemptToAddBookToCatalogs(book) { try { addBookToCatalogs(book); } catch (CatalogingError ex) { log.error ("Error cataloging " + book.toString(), ex); } } private void addBookToCatalogs(book) { addBookToAuthorCatalog(book); addBookToTitleCatalog(book); addBookToSubjectCatalog(book); }
3.6 Distinguish between data structures and “true” objectsSome classes/structs exist purely to specify a data structure, e.g.,
struct AuthorLinkedListNode { Author data; AuthorLinkedListNode* next; AuthorLinkedListNode (const Author& author, AuthorLinkedListNode* nextNode = nullptr)
: data(author), next(nextNode) {} };
This data type is not intended to be seen outside of Book:
class Book {private: AuthorLinkedListNode* first; AuthorLinkedListNode* last; ⋮ public:
so there’s really little reason to encapsulate its data members.
In some cases we might even enforce that idea
class Book { private: struct AuthorLinkedListNode { Author data; AuthorLinkedListNode* next; AuthorLinkedListNode (const Author& author, AuthorLinkedListNode* nextNode = nullptr) : data(author), next(nextNode) {} }; AuthorLinkedListNode* first; AuthorLinkedListNode* last; ⋮ public:
although this is not always possible.
3.7 The Law of Demeter“Talk to friends, not strangers.”
The law of Demeter: a method f of a class C should only call the methods of:
C
an object created by fan object passed as an argument to fan object held in a (data structure of a) data member of C
This law is intended to discourage code like this:
class Library { ⋮ void checkOutBookToPatron (Book book, Patron patron) { if (patron.getBranch().getLendingRecord(patron).getNumCheckedOut() < patron.getBranch().getLendingRecord(patron).getCheckOutLimit()) { Date dueDate = Calendar.today().addDays(book.checkOutTerm()); patron.getBranch().getLendingRecord(patron) .addCheckout(book, dueDate); book.checkOutTo(patron, dueDate); } else { throw new CheckoutLimitReached(patron); } }
Some people find the chained calls hard to read.
Perhaps more to the point, this style hides a coupling of this function to the interface of classes LibraryBranch and LendingRecord.
Making the coupling explicit might be an improvement:
class Library { ⋮ void checkOutBookToPatron (Book book, Patron patron) { LibraryBranch patronsHomeBranch = patron.getBranch(); LendingRecord patronLendingRecord = branch.getLendingRecord(patron); if (patronLendingRecord.getNumCheckedOut() < patronLendingRecord.getCheckOutLimit()) { Date dueDate = Calendar.today().addDays(book.checkOutTerm()); patronLendingRecord.addCheckout(book, dueDate); book.checkOutTo(patron, duedate); } else { throw new CheckoutLimitReached(patron); } }
but it doesn’t make the coupling go away.
The highlighted calls violate the Law of DemeterLook at the other calls. Do you see why they don’t violate that law?
If anything, it makes it clearer that we are not sticking to a uniform level of abstraction here. So, better still:
class Library { ⋮ void checkOutBookToPatron (Book book, Patron patron) { if (patron.canCheckOutMoreBooks()) { Date dueDate = Calendar.today().addDays(book.checkOutTerm()); patron.addBookToCheckedOutList(book, dueDate); book.checkOutTo(patron, duedate); } else { throw new CheckoutLimitReached(patron); } } class Patron { ⋮ public void addBookToCheckedOutList (Book book, Date dueDate) { LibraryBranch patronsHomeBranch = branch; LendingRecord patronLendingRecord = branch.getLendingRecord(patron); patronLendingRecord.addCheckout(book, dueDate); } public boolean canCheckOutMoreBooks() { LibraryBranch patronsHomeBranch = branch;; LendingRecord patronLendingRecord = branch.getLendingRecord(patron); return patronLendingRecord.getNumCheckedOut() < patronLendingRecord.getCheckOutLimit() }
which still has multiple violations, so we need to keep going:
class Patron { ⋮ public void addBookToCheckedOutList (Book book, Date dueDate) { LibraryBranch patronsHomeBranch = branch; branch.addBookToCheckedOutList(patron, book, dueDate); }
public boolean canCheckOutMoreBooks() { LibraryBranch patronsHomeBranch = branch;; return branch.getNumCheckedOut(patron) < branch.getCheckOutLimit(patron); } class LibraryBranch { ⋮ public void addBookToCheckedOutList (Patron patron, Book book, Date dueDate) { LendingRecord patronLendingRecord = lendingRecords.get(patron); patronLendingRecord.addCheckout(book, dueDate); } public boolean canCheckOutMoreBooks() { LendingRecord patronLendingRecord = lendingRecords.get(patron); return patronLendingRecord.getNumCheckedOut() < patronLendingRecord.getCheckOutLimit(); }
Apply this “law” with caution.Personally, I like the 2nd version better.Martin also argues against functions taking more than one or two parameters, but this kind of refactoring for Demeter can result in longer parameterlists.
3.8 Error HandlingFavor exceptions rather than special return codes, returning null, etc.Martin argues for unchecked exceptions rather than checked to avoid percolating throws declarations (and their accompanying coupling) throughout thecode.
3.9 Classes should be small!Martin argues strongly against large classes.
I.e., the number of responsibilities should be small.
Roughly speaking, “responsibilities” are measured in terms of public members.
get/set pairs probably count as only one.
But refactoring to accomodate some of his other recommendations (e.g., “functions should do one thing”) tends to make the number of function membersexplode.
The trick is that the added members are generally private, not public.
Cohesion
When is the list of public members too big?When you cannot assign a simple overall description of what they all do in the class.
The class is not cohesive if its public members perform wildly different tasks.The Single Responsibility Principle (SRP, part of SOLID) states that every class should have one overall responsibility.
Martin: We should also be able to write a brief description of the class in about 25 words, without using the words “if,” “and,” “or,” or “but.”
1: This practice is not commonly included in writing about Clean Coding, but I think that’s because it is generally taken for granted that “every” programmerdoes this. But I see lots of violations of this practice in student coding every semester. – SJZ
Integrated Development EnvironmentsSteven J Zeil
Last modified: Dec 21, 2019
Contents:1 The Components of an IDE2 IDE Examples
2.1 emacs2.2 Microsoft Visual2.3 NetBeans2.4 Single-Language IDEs
3 Eclipse3.1 Availability
Abstract
Integrated Develop Environments (IDEs) are software packages that attempt to provide comprehensive support for coding, testing, and debugging
and possible other software construction activities
In this lesson we discuss the minimum expectations we have for an IDE. We also look at some of the optional but desirable features we would like to see.
We will survey some common popular IDEs and comment briefly on the features they provide, before settling down upon Eclipse, the IDE that will be usedthroughout the remainder of this course.
1 The Components of an IDEWhat’s the minimum that we expect in an IDE?
editor
build support
maybe no more than compiler & linkerinvocationwith error messages captured/interpreted/walked by editor
run/execute programs
debugger
The Components of an IDE (optional)
What would we like to see in an IDE?
syntax highlighting & aid in editor
Do we need to wait until we actually run the compiler to be notified of simple mistakes?
documentation (API) look-upe.g., I know this variable m is a pointer to a std::map<int,string>, so what can I write after “m->”?
flexible/configurable build supportWhat if our project uses multiple programming languages?What if we have a program that generates part of the source code and so needs to be run before we start compiling?
packaging/deployment options
preparing zip, jar, and war archivespackaging code as a static or dynamic libraryposting compiled code to an online repository
The Components of an IDE (deluxe)
What makes us positively giddy when we see it in an IDE?
smart feedback in the editorlearns API of code we wrote
e.g., I know this variable p is a pointer to a MyOwnADT, so what can I write after “p->”?suggestions
coding aids in editortemplatescommon refactoring (transformations)
documentation generationtest integrationintegration with version control
2 IDE Examples
2.1 emacsThe *nix swiss army knife of editors, emacs has long functioned as a basic IDE:
syntax-highlighting editorbuild support (invokes *nix make)
parses error messages from compilers & other toolsdebugger interfaceworks directly with many version control systems
References, if you are unfamiliar with this:
Compiling in emacsemacs Debugging mode (CS252)
2.1.1 emacs Strengths and Weaknesses
highly portablesupports virtually any language you would have a compiler foreven in windowed mode, leans toward keyboard rather than mouse
(Not sure if that’s a pro or a con – personally I hate having to lift my hand from the keyboard to reach for my mouse every few seconds. YMMV.)outdated interface
I’m just waiting to see, some day, some Linux IDE come out that announces a “retro” emacs-style skin.
high learning curve
2.2 Microsoft VisualVisual Studio
syntax-highlighting editorbackground compilation provides quick feedback on simple errors
built-in build managerlimited configurability
debugger interfacesome designer tools (e.g., design classes in UML)
2.2.1 Visual Strengths and Weaknesses
wide variety of languages (but Microsoft processors)single-OSclosely integrated with Microsoft compilersmodern, mouse-oriented interface
I’ve never been fond of Visual, but that comes more from my opinion of the MS compilers. MS C++ had recurring issues with basic standards conformance andstd library implementation. And MS’s support of Java was perpetually luke-warm.
2.3 NetBeansFree IDE originally distributed by Sun as “the” development platform for Java.
Still largely Java centric, though some support for other languagesparticularly web-related languages like Javascript, CSS, XSL
Portable (written in Java)Tends to track the trends and hot topics in the Java world promptlyeditor, build manager, debuggermoderately extensible
Netbeans and Visual clearly stole interface ideas from one another.
(Then Eclipse came along and stole from them both. Nowadays it’s pretty clear that they are all very much aware of anything resembling an innovation by oneof the others.)
I have not used NetBeans in a long time. I remember it as being incredibly sluggish even on reasonably high-powered desktops.
My enduring impression is that Eclipse seemed to do everything NetBeans wanted to do, did it about 6 months later, but did it better.
2.4 Single-Language IDEsThe open source community has produced numerous single-language IDEs.
Many are focused on educational use.
Examples:
C++Bloodshed Dev-C++, Code::Blocks
JavaBlueJ, Dr. Java, jGrasp
3 EclipseProbably the hottest IDE in the open source world:
syntax-highlighting editor, multi-language supportstrong hinting based on both standard and user-written APIs, interface aidtemplates and refactoring
build supporteasily configured or switched to other build tools
background compilation for quick detection of language errorsintegrated *unit testing supportsolid debugger, intuitive handling of threadssome packaging & deployment supportintegrates with most version control systemsmodular plug-in extensibility with a rich variety available
Eclipse is available here.
3.1 AvailabilityEclipse is installed on both the CS Dept Windows PCs and Linux servers
Includes basic packages for Java & C++ support
We will be working with much more advanced tools in this course.
You will need to install your own plugins.
Every student will therefore need to prepare their own personal development environment with
Java 1.8g++Eclipse
This will be discussed in more detail in an upcoming assignment. .
User StoriesSteven J Zeil:
Last modified: Dec 21, 2019
Contents:1 User stories
1.1 Writing User Stories1.2 Characteristics of good stories1.3 Example (Spreadsheet)1.4 Special Stories
2 Organizing Constructions via Stories2.1 Planning an Increment
3 Stories become Tasks3.1 Tasking Example 13.2 Task Boards3.3 The Definition of Done3.4 Tasking Example 2 (Spreadsheet)
4 Thinking Incrementally4.1 Vertical Thinking
Abstract
User stories are used in incremental SDPMs as a way of organizing work during software construction.
A user story is a simple description of desired functionality, often written as a single sentence on an ordinary index card.
Over time, user stories are modified by
adding an estimated effort to implement that story.adding priority info indicating how important this story is to the end users.splitting stories that are too big to be implemented during a single pass of an incremental processmerging related stories that have become too detailed to implement separately.
In an incremental construction process, small sets of stories are chosen for an upcoming increment as the target for the next round of construction.
1 User stories
Key idea in all agile variations.
A user story is
a mechanism for incremental requirements elicitation and analysis.a way of organizing and tracking work during iterative development
1.1 Writing User StoriesOne or two-line description of work to be done
Usually written on index cardsOften posted on a bulletin board in team workspaces
Each story describes something of value to a customere.g., “Complete the integration tests” is not a story.
Stories have clear completion criteria.
They must be validatable.
1.1.1 Examples of User Stories
As a calendar owner, I want to view my schedule for the coming week.
As a visitor, I want to see when a calendar owner has free time in their schedule.
As a calendar owner, I want to receive email notication when someone accepts a proposed appointment.
As a systems administrator, I want to back up all calendars so that the data is safeguarded.
These illustrate some common patterns:
“As a {role}, I want {goal/desire}”“As a {role}, I want {goal/desire} so that {benefit}”
1.1.2 Stories are not Requirements
…despite the focus on functionality and non-functional characteristics.
Stories are “a promissory note for a future conversation” (Cockburn)A use-case is a transcript of that conversation (Standley)
Stories are a way of prioritizing workIncluding the work of eliciting detailed requirements
1.2 Characteristics of good stories
1.2.1 Customer centric
Stories should express a customer’s point of view, using the customer’s language.
What is the value of this story to the customer?Which customers is this story important to, and how does it affect them?
One way to achieve this is to ask the customers to write the stories.
Stories should be rated by customer priority
1.2.2 Estimatable
At some point in the process, we must add estimates to our stories indicating how much time we think it will take to complete them. Such estimates aid us infuture scheduling. We want to select, for each upcoming increment, enough stories to add a substantial amount of functionality, but not so many that we will beunable to complete them in the time allotted for that increment.
The estimates are commonly not written in hours, person-days or other real time units, at least not unless your team is actually experienced enough to do a goodjob at coming up with such numbers. Instead, a groups of “typical effort” stories is selected as defining unit 1.0 difficulty, and the others are expresssed innumbers relative to that, e.g., “I think this will take half again as long as the typical stories, so I will estimate it at 1.5.”
The team must estimate the effort required to implementation a story.
Often done on a relative scale.
“This story will take half again as much time as our average story.”
Big, complicated stories are harder to estimate
Split them into smaller stories
1.2.3 Validatable
Good stories have completion criteria that can be validated.
It’s important to know when we are done implementing a story.
Agile generally recognizes stories as only 0% or 100% completed – no fractions.
Definition of Done: team’s agreed-upon criteria for declaring work done (e.g., tests passed, integrated, documented, …)
A common saying among agile teams:
“I know that you are done, but are you DONE-done?”
Non-functional stories
Stories can describe new functionality or improvements to existing functionality:
Performance requirements,ScalabilityPortability
May be harder to define precise completion criteria.
1.2.4 Some Examples of “Bad” Stories
Automate integration build
no customer value
Improve performance
no completion criteria
Implement interfaces between subsystems
too big – cannot be accomplished by 1/2 people in a short period of timehas “obvious” decompositions (separate interfaces between each pair of subsystems) that would make it more suitable
1.3 Example (Spreadsheet)
Look at these stories
1.4 Special StoriesThe traditional user story is customer-centric.
But sometimes there are units of work required that don’t fit the traditional definition.
1.4.1 Documentation stories
Agile places less emphasis on documentationMost (e.g., API documentation) is just factored in as part of the effort of implementing a normal storyOccasionally, special documentation needs arise
user manuals, certification requirements
1.4.2 Bug Stories
Fixes for bugs that evaded your unit testsCan be hard to estimate effortallocate fixed time, then schedule a “sequel” story
1.4.3 Spike Stories
Can’t estimate some stories because of lack of informationSpike stories say “Research how to estimate story X”.Name comes from practice of spike solutions, simple throw-away prototypes or experiments
2 Organizing Constructions via StoriesRemember that each increment is supposed to yield a runnable program that implements some addition functionality not present in earlier increments.
Something that we can actually show to a manager/customer/user
Stories also add functionality, but an individual story might not yield something visible while running the program.
2.1 Planning an IncrementIn planning our next increment or release, we select a set of stories to be implemented during our next development interval.
Together, they should add up to a visible addition of functionality to the working system.
Use the estimates attached to each story to scale the amount of work planned for this development interval to a reasonable size.
Early in a project, we might need to guess just how much time it will take to 1 estimated unit of effort.
This should become clearer as we move forward through the project.
2.1.1 Posting Stories
There are a variety of ways to visually organize stories to enhance planning and to give visual evidence of the progress being made.
Stories grouped on a story board to help “enrich” the stories
Story boards can be organized in many different ways
2.1.2 Backlogs
A term used in conjunction with story-based planning is “backlog”.
In normal English usage, “backlog” sounds negative: You have a backlog because you are “backed up” – your progress has been too slow.
In incremental development, there is no such negative connotation.
The project backlog is a list of unimplemented:
Stories, orRequirements for which stories have not been written.
3 Stories become TasksWhen we pick up a story for development there may be many steps required to actually complete it.
Typically we indicate this by listing (often on the same card as the story) the required tasks.We then track the completion status of individual tasks.
It’s possible that a developer will do some tasks on one story, then move to tasks of a second story, then return to the first, etc.
3.1 Tasking Example 1In the spreadsheet stories, I suggested that the stories for the first increment might be
As a calculation author I would like to load a spreadsheet via the CLI.As a calculation author I would like to generate a value report via the CLI.As an API user I would like to create a new spreadsheet.As an API user I would like to load a spreadsheet from a file containing only simple assignments.As an API user I would like to add a numeric literal to a cell in a spreadsheet.As an API user I would like to obtain the value of a cell whose formula contains only literals.
Of these, I would likely pick
As an API user I would like to create a new spreadsheet.
to start.
Then, I would break it into tasks, some of which would be necessitated by this being the first story developed in a project:
1. Set up project repository
2. Set up directories and initial build file.
3. Design initial spreadsheet API.
4. Write unit test for spreadsheet creation via that API.
5. Implement the spreadsheet creation function.
6. Commit implementation to the repository.
Some comments about these tasks:
The italicized items are all concepts that we will tackle in a subsequent lesson.
The sequence defined by items 3-4-5 is characteristic of Test-Driven Development:
We always write a test that will initially fail (if it can be run at all) before doing the implementation.That way we
know when we have succeeded with our implementation.already have a test driver ready the moment we have written part of the actual code.
Although, eventually, we will have an entire test for the spreadsheet API, we don’t try to do that all at once. The tests are written incrementally, justlike the application itself.
3.2 Task BoardsTask boards show the completed and in-progress stories for the current increment:
On this board, the stories are in the cards on the left.The post-it notes represent tasks, and are moved to the right as progress is made.
The post-its often have the name(s) of the developers that have taken on each task.
The “To do” column is sometimes labeled the “sprint backlog”
This is the collection of not-completed tasks scheduled for the current increment.The name comes from the Scrum agile process model, where a period of development for one increment is called a “sprint”.
3.3 The Definition of Done
"I know you say that you are done. But are you done done? – apocryphal question from one team member to another.
The definition of done is a set of criteria that must be met before a development team agrees that a story has been completed and that the people assigned to itare ready to move on to tackle something else.
Typically, this definition is established by the team at the start of the project.
Typical Criteria in a Definition of Done
Code passes all unit tests.System level (functional) tests have been created/passed.Code is checked in and shared with the rest of the team.Code has been integrated and is used by other code in the project.Code meets documentation standards.Code has been reviewed by other team members and found free of major problems.Code has been reviews by analysis tools and found free of major problems.
3.3.1 Tasks can Follow the Definition of Done
A common recommendation for division of a story into tasks is to turn each criterion chosen for the teams’ DoD into a task.
3.4 Tasking Example 2 (Spreadsheet)
I consult my task board and decide that I will next work on the story toimplement a sqrt operator.
My project “definition of done” says that I must have
coded the story (includes writing nad passing unit tests)created a system testintegrated the new code
so I would expect to divide this into at least three tasks.
As it happens, I know that there are two nearly independent coding issuesinvolved:
1. I need to add a new “square root operator” class that captures the idea ofevaluating a square root and the output of expressions involving squareroots.
2. I need to modify the expression parser so that it recognizes expressions ofthe form “sqrt(…)”.
Those are independent enough that I choose to split them.
So I start adding tasks to the swim lane for my chosen story:
until I have my DoD criteria covered:
When I am actually ready to start work, I assign one of the “code” tasks tomyself and move it into the “In progress” column.
4 Thinking IncrementallyTo me, one of the challenges in designing good stories and in scheduling them is to envision possible increments.
When a large portion of the implementation is done, this is relatively easy.
Just list the missing features. Adding a feature (or two, or three) is a “natural” increment.
But what about when you have nothing at all working yet?
It can feel like you need to get a massive amount implemented before anything works at all!
4.1 Vertical Thinking
Partly, I think this is awkward because there’s a mis-match between how we design and think about codeand how we schedule increments.
We design code in “horizontal” layers of abstraction.
But an increment is a “vertical slice” of functioning code.
4.1.1 Iterative Means We Can Come Back
Incremental development is also iterative – we can revisit components in later iterations.
Sometimes that’s easy.
It’s usually pretty easy to implement part of a menu or toolbar.Part of a window is usually easy too
Sometimes it’s not
Think of a typical ADT - how can you implement part of it?
4.1.2 Faking It
If we need to implement part of a complicated ADT during an increment…
Choose a quick (to implement) data structure now, with the intention of replacing it later when we need more of the ADT API to work.
Or, take a lesson from integration testing
Create stubs for unimplemented lower-layer components.Carried to an extreme, you can be pnambic
Verification and ValidationSteven Zeil
Last modified: Dec 21, 2019
Contents:1 The Process2 Non-Testing V&V
2.1 Code Review2.2 Mathematically-based verification2.3 Static analysis tools2.4 Cleanroom software development
Abstract
Verification & Validation: any activities that seek to assure that a software system meets the users’ needs.
The principle objectives are
the discovery of defects in a system, and
the assessment of whether or not the system is usable in an operational situation.
The most familiar form of V&V is testing.
Through the rest of this module, we will indeed be taking a close look at testing, unit testing in particular. Before doing that, however, it’s worth noting thattesting is not the only way to do V&V.
We will look at various forms of
code review and inspection,proof, andstatic analysis
as alternatives or, more often, supplements to testing.
1 The Process
Verification & Validation
Verification:
“Are we building the product right”The software should conform to its (most recent) specification
Validation:
“Are we building the right product”The software should do what the user really requires
Testing
Testing is the act of executing a program with selected data to uncover bugs.
As opposed to debugging, which is the process of finding the faulty code responsible for failed tests.
Testing is the most common, but not the only form of V&V
What Can We Find?
Fault: A defect in the source code.
Failure: An incorrect behavior or result.
Error: A mistake by the programmer, designer, etc., that led to the the fault.
Industry figures of 1-3 faults per 100 statements are quite common.
2 Non-Testing V&V
Static Verification
Verifying the conformance of a software system and its specification without executing the code
Involves analyses of source text by humans or software
Can be carried out on ANY documents produced as part of the software process
Discovers errors early in the software process
Usually more cost-effective than testing for defect detection at the unit and module level
Allows defect detection to be combined with other quality checks
Static verification effectiveness
It has been claimed that
More than 60% of program errors can be detected by informal program inspections
More than 90% of program errors may be detectable using more rigorous mathematical program verification
The error detection process is not confused by the %existence of previous errors
2.1 Code ReviewInspecting the code in an effort to detect errors
Desk Checking
Inspection
2.1.1 Desk Checking
An exercise conducted by the individual programmer.
“Playing computer” with the aid of a listing.
Values of variables are tracked using pencil and paper as the programmer moves step-by-step through the code.
Can be done with pseudocode, diagrams, etc. even before code has been written
A good way to find fundamental flaws in algorithms, especially before actually writing the code.
Useful in checking the results of an intended change during debugging.
2.1.2 Inspection
Formalized approach to document reviews
Intended explicitly for defect detection (not correction)
Defects may be logical errors, anomalies in the code that might indicate an erroneous condition (e.g. an uninitialized variable) or non-compliance withstandards
Inspection pre-conditions
A precise specification must be available.
Team members must be familiar with the organization standards.
Syntactically correct code must be available.
An error checklist should be prepared.
Management must accept that inspection will increase costs early in the software process.
Management must not use inspections for staff appraisal.
Inspection procedure
1. System overview presented to inspection team
Code and associated documents are distributed to inspection team in advance
Inspection takes place and discovered errors are noted
After inspection meeting,
Modifications are made to repair discovered errorsRe-inspection may or may not be required
Inspection teams
Made up of at least 4 membersAuthor of the code being inspectedReader who reads the code to the teamModerator who chairs the meeting and notes discovered errors
Sometimes a separate “secretary” / “scribe” is used
Inspector(s) who finds errors, omissions and inconsistencies
Inspection rate
500 statements/hour during overview125 source statement/hour during individual preparation90-125 statements/hour can be inspectedInspection is therefore an expensive processInspecting 500 lines costs about 40 man/hours effort ( per line)
Inspection checklists
Checklist of common errors should be used to drive the inspection
Error checklist is programming language dependent
The “weaker” the type checking, the larger the checklist
Examples: Initialization, Constant naming, loop termination, array bounds, etc.
Inspection checks
What kinds of faults would appear in a checklist?
Data Faults
Are all variables initialized before use?Have all constants been named?Should array lower bounds be 0, 1, or something else?Should array upper bounds be size of the array or size ?If character strings are used, is a delimited explicitly.Are all data members initialized in every constructor?Is C++’s “Rule of the Big 3” satisfied?
Control Faults
For each conditional statement, is the condition correct?Is each loop certain to terminate?Are compound statements correctly bracketed?In case statements, are all possible cases accounted for?
≥ $8
−1
I/O Faults
Are all input variables used?Are all output variables assigned before being output?
Interface faults
Do all function/procedure calls have the correct number of parameters?Do the formal and actual parameter types match?Are the parameters in the right order?If components access shared memory, do they have the same model of the shared memory structure?
Storage Mgmt Faults
If a linked structure is modified, have all links been correctly assigned?If dynamic storage is used, has space been allocated correctly?Is space explicitly deallocated after it is no longer required?Are all pointer data members deallocated in the destructor?
Exception Mgmt Faults
Have all possible error conditions been taken into account?
Stylistic/standards Faults
Are names understandable?Does code conform to standards for commenting?Does code provide capturable outputs for testing?Does code take advantage of possible re-use?
2.2 Mathematically-based verificationVerification is based on mathematical arguments which demonstrate that a program is consistent with its specification
Programming language semantics must be formally defined
The program must be formally specified
Program proving
Rigorous mathematical proofs that a program meets its specification are long and difficult to produce
Some programs cannot be proved because they use constructs such as interrupts.
These may be necessary for real-time performance
The cost of developing a program proof is so high that it is not practical to use this technique in the vast majority of software projects
Program verification arguments
Less formal, mathematical arguments can increase confidence in a program’s conformance to its specification
Must demonstrate that a program conforms to its specification
Must demonstrate that a program will terminate
Model Checking
Simplified models on which properties can be proved
FSAMarkov Chains
Focus on properties short of correctness
e.g., avoiding race conditions
Machine-Assisted
2.3 Static analysis toolsSoftware tools for source text processing
Try to discover potentially erroneous conditions in a program and bring these to the attention of the V & V team
Very effective as an aid to inspections. A supplement to but not a replacement for inspections
Static analysis checks
What kinds of faults can be detected by static analysis?
Data faults
Variables used before initializationVariables declared but never usedVariables assigned twice but never used between assignmentsPossible array bounds violationsUndeclared variables
Control faults
Unreachable codeUnconditional branches into loops
Interface faults
Parameter type mismatchesParameter number mismatchesFunction return values unusedUncalled functions and procedures
Storage mgmt faults
Unassigned pointersPointer arithmetic
Stages of static analysis
Control flow analysis.
Checks for loops with multiple exit or entry points, finds unreachable code, etc.
Data use analysis.
Detects uninitialized variables, variables written twice without an intervening assignment, variables which are declared but never used, etc.
Interface analysis.
Checks the consistency of routine and procedure declarations and their use
Information flow analysis.
Identifies the dependencies of output variables. Does not detect anomalies itself but highlights information for code inspection or review
Path analysis.
Identifies paths through the program and sets out the statements executed in that path. Again, potentially useful in the review process
These stages generate vast amounts of information.
Must be used with care.
2.4 Cleanroom software developmentThe name is derived from the ‘Cleanroom’ process in semiconductor fabrication. The philosophy is defect avoidance rather than defect removal.
Software development process based on:Incremental developmentFormal specification.Static verification using correctness argumentsStatistical testing to determine program reliability.
Testing is actually forbidden!Code generation is suppressed.Compilers used for syntax/semantic checking only.
Cleanroom process teams
Specification team.
Responsible for developing and maintaining the system specification
Development team.
Responsible for developing and verifying the software. The software is NOT executed during this process
Certification team.
Responsible for developing a set of statistical tests to exercise the software after development.
Reliability growth models used to determine when reliability is acceptable
Results in IBM have been very impressive with few discovered faults in delivered systems
Some independent assessment shows that the process is no more expensive than other approaches
But no controlled studiesAnd their assessment of other approaches is questionable
Fewer errors than in a ‘traditional’ development process
Not clear how this approach can be transferred to an environment with less skilled or less highly motivated engineers
Lab - EclipseSteven Zeil
Last modified: Dec 21, 2019
This lab will give you the opportunity to explore some of the kinds of problems that static analysis tools can help you with. This will be a fairly basic exercise.We won’t be installing any new tools for this lab – just making some better use of an already familiar tool, the g++ compiler.
We will look at some more advanced tools later in the sememster.
1 Starting1. Set up a C++ project to use in this lab. You can use any IDE, though if you have been working through the earlier assignment on Eclipse, it would
probably make the most sense to use that. But you can use Code::Blocks or even emacs. You are going to be doing a lot of stepping though errormessages and examining the associated code, so pick somethings that does that job well.
Your C++ code for this project should be reasonably complicated. It should involve multiple .cpp files and some use of classes. It might even besomething that you never got working correctly, though it should compile.
2. Build (compile) your project in the normal fashion. Most IDEs will display somewhere the compilation commands that they are running on your behalf.Take note of these. (In Eclipse, you can find these in the Console tab of the bottom central window.)
Also take note of any compiler warnings that were issued. (Presumably you did not get actual errors that owuld have prevented compilation.) Stepthrough these warnings and take note of the kind of things the compiler is bringing to your attention.
3. At the end of this exercise summarize your results on the discussion board.
2 -WallThe -Wall flag, added to a g++ compilation, causes the compiler to emit warnings for a number of practices that, in the opinion of the g++ designers, are bothquestionable and easily avoided.
Some of the things checked by -Wall include
out of bound accesses to arrays, when compiled with -O2
use of C++11 features when the explicit option for these has not been supplieduse of char in array subscriptsabuse of enumeration types in comparisonsbad formats in printfpossibly uninitialized variables
…
You can find the full list here, which is best read with a separate browser window open to this index.
1. If your project build does not already include -Wall, add it to the compiler options. (See Changing Compiler Options below.)
Rebuild your project. (You may need to clean/clear the project first to force a full rebuild, because you have not actually touched any of the source code.)
2. If your project build already included -Wall, try removing it and rebuilding. Any changes?
3. Walk through the messages now. Do you see any changes? If not, try adding one of the problems described earlier as being under the purview of -Walland rebuild again.
Take particular note of any messages that indicate a true potential problem with your code. Take note also of nay false alarms, things that you know areOK even though they were flagged. (Keep your mind open, at least, to the possibility that there may be potential problems that you aren’t aware of.)
4. Any surprises? Discover any hidden problems? Or, maybe, just find some of the warnings you got to be inexplicable? Post about your experiences in theHallway Forum.
3 -Wextra-Wextra adds still more checks, including warning for empty loop and if bodies, comparisons between signed and unsigned integers, and parameters tofunctions that are never used in the function body.
1. Repeat the process from -Wall, this time adding -Wextra in addition to -Wall.
4 -Weffc++Weffc++ adds warnigns for a number of issues from Scott Meyers’ Effective C++ and More Effective C++.
These include warnings if you fail to implement your own version of the copy constructor and assignment operator for classes that have dynamic allocation.(You should recall discussions of the C++ Rule of the Big 3 from your earlier courses.) Also flagged are operator= implementations that fail to return *this,and the use of assignment rather than initialization within constructors.
1. Repeat the process from -Wall, this time adding -Weffc++ in addition to -Wall.
5 Closing ThoughtsThese options can give you early warning of potential problems, but only if you actually read and consider the warning messages. The struggle with most staticanalysis regimens is to balance the value of the legitimate warnings against the annoyance of wading through many false alarms.
6 Appendix: Changing Compiler OptionsRead on if you are not familiar with the process of changing the options used to compile C++ code in your favorite IDE.
6.1 emacs/makeIf you are using emacs as your IDE, you are probably using a make file to control your build.
Look in the make file for the actual g++ commands and add the desired option there.
It’s a common style among writers of make files to invoke the compiler via a variable name, usually “CXX”, e.g.,
CXX=g++ ⋮ $(CXX) -o $@ -c $*.cpp
The reason is that this make file can then easily be modified to use a different compiler by changing that variable. In fact, it can be changed directly fromthe command line. For example, if I wanted to generate a Windows executable when running the compiler on a Linux box, I say
make -DCXX=CXX=x86_64-w64-mingw32-g++
to use a Windows cross-compiler instead of the normal g++.
It’s also common to package the compilation options in a variable. For example, I often use CPPFLAGS, in conjunction with a rule like:
CXX=g++ CPPFLAGS=-g -std=c++11 %.o: %.cpp $(CXX) $(CPPFLAGS) -MMD -o $@ -c $*.cpp
So any change to the CPPFLAGS variable would update all compilations generated by this make file.
6.2 EclipseTo change C++ compilation options in Eclipse, much depends on whether you are using the Eclipse build-in project manager or a separate make file to controlthe build. If you are using a make file, then follow the instructions given above.
If you are working with the built-in manager, go to the Project menu and select Properties, `C/C++ Build“, ”Settings“, then the ”Tool Settings“ tab. You canthen select the option flags you want directly in the ”GCC C++ Compiler“ ”Warnings“ or ”Miscellaneous“ area. Note that when you select an option in one ofthese areas, then it gets added to the summary ”All options“ at the ”GCC C++ Compiler" area.
6.3 Code::BlocksFrom the Project menu, select Build Options, then Compiler Settings. You can then try to select your desired option form the description in CompilerFlags or type it in directly under Other options.
TestingSteven Zeil
Last modified: Dec 21, 2019
Contents:1 The Testing Process
1.1 The Steps in a Testing Process2 Stages of Testing3 Unit Testing
3.1 Scaffolding4 Integration Testing
Abstract
Testing is a critical skill for any software developer, but its treatment in most textbooks belies the complexity of this process.
We review the component activities that go into testing code, from the design of testcases to the invocation of the testing oracle to judge correctness of the output.
We will then examine the ways in which testing fits within a process model, looking at unit testing, integration testing, system testing, acceptance testing, andregression testing.
1 The Testing Process
The diagram here illustrates the steps involved in testing code.
Although you have been doing testing for some time as CS students, most of yourtesting has probably been informal. Still most of the activities here should be familiarto you.
Beginning from an overall test plan (or test specification),
we eventually seek to discover a collection of failures
These failures become the input to the process of debugging, where weseek to find the faults in the code responsible for those failures.
Terminology
Failure: An execution on which incorrect behavior occurs
Fault: A defect in the code that (may) cause a failure
Related to these, though not part of our testing process:
Error: A human mistake that results in a fault, or,
alternatively, the difference between the expected output and the actual output on a failed test.
These terms get abused a lot, but there really is a clear difference among the three.
In informal discussions, we sometimes like to make reference to something that is wrong even if we aren’t quite sure (or just don’t care) whether it is an error, afailure or a fault. I tend to use the words like “bug” or “problem”, or “defect” in those circumstances, as these do not have formal definitions.
1.1 The Steps in a Testing Process
A test plan (more properly, a test specification) describes a set of test cases.
Test case: a general description of a required test
For any given test case, there may be many possible inputs that would serve. Forexample, in testing a square root function, we might have a test case “find asquare root where the value cannot be represented exactly in floating point”, forwhich possible inputs would be 2.0, 3.0, 200.0, etc.
With that in mind, the first step in testing is to
1. Derive inputs for each test case.
In most cases, you will also need to record the expected outputs or behavior foryour test inputs.
The inputs and expected outputs may be recorded in a database of regression tests for later. But the most obvious use for the new inputs is to…
2. Execute the tests
The test inputs are fed into the program being tested and the actual outputs collected.
3. Determine which tests have failed.
The test inputs, actual outputs obtained from their execution, and the expected outputs are passed on to the testing oracle. The oracle is the person,program, or process used to determine if a test has failed.
4. Pass the failures on for debugging.
The purpose of debugging is to determine the faults i nthe code that are actually responsible for the failures observed during testing.
1.1.1 Oracles
The testing oracle is the person, program, or process used to determine if a test has failed.
The term “oracle” stems from the “Oracle of Delphi”, a priestess of Apollo in ancient Greece who was believed to have the gift of divination, although herpronouncements were famously cryptic. In Computer Science, “oracles” are invoked as models for answering questions that cannot be entirely solved byalgorithms.
Testing oracles come in many forms, ranging from automated oracles that we will consider in a later lesson, to the “eyeball oracle” that you probably employfor most of your academic assignments.
The eyeball oracle is notoriously prone to missing failures. We just aren’t very good at looking at thousands of lines of output and picking out the one or twothat are incorrect. On the other hand, the eyeball oracle is very good at noticing faults from the early stages of requirements and design that were faithfullytranslated into code (i.e., the eyeball oracle does more validation than verification.
Another common form of oracle is the “head to head” oracle. If we are developing a system to replace an existing one, then we can run the test inputs throughboth systems and compare them, usually by simple byte-by-bye comparison (e.g., the diff command).
This sort of situation (head to head testing) is more common than you might think. Developing new, first-time systems from scratch is a comparatively rareactivity. Even if we expect to add some new functions to the new system (or are adding functionality to the existing one), we can use head-to-head for theshared portion of the two.
1.1.2 Regression
The regression log or regression database is a collection of tests and expected outputs from past testing.
It is used, during regression testing, to quickly rerun old tests. Regression databases can quickly grow to thousands or tens of thousands of cases or more. Itbecomes particularly important that we not rely on the eyeball oracle for evaluating regression tests.
2 Stages of TestingWe recognize several different stages of testing. These differ in scope (how much of the program is involved) and purpose (who conducts the testing and whatinformation do they derive from it).
Unit Test: Tests of individual subroutines and modules,
usually conducted by the programmer.
Integration Test: Tests of “subtrees” of the total project hierarchy chart (groups of subroutines calling each other).
generally a team responsibility.
System Test: Test of the entire system,
supervised by team leaders or by V&V specialists.Many companies have independent teams for this purpose.
Regression Test: Unit/Integration/System tests that are repeated after a change has been made to the code.
Acceptance Test: A test conducted by the customers or their representatives to decide whether to purchase/accept a developed system.
Testing goals
Focusing on the differing purposes of testing, …
Unit Test: does it work?
Integration Test: does it work?
System Test: does it work?
Regression Test: has it changed?
Acceptance Test: should we pay for it?
Regression testing is particularly interesting. We regression test after a change to make sure we have not inadvertently broken anything else. In fact, we reallyare looking for unintended effects of our changes.
Regression logs commonly record tests that have been both passed and failed, and we want to be informed of changes in that status.
So, while most testing has possible outcomes “pass” or “fail”, regression testing has outcomes
Expected pass: this test used to pass, and it still doesExpected fail: this test used to fail, and it still doesUnexpected pass: this test used to fail, and but not it passes
This can be a concern because, if we did not intend to fix that failure with our most recent changes, it suggests that we now have a fault somewherein the code that is now hidden — we no longer have a test case revealing the fault, so it’s lurking in there somewhere, just waiting to spring out atsome particularly inopportune time.
Unexpected fail: this test used to pass, and now it fails.
Sadly, very common. This means that while fixing one bug, we broke something else.
3 Unit TestingWe’re going to spend a lot of time taking about unit testing this semester, so it deserves some special attention now.
By testing modules in isolation from the rest of the system
Easier to design and run extensive tests
Much easier to debug any failures
Errors caught much earlier
Main challenge is how to test in isolation
3.1 ScaffoldingTo do Unit tests, we have to provide replacements for parts of the program that we will omit from the test.
Scaffolding is any code that we write, not as part of the application, but simply to support the process of Unit and Integration testing.
Scaffolding comes in two forms
Drivers
Stubs
3.1.1 Drivers
A driver is test scaffolding that calls the module being tested.
Often just a simple main program that reads values, uses them to construct ADT values, apply ADT operations and print the results
3.1.2 Stubs
Stubs are replacements for code begin called from the unit under test
Must match the “real” APIMay need to provide simulated output parameters and return values
4 Integration TestingIntegration testing is testing that combines several modules, but still falls short of exercising the entire program all at once.
Integration testing usually combines a small number of modules that call upon one another.
Integration testing can be conducted
bottom-up
(start by unit-testing the modules that dont’call anything else, then add the modules that call those starting modules and thest the combination, thenadd the modules that call those, and so on until you are ready to test main().)
relieves the need for stubs
or top-down
(start by unit-testing main() with stubs for everything it calls, then replace those stubs by the real code, but leaving in stubs for anything calledfrom the replacement code, then replacng those stubs, and so on, until you have assembled and tested the entire program).
It’s worth noting that unit testing and integration testing can sometimes use some of the same test inputs (and maybe the same expected outputs), because we aretesting the software in different configurations.
Choosing TestsSteven Zeil
Last modified: Dec 21, 2019
Contents:1 Types of testing2 Representative Testing
2.1 The Operational Profile2.2 Reliability Growth Models
3 Directed Testing3.1 Black-Box Testing3.2 White-Box Testing3.3 Reliability Modeling with Directed Tests
Abstract
Testing starts with the design of test cases that express our strategy for testing our code. Where do these come from?
Testing strategies are generally divided into three categories:
Blackbox testing chooses tests based upon the requirements, without consulting the implementation. In fact, black-box tests are often developed beforecoding has even begun.
Whitebox testing chooses tests based upon the details and structure of the implementation.
Representative testing chooses test data based upon projections of how the program will be used in operation.
We will examine each of these possibilities.
1 Types of testingWe can differentiate testing strategies by overall goal:
Statistical testing
tests designed to reflect the frequence of user inputs.
Used for reliability estimation.
Defect testing
tests designed to discover system defects.A successful defect test is one which reveals the presence of defects in a system.
Choosing Test Data
From these motives come different approaches to choosing test data
Representative testing
tests designed to reflect the frequence of user inputs.Used for reliability estimation.
Directed testing
Tests designed to discover system defects.A successful directed test is one which reveals the presence of defects in a system.
2 Representative TestingChoose data that is representative of the way the end users will exercise the software.
Advantages:realism — catches the kinds of failures that the users would have encounteredrelatively cheap to generate teststime to failure during test reflects time to failure in operation
useful in statistical reliability modeling
Producing Representative Tests
Test data can be obtained via
collected data from an existing system
A useful option when we are modifying an existing system, which is probably more common than projects where we build a new system from scratch.
“System” here is generic. Even if the old system was completely manual, we may be able to collect and then digitize input data to the old system.
further selection may be needed
cannot accomodate new functionality
random generators
non-uniform, to match desired distribution
The conventional rand() function yields a uniform distribution — each possible output is equally likely. But random number generators can beimplemented for just about any probability distribution – normal, exponential, etc.
can be hard to generate non-numeric ADT values
Think about the problem of generating random strings to serve as names. If you simply generate sequences of randomly selected lengthcontaining randomly selected characters, the results won’t be very name-like and may strain the ability of code for dealing with fist & last names,etc. Such random strings also tend to look alike ot the human eye, making the visual evaluation of test output nearly impossible.
What I have done in the past is to get lists of common first names and separate lists of real last names (publicly available U.S. census data is greatfor that, BTW). Then randomly select one first name and one last name and combine them together. Granted, you do get some interesting ethniccombinations that way, e.g., “Nguyen Smith” or “Antonio O’Reilly”.
Now consider the further problem of randomly generating a list of customer transactions, of which these names are one component. Now you havethe further problem of how often a particular customer name should be repeated. If you randomly generate a name for each transaction, you willwind up with almost no repeats, and that could lead to some very unrealistic transaction sequences.
Representative Testing
Requires an operational profile for its definitionThe operational profile defines the expected pattern of software usage
2.1 The Operational ProfileThe operational profile is a description of the probability distribution of the input.
It describes how often, during operational program use, certain “kinds” of inputs will be seen.
Sample Op Profile
Input Category PercentageTransaction Proc. 85%
Input Category PercentageBalancing 14%Year-end Report 1%
For an accounting program, we might start with an observation that past activities have broken down like this.
But then we might note that transactions come in many different kinds…
Sample Op Profile
Input Category PercentageTransaction Proc. 85%
New account 7%Close account 3%Debits 70%Credits 20%
Balancing 14%Year-end Report 1%
After noting that the requirements specify different behaviors for transaction on existing accounts and non-existent accounts, we might look at companyrecords for still more statistics…
Sample Op Profile
Input Category PercentageTransaction Proc. 85%
New account 7%new 90%already exists 10%
Close account 3%non-existent 15%exists 85%
Debits 70%non-existent 25%exists 75%
Credits 20%
Input Category Percentagenon-existent 25%exists 75%
Balancing 14%Year-end Report 1%
The breakdown of transactions into cases bases on whether the account is new (non-existent) versus already existing suggests something of an answer to theearlier discussion of randomly generating customer names and asking how often they should repeat. By explicitly measuring how often a new transactioninvolves an existing account, we know whether to randomly generate a new name or to randomly select from among already-generated ones.
Representative testing difficulties
Uncertainty in the operational profile
This is a particular problem for new systems with no operational history. Less of a problem for replacement systems
High costs of generating the operational profile
Costs are very dependent on what usage information is collected by the organisation which requires the profile
Statistical uncertainty
Difficult to estimate level of confidence in operational profileUsage pattern of software may change with time
One example of such changes are so-called “secondary effects” in which the very existence of a new system itself alters the way in which its usersbehave. One of the classic examples of secondary effects is the U.S. Interstate Highway system. The earliest stretches completed in this systemsoon became heavily congested as the number of vehicles using them far exceeded initial estimates. Part of the problem was that the very existenceof the Interstate Highway system enabled urban workers to live and commute from well outside the city limits, so that a road system designed foruse in moving from one city to the next instead was used as the daily rush-hour commutes between urban centers and the suburbs.
2.2 Reliability Growth ModelsA growth model is a mathematical model of the system reliability change as it is tested and faults are removed
Used as a means of reliability prediction by extrapolating from current data
Reliability modeling procedure
Determine operational profile of the software
Generate a set of test data corresponding to the profile
Apply tests, measuring amount of execution time between each failure
After a statistically valid number of tests have been executed, reliability can be measured
Reliability metrics
Some common metrics1 that come out of these statistical models are:
Probability of failure on demand
This is a measure of the likelihood that the system will fail when a service request is madePOFOD = 0.001 means 1 out of 1000 service requests result in failureRelevant for safety-critical or non-stop systems
Rate of fault occurrence (ROCOF)
Frequency of occurrence of unexpected behaviourROCOF = 0.02 means 2 failures are likely in each 100 operational time unitsRelevant for operating systems, transaction processing systems
Mean time to failure
Measure of the time between observed failuresMTTF = 500 means that the average time between failures is 500 time unitsRelevant for systems with long transactions e.g. CAD systems
Availability
Measure of how likely the system is available for use. Takes repair/restart time into accountAvail = 0.998 means software is available for 998 out of 1000 time unitsRelevant for continuously running systems e.g. telephone switching systems
2.2.1 Collecting data for reliability measurement
Measure the number of system failures for a given number of system inputs
Used to compute POFOD
Measure the time (or number of transactions) between system failures
Used to compute ROCOF and MTTF
Measure the time to restart after failure
Used to compute Avail
Time units
Time units in reliability measurement must be carefully selected. Not the same for all systems
Raw execution time (for non-stop systems)
Calendar time (for systems which have a regular usage pattern e.g. systems which are always run once per day)
Number of transactions (for systems which are used on demand)
2.2.2 Jelinski-Moranda Model
Assumptions:
Software contains faults ( is unknown)
Each fault manifests (causes a failure) at rate
Faults manifest independently
Faults are fixed perfectly, without introducing new ones
If we have repaired faults, the program’s failure rate is
Observed reliability growth
Simple equal-step model but does not reflect reality
Reliability does not necessarily increase with change as the change can introduce new faults
The rate of reliability growth tends to slow down with time as frequently occurring faults are discovered and removed from the software
N N
ϕ
i λ
= (N − i)ϕλi
2.2.3 Musa Logarithmic Poisson Model
Assumptions:
Software can never be completely free of faults.
Faults manifest independently
Faults are found in decreasing order of failure rate.
The program failure rate before repairing any faults is
Faults are fixed perfectly, without introducing new ones
If we have repaired faults, the program’s failure rate is
Fitting Example
This data shows the failure rate of a program (measured as where was the length of time the testers were able to run the program before seeing afailure.
Here we have fit this data to the Jelinski-Moranda and Musa Logarithmic models. (Because I have plotted this data using a logarithmic scale for the y-axis, the ML model appears as a straight line in this plot.)
λ0
i λ
=λi λ0e−θi
1/t t
3 Directed TestingChoose tests designed to reveal
many faults
as quickly as possible
Choosing Good Test Data
Techniques for selecting directed test data are generally termed either
white-box, orblack-box
3.1 Black-Box TestingBlack-box (a.k.a. specification-based) testing chooses tests without consulting the implementation.
based simply upon our understanding of what the unit is supposed to do.
One of the goals of black-box testing is to be sure that every distinct behavior expected of a unit has been triggered in at least one test. Another is to try tochoose tests that are likely to cause trouble, no matter what the actual algorithm is.
Some of the best-known techniques for choosing black-box tests focus on the input values that will be supplied to the unit during testing.
Functional Coverage
a.k.a Equivalence partitioning
Choose at least one test that covers each distinct “behavior” described in the requirements.
Different functions performed by the programDifferent types or ranges of input that get treated or described separately.Different types or ranges of output that get treated or described separately.
Large, structured projects place emphasis on tracking requirements to functional test cases
Boundary Values Testing
a.k.a., Extremal Values Testing
Choose as test data the largest and the smallest values for each input and for each “functional” caseOften misunderstood as simply choosing largest & smallest possible inputsAssumes that we have started by attempting functional coverage
Special Values Testing
Choose as test data those certain data values that just tend to cause trouble.
Programmers eventually develop a sense for these. They include
For integers: -1, 0, 1
For floating point numbers: -e, 0, +e, where “e” is a very small number
For strings: the empty string, strings containing only blanks, strings containing no alphabetic characters
What is “Special”?
As we move to other data structures, we may develop a suspicion of other special values, e.g.,
For times of the day: midnight, noonFor containers of data: an empty container
Special values and Boundary values often overlap
(F14 example)
3.2 White-Box TestingWhite-Box (a.k.a. Implementation-based testing) uses information from the implementation to choose tests.
Formally, most white-box testing involves coverage measures, E.g., statement coverage, branch coverage
For example, a common goal in white-box testing is to be sure that every code statement is executed at least once by some test. Another common goal isthat, for every conditional branch (ifs, loops, etc.) in the code, that on at least one test that condition will have been true and on at least one test thatcondition will have been false.
The difficulty with such white-box testing is that it’s almost impossible to be sure you have met one of these goals unless you have special testingsoftware to track the execution of statements and branches over the whole history of testing. Lacking this software, it’s easier to do black-box testing.
Informally, design tests specifically to exercise “tricky” parts of the code
However, there is a kind of informal white-box testing that we can do. As programmers, we often write a bit of code and think to ourselves “I wonder ifthis is really going to work?” That’s a good time to pause and design a test that will check exactly that bit of dicey code.
Common forms:
Structural Testing (a.k.a., “path testing” (not per your text)
Designate a set of paths through the program that must be exercised during testing.
Statement CoverageBranch CoverageCyclomatic coverage (“independent path testing”)Data-flow Coverage
Mutation testing
Perturbation testing
3.2.1 Statement Coverage
Require that every statement in the code be executed at least once during testing.
Special programs (“software tools”) will monitor this requirement for you.
gprof in GNU gcc/g++ suiteClover and JaCoCo for Java
Example
cin >> x >> y; while (x > y) { if (x > 0) cout << x; x = f(x, y); } cout << x;
What kinds of tests are required for statement coverage?
3.2.2 Branch Coverage
Requires that every “branch” in the flowchart be tested at least once
Equivalent to saying that each conditional stmt must be tested as both true and falseBranch coverage implies Statement Coverage, but not vice versa
if X < 0 then X := -X;
Y := sqrt(X);
Branch Coverage example
cin >> x >> y; while (x > y) { if (x > 0) cout << x; x = f(x, y); } cout << x;
What kinds of tests are required for branch coverage?
3.2.3 Cyclomatic Coverage
(a.k.a “independent path coverage”, “path testing”)
The latter term (used in your text) should be discouraged as it is both vague and means something entirely different to most of the testing community
Each independent path must be tested
An independent path is one that includes a branch not previously taken.
A Control Flow Graph
What are the independent paths?
1, 2, 3, 4, 12, 13
1, 2, 3, 5, 6, 11, 2, 12, 13
1, 2, 3, 5, 7, 8, 10, 11, 2, 12, 131, 2, 3, 5, 7, 9, 10, 11, 2, 12, 13
3.2.4 Cyclomatic Complexity
The number of independent paths in a program can be discovered by computing the cyclomatic complexity (McCabe, 1976)
This is a popular metric for module complexity.
Actually pretty trivial: for structured programs with only binary decision constructs, equals number of conditional statements +1
relation to testing is dubious
simply branch coverage hidden behind smoke and mirrors
Uniqueness
CC(G) = Number(edges) − Number(nodes) + 1
Sets of independent paths are not unique, nor is their size.
Earlier we gave this set of 4 paths as a cover
1, 2, 3, 4, 12, 131, 2, 3, 5, 6, 11, 2, 12, 131, 2, 3, 5, 7, 8, 10, 11, 2, 12, 131, 2, 3, 5, 7, 9, 10, 11, 2, 12, 13
All are independent.
But this set of two paths also covers all
1, 2, 3, 5, 6, 11, 2, 3, 5, 7, 8, 10, 11, 2, 3, 5, 7, 9, 10, 11, 2, 12, 131, 2, 3, 4, 12, 13
And there are no paths independent of these two.
3.2.5 Data-Flow Coverage
Attempts to test significant combinations of branches.
Any stmt i where a variable X may be assigned a new value is called a definition of X at i: def(X,i)
Any stmt i where a variable X may be used/retrieved is called a reference or use of X at i: ref(X,i)
def-clear
A path from stmt i to stmt j is def-clear with respect to X if it contains no definitions of X except possibly at the beginning (i) and end (j)
all-defs
The all-defs criterion requires that each definition def(X,i) be tested some def-clear path to some reference ref(X,j).
1: cin >> x >> y; d(x,1) d(y,1)2: while (x > y) r(x,2), r(y,2)3: {4: if (x > 0) r(x,4)5: cout << x; r(x,5)6: x = f(x, y); r(x,6), r(y,6), d(x,6)7: }8: cout << x; r(x,8)
What kinds of tests are required for all-defs coverage?
all-uses
The all-uses criterion requires that each pair (def(X,i), ref(X,j)) be tested using some def-clear path from i to j.
1: cin >> x >> y; d(x,1) d(y,1)2: while (x > y) r(x,2), r(y,2)3: {4: if (x > 0) r(x,4)5: cout << x; r(x,5)6: x = f(x, y); r(x,6), r(y,6), d(x,6)7: }8: cout << x; r(x,8)
What kinds of tests are required for all-uses coverage?
3.2.6 Mutation Testing
Given a program ,
Form a set of mutant programs that differ from by some single change
These changes (called mutation operators) include:
exchanging one variable name by anotheraltering a numeric constant by some small amountexchanging one arithmetic operator by anotherexchanging one relational operator by anotherdeleting an entire statementreplacing an entire statement by an abort() call
but can be almost any single small change.
P
P
Mutation Testing (cont.)
Run and each mutant on a previously chosen set of tests
Compare the output of each to that of
If the outputs differ on any test, is killed and removed from the set of mutant programsIf the outputs are the same on all tests, is still considered alive.
Mutation Testing (cont.)
A set of test data is considered inadequate if it cannot distinguish between the program as written ( ) and programs that differ from it by only a simple change.
So if any mutants are still alive after running a set of tests, we augment the tests until we can kill all the mutants.
Mutation Testing Problems
Even simple programs yield tens of thousands of mutants. Executing these is time-consuming.
But most are killed on first few testsAnd the process is automated
Some mutants are actually equivalent to the original program:
⋮ ⋮ X = Y; X = Y; if (X > 0) then if (Y > 0) then} ⋮ ⋮
Identifying these can be difficult (and cannot be automated)
3.2.7 Perturbation Testing
Perturbation testing (Zeil) treats each arithmetic expression in the code as if it had been modified by the addition of an error term , where are the program variables and can be a polynomial of arbitrarily high degree (can approximate almost any error)
Monitor the variable values actually encountered during testing
Solve for the possible functions that would have escaped detection on the tests done so far
If there are none, we’re done.
P Pi
Pi P
Pi
Pi
P
f( )x̄ f( ) + e( )v̄ v̄ v
e
e
If some exist, can generally be expressed as “coincidental” relations between variablese.g., x and y have always been equal in all tests, so substitutions of one for the other could yet not have been detected
3.3 Reliability Modeling with Directed TestsMost literature on reliability models assumes that it can only be done with representative testing, because
Directed tests’ time-to-failure is unrelated to operational time-to-failure
Directed tests may find faults “out of order”
Order Statistic Model
Zeil & Mitchell (1996) presented a model for reliability growth under either representative or directed testing.
Assumptions:
Software contains faults, whose failure rates are described by a distribution .
Faults manifest independently
The test process is biased towards finding faults with higher failure rates.
Measurement Process
Fault failure rates are measured when the fault has been identified and corrected.
“post-mortem” analysis
Faults are then sorted by failure rate.
The sorted data is fitted to an order statistic distribution.
Order statistics is the study of the probability of selecting certain values when the selection process is biased.
Fitting Example
N F
This plot shows an alternative scenario in which the testers started by using representative testing, but once the intervals between failures (and, therefore,between fixes) became lengthy, switched to directed testing to accelerate the process of actually finding and fixing bugs. The Order Statistic model is able, aftera period of adjustment to the new testing approach, to model (predict) the severity of the remaining bugs.
1: For some reason, in this field people like to talk about “metrics” rather than the entirely equivalent but less impressive sounding “measures”.
Automating the Testing OracleSteven J Zeil
Last modified: Dec 21, 2019
Contents:1 Automating the Oracle
1.1 How can oracles be automated?1.2 Examining Output1.3 Limitations of Capture-and-Examine Tests
2 Self-Checking Unit & Integration Tests2.1 First Cut at a Self-Checking Test2.2 Better Idea: Test the Public Functions Against Each Other2.3 assert() might not be quite what we want
3 JUnit Testing3.1 JUnit Basics3.2 Structure of a JUnit Test3.3 A JUnit Example3.4 Setting Up Tests3.5 Writing Expressive Tests3.6 Matchers3.7 JUnit in Eclipse
4 C++ Unit Testing4.1 Google Test4.2 Boost Test Framework4.3 CppUnitLite
Earlier we introduced this model of the basic testing process.
In this lesson, we turn our attention to the oracle, the process for determining whether a test has failed.
We will argue that the economics of testing provide a powerful incentive to automate the oracle. Traditionally, this has been done by capture-and-examine,capturing outputs and then using a separate automated process to examine and pass judgement upon those outputs.
Current practice, however, emphasizes self-checking tests, drivers that both feed input to the code under test and immediately evaluating its responses. Self-checking tests are supported by a *Unit framework. We will look at popular frameworks for both Java and C++.
1 Automating the Oracle
Why Automate the Oracle?
Most of the effort in large test suites is evaluating correctness of outputs
The oracle is a decision procedure for deciding whether the output of a particular test is correct.
Humans (“eyeball oracles”) are notoriously unreliable
better to have tests check themselves.
Regression test suites can be huge.
Thousands, tens of thousands, even hundreds of thousands of tests are not unheard of. If your idea of testing is running some code and visually inspectingthe output, you can see that won’t work on this kind of scale. Regression tests have almost always been designed to check themselves. Often this wasdone by recording, during unit, integration, or systems test, the outputs produced by each test input. During regression testing, the same inputs are rerun,and the outputs compared to the earlier ones that had been recorded. If the outputs change, an alert is printed. If the outputs stay the same, testing movesquietly on to the next test.
Modern development methods emphasize rapid, repeated unit tests
It might not be obvious that self-checking is quite valuable for unit and integration testing as well. But if test outputs have to be inspected by a human,then anything more than a very few tests becomes a tedious thing to do. This motivates programmers to write very few tests and to run them infrequently,both of which are very bad ideas.
Test-driven development: Develop the tests first, then write the code.
In fact, many of the latest trends in software development place a lot more emphasis on almost continual unit testing. In so-called agile or extremeprogramming, programmers are expected to write tests before implementing their functions or ADTs, and to continually rerun the tests as units areadded or changed. (In effect, we get a kind of “rolling” integration test.)
In continuous integration processes, every time a programmer stops work on a unit, not only are its unit tests run, but the changes are automaticallyintegrated into the entire program, and integration and systems tests are re-run.
But that's just not going to happen if testing is hard to do.
Debugging: if you can’t reproduce the error, how will you know when you’ve fixed it?
If a bug is reported or a new feature requested, the first step is to add new tests that fail because of the bug or missing feature. Then we will know we havesuccessfully fixed the problem when we are able to pass that test.
The best way to be sure programmers rerun the tests on a regular basis is to make the test run part of the regular build process (e.g., build the test runs into theproject make file) and to make them self-checking.
1.1 How can oracles be automated?Output Capture
If we are doing systems/regression tests, the first step towards automation is to capture the output:
If a program produces output files, one can self-check by creating a file representing the expected correct output, then running the program to get the actualoutput file and using a simple comparison utility like the Unix diff or cmp commands to see if the actual output is identical to the expected output.
For system-level regression tests, this is even simpler. Once we have a program that passes our system tests, we run it on those tests and save the outputs. Thosebecome the expected output files for later regression testing. (Remember, the point of regression testing is to determine if any behavior has changed due torecent updates to the code.)
If the program updates a database, it may be possible to capture entire databases in a similar fashion. Alternatively, we write database queries to check forchanges in the records most likely to have been affected by a test.
On the other hand, if the program’s main function is to present information on a screen, self-checking is very difficult. Screen captures are often not much use,because we are unlikely to want to deal with changes where, say one window is a pixel wider or a pixel to the left of where it had been in a prior test. Self-checking tests for programs like this either require extremely fine control over all possible interactive inputs and graphics device characteristics, or they requirea careful “design for testability” to record, in a testing log file, information about what is being rendered on the screen. (We’ll revisit this idea later in thesemester when we discuss the MVC pattern for designing user interfaces.)
Output Capture and Drivers
At the unit and integration test level, we are testing functions and ADT member functions that most often produce data, not files, as their output. That data couldbe of any type.
How can we capture that output in a fashion that allows automated examination?
Traditional answer is to rely on the scaffolding to emit output in text form.
A more sophisticated answer, which we will explore later, is to design these tests to be self-checking.
1.2 Examining Output
1.2.1 File Tools
diff, cmp and similar programs compare two text files byte by byteused to compare expected and actual outputuseful in back-to-back testing of
old system to its new replacementsystem before and after a bug repairbut also used with manually generated expected output
parameters allow special treatments of blanks, empty lines, etc.some versions can be used with binary files
Alternatives
More sophisticated tests can be performed via grep and similar utilitiessearch file for data matching a regular expression
Custom oracles
Some programs lend themselves to specific, customized oracles
For example, a program to invert a matrix can be checked by multiplying its input and output together — should yield the identity matrix.
pipe output from program/driver directly into a custom evaluation program, e.g.,
testInvertMatrix matrix1.in > matrix1.out multiplyCheck matrix1.in < matrix1.out
or
testInvertMatrix matrix1.in | multiplyCheck matrix1.in
Most useful when oracle can be written with considerably less effort than the program under test
1.2.2 expect
expect is a shell for testing interactive programs.
an extension of TCL (a portable shell script).
Key expect Commands
spawn: Launch an interactive program.
send: Send a string to a spawned program, simulating input from a user.
expect: Monitor the output from a spawned program. Expect takes a list of patterns and actions:
pattern1 {action1} pattern2 {action2} pattern3 {action3} ⋮
and executes the first action whose pattern is matched.
Patterns can be regular expressions or simpler “glob” patterns
interact: Allow person running expect to interact with spawned program. Takes a similar list of patterns and actions.
Sample Expect Script
Log in to other machine and ignore “authenticity” warnings.
#!/usr/local/bin/expectset timeout 60 spawn ssh $argvwhile {1} { expect {
eof {break} "The authenticity of host" {send "yes\r"} "password:" {send "$password\r"} "$argv" {break} # assume machine name is in prompt } } interact close $spawn_id
Expect: Testing a program
puts "in test0: $programdir/testsets\n" catch { spawn $programdir/testsets ➀ expect \ ➁ "RESULT: 0" {fail "testsets"} \ ➂ "missing expected element" {fail "testsets"} \ "contains unexpected element" {fail "testsets"} \ "does not match" {fail "testsets"} \ "but not destroyed" {fail "testsets"} \ {RESULT: 1} \{pass "testsets"} \ ➃ eof {fail "testsets"; puts "eofbsl nl"} \ timeout {fail "testsets"; puts "timeout\n"} } catch { close wait }
➀ Launches the testsets program
➁ Watches the output for one of the following conditions
➂ The “RESULT” line is the normal output. A result of 0 is a test case failure.
➃ A result of 1 is a test case success
The various other options are diagnostic/error messages that could appear if the program never reaches the RESULT output.
1.3 Limitations of Capture-and-Examine Tests
Structured Output
For unit/integration test, output is often a data structure.
Data must be serialized to generate text output and parsed to read the subsequent input
A lot of work
Easy to omit detailsCan introduce bugs of its own
Similar issues can exist with the need to supply structured inputs
Repository Output
For system and high-level unit/integration tests, output may be updates to a database or other repository.
Must be indirectly “captured” via subsequent query/accesssignificant setup and cleanup effort per test
need separate test stores
Graphics Output
For system and high-level unit/integration tests, output may be graphics
very hard to capture
Similar issues can arise supplying GUI input
Supplying a repeatable sequence of input events (key presses, mouse movement & clicks, etc)Sometimes timing-critical
2 Self-Checking Unit & Integration TestsAddresses problem of capture-and-examine for structured data
Each test case is a function.
That function constructs required inputs …and passes those inputs to the module under test …
and examines the output …
… all within the memory space of the running function
In testing an ADT, we are not testing an individual function, but a collection of related functions. In some ways that makes thing easier, because we can usemany of these functions to help test one another.
2.1 First Cut at a Self-Checking TestSuppose you were testing a SetOfInteger ADT and had to test the add function in isolation, you would need to know how the data was stored in the set andwould have to write code to search that storage for a value that you had just added in your test. E.g.,
void testAdd (SetOfInteger aSet) { aSet.add (23); bool found = false; for (int i = 0; i < aSet.numMembers && !found; ++i) found = (aSet[i] == 23); assert(found); }
2.1.1 What’s Good and Bad About This?
void testAdd (SetOfInteger aSet) { aSet.add (23); bool found = false; for (int i = 0; i < aSet.numMembers && !found; ++i) found = (aSet.data[i] == 23); assert(found); }
Good: captures the notion that 23 should have been added to the set
Good: requires no human evaluation
Bad: relies on underlying data structure
Requires the tester to think at multiple levels of abstractionTest is fragile: if implementation of SetOfInteger changes, test can become uselessMight not even compile - those data members are probably private
2.2 Better Idea: Test the Public Functions Against Each OtherOn the other hand, if you are testing the add and the contains function, you could use the second function to check the results of the first:
void testAdd (SetOfInteger aSet) { aSet.add (23); assert (aSet.contains(23)); }
Simpler
Robust: tests remain valid even if data structure changes
Legal: Does not require access to private data
Not only is this code simpler than writing your own search function as part of the test driver, it continues to work even if the data structure used to implementthe ADT should be changed. What’s more, it is, in a sense, a more thorough test, since it tests two functions at once. Finally, there’s the simple fact that the testwith the explicit loop probably won’t even compile, since it refers directly to data members that are almost certainly private.
In a sense, we have made a transition from white-box towards black-box testing. The new test case deliberately ignores the underlying structure.
2.2.1 Idiom: Preserve and Compare
void testAdd (SetOfInteger startingSet) { SetOfInteger aSet = startingSet; aSet.add (23); assert (aSet.contains(23)); if (startingSet.contains(23)) assert (aSet.size() == startingSet.size()); else assert (aSet.size() == startingSet.size() + 1); }
Note that we
save the original ADT value, thenmodify a copy, and thencompare the original and copy to see the changes
2.2.2 More Thorough Tests
You can see the usefulness of “preserve and comapre” in this more thorough test.
void testAdd (SetOfInteger aSet) { for (int i = 0; i < 1000; ++i) { int x = rand() % 500; bool alreadyContained = aSet.contains(x); int oldSize = aSet.size(); aSet.add (23); assert (aSet.contains(x)); if (alreadyContained) assert (aSet.size() == oldSize); else assert (aSet.size() == oldSize + 1); } }
2.3 assert() might not be quite what we wantOur use of assert() in these examples has mixed results
Good: stays quiet as long as we are passing tests
failures easily detected by humans
Bad: testing always stops at the first failure
In a large suite of many such test cases, we may be missing out on info that would be useful for debugging
Bad: diagnostics are limited to file name and line number where the assertion failed.
3 JUnit TestingJUnit is a testing framework that has seen wide adoption in the Java community and spawned numerous imitations for other programming languages.
Introduces a structure for a
suite (separately executable program) oftest cases (functions),each performing multiple self-checking tests (assertions)
using a richer set of assertion operators
also provides support for setup, cleanup, report generation
readily integrated into IDEs
3.1 JUnit BasicsGetting Started: Eclipse & JUnit
Let’s suppose we are building a mailing list application. the mailing list is a collection of contacts.
Contact.java +
package mailinglist; /** * A contact is a name and address. * <p> * For the purpose of this example, I have simplified matters * a bit by making both of these components simple strings. * In practice, we would expect Address, at least, to be a * more structured type. * * @author zeil * */public class Contact implements Cloneable, Comparable<Contact> { private String theName; private String theAddress; /** * Create a contact with empty name and address. * */ public Contact () { theName = ""; theAddress = ""; } /** * Create a contact * @param nm name * @param addr address */ public Contact (String nm, String addr) {
theName = nm; theAddress = addr; } /** * Get the name of the contact * @return the name */ public String getName() { return theName; } /** * Change the name of the contact * @param nm new name */ public void setName (String nm) { theName= nm; } /** * Get the address of the contact * @return the address */ public String getAddress() { return theAddress; } /** * Change the address of the contact * @param addr new address */ public void setAddress (String addr) { theAddress = addr; } /** * True if the names and addresses are equal */ public boolean equals (Object right) { Contact r = (Contact)right; return theName.equals(r.theName) && theAddress.equals(r.theAddress); }
public int hashCode () { return theName.hashCode() + 3 * theAddress.hashCode(); } public String toString() { return theName + ": " + theAddress; } public Object clone() { return new Contact(theName, theAddress); } /** * Compare this contact to another. * Return value > 0 if this contact precedes the other, * == 0 if the two are equal, and < 0 if this contact * follows the other. */ public int compareTo (Contact c) { int nmcomp = theName.compareTo(c.theName); if (nmcomp != 0) return nmcomp; else return theAddress.compareTo(c.theAddress); } }
MailingList.java +
package mailinglist; /** * A collection of names and addresses */public class MailingList implements Cloneable { /** * Create an empty mailing list * */ public MailingList() { first = null; last = null; theSize = 0;
} /** * Add a new contact to the list * @param contact new contact to add */ public void addContact(Contact contact) { if (first == null) { // add to empty list first = last = new ML_Node(contact, null); theSize = 1; } else if (contact.compareTo(last.contact) > 0) { // add to end of non-empty list last.next = new ML_Node(contact, null); last = last.next; ++theSize; } else if (contact.compareTo(first.contact) < 0) { // add to front of non-empty list first = new ML_Node(contact, first); ++theSize; } else { // search for place to insert ML_Node previous = first; ML_Node current = first.next; assert (current != null); while (contact.compareTo(current.contact) < 0) { previous = current; current = current.next; assert (current != null); } previous.next = new ML_Node(contact, current); ++theSize; } } /** * Remove one matching contact * @param c remove a contact equal to c */ public void removeContact(Contact c) { ML_Node previous = null; ML_Node current = first; while (current != null && c.getName().compareTo(current.contact.getName()) > 0) { previous = current; current = current.next; } if (current != null && c.getName().equals(current.contact.getName())) remove(previous, current); }
/** * Remove a contact with the indicated name * @param name name of contact to remove */ public void removeContact(String name) { ML_Node previous = null; ML_Node current = first; while (current != null && name.compareTo(current.contact.getName()) > 0) { previous = current; current = current.next; } if (current != null && name == current.contact.getName()) remove(previous, current); } /** * Search for contacts * @param name name to search for * @return true if a contact with an equal name exists */ public boolean contains(String name) { ML_Node current = first; while (current != null && name.compareTo(current.contact.getName()) > 0) { current = current.next; } return (current != null && name == current.contact.getName()); } /** * Search for contacts * @param name name to search for * @return contact with that name if found, null if not found */ public Contact getContact(String name) { ML_Node current = first; while (current != null && name.compareTo(current.contact.getName()) > 0) { current = current.next; } if (current != null && name == current.contact.getName()) return current.contact; else return null; } /** * combine two mailing lists * */ public void merge(MailingList anotherList) {
// For a quick merge, we will loop around, checking the // first item in each list, and always copying the smaller // of the two items into result MailingList result = new MailingList(); ML_Node thisList = first; ML_Node otherList = anotherList.first; while (thisList != null && otherList != null) { int comp = thisList.contact.compareTo(otherList.contact); if (comp <= 0) { result.addContact(thisList.contact); thisList = thisList.next; /* if (comp == 0) otherList = otherList.next; [Deliberate bug ] */ } else { result.addContact(otherList.contact); otherList = otherList.next; } } // Now, one of the two lists has been entirely copied. // The other might still have stuff to copy. So we just copy // any remaining items from the two lists. Note that one of these // two loops will execute zero times. while (thisList != null) { result.addContact(thisList.contact); thisList = thisList.next; } while (otherList != null) { result.addContact(otherList.contact); otherList = otherList.next; } // Now result contains the merged list. Transfer that into this list. first = result.first; last = result.last; theSize = result.theSize; } /** * How many contacts in list? */ public int size() { return theSize; } /** * Return true if mailing lists contain equal contacts */ public boolean equals(Object anotherList) { MailingList right = (MailingList) anotherList; if (theSize != right.theSize) // (easy test first!)
return false; else { ML_Node thisList = first; ML_Node otherList = right.first; while (thisList != null) { if (!thisList.contact.equals(otherList.contact)) return false; thisList = thisList.next; otherList = otherList.next; } return true; } } public int hashCode() { int hash = 0; ML_Node current = first; while (current != null) { hash = 3 * hash + current.contact.hashCode(); current = current.next; } return hash; } public String toString() { StringBuffer buf = new StringBuffer("{"); ML_Node current = first; while (current != null) { buf.append(current.contact.toString()); current = current.next; if (current != null) buf.append("\n"); } buf.append("}"); return buf.toString(); } /** * Deep copy of contacts */ public Object clone() { MailingList result = new MailingList(); ML_Node current = first; while (current != null) { result.addContact((Contact) current.contact.clone()); current = current.next; } return result; }
private class ML_Node { public Contact contact; public ML_Node next; public ML_Node(Contact c, ML_Node nxt) { contact = c; next = nxt; } } private int theSize; private ML_Node first; private ML_Node last; // helper functions private void remove(ML_Node previous, ML_Node current) { if (previous == null) { // remove front of list first = current.next; if (last == current) last = null; } else if (current == last) { // remove end of list last = previous; last.next = null; } else { // remove interior node previous.next = current.next; } --theSize; } }
Set up an Eclipse project with these two files
Make sure that these compile successfully with no errors.
In the Eclipse Build Path, add a library: JUnit,
Choose JUnit4 if given a choice of versions 3 or 4
3.2 Structure of a JUnit Test
A JUnit test is a class.
It contains one or more “test cases” or “test functions” – member functions that return void, take no parameters, and are marked with @Test.
Each test case
1. Sets up any necessary test data.2. Runs the code to be tested on that data.3. Examines the results of the test via one or more assertions.
A test case can
1. Succeed if all assertions are true.2. Fail if any assertion is false (or if the fail(...) function is called explicitly).3. Terminate with an error if the test case itself throws an exception.
3.2.1 Assertions
assertTrue( condition ): condition should be true.assertFalse( condition ): condition should be false.assertEquals( x , y): x and y should be equal. (For objects, this is tested using the equals(...) function. For primitives like int, the == operator isused.)assertEquals( x , y , delta): Floating point numbers x and y should be approximately equal, within plus or minus delta.assertArrayEquals( array1 , array2): array1 and array2 should have the same length and corresponding elements in these arrays should be equal.assertSame( obj1 , obj2): obj1 and obj2 should hold the same address.assertNotSame( obj1 , obj2): obj1 and obj2 should not hold the same address.assertNull( obj1 ): obj1 should be a null reference.assertNotNull( obj1 ): obj1 should not be a null reference.
Each of these can have an optional first argument of a string that will be printed when the assertion fails. For example,
Integer pos = search(arr, x); assertNotNull (pos); assertNotNull ("Could not find x in arr", pos);
3.3 A JUnit Example
3.3.1 A First Test Suite
Right-click on the project and select New ... JUnit Test Case.
Give it a name (e.g., TestMailingList)
in the same mailinglist package as the two classes
For “Class under test”, use the Browse button and select our MailingList class
Click Next, then select the MailingList() and addContact( … ) functions
3.3.2 A First Test Suite (cont.)
You’ll get something like this:
package mailinglist; import static org.junit.Assert.*; import org.junit.Before; import org.junit.Test; public class TestMailingList { @Test public void testMailingList() { fail("Not yet implemented"); } @Test public void testAddContact() { fail("Not yet implemented"); } }
Save this, and run it (right-click on the new file in the Package Explorer, and select Run As ... JUnit Test)
You’ll see what failed tests look like.Try double-clicking on the failed cases and observe the effects on the source code listing and the Failure Trace
3.3.3 A First Test
Let’s implement a test for the mailing list constructor.
/** * Create an empty mailing list *
*/public MailingList() {
Glancing through the MailingList interface, what would we expect of an empty mailing list?
the size() would be zero
any contact we might search for would not be contained in the list
3.3.4 Testing the Constructor
Change the testMailingList function to
testmlConst.java +
@Testpublic void testMailingList() { MailingList ml = new MailingList(); ➀ assertFalse (ml.contains("Jones")); ➁ assertNull (ml.getContact("Smith")); assertEquals (0, ml.size()); ➂ }
➀ Before we can test any function, we have to invoke it
➁ Notice the tests using different variations on the idea of an assertion
assert( ... ) itself remains a language primitive - we don’t use that but use these JUnit variations insteadUse assertTrue instead of assertThe JUnit versions don’t shut down all testing the way the primitive assert( ... ) would do
The first failed JUnit assertion shuts down the test case (function)
➂ The order of parameters in assertEquals is significant
expected value comes first
3.3.5 Running the Constructor Test
Run the test suite as beforeIt should succeed (testAddContact will still fail)
Change the “0” in the final test to “1”. Run again, and observe the message in the Failure Trace.Restore the “0”. Change the first line to
MailingList ml = null; // new MailingList();
and run again.
Notice that a “test error” is marked differently than a “failed test”
Restore the first line
3.3.6 Testing addContact
Now let’s add a test for addContact:
After adding a contact
the size() should increase
the contact name should be contained in the list
we should be able to find the contact that we just added.
Testing addContact - first pass
Try this test (run it!)
@Testpublic void testAddContact() { MailingList ml = new MailingList(); Contact jones = new Contact("Jones", "21 Penn. Ave."); ml.addContact (jones); assertTrue (ml.contains("Jones")); assertFalse (ml.contains("Smith")); assertEquals ("Jones", ml.getContact("Jones").getName()); assertNull (ml.getContact("Smith")); assertEquals (1, ml.size()); }
It works. But it feels awfully limited.
3.4 Setting Up Tests
Before we try making that test more rigorous, let’s look at how we can organize the set up of auxiliary data like our contacts:
Contact jones; @Beforepublic void setUp() throws Exception { jones = new Contact("Jones", "21 Penn. Ave."); } ⋮ @Test public void testAddContact() { MailingList ml = new MailingList(); ml.addContact (jones); ⋮
3.4.1 Fixtures
The class that contains the data shared among the tests in a suite is called a fixture.
A function marked as @Before will be run before each of the test case functions.
Used to (re)initialize data used in multiple tests
Why every time?
Helps keep test cases independentDuring debugging, it’s common to select and run single test cases in isolation
Can do cleanup in a similar fashion with “@After”
3.4.2 Testing addContact - setup
Contact jones; Contact baker; Contact holmes; Contact wolfe; MailingList mlist; @Beforepublic void setUp() throws Exception { jones = new Contact("Jones", "21 Penn. Ave."); baker = new Contact("Baker", "Drury Ln."); holmes = new Contact("Holmes", "221B Baker St."); wolfe = new Contact("Wolfe",
"454 W. 35th St."); mlist = MailingList(); mlist.addContact(baker); mlist.addContact (holmed); mlist.addContact (baker); }
Create a series of contacts
3.4.3 Testing addContact - improved
@Testpublic void testAddContact() { assertTrue (mlist.contains("Jones")); assertEquals ("Jones", mlist.getContact("Jones").getName()); assertEquals (4, ml.size()); }
Still seems a bit limited - we’ll address that later.
3.5 Writing Expressive TestsOne goal is writing tests is to make them both easy to read and to set them up so that the messages they issue upon failure are as helpful as possible.
3.5.1 Choose the most limited assertion.
These two assertions mean the same thing:
assertTrue(string1.equals(string2)); assertEquals(string1, string2);
I would argue that the second is easier to read. Moreover, if these assertions fail, the second gives a more informative message:
import static org.junit.Assert.*; import org.junit.Test; public class testMessages { String string1 = "abcdef"; String string2 = "abcZef";
@Test public void test1() { assertTrue (string1.equals(string2)); } @Test public void test2() { assertEquals (string1, string2); } }
results in the messages:
test1: java.lang.AssertionError at testMessages.java:14test2: org.junit.ComparisonFailure: expected:<abc[d]ef> but was:<abc[Z]ef> at testMessages.java:19
The second message is clearly more helpful.
You can alleviate this a bit by adding a message
assertTrue ("string1 is not equal to string2", string1.equals(string2));
but, really, it’s still not as good.
Use assertTrue and assertFalse only when more specific assertions do not apply. Then consider adding messages to them explaining what theyare actually checking for.
For example, if what we really wanted to assert was that one string contained the other, then we would be stuck with
assertTrue ("string1 does not contain " + string2", string1.contains(string2));
3.6 MatchersBecause the set of basic assertions is, of necessity, limited, a newer style of assertion has arisen that
1. Tries to be more expressive for many common tests.
2. Can be extended to new “kinds” of assertions fitted to custom ADTs.
The assertion style looks like
assertThat (object, matcher);
(Again, an optional message can be added to the front of the parameter list, but it is generally hopes that the new style reduces the need for this.)
In this new style,
assertThat (obj1, equalTo(obj2));
means the same thing as
assertEquals (obj1, obj2);
but uses the equalTo matcher instead.
3.6.1 Core matchers
The Hamcrest Matchers include a variety of primitives for checking one value:
x .equalTo( y ): x should be equal to y.
Sometimes abbreviated as x .is( y ).
x .instanceOf( class ): x should be an instance of a particular class
Sometimes abbreviated as x .isA( class ).
x .nullValue(): x should be a null reference.
x .notNullValue(): x should not be a null reference.
x .sameInstance( y ): x and y should hold the same address.
3.6.2 Core matchers – modifiers
Other core matchers are used to modify the way that matchers are applied
not( matcher ): The matcher should return false.
allOf( matcher1, matcher2, … ); all of the matchers should return true.
anyOf( matcher1, matcher2, … ); at least one of the matchers should return true.
anything(): this matcher always returns true.
With these, we can write assertions like:
assertThat(not(x.equalTo(y)); assertThat(anyOf(nullValue(x), not(x.equalTo(y)));
3.6.3 Additional Matchers
Also in the Hamcrest library, matchers for strings, numbers, iterable containers, etc.:
assertThat(x, lessThan(y)); assertThat(string1, isEmptyOrNullString()); assertThat(string1, startsWith("Hello")); assertThat(myList, hasItem("Smith"); assertThat(myList, contains(smallerList); assertThat(myList, containsInAnyOrder("Jones", "Doe", "Smith");
See the Matchers class API for a full list.
3.7 JUnit in EclipseThe Eclipse IDE for Java includes a copy of the JUnit library.
You need to add it to your project settings.
Right-click on your Java project, select Build Path, Add Libraries..., JUnit, and select JUnit4.
4 C++ Unit Testing4.1 Google TestGoogle Test, a.k.a. gtest, provides a similar *Unit environment for C++
Download & follow instructions to prepare library
gtest will be added to your project as source codeeasiest is to copy the files from fused-src/gtest/ files into a subdirectory gtest within your project.
For Eclipse support, add the Eclipse CDT C/C++ Tests Runner plugin
Example
This time, the C++ version of the mailing list.
1. Source code is here.Unpack into a convenient directory.
2. With Eclipse create a C++ project in that directory.3. Compile4. Run the resulting executable as a local C++ application5. Run as a *unit test:
Right-click on the binary executable, select “Run as … Run configurations”.Select C/C++ Unit, click “New” buttonOn the “C/C++ Testing” tab, select Tests Runner: Google Tests RunnerClick “Run”
Examining the ADT
mailinglist.h +
#ifndef MAILINGLIST_H#define MAILINGLIST_H #include <iostream>#include <string> #include "contact.h"#include "mlIterator.h" /** A collection of names and addresses */class MailingList { public: typedef MLConstIterator iterator; typedef MLConstIterator const_iterator; MailingList();
MailingList(const MailingList&); ~MailingList(); const MailingList& operator= (const MailingList&); // Add a new contact to the list void addContact (const Contact& contact); // Does the list contain this person? bool contains (const Name&) const; // Find the contact const Contact& getContact (const Name& nm) const; //pre: contains(nm) // Remove one matching contact void removeContact (const Contact&); void removeContact (const Name&); // combine two mailing lists void merge (const MailingList& otherList); // How many contacts in list? int size() const; bool operator== (const MailingList& right) const; bool operator< (const MailingList& right) const; // Provides iterator over contacts const_iterator begin() const; const_iterator end() const; private: struct ML_Node { Contact contact; ML_Node* next; ML_Node (const Contact& c, ML_Node* nxt) : contact(c), next(nxt) {} }; ML_Node* first; ML_Node* last; int theSize; // helper functions
void clear(); void remove (ML_Node* previous, ML_Node* current); friend std::ostream& operator<< (std::ostream& out, const MailingList& addr); }; // print list, sorted by Contactstd::ostream& operator<< (std::ostream& out, const MailingList& list); #endif
Should look familiar after the Java version
Examining the Tests
MailingListTests.cpp +
#include "mailinglist.h" #include <string>#include <vector> #include "gtest/gtest.h" namespace { using namespace std; // The fixture for testing class MailingList.class MailingListTests : public ::testing::Test { ➀ public: Contact jones; ➃ MailingList mlist; virtual void SetUp() { jones = Contact("Jones", "21 Penn. Ave."); ➄ mlist = MailingList(); mlist.addContact (Contact ("Baker", "Drury Ln.")); mlist.addContact (Contact ("Holmes", "221B Baker St.")); mlist.addContact (Contact ("Wolfe", "454 W. 35th St.")); } virtual void TearDown() { } };
TEST_F (MailingListTests, constructor) { MailingList ml; EXPECT_EQ (0, ml.size()); ➁ EXPECT_FALSE (ml.contains("Jones")); } TEST_F (MailingListTests, addContact) { mlist.addContact (jones); ➅ EXPECT_TRUE (mlist.contains("Jones")); ➂ EXPECT_EQ ("Jones", mlist.getContact("Jones").getName()); EXPECT_EQ (4, ml.size()); } } // namespace
Roughly similar to the JUnit tests
➀ The class provides a fixture where data can be shared among test cases
This is optional, but common
➁ Test cases are introduced with TEST_F
First argument identifies the suite (same as fixture name)Second argument names the test caseThe combination must be uniqueUse TEST if you don’t provide a fixture class
➂ The test cases feature assertions similar to those seen in JUnit
EXPECT assertions allow testing to continue after failureASSERT variations also exist that shut down testing on failure
➃ Public members of the fixture class will be visible to all test cases
➄ and are assigned the values during SetUp, run before each test case➅ You can see the fixture members used here
4.2 Boost Test Framework
Boost UTF
Boost is a well-respected collection of libraries for C++.
Many of the new library components of C++11 were distributed in Boost for “beta test”.
Other, more specialized libraries will remain separately distributed.
This include another popular *Unit framework for C++, the Boost Unit Test Framework (UTF).
Basic principles are similar to Google Test
Also has some support for building trees of test suites.
Boost Test
The UTF can be added as a single header #include or, for greater efficiency when dealing with multiple suites, compiled to form a static or dynamiclibrary.
Easiest to start with the single header approach. Download and unpack the Boost library.
Add the Boost include directory to your C++ project’s search path for system headers (#include < ... >)If you re using a makefile, add the -I compiler option, e.g., -I /home/zeil/src/boost/includeIn Eclipse, this is Project ... Properties ... C/C++ Build ... Settings ... GCC C++ compiler ... Includes.
For Eclipse support, use the same Eclipse CDT C/C++ Tests Runner plugin
Example
Again, the C++ version of the mailing list.
1. Source code is here.Unpack into a convenient directory.
2. With Eclipse create a C++ project in that directory.Add path to Boost include directory
3. Compile4. Run the resulting executable as a local C++ application5. Run as a *unit test:
Right-click on the binary executable, select “Run as … Run configurations”.Select C/C++ Unit, click “New” buttonOn the “C/C++ Testing” tab, select Tests Runner: Boost Tests RunnerClick “Run”
Examining the Tests
MailingListTests2.cpp +
#define BOOST_TEST_MODULE MailingList test #include "mailinglist.h" #include <string>#include <vector> #include <boost/test/included/unit_test.hpp> using namespace std; // The fixture for testing mailing lists.class MailingListTests { ➀ public: Contact jones; ➁ MailingList mlist; MailingListTests() { ➃ jones = Contact("Jones", "21 Penn. Ave."); mlist.addContact (Contact ("Baker", "Drury Ln.")); mlist.addContact (Contact ("Holmes", "221B Baker St.")); mlist.addContact (Contact ("Wolfe", "454 W. 35th St.")); } ~MailingListTests() { } }; BOOST_AUTO_TEST_CASE ( constructor ) { ➄ MailingList ml; BOOST_CHECK_EQUAL (0, mlist.size()); ➅ BOOST_CHECK (!mlist.contains("Jones")); } BOOST_FIXTURE_TEST_CASE (addContact, MailingListTests) { ➆ mlist.addContact (jones); ➂
BOOST_CHECK (mlist.contains("Jones")); BOOST_CHECK_EQUAL ("Jones", mlist.getContact("Jones").getName()); BOOST_CHECK_EQUAL (4, mlist.size()); }
➀ This names the suite.
➁ The class provides a fixture where data can be shared ➂ among test cases
Optional, but commonSimpler in Boost than in Google
Can be any class.Initialization is done in the class constructor instead of in a special function. ➃
➄ Test cases that don’t need anything from a fixture are introduced with BOOST\_AUTO\_TEST\_CASE
Argument names the test case
➅ The assertions have different names but are similar in function and variety to JUnit and GTest
A full list is here.
➆ Test cases that need a fixture are introduced with BOOST_FIXTURE_TEST_CASE
Second argument identifies the fixture classPublic members of the fixture class will be visible to test cases ➂
4.3 CppUnitLiteCppUnitLite is my own C++ test framework, with features:
Supports the newer “matcher” style of JUnit/HamcrestLightweight – requires adding one .h and one .cpp file to a project.Emulates GoogleTest when run within EclipsePortable: works in Linux, MacOs, and Windows with CygWin or MingWUnit tests are time-limited by default (Linux/MacOs only)
But timing functions are turned off when running in a debugger.When running in a debugger, a breakpoint is generated automatically on test assertion failure.
Future versions of this framework are intended to offer a mocking framework that is simpler and more intuitive than currently available elsewhere.
4.3.1 Example
MailingListTestsLite.cpp +
#include "unittest.h" ➀#include "mailinglist.h" #include <string>#include <vector> using namespace std; Contact jones; MailingList mlist; void setUp() { jones = Contact("Jones", "21 Penn. Ave."); mlist = MailingList(); mlist.addContact (Contact ("Baker", "Drury Ln.")); mlist.addContact (Contact ("Holmes", "221B Baker St.")); mlist.addContact (Contact ("Wolfe", "454 W. 35th St.")); } UnitTest (constructor) { ➁ MailingList ml; assertThat (ml.size(), is(0)); ➂ assertFalse (ml.contains("Jones")); } UnitTest (addContact) { setup(); ➃ mlist.addContact (jones); assertTrue (mlist.contains("Jones")); assertThat (mlist.getContact("Jones").getName(), is("Jones")); assertThat (ml.size(), is(4)); assertThat (ml, hasItem(jones)); ➄ }
➀ Imports the unit test framework.➁ New test cases are introduced by UnitTest or UnitTestTimed.
All tests are time-limited by default (not supported in Windows) but UnitTestTimed overrides the default.
➂ A typical assertion, in the newer “matcher” style favored by JUnit.
➃ In general, UnitTestLite aovoids creating special constructs for things that can easily be done by normal programming, such as running a setupfunction at the start of a test.➄ An example of testing a collection. hasItem searches any data structure that provides iterators.
This could also be written as
assertThat (jones, isIn(ml));
Lab: Unit TestingSteven J Zeil
Last modified: Dec 21, 2019
Contents:1 Choose Your Project2 C++ Testing3 Java Testing
This is a self-assessment activity to give you practice in working with unit test frameworks. Feel free to share your problems, experiences, and choices in theForum.
1 Choose Your ProjectFor this lab, you will want a fairly simple program, in C++ or Java, that contains at least one well-defined ADT.
You might find this among some of your old homework for CS250, 330, 361, or 382. If you cannot find anything suitable, you might try
this program from Malik, chapter 12 (or 10 or … It depends on what Edition you have.).
You may want to watch 4.4 Testing: C++, JUnit, Pyunit from CS 330.
If you opt to use the Java Tic-Tac-Toe example, you have a reasonable starting point. However, as discussed during 4.4. Testing: C++ JUnit, Pyunit theexample tests are incomplete. Pick one of the non-Player C++ or Java ADTs.
Pick an ADT (not the main program) for testing.
If you have selected a C++ program, proceed to the next section. If you have selected a Java program, jump ahead (for now) to Java Testing.
2 C++ Testing1. If your original program was in Java, translate it to C++.
2. Set up an Eclipse project for your C++ code.
3. Add either the Google Test or Boost Test framework to your project.
4. Write a very simple unit test to start with - something that just asserts that 1 + 1 == 2.
5. Compile this test, in the presence of your other code (even though it won’t be using that code yet).
You may find it necessary to remove or rename your “real” main() function for this purpose, as the default Eclipse project builder for C++ can’t reallydeal with multiple executables. We’ll have to live with this until we learn how to add custom build managers.
6. Run the test driver as a “normal” C++ executable program.
7. Run the test driver as a unit test under the Eclipse framework. You should see the Eclipse GUI report on the number of test successes.
8. Now replace that simple test with a real set of tests for your chosen ADT.
If you are attempting this lab before we have covered ADT testing, use your best general Black-box skills. After we have covered ADT testing, return andexamine your tests from a mutator/accessor perspective. What, if anything, would you add for a good ADT test?
9. Again, run your tests both as normal executable programs and as unit tests under the Eclipse framework.
10. If you have all of your tests in a single .cpp file, try splitting them into two test sets in separate .cpp files. Or, if your program has more than one ADT,write a test or two for the other ADT in a separate .cpp file.
Again, verify that you can compile and run these tests under the Eclipse Framework.
3 Java Testing1. If your original program was in C++, translate it to Java.
2. Set up an Eclipse project for your Java code.
3. Add the JUnit library to the build path of your Java project.
4. Write a very simple unit test to start with - something that just asserts that 1 + 1 == 2.
5. Compile this test.
6. Run the test driver as a unit test under the Eclipse framework. You should see the Eclipse GUI report on the number of test successes.
7. Now replace that simple test with a real set of tests for your chosen ADT.
If you are attempting this lab before we have covered ADT testing, use your best general Black-box skills. After we have covered ADT testing, return andexamine your tests from a mutator/accessor perspective. What, if anything, would you add for a good ADT test?
8. Again, run your tests under the Eclipse framework.
9. If you have all of your tests in a single .java file, try splitting them into two test sets in separate .java files. Or, if your program has more than one ADT,write a test or two for the other ADT in a separate .java file.
Again, verify that you can compile and run these tests under the Eclipse Framework.
If your original program was Java, and you skipped the C++ section initially, go back now and do the C++ portion of this lab.
Testing ADTsSteven J Zeil
Last modified: Dec 21, 2019
Contents:1 Be Smart: Generate and Check
1.1 Generate and Check2 Be Thorough: Mutators and Accessors
2.1 Example: Unit Testing of the MailingList Class2.2 Testing for Pointer/Memory Faults2.3 Case Study 1: Unit Test of NumericLiteral2.4 Is this Overkill?
3 Be Independent:
Abstract
It would be nice if every new ADT we wrote worked correctly the first time it compiled properly, but the real world just doesn’t work that way.
One advantage of organizing your code into a collection of ADTs is that ADTs provide, not only a convenient way to package up the functionality of your code,but also a convenient basis for testing.
The *Unit test frameworks give us a powerful tool for unit testing. But we still need to pick the tests.
In this lesson we look at how to use a unit test framework to “thoroughly” test an ADT. Our approach is based upon dividing the ADT interface into
mutators: functions that alter the state or value of the ADT, andaccessors: functions that examine the state of the ADT.
Our approach then becomes:
1. Each test case explores one mutator, and each mutator has at least one test case.2. In each test case, assert the effect of that mutator as revealed through each accessor.
1 Be Smart: Generate and CheckTesting addContact
In our earlier test for addContact, we weren’t particularly thorough:
TEST_F (MailingListTests, addContact) { mlist.addContact (jones); EXPECT_TRUE (mlist.contains("Jones")); EXPECT_EQ ("Jones", mlist.getContact("Jones").getName()); EXPECT_EQ (4, ml.size()); }
Missing functional case: adding a contact that already exists
Missing boundary/special case: adding to an empty container
Missing special cases: Adding to beginning or end of an ordered sequence
Adding Variety
Some of our concerns could be addressed by adding tests but varying the parameters:
TEST_F (MailingListTests, addExistingContact) { mlist.addContact (baker); EXPECT_TRUE (mlist.contains("Baker")); EXPECT_EQ ("Baker", mlist.getContact("Baker").getName()); EXPECT_EQ (3, ml.size()); }
1.1 Generate and CheckA useful design pattern for producing well-varied tests is
for v: varieties of ADT { ADT x = generate(v); result = applyFunctionBeingTested(x); check (x, result); }
generate and check could be separate functions or in-line
depends on whether you can re-use in multiple tests
Most common way to introduce “variety” would be size
Could also be different constructors, in which case you might not literally have a loop:
ADT x (constructor1); result = applyFunctionBeingTested(x); check (x, result); ADT x2 (constructor2, ... ); result = applyFunctionBeingTested(x2); check (x2, result);
Example: addContact
A more elaborate fixture will aid in generating mailing lists of different sizes:
fixture.cpp +
// The fixture for testing class MailingList.class MailingListTests { public: Contact jones; vector<Contact> contacts; MailingListTests() { jones = Contact("Jones", "21 Penn. Ave."); contacts.clear(); contacts.push_back (Contact ("Baker", "Drury Ln.")); contacts.push_back (Contact ("Holmes", "221B Baker St.")); contacts.push_back(jones); contacts.push_back (Contact ("Wolfe", "454 W. 35th St.")); } ~MailingListTests() { } MailingList generate(int n) const { MailingList m; for (int i = 0; i < n; ++i) m.addContact(contacts[i]); return m; } };
Example: addContact - many sizes
testAdd1.cpp +
BOOST_FIXTURE_TEST_CASE (addContact, MailingListTests) { for (unsigned sz = 0; sz < contacts.size(); ++sz) { MailingList ml0 = generate(sz); MailingList ml (ml0); bool alreadyContained = ml.contains("Jones"); ml.addContact (jones); BOOST_CHECK (ml.contains("Jones")); BOOST_CHECK_EQUAL ("Jones", ml.getContact("Jones").getName()); if (alreadyContained) BOOST_CHECK_EQUAL (ml.size(), sz); else BOOST_CHECK_EQUAL (ml.size(), sz+1); } }
Here we generate mailing lists of a variety of sizes and add Jones to them
At larger sizes, Jones is already in the list – functional case covered
sz == 0 covers one of our boundary/special cases
Example: addContact - ordering
testAdd2.cpp +
BOOST_FIXTURE_TEST_CASE (addContact, MailingListTests) { for (unsigned sel = 0; sel < contacts.size(); ++sel) { Contact& toAdd = contacts[sel]; const Name& nameToAdd = contacts[sel]; for (unsigned sz = 0; sz < contacts.size(); ++sz) { MailingList ml0 = generate(sz); MailingList ml (ml0); bool alreadyContained = ml.contains(nameToAdd); ml.addContact (toAdd); BOOST_CHECK (ml.contains(nameToAdd)); BOOST_CHECK_EQUAL (nameToAdd, ml.getContact(nameToAdd).getName()); if (alreadyContained) BOOST_CHECK_EQUAL (ml.size(), sz); else
BOOST_CHECK_EQUAL (ml.size(), sz+1); } } }
We an also explore adding to different positions (beginning, middle, end)by varying which element we add
2 Be Thorough: Mutators and AccessorsOK, let’s say we want to design a unit test suite for our MailingList ADT.
Just staring at the ADT interface, where do we start? The criteria we suggested earlier (typical values, extremal values, special values) may be of help, but it’sfar from obvious how to apply those ideas.
There is an organized way to approach this test design. It starts with the recognition that the member functions of an ADT can usually be divided into twogroups - the mutators and the accessors.
Mutator functions are the functions that alter the value of an object. These include constructors and assignment operators.
Accessor functions are the functions that “look at” the value of an object but do not change it. Some functions may both alter an object and return part of itsvalue - we’ll have to wing it a bit with those.
Mutators and Accessors
To test an ADT, divide the public interface into
mutators: functions that alter the value of the object
accessors: functions that “look at” the current value of an object
Occasional functions will fall in both classes
Organizing ADT Tests
The basic procedure for writing an ADT unit test is to
1. Consider each mutator in turn.
2. Write a test that begins by applying that mutator function.
3. Then consider how that mutator will have affected the results of each accessor.
4. Write assertions to test those effects.
Commonly, each mutator will be tested in a separate function.
2.1 Example: Unit Testing of the MailingList Classmailinglist.h +
#ifndef MAILINGLIST_H#define MAILINGLIST_H #include <iostream>#include <string> #include "contact.h" /** A collection of names and addresses */class MailingList { public: MailingList(); MailingList(const MailingList&); ~MailingList(); const MailingList& operator= (const MailingList&); // Add a new contact to the list void addContact (const Contact& contact); // Does the list contain this person? bool contains (const Name&) const; // Find the contact const Contact& getContact (const Name& nm) const; //pre: contains(nm) // Remove one matching contact void removeContact (const Contact&); void removeContact (const Name&); // combine two mailing lists void merge (const MailingList& otherList);
// How many contacts in list? int size() const; bool operator== (const MailingList& right) const; bool operator< (const MailingList& right) const; private: struct ML_Node { Contact contact; ML_Node* next; ML_Node (const Contact& c, ML_Node* nxt) : contact(c), next(nxt) {} }; ML_Node* first; ML_Node* last; int theSize; // helper functions void clear(); void remove (ML_Node* previous, ML_Node* current); friend std::ostream& operator<< (std::ostream& out, const MailingList& addr); }; // print list, sorted by Contactstd::ostream& operator<< (std::ostream& out, const MailingList& list); #endif
Look at our MailingList class.
Question: What are the mutators? What are the accessors?
Answer: +
The mutators are
the two constructors,
the assignment operator,
addContact,
both removeContact functions, and
merge.
The accessors are
size,
contains
getContact
operator== and operator<
the output operator operator<<
Unit Test Skeleton
Here is the basic skeleton for our test suite.
skeleton.cpp +
#define BOOST_TEST_MODULE MailingList test #include "mailinglist.h" #include <string>#include <sstream>#include <vector> #include <boost/test/included/unit_test.hpp> using namespace std;
Now we start going through the mutators.
2.1.1 Testing the Constructors
The first mutators we listed were the two constructors. Let’s start with the simpler of these.
Apply The Mutator
BOOST_AUTO_TEST_CASE ( constructor ) { MailingList ml; ⋮
First we start by applying the mutator.
Apply the Accessors to the Mutated Object
Then we go down the list of accessors and ask what we expect each one to return.
BOOST_AUTO_TEST_CASE ( constructor ) { MailingList ml; BOOST_CHECK_EQUAL (0, ml.size()); BOOST_CHECK (!ml.contains("Jones")); BOOST_CHECK_EQUAL (ml, MailingList()); BOOST_CHECK (!(ml < MailingList())); }
1. It’s pretty clear, for example, that the size() of the list will be 02. contains() would always return false3. The EQUAL test checks the operator==4. We check for consistency of operator<
We can’t check the accessors
getContact
can’t satisfy the pre-condition, and
operator<<
behavior unspecified
Testing the Copy Constructor
testCons1.cpp +
BOOST_FIXTURE_TEST_CASE ( copyConstructor, MailingListTests) { for (unsigned sz = 0; sz < contacts.size(); ++sz) { MailingList ml0 = generate(sz); ➀ MailingList ml1 = ml0; // copy constructor ➁ shouldBeEqual(ml0, ml1); ➂ } { // Minimal check for deep copy - changing one should not affect the other MailingList ml0 = generate(2); MailingList ml1 = ml0; // copy constructor ml1.addContact(jones); ➃ BOOST_CHECK_EQUAL (2, ml0.size()); BOOST_CHECK_NE (ml0, ml1); } }
➀ We use the generate-and-check pattern.
➁ Here we invoke the function under test.
➂ The check function - we’ll look at this in a moment
➃ The second half of this is more subtle. I expect that copies are distinct. Once a copy is made, updating one object should not change the other.
A failure suggests that we have done an improper shallow copy.
The Check Function for Copying
shouldBeEqual.cpp +
// The fixture for testing class MailingList.class MailingListTests { public: Contact jones; vector<Contact> contacts; MailingListTests() { ⋮ void shouldBeEqual (const MailingList& ml0, const MailingList& ml1) const
{ BOOST_CHECK_EQUAL (ml1.size(), ml0.size()); ➀ for (int i = 0; i < ml0.size(); ++i) ➁ { BOOST_CHECK_EQUAL(ml1.contains(contacts[i].getName()), ml0.contains(contacts[i].getName())); if (ml1.contains(contacts[i].getName())) BOOST_CHECK_EQUAL(ml1.getContact(contacts[i].getName()), ml0.getContact(contacts[i].getName())); } BOOST_CHECK_EQUAL (ml0, ml1); ➂ BOOST_CHECK (!(ml0 < ml1)); BOOST_CHECK (!(ml1 < ml0)); ostringstream out0; ➂ out0 << ml0; ostringstream out1; out1 << ml1; BOOST_CHECK_EQUAL (out0.str(), out1.str()); } };
➀ Sizes should be equal
➁ Boths lists should agree as to what they contain.
➂ Relational ops - should be equal and not less/greater
➃ Whatever they print, it should match
2.1.2 Testing the Assignment Operator
testAsst.cpp +
BOOST_FIXTURE_TEST_CASE ( assignment, MailingListTests) { for (unsigned sz = 0; sz < contacts.size(); ++sz) { MailingList ml0 = generate(sz); MailingList ml1; MailingList ml2 = (ml1 = ml0); // assignment ➀ shouldBeEqual(ml0, ml1); ➁ shouldBeEqual(ml0, ml2); // assignment returns a value } {
// Minimal check for deep copy - changing one should not affect the other MailingList ml0 = generate(2); MailingList ml1; ml1 = ml0; // copy constructor ml1.addContact(jones); BOOST_CHECK_EQUAL (2, ml0.size()); BOOST_CHECK_NE (ml0, ml1); } }
Very similar to testing the copy constructor
➀ But remember that assignment operators not only change the value on the left, but also return a copy of the assigned value.
We need to check both.
➁ Fortunately, we can re-use the check function developed for the copy constructor.
2.1.3 Testing addContact
testAdd.cpp +
BOOST_FIXTURE_TEST_CASE (addContact, MailingListTests) { for (unsigned sel = 0; sel < contacts.size(); ++sel) { Contact& toAdd = contacts[sel]; const Name& nameToAdd = toAdd.getName(); for (unsigned sz = 0; sz < contacts.size(); ++sz) { MailingList ml0 = generate(sz); MailingList ml (ml0); bool alreadyContained = ml.contains(nameToAdd); ml.addContact (toAdd); BOOST_CHECK (ml.contains(nameToAdd)); BOOST_CHECK_EQUAL (toAdd, ml.getContact(nameToAdd)); if (alreadyContained) { BOOST_CHECK_EQUAL (ml.size(), (int)sz); BOOST_CHECK_EQUAL (ml0, ml); BOOST_CHECK (!(ml0 < ml)); BOOST_CHECK (!(ml < ml0)); } else { BOOST_CHECK_EQUAL (ml.size(), (int)sz+1); BOOST_CHECK_NE (ml0, ml); BOOST_CHECK ((ml0 < ml) || (ml < ml0)); // one must be true
BOOST_CHECK (!((ml0 < ml) && (ml < ml0))); // ...but not both } ostringstream out; out << ml; BOOST_CHECK_NE (out.str().find(nameToAdd), string::npos); } } }
Similar to version developed earlier, but now we go systematically through all the accessorsNeeded to add checks on relational operators and output operator
2.1.4 And so on…
We continue in this manner until done.
fullTest.cpp +
#define BOOST_TEST_MODULE MailingList test #include "mailinglist.h" #include <string>#include <sstream>#include <vector> #include <boost/test/included/unit_test.hpp>//#define BOOST_TEST_DYN_LINK 1//#include <boost/test/unit_test.hpp> using namespace std; // The fixture for testing class MailingList.class MailingListTests { public: Contact jones; vector<Contact> contacts; MailingListTests() { jones = Contact("Jones", "21 Penn. Ave."); contacts.clear(); contacts.push_back (Contact ("Muffin Man", "Drury Ln.")); contacts.push_back (Contact ("Holmes", "221B Baker St.")); contacts.push_back(jones); contacts.push_back (Contact ("Wolfe", "454 W. 35th St.")); }
~MailingListTests() { } MailingList generate(int n) const { MailingList m; for (int i = 0; i < n; ++i) m.addContact(contacts[i]); return m; } void shouldBeEqual (const MailingList& ml0, const MailingList& ml1) const { BOOST_CHECK_EQUAL (ml1.size(), ml0.size()); for (int i = 0; i < ml0.size(); ++i) { BOOST_CHECK_EQUAL(ml1.contains(contacts[i].getName()), ml0.contains(contacts[i].getName())); if (ml1.contains(contacts[i].getName())) BOOST_CHECK_EQUAL(ml1.getContact(contacts[i].getName()), ml0.getContact(contacts[i].getName())); } BOOST_CHECK_EQUAL (ml0, ml1); BOOST_CHECK (!(ml0 < ml1)); BOOST_CHECK (!(ml1 < ml0)); ostringstream out0; out0 << ml0; ostringstream out1; out1 << ml1; BOOST_CHECK_EQUAL (out0.str(), out1.str()); } }; BOOST_AUTO_TEST_CASE ( constructor ) { MailingList ml; BOOST_CHECK_EQUAL (0, ml.size()); BOOST_CHECK (!ml.contains("Jones")); BOOST_CHECK_EQUAL (ml, MailingList()); BOOST_CHECK (!(ml < MailingList())); } BOOST_FIXTURE_TEST_CASE ( copyConstructor, MailingListTests) { for (unsigned sz = 0; sz < contacts.size(); ++sz) { MailingList ml0 = generate(sz);
MailingList ml1 = ml0; // copy constructor shouldBeEqual(ml0, ml1); } { // Minimal check for deep copy - changing one should not affect the other MailingList ml0 = generate(2); MailingList ml1 = ml0; // copy constructor ml1.addContact(jones); BOOST_CHECK_EQUAL (2, ml0.size()); BOOST_CHECK_NE (ml0, ml1); } } BOOST_FIXTURE_TEST_CASE ( assignment, MailingListTests) { for (unsigned sz = 0; sz < contacts.size(); ++sz) { MailingList ml0 = generate(sz); MailingList ml1; MailingList ml2 = (ml1 = ml0); // assignment shouldBeEqual(ml0, ml1); shouldBeEqual(ml0, ml2); // assignment returns a value } { // Minimal check for deep copy - changing one should not affect the other MailingList ml0 = generate(2); MailingList ml1; ml1 = ml0; // copy constructor ml1.addContact(jones); BOOST_CHECK_EQUAL (2, ml0.size()); BOOST_CHECK_NE (ml0, ml1); } } BOOST_FIXTURE_TEST_CASE (addContact, MailingListTests) { for (unsigned sel = 0; sel < contacts.size(); ++sel) { Contact& toAdd = contacts[sel]; const Name& nameToAdd = toAdd.getName(); for (unsigned sz = 0; sz < contacts.size(); ++sz) { MailingList ml0 = generate(sz); MailingList ml (ml0); bool alreadyContained = ml.contains(nameToAdd); ml.addContact (toAdd); BOOST_CHECK (ml.contains(nameToAdd)); BOOST_CHECK_EQUAL (toAdd, ml.getContact(nameToAdd)); if (alreadyContained) { BOOST_CHECK_EQUAL (ml.size(), (int)sz);
BOOST_CHECK_EQUAL (ml0, ml); BOOST_CHECK (!(ml0 < ml)); BOOST_CHECK (!(ml < ml0)); } else { BOOST_CHECK_EQUAL (ml.size(), (int)sz+1); BOOST_CHECK_NE (ml0, ml); BOOST_CHECK ((ml0 < ml) || (ml < ml0)); // one must be true BOOST_CHECK (!((ml0 < ml) && (ml < ml0))); // ...but not both } ostringstream out; out << ml; BOOST_CHECK_NE (out.str().find(nameToAdd), string::npos); } } } BOOST_FIXTURE_TEST_CASE (removeContact, MailingListTests) { for (unsigned sel = 0; sel < contacts.size(); ++sel) { Contact& toAdd = contacts[sel]; const Name& nameToAdd = toAdd.getName(); for (unsigned sz = 0; sz < contacts.size(); ++sz) { MailingList ml0 = generate(sz); MailingList ml (ml0); bool alreadyContained = ml.contains(nameToAdd); ml.removeContact (toAdd); BOOST_CHECK (!ml.contains(nameToAdd)); if (!alreadyContained) { BOOST_CHECK_EQUAL (ml.size(), (int)sz); BOOST_CHECK_EQUAL (ml0, ml); BOOST_CHECK (!(ml0 < ml)); BOOST_CHECK (!(ml < ml0)); } else { BOOST_CHECK_EQUAL (ml.size(), (int)sz-1); BOOST_CHECK_NE (ml0, ml); BOOST_CHECK ((ml0 < ml) || (ml < ml0)); // one must be true BOOST_CHECK (!((ml0 < ml) && (ml < ml0))); // ...but not both } ostringstream out; out << ml; string outs = out.str(); BOOST_CHECK_EQUAL (outs.find(nameToAdd), string::npos); } }
} BOOST_FIXTURE_TEST_CASE (removeContactByName, MailingListTests) { for (unsigned sel = 0; sel < contacts.size(); ++sel) { Contact& toAdd = contacts[sel]; const Name& nameToAdd = toAdd.getName(); for (unsigned sz = 0; sz < contacts.size(); ++sz) { MailingList ml0 = generate(sz); MailingList ml (ml0); bool alreadyContained = ml.contains(nameToAdd); ml.removeContact (nameToAdd); BOOST_CHECK (!ml.contains(nameToAdd)); if (!alreadyContained) { BOOST_CHECK_EQUAL (ml.size(), (int)sz); BOOST_CHECK_EQUAL (ml0, ml); BOOST_CHECK (!(ml0 < ml)); BOOST_CHECK (!(ml < ml0)); } else { BOOST_CHECK_EQUAL (ml.size(), (int)sz-1); BOOST_CHECK_NE (ml0, ml); BOOST_CHECK ((ml0 < ml) || (ml < ml0)); // one must be true BOOST_CHECK (!((ml0 < ml) && (ml < ml0))); // ...but not both } ostringstream out; out << ml; BOOST_CHECK_EQUAL (out.str().find(nameToAdd), string::npos); } } } BOOST_FIXTURE_TEST_CASE ( merging, MailingListTests) { MailingList ml0; ml0.addContact(contacts[0]); ml0.addContact(contacts[1]); ml0.addContact(contacts[2]); MailingList ml1; ml1.addContact(contacts[2]); ml1.addContact(contacts[3]); ml1.merge(ml0); shouldBeEqual(ml1, generate(4)); }
Yes, BTW, I did discover quite a few bugs in my MailingList code while putting this together.
2.2 Testing for Pointer/Memory FaultsIn our example, we did not write explicit tests for the destructor. Partly that’s because the destructor is already being invoked a lot - every time we exit afunction or a {…} statement block that declares a local variable. So we have some good reason to hope that the destructor has been well exercised already.
Also, most of the effects of a destructor are hard to see directly. How do you check to see if memory has been successfully deleted?
Most destructors simply delete pointers, and pointer issues are particularly difficult to test and debug.
One way that pointer/memory use can be tested is to employ specialized tools that replace the system functions for allocating and deallocating memory(alloc and free) with special versions that
keep track of all blocks of memory that have been allocated,
whether those blocks have been freed,
whether any block has been freed more than once, etc.
Tools for Testing Pointer/Memory Faults
Purify is a well known commercial package for this purpose, but is not cheap.
I have had good results with a free tool called LeakTracer.
2.3 Case Study 1: Unit Test of NumericLiteralOne of the stories in our spreadsheet example involves numeric literals, the component of an expression that represents a constant numeric value like “3.14159”or “42”.
As an API user I would like to add a numeric literal to a cell in a spreadsheet.
Numeric literals are a kind of Expression, a central idea in the spreadsheet. This story offers an opportunity for a look at a realistic set of tests.
2.3.1 Expressions
Expression.java +
package edu.odu.cs.espreadsheet.expressions; import java.io.StringReader; import edu.odu.cs.espreadsheet.ExpressionParseError; import edu.odu.cs.espreadsheet.Spreadsheet; import edu.odu.cs.espreadsheet.values.Value; /** * Expressions can be thought of as trees. Each non-leaf node of the tree * contains an operator, and the children of that node are the subexpressions * (operands) that the operator operates upon. Constants, cell references, * and the like form the leaves of the tree. * * For example, the expression (a2 + 2) * c26 is equivalent to the tree: * * * * / \ * + c26 * / \ * a2 2 * * @author zeil * */public abstract class Expression implements Cloneable { /** * How many operands does this expression node have? * * @return # of operands required by this operator */ public abstract int arity(); /** * Get the k_th operand * @param k operand number * @return k_th operand if 0 < k < arity() * @throws IndexOutOfBoundsException if k is outside of those bounds */ public abstract Expression operand(int k) throws IndexOutOfBoundsException;
/** * Evaluate this expression, using the provided spreadsheet to resolve * any cell referneces. * * @param usingSheet spreadsheet form which to obtain values of * cells referenced by this expression * * @return value of the expression or null if the cell is empty */ public abstract Value evaluate(Spreadsheet usingSheet); /** * Copy this expression (deep copy), altering any cell references * by the indicated offsets except where the row or column is "fixed" * by a preceding $. E.g., if e is 2*D4+C$2/$A$1, then * e.copy(1,2) is 2*E6+D$2/$A$1, e.copy(-1,4) is 2*C8+B$2/$A$1 * * @param colOffset number of columns to offset this copy * @param rowOffset number of rows to offset this copy * @return a copy of this expression, suitable for placing into * a cell (ColOffSet,rowOffset) away from its current position. * */ public abstract Expression clone (int colOffset, int rowOffset); /** * Copy this expression. */ @Override public Expression clone () { return clone(0,0); } /** * Attempt to convert the given string into an expression. * @param in * @return */ public static Expression parse (String in) throws ExpressionParseError { try { parser p = new parser(new ExpressionScanner(new StringReader(in))); Expression expr = (Expression)p.parse().value; return expr;
} catch (Exception ex) { throw new ExpressionParseError("Cannnot parse " + in); } } @Override public String toString () { ⋮ } @Override public abstract boolean equals (Object obj); @Override public abstract int hashCode (); // The following control how the expression gets printed by // the default implementation of toString /** * If true, print in inline form. * If false, print as functionName(comma-separated-list). * * @return indication of whether to print in inline form. * */ public abstract boolean isInline(); /** * Parentheses are placed around an expression whenever its precedence * is lower than the precedence of an operator (expression) applied to it. * E.g., * has higher precedence than +, so we print 3*(a1+1) but not * (3*a1)+1 * * @return precedence of this operator */ public abstract int precedence(); /** * Returns the name of the operator for printing purposes. * For constants/literals, this is the string version of the constant value. * * @return the name of the operator for printing purposes. */
public abstract String getOperator(); }
A lot of this design was lifted from an earlier project of mine.
Expression is an abstract class – we never expect to see any actual objects of this type.
But we will have lots of interesting subclasses (one per operator) that will have “real” instances.
2.3.2 Numeric Literals
The NumericLiteral class is a subclass of Expression and needs to supply function bodies for all of the unimplemented functions declared in Expression.
NumericLiteral.java +
package edu.odu.cs.espreadsheet.expressions; import edu.odu.cs.espreadsheet.Spreadsheet; import edu.odu.cs.espreadsheet.values.NumericValue; import edu.odu.cs.espreadsheet.values.Value; /** * This class represents numeric constants appearing within an expression. * * @author zeil * */publicclass NumericLiteral extends Expression { ⋮ /** * Create a default numeric literal. Equivalent to NumericLiteral("0"); */ public NumericLiteral () { ⋮ } public NumericLiteral (String lit) { ⋮ }
/** * How many operands does this expression node have? * * @return # of operands required by this operator */ public int arity() {return 0;} /** * Get the k_th operand * @param k operand number * @return k_th operand if 0 < k < arity() * @throws IndexOutOfBoundsException if k is outside of those bounds */ public Expression operand(int k) { ⋮ } /** * Evaluate this expression, using the provided spreadsheet to resolve * any cell references. * * @param usingSheet spreadsheet form which to obtain values of * cells referenced by this expression * * @return value of the expression or null if the cell is empty */ public Value evaluate(Spreadsheet s) { ⋮ } /** * Copy this expression (deep copy), altering any cell references * by the indicated offsets except where the row or column is "fixed" * by a preceding $. E.g., if e is 2*D4+C$2/$A$1, then * e.copy(1,2) is 2*E6+D$2/$A$1, e.copy(-1,4) is 2*C8+B$2/$A$1 * * @param colOffset number of columns to offset this copy * @param rowOffset number of rows to offset this copy * @return a copy of this expression, suitable for placing into * a cell (ColOffSet,rowOffset) away from its current position. * */ @Override public NumericLiteral clone (int colOffset, int rowOffset) {
⋮ } // The following control how the expression gets printed by // the default implementation of put(ostream&) /** * Attempt to convert the given string into an expression. * @param in * @return */ public boolean isInline() {return true;} /** * If true, print in inline form. * If false, print as functionName(comma-separated-list). * * @return indication of whether to print in inline form. * */ public int precedence() {return 1000;} /** * Returns the name of the operator for printing purposes. * For constants/literals, this is the string version of the constant value. * * @return the name of the operator for printing purposes. */ public String getOperator() {return literal;} @Override public boolean equals(Object obj) { ⋮ } @Override public int hashCode() { ⋮ } }
I’m not showing the actual implementaiton (function bodies) here because
It’s not relevant to the design of the tests.In fact, as we’ll see very shortly, I usually write the tests before I implement the ADT. So, in practice, those function bodies would be empty when I dothis.
2.3.3 Writing the Unit tests
NumericLiteral desperately needs a good set of unit test cases because we need to be sure that we have covered all of the various input format possibilities:
TestNumericLiteral.java +
/** * */package edu.odu.cs.espreadsheet.expressions; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import org.junit.Test; import edu.odu.cs.espreadsheet.ExpressionParseError; import edu.odu.cs.espreadsheet.Spreadsheet; /** * @author zeil * */public class TestNumericLiteral { public final NumericLiteral nl0 = new NumericLiteral("2.345"); public final Spreadsheet ss = new Spreadsheet(); /** * Test method for {@link edu.odu.cs.espreadsheet.expressions.NumericLiteral#NumericLiteral()}. */ @Test public final void testNumericLiteral() { NumericLiteral nl = new NumericLiteral(); ➀ assertEquals(0, nl.arity()); ➁ try { nl.operand(0); fail("Expected IndexOutOfBoundsException"); } catch (IndexOutOfBoundsException ex) { // OK }
assertEquals (0.0, nl.evaluate(ss).toDouble(), 1.0E-10); assertEquals ("0", nl.toString()); assertEquals (true, nl.isInline()); assertTrue (nl.precedence() > 100); assertEquals ("0", nl.getOperator()); assertFalse (nl0.equals(nl)); } /** * Test method for {@link edu.odu.cs.espreadsheet.expressions.NumericLiteral#NumericLiteral(java.lang.String)}. */ @Test public final void testNumericLiteralString() { NumericLiteral nl = new NumericLiteral("2.3450"); assertEquals(0, nl.arity()); try { nl.operand(0); fail("Expected IndexOutOfBoundsException"); } catch (IndexOutOfBoundsException ex) { // OK } assertEquals (2.345, nl.evaluate(ss).toDouble(), 1.0E-10); assertEquals ("2.3450", nl.toString()); assertEquals (true, nl.isInline()); assertTrue (nl.precedence() > 100); assertEquals ("2.3450", nl.getOperator()); assertFalse (nl0.equals(nl)); } /** * Test method for {@link edu.odu.cs.espreadsheet.expressions.NumericLiteral#clone(int, int)}. */ @Test public final void testCloneIntInt() { NumericLiteral nl = nl0.clone(1,1); assertEquals(0, nl.arity()); try { nl.operand(0); fail("Expected IndexOutOfBoundsException"); } catch (IndexOutOfBoundsException ex) { // OK } assertEquals (nl0.evaluate(ss), nl.evaluate(ss)); assertEquals (nl0.toString(), nl.toString()); assertEquals (nl0.isInline(), nl.isInline()); assertEquals (nl0.precedence(), nl.precedence()); assertEquals (nl0.getOperator(), nl.getOperator()); assertEquals (nl0, nl); }
/** * Test method for {@link edu.odu.cs.espreadsheet.expressions.Expression#clone()}. */ @Test public final void testClone() { NumericLiteral nl = (NumericLiteral)nl0.clone(); assertEquals(0, nl.arity()); try { nl.operand(0); fail("Expected IndexOutOfBoundsException"); } catch (IndexOutOfBoundsException ex) { // OK } assertEquals (nl0.evaluate(ss), nl.evaluate(ss)); assertEquals (nl0.toString(), nl.toString()); assertEquals (nl0.isInline(), nl.isInline()); assertEquals (nl0.precedence(), nl.precedence()); assertEquals (nl0.getOperator(), nl.getOperator()); assertEquals (nl0, nl); } /** * Test method for {@link edu.odu.cs.espreadsheet.expressions.Expression#parse(java.lang.String)}. * @throws ExpressionParseError */ @Test public final void testNumericParse() throws ExpressionParseError { String input = "12.34"; ➂ Expression e = Expression.parse(input); assertTrue (e instanceof NumericLiteral); assertEquals (new NumericLiteral(input), e); input = "1.2E25"; e = Expression.parse(input); assertTrue (e instanceof NumericLiteral); NumericLiteral nl = (NumericLiteral)e; assertEquals (1.2E25, new Double(nl.getOperator()).doubleValue(), 1.0E20); input = "1.2E-5"; e = Expression.parse(input); assertTrue (e instanceof NumericLiteral); nl = (NumericLiteral)e; assertEquals (1.2E-5, new Double(nl.getOperator()).doubleValue(), 1.0E-10); } }
➀ These tests are good examples of our mutator/accessor approach to devising unit tests.
The mutators for this class are the two constructors and the two clone functions.
The accessors are arity(), operand(i), evaluate(ss), isInLine(), precedence(), getOperator(), ’equals(…), hashCode(), andtoString()`.
In this test we invoke one of the mutators (a constructor)
➁ Then we run through each of the accessors in turn.
This pattern is repeated for each of the mutators.
➂ A more subtle “mutator”: Expression has a static function for parsing strings. It is, more or less, a mutator for all subclasses of expression. So we testit here as well.
Could have made this part of a unit test for Expression, but that would have led to a single test that had to know about every subclass ofExpression, which would have grown quickly unwieldy.
2.4 Is this Overkill?Many, perhaps most, ADTs provide a small number of operations that manipulate the data in a non-trivial fasion, and a lage number of get/set attribute functionsthat, conceptually at least, simply store and retrieve a private data member.
For example, suppose we wanted to test the following ADT:
Address.java +
package mailinglist; /** * A contact is a name and address. * <p> * For the purpose of this example, I have simplified matters * a bit by making both of these components simple strings. * In practice, we would expect Address, at least, to be a * more structured type. * * @author zeil * */public class Address implements Cloneable { private String name; private String streetAddress; private String city; private String state; private String zipCode;
/** * Create an address with all empty fields. * */ public Address () { name = ""; streetAddress = ""; city = ""; state = ""; zipCode = ""; } /** * Create an address. */ public Address (String nm, String streetAddr, String city, String state, String zip) { name = nm; streetAddress = streetAddr; this.city = city; this.state = state; zipCode = zip; } /** * @return the theName */ public String getName() { return name; } /** * @param theName the theName to set */ public void setName(String theName) { this.name = theName; } /** * @return the streetAddress */ public String getStreetAddress() { return streetAddress; } /**
* @param streetAddress the streetAddress to set */ public void setStreetAddress(String streetAddress) { this.streetAddress = streetAddress; } /** * @return the city */ public String getCity() { return city; } /** * @param city the city to set */ public void setCity(String city) { this.city = city; } /** * @return the state */ public String getState() { return state; } /** * @param state the state to set */ public void setState(String state) { this.state = state; } /** * @return the zipCode */ public String getZipCode() { return zipCode; } /** * @param zipCode the zipCode to set */ public void setZipCode(String zipCode) { this.zipCode = zipCode; } /** * True if the names and addresses are equal
*/ public boolean equals (Object right) { Address r = (Address)right; return name.equals(r.name) && streetAddress.equals(r.streetAddress) && city.equals(r.city) && state.equals(r.state) && zipCode.equals(r.zipCode); } public int hashCode () { return name.hashCode() + 3 * streetAddress.hashCode() + 5 * city.hashCode() + 7 * state.hashCode() + 11 * zipCode.hashCode(); } public String toString() { return name + ": " + streetAddress + ": " + city + ", " + state + " " + zipCode; } public Object clone() { return new Address(name, streetAddress, city, state, zipCode); } }
You can see that, in this case, the bulk of the operations are simple gets and sets. There are a few operations that work, in some sense, on the whole ADT,mainly for the purpose of output and comparisons.
If we were to test this, we would identify the mutators (the two constructors, the five set… functions, and the clone function) and accessors (the five get…functions and the toString, equals, and hashCode functions). Then we would create a test function for each mutator. For example, for the setCity function,we might write:
public class TestAddress { final private String name0 = "John Doe"; final private String street0 = "221B Baker St."; final private String city0 = "Podunk"; final private String state0 = "IL"; final private String zip0 = "01010";
⋮ @Testpublic final void testSetCity() { String city1 = "Norfolk"; ➀ Address addr0 = new Address(name0, street0, city0, state0, zip0); Address addr1 = new Address(name0, street0, city0, state0, zip0); addr1.setCity(city1); ➁ assertEquals (name0, addr1.getName()); ➂ assertEquals (street0, addr1.getStreetAddress()); assertEquals (city1, addr1.getCity()); assertEquals (state0, addr1.getState()); assertEquals (zip0, addr1.getZipCode()); assertFalse (addr1.equals(addr0)); assertTrue (addr1.toString().contains(city1)); }
This follows a pattern that should be increasingly familiar:
➀ Set up the data values that we will need.➁ Invoke the mutator being tested.➂ Evaluate and test each of the accessor functions on the mutated ADT value.
You can see that four out of the first five assertions actually assert that this value was unaffected by the mutator. Some students look at this and wonder why webother. Isn’t that a lot of wasted code? It doesn’t seem to really be relevant to the mutator function (setCity) that we are testing in this function. And when youconsider that there will be similar “waste” in each of the other functions for testing the other mutator functions, this can seem excessive.
But there are a number of reasons why these “does not change” assertions are worth performing:
Our intuition is that a working implementation of setCity won’t have any effect on the street address, zip code, etc.. But, if we knew that the code wasworking, we wouldn’t be testing it!
True story: I often write an ADT like this by typing out the first pair of get and set functions (e.g., getName and setName), then copying and pasting thosemultiple times, and then using my editor’s search-and-replace function to alter the attribute names in the subsequent pairs.
On more than one occasion, I have forgotten to change the name of the data member being retrieved by a get/set pair, winding up with a bug somethinglike like this:
public String getCity() { return name; }
public void setCity(String city) { this.name = name; }
This bug was detected by the "getName() does not change when setCity is called) assertion in the testSetCity test function.
Our intuition that the behavior of “getCity” has no bearing on the testing of “setCity” is based on a misunderstanding of what the test functions aredoing. Although we allocate one test function per mutator, each such function is not merely testing its associated mutator. It is testing both that mutatorand all the accessors.
The collected tests of all of accessor functions are distributed over the entire set of test functions. If we eliminated all checks of an accessor getX but theone in the testSetX function, that accessor would wind up being woefully under-tested.
On the other hand, what would be excessive would be to devote separate test functions to both mutators and accessors (depeite the fact that this is whatEclipse volunteers to do if you use its “New … Junit Test Case” helper). The only way to test an accessor is to use some mutator to set the ADT value,and we’re already doing that the the test functions for mutators.
Our intuition that the behavior of a getX accessor is independent of a setY mutator for a different attribute is colored by our assumption that these get/setfunctions are doing nothing but retrieving simple data members.
But that’s not always the case, and, even if that’s how the ADT is implemented now, it might not be implemented that way in the future.
If the program is distributed, so that address values are actually being transferred over the network, we will likely receive and sometimes storeaddresses in a serialized form, essentially a single string.
In that case, we would need to tease out the individual field values like cities, states, etc., and the possibility of “cross-talk” among the fields due tobuggy code becomes significant.
Similar serialized forms are common if we have a program that mixes code written in different programming langauges.
A similar problem can arise if the actual Address values need to be stored in and retrieved from a database. Perhaps the databse is set up tocombine, for example, the city and state in a single field.
These are Black-box tests, and we want them to remain valid even if the implementation changes.
In general, it’s always a bit dangerous to argue that tests are unnecessary based on our intuition about how the code will behave when it runs correctly. Ourchoice of tests really have to be more informed by the possibilities of how the code might misbehave when it has bugs.
Furthermore, these “extra” assertions are not really all that oppressive. Most of them, written once, can be copied and pasted into the next test function. Forexample, the testSetState function could be:
@Testpublic final void testSetState() {
String state1 = "VA"; Address addr0 = new Address(name0, street0, city0, state0, zip0); Address addr1 = new Address(name0, street0, city0, state0, zip0); addr1.setState(state1); assertEquals (name0, addr1.getName()); assertEquals (street0, addr1.getStreetAddress()); assertEquals (city0, addr1.getCity()); assertEquals (state1, addr1.getState()); assertEquals (zip0, addr1.getZipCode()); assertFalse (addr1.equals(addr0)); assertTrue (addr1.toString().contains(state1)); }
The highlighted portions are the only changes from the earlier testSetCity function.
A final comment on the “Is this excessive?” question:
These tests are not, by any measure, a perfect or full set of tests that will detect all bugs. Having done this first pass, we would still want to look at thetests from the perspective of good black-box testing, possibly adjusting the vlues used for inputs or maybe doing more than one invocation of somemutators.
That said, this is still more testing and better testing than most programmers perform if they don’t let themselves by guided by some approach, like thismutator/accessor approach, for being thorough and systematic in their test design.
Finally, many students’ perception of what makes testing difficult and time-consuming is rooted in the practice of running tests “manually” and inspectingall outputs by eye. No wonder, then, that even running a small test set seems like drudgery.
But we’ve just automated the checking part of testing.
And with Eclipse and other IDE’s we can run those tests with single mouse action. And, shortly, we’ll integrate the tests into our automated build processso that launching tests does not even require that small effort.
3 Be Independent:A true unit test should not depend on other modules of the program
The *unit frameworks we have looked at provide drivers.But scaffolding can also involve stubs.
Which are often harder to write
We could
Wait until integration test, or
Prepare stubs for unit testing and accept that they can only be rough approximations, or
Create stubs for unit testing and replace them later when the lower-level modules are ready.
Is there a virtue to independence?
Integration tests are not independent.
Can lead to slower testing.Can complicate debuggingBut are certainly more realistic than unit tests with simple stubs
Increasingly, *unit frameworks are providing support for stubbing.
We’ll cover this in the next lesson on mock objects.
Test-Driven DevelopmentSteven J Zeil
Last modified: Jan 7, 2020
Contents:1 Test-First Development
1.1 Debugging: How Can You Fix What You Can’t See?1.2 Test-Writing as a Design Activity1.3 The Cycle of Unit Test Failures
2 TFD during Incremental Development2.1 Case Study: TFD of a Spreadsheet Story
3 Test-Driven Development3.1 The “Three Rules of TDD”3.2 Example of TDD
Abstract
Test-Driven Development treats unit testing as an integral part of the design and implementation process. Often summarized as “test first, code after”, TDD isactually a recognition that in writing tests, we are
exploring the API design, andanticipating design issues, as well asproviding for effortless validation of our upcoming implementation.
1 Test-First DevelopmentWith our new knowledge of unit-testing frameworks, ideally, we have made it easier to write self-checking unit tests than to write the actual code to be tested.
We encourage an approach of writing the tests before writing the code.
1.1 Debugging: How Can You Fix What You Can’t See?The test-first philosophy is easiest to understand in a maintenance/debugging context.
Before attempting to debug, write a test that reproduces the failure.
How else will you know when you’ve fixed it?
From a practical point of view, debugging generally involves running the buggy input, over and over, while you add debugging output or step throughwith a debugger.
You want that to be as easy as possible.Doing this at the unit level, instead of in the context of the entire system, is often a dramatically better use of your time and effort.
1.2 Test-Writing as a Design ActivityEvery few years, software designers rediscover the principle of writing tests before implementing code.
Agile and TDD (Test-Driven Development) are just the latest in this long chain.
Writing tests while “fresh” yields better tests than when they are done as an afterthought.
Thinking about boundary and special values tests helps clarify the software design
Reduces the number of eventual faults actually introduced
Encourages designing for testability
Making sure that the interface provides the “hooks” you need to do testing in the first place.
1.2.1 Tests are Examples
“If it’s hard to write a test, it’s a signal that you have a design problem, not a testing problem. Loosely coupled, highly cohesive code is easy totest.” – Kent Beck
In writing a test, you are actually writing sample code of how the unit’s interface can be used.
Valuable as documentation
It’s very common when writing tests to discover that the interface is incorrect or inadequate.
Interface may not have parameters to supply needed data, or may have the wrong parameters.Functions may be missing that would manipulate the ADT state.Functions may be missing that owuld allow us to examine the ADT state to see the effects of a manipulation
The very act of trying to write black-box tests becomes itself an exercise in validation of the interface design!
1.3 The Cycle of Unit Test Failures
Here you can see a plot of test cases on the vertical axis versus time (actually,commits to the version control system) on the horizontal axis during a project onwhich I practiced TFD.
Tests passed are shown in blue and failed tests are shown in red.
Notice the repeated pattern:
There are multiple sudden rises in the number of tests failed.
These are matched by a simultaneous and equal increase in the totalnumber of tests. So what we are seeing is the additon of new tests that we, initially, fail.That’s because in TFD we write tests first for code that we have not yet developed. Naturally, we fail those tests.
Each such rise is followed by an eventual decrease in the number of failed tests while the total number of tests stays constant (so the blue area grows).
That’s because, after developing the new tests, we started working on implementing the new funcitonality, eventually passing most of those newtests.
There remains, through most of the project, a base set of red tests that we never quite pass.
That’s because I am often adding both unit and integration tests.Unit tests will test our modified ADT(s) in isolation from the rest of the system. They can be passed as soon as we finish the ADT modifications.Integration tests test the modified ADT(s) together with other portions of the system. We can’t pass these until later in the project when those otherportions of hte system have actually been implemented.
You can see, near the end of the project time, that the “stubborn” base of failed tests is finally starting to decrease.
2 TFD during Incremental DevelopmentMy stereotypical division of a story into tasks is typically
1. Create/modify the API to describe a new desired behavior.2. Write the unit tests.3. Implement the new behavior.4. Integrate and commit changes.
Compare this to the steps of TDD, above, and you can see that they are compatible.
2.1 Case Study: TFD of a Spreadsheet StoryHere are some short videos illustrating my application of task 1 and task 2 of a story for the Embeddable Spreadsheet project.
3 Test-Driven DevelopmentTest-Driven Development (TDD) is a stronger form of Test-First Development.
In TDD, we repeatedly:
1. Write an automated test case for a new desired behavior.
This case must, initially, fail.Not compiling counts as “failing”.
2. Write just enough new code to pass the test.
3. Refactor the code to make it acceptable quality.
This ties in very nicely with some of our previous discussion of incremental development. In particular, compare to the way we break stories into tasks.
3.1 The “Three Rules of TDD”From Robert Martin
Over the years I have come to describe Test Driven Development in terms of three simple rules. They are:
1. You are not allowed to write any production code unless it is to make a failing unit test pass.2. You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures.3. You are not allowed to write any more production code than is sufficient to pass the one failing unit test.
You must begin by writing a unit test for the functionality that you intend to write. But by rule 2, you can’t write very much of that unit test. Assoon as the unit test code fails to compile, or fails an assertion, you must stop and write production code. But by rule 3 you can only write theproduction code that makes the test compile or pass, and no more.
If you think about this you will realize that you simply cannot write very much code at all without compiling and executing something. Indeed, thisis really the point. In everything we do, whether writing tests, writing production code, or refactoring, we keep the system executing at all times.
The time between running tests is on the order of seconds, or minutes. Even 10 minutes is too long.
Less time spent debugging: the code worked just a minute ago.Every hour you are adding multiple tests: tests accumulate quicklyLess resistance to cleaning up bad/ugly code: you won’t break things (badly). You have the tests!
3.2 Example of TDDThe Bowling Game Kata (from Martin.)
Stubs and MockingSteven J Zeil
Last modified: Dec 21, 2019
Contents:1 Stubs2 Mock Objects
2.1 Google Mock (C++)2.2 JMockit (Java)
3 Case Studies3.1 Example 1 (Spreadsheet CLI)3.2 Example 2: Adding Numeric Literals to the Spreadsheet
4 Are Mock Frameworks Useful?
Abstract
The *Unit frameworks that we have looked at provide powerful support for writing test drivers. But we know that scaffolding comes in two forms: drivers andstubs.
In this lesson, we look at a modern approach to stubs, mock objects. Suppose that we are testing class A and that A calls functions from a class B. A mock objectfor B is interface-compatible with B, but combines a simple (possibly automatically-generated) implementation together with test instrumentation to help usdetermine if A is calling upon B properly during testing.
1 StubsThe Lowly Stub
Most of what *Unit does is to provide a standard, convenient framework for writing test drivers.
But unit testing often requires stubs as well
supplying values/operations to the module under testprocessing outputs from the module under test
We can avoid stubs by doing bottom-up integration
reduces independence of test suitesrestricts development options
Where ADTs and Stubs Meet
In some ways, ADTs and OOP make it easier to do stubs.
A mock object is an object whose interface emulates a “real” class for testing purposes.
Mock objects may …
be simpler than the real thing
stand-in for classes not yet implemented
decouple test suites
offer mechanisms for data collection, thus improving testability
Example: Mailing List
In our MailingList tests, we relied on a Contact class.
Here is the real interface for Contact:
contactnmad.h +
#ifndef CONTACT_H#define CONTACT_H #include <iostream>#include <string> #include "name.h"#include "address.h" class Contact { Name theName; Address theAddress; public: Contact (Name nm, Address addr) : theName(nm), theAddress(addr) {}
Name getName() const {return theName;} void setName (Name nm) {theName= nm;} Address getAddress() const {return theAddress;} void setAddress (Address addr) {theAddress = addr;} bool operator== (const Contact& right) const { return theName == right.theName && theAddress == right.theAddress; } bool operator< (const Contact& right) const { return (theName < right.theName) || (theName == right.theName && theAddress < right.theAddress); } }; inlinestd::ostream& operator<< (std::ostream& out, const Contact& c) { out << c.getName() << " @ " << c.getAddress(); return out; } #endif #ifndef NAME_H#define NAME_H #include <iostream>#include <string> struct Name { std::string last; std::string first; std::string middle; Name (std::string lastName, std::string firstName, std::string middleName) : last(lastName), first(firstName), middle(middleName) {}
bool operator== (const Name& right) const; bool operator< (const Name& right) const; }; std::ostream& operator<< (std::ostream& out, const Name& addr); #endif #ifndef ADDRESS_H#define ADDRESS_H #include <iostream>#include <string> struct Address { std::string street1; std::string street2; std::string city; std::string state; std::string zipcode; Address (std::string str1, std::string str2, std::string cty, std::string stte, std::string zip) : street1(str1), street2(str2), city(cty), state(stte), zipcode(zip) {} bool operator== (const Address& right) const; bool operator< (const Address& right) const; }; std::ostream& operator<< (std::ostream& out, const Address& addr); #endif
What Do We Need for the MailingList Test?
But in our tests, the only things we needed to do with contacts was
create them with known names
copy/assign them
getName()
compare them, ordering by name
A Mock Contact
So we made do with a simpler interface that
made it easier to create and check contacts during testing
but would still compile with the existing MailingList code
contact.h +
#ifndef CONTACT_H#define CONTACT_H #include <iostream>#include <string> typedef std::string Name; typedef std::string Address; class Contact { Name theName; Address theAddress; public: Contact() {} Contact (Name nm, Address addr) : theName(nm), theAddress(addr) {} Name getName() const {return theName;} void setName (Name nm) {theName= nm;} Address getAddress() const {return theAddress;} void setAddress (Address addr) {theAddress = addr;} bool operator== (const Contact& right) const
{ return theName == right.theName && theAddress == right.theAddress; } bool operator< (const Contact& right) const { return (theName < right.theName) || (theName == right.theName && theAddress < right.theAddress); } }; inlinestd::ostream& operator<< (std::ostream& out, const Contact& c) { out << c.getName() << " @ " << c.getAddress(); return out; } #endif
Replacing the Name and Address classes by simple strings.
2 Mock ObjectsObject-oriented languages make it easier to create some mock objects
Declare the mock as a subclass of the real interface
Override the functions as desired
Example: for an application that drew graphics using the Java java.awt.Graphics API, I created the following as a mock class:
GraphicsStub.java +
package Pictures; import java.awt.Color; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Image; import java.awt.Rectangle; import java.awt.Shape;
import java.awt.image.ImageObserver; import java.text.AttributedCharacterIterator; /** * Special version of Graphics for testing that simply writes all graphics requests to * a string. * * @author zeil * */public class GraphicsStub extends Graphics { private StringBuffer log; public GraphicsStub() { log = new StringBuffer(); } public void clearRect(int arg0, int arg1, int arg2, int arg3) { entering("clearRect", "" + arg0 + ":" + arg1 + ":" + arg2 + ":" + arg3); } public void clipRect(int arg0, int arg1, int arg2, int arg3) { entering("clipRect", "" + arg0 + ":" + arg1 + ":" + arg2 + ":" + arg3); } public void copyArea(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) { entering("copyArea", "" + arg0 + ":" + arg1 + ":" + arg2 + ":" + arg3 + ":" + arg4 + ":" + arg5); } public Graphics create() { entering("create", ""); return new GraphicsStub(); } public void dispose() { entering("dispose", ""); } public void drawArc(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) { entering("drawArc", "" + arg0 + ":" + arg1 + ":" + arg2 + ":" + arg3 + ":" + arg4 + ":" + arg5); } public boolean drawImage(Image arg0, int arg1, int arg2, ImageObserver arg3) {
entering("drawImage", "" + arg0 + ":" + arg1 + ":" + arg2 + ":" + arg3); return true; } public boolean drawImage(Image arg0, int arg1, int arg2, Color arg3, ImageObserver arg4) { entering("drawImage", "" + arg0 + ":" + arg1 + ":" + arg2 + ":" + arg3); return true; } public boolean drawImage(Image arg0, int arg1, int arg2, int arg3, int arg4, ImageObserver arg5) { entering("drawImage", "" + arg0 + ":" + arg1 + ":" + arg2 + ":" + arg3 + ":" + arg4); return true; } public boolean drawImage(Image arg0, int arg1, int arg2, int arg3, int arg4, Color arg5, ImageObserver arg6) { entering("drawImage", "" + arg0 + ":" + arg1 + ":" + arg2 + ":" + arg3 + ":" + arg4 + ":" + arg5); return true; } public boolean drawImage(Image arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8, ImageObserver arg9) { entering("drawImage", "" + arg0 + ":" + arg1 + ":" + arg2 + ":" + arg3 + ":" + arg4 + ":" + arg5 + arg6 + ":" + arg7 + ":" + arg8); return true; } public boolean drawImage(Image arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8, Color arg9, ImageObserver arg10) { entering("drawImage", "" + arg0 + ":" + arg1 + ":" + arg2 + ":" + arg3 + ":" + arg4 + ":" + arg5 + arg6 + ":" + arg7 + ":" + arg8 + ":" + arg9); return true; } public void drawLine(int arg0, int arg1, int arg2, int arg3) { entering("drawLine", "" + arg0 + ":" + arg1 + ":" + arg2 + ":" + arg3); } public void drawOval(int arg0, int arg1, int arg2, int arg3) { entering("drawOval",
"" + arg0 + ":" + arg1 + ":" + arg2 + ":" + arg3); } public void drawPolygon(int[] arg0, int[] arg1, int arg2) { entering("drawPolygon", "" + arg0 + ":" + arg1 + ":" + arg2); } public void drawPolyline(int[] arg0, int[] arg1, int arg2) { entering("drawPolyline", "" + arg0 + ":" + arg1 + ":" + arg2); } public void drawRoundRect(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) { entering("drawRoundRect", "" + arg0 + ":" + arg1 + ":" + arg2 + ":" + arg3 + ":" + arg4 + ":" + arg5); } public void drawString(String arg0, int arg1, int arg2) { entering("drawString", "" + arg0 + ":" + arg1 + ":" + arg2); } public void drawString(AttributedCharacterIterator arg0, int arg1, int arg2) { entering("drawString", "" + arg0 + ":" + arg1 + ":" + arg2); } public void fillArc(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) { entering("fillArc", "" + arg0 + ":" + arg1 + ":" + arg2 + ":" + arg3 + ":" + arg4 + ":" + arg5); } public void fillOval(int arg0, int arg1, int arg2, int arg3) { entering("fillOval", "" + arg0 + ":" + arg1 + ":" + arg2 + ":" + arg3); } public void fillPolygon(int[] arg0, int[] arg1, int arg2) { entering("fillPolygon", "" + arg0 + ":" + arg1 + ":" + arg2); } public void fillRect(int arg0, int arg1, int arg2, int arg3) { entering("fillRect", "" + arg0 + ":" + arg1 + ":" + arg2 + ":" + arg3); }
public void fillRoundRect(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) { entering("fillRoundRect", "" + arg0 + ":" + arg1 + ":" + arg2 + ":" + arg3 + ":" + arg4 + ":" + arg5); } private Shape clip = null; public Shape getClip() { return clip; } public Rectangle getClipBounds() { return null; } private Color color; public Color getColor() { return color; } public Font getFont() { return null; } public FontMetrics getFontMetrics(Font arg0) { return null; } public void setClip(Shape arg0) { entering("setClip", "" + arg0); clip = arg0; } public void setClip(int arg0, int arg1, int arg2, int arg3) { entering("setClip", "" + arg0 + ":" + arg1 + ":" + arg2 + ":" + arg3); } public void setColor(Color arg0) { entering("setColor", "" + arg0); color = arg0; } public void setFont(Font arg0) { entering("setFont", "" + arg0); }
public void setPaintMode() { entering("setPaintMode", ""); } public void setXORMode(Color arg0) { entering("setXORMode", "" + arg0); } public void translate(int arg0, int arg1) { entering("translate", "" + arg0 + ":" + arg1); } public String toString() { return log.toString(); } public void clear() { log = new StringBuffer(); } private void entering(String functionName, String args) { log.append(functionName); log.append(": "); log.append(args); log.append("\n"); } }
It basically writes each successive graphics call into a string, that can later be examined by unit test cases.
OO Mock Example
Here is a test using that mock:
linesegTest.java +
package PictureTests; import java.awt.Color; import java.awt.Point; import java.lang.reflect.Method; import java.util.Scanner;
import junit.framework.*; import junit.textui.TestRunner; import Pictures.GraphicsStub; import Pictures.LineSegment; import Pictures.Shape; /** * Test of the LineSegment class */public class LineSegmentTest extends TestCase { ⋮ public void testPlot() { Point p1 = new Point(22, 341); Point p2 = new Point(104, 106); LineSegment ls = new LineSegment(p1, p2, Color.blue, Color.red); GraphicsStub g = new GraphicsStub(); GraphicsStub gblue = new GraphicsStub(); gblue.setColor(Color.blue); GraphicsStub gline1 = new GraphicsStub(); gline1.drawLine(22, 341, 104, 106); GraphicsStub gline2 = new GraphicsStub(); gline2.drawLine(104, 106, 22, 341); ls.plot(g); assertTrue (g.toString().contains(gblue.toString())); assertTrue (g.toString().contains(gline1.toString()) || g.toString().contains(gline2.toString())); } ⋮
The (main) mock graphics object is created here.
The function being tested in called here.
Then, assertions like this one can test the info recrded in the mock object.
Mock Frameworks
Increasingly, unit test frameworks are including special support for mock object creation.
2.1 Google Mock (C++)
For example, the Google Mock Framework provides support for creating mock objects that automatically keep a log of calls made to them.
And adds special functions and test assertions to both test that the expected calls were made and controlling what results will be returned from those calls.
**Mocking Up an Example **
Suppose that we are writing an application that uses this class:
class Turtle { ⋮ virtual ~Turtle() {} virtual void PenUp() = 0; virtual void PenDown() = 0; virtual void Forward(int distance) = 0; virtual void Turn(int degrees) = 0; virtual void GoTo(int x, int y) = 0; virtual int GetX() const = 0; virtual int GetY() const = 0; };
To set up the test of the application code that uses this, we derive a mock class that has the same interface…
A Mock Class
#include "gmock/gmock.h" class MockTurtle : public Turtle { public: ... MOCK_METHOD0(PenUp, void()); MOCK_METHOD0(PenDown, void()); MOCK_METHOD1(Forward, void(int distance)); MOCK_METHOD1(Turn, void(int degrees)); MOCK_METHOD2(GoTo, void(int x, int y)); MOCK_CONST_METHOD0(GetX, int()); MOCK_CONST_METHOD0(GetY, int()); };
Note that both the macro names and the function signatures are related to the original interface.There are scripts that can sometimes generate this automatically.
Using the Mock
Then in unit tests for the application class, we
1. use instances of the mock class2. Write assertions that describe how we expect the stub to be exercised.
#include "mock-turtle.h"#include "gmock/gmock.h"#include "gtest/gtest.h"using ::testing::AtLeast; TEST(DrawACircle, CanDrawSomething) { MockTurtle turtle; EXPECT_CALL(turtle, PenDown()) .Times(AtLeast(1)); Painter painter(&turtle); EXPECT_TRUE(painter.DrawCircle(0, 0, 10)); }
So, if we call painter.drawCircle(...), we
expect it to return trueexpect it to call tutle.PenDown() at least once.
Stubs can Generate Output
EXPECT_CALL(g, foo(_)) .TIMES(3) .WillRepeatedly(Return(150)); EXPECT_CALL(g, foo(1)) .WillOnce(Return(100)); EXPECT_CALL(g, foo(2)) .WillOnce(Return(200));
This assertion says
a function g.foo(x) will be called 5 times,with x being 1 and 2, one time eachand an unspecified value on the other three calls
It also specifies what value should be returned on each call.
If the function is called 6 times, or if one or more of the 5 expected calls are not made before the test case ends, the EXPECT_CALL assertion fails, just likeother test assertions.
2.2 JMockit (Java)There are a number of popular mocking frameworks for Java: JMock, EasyMock, Mockito
JMockit is one of newer ones.
IMO, it seems one of the more elegant.
2.2.1 The Record-Replay-Verify Model
A test with JMockit mock objects typically follows a structure of
RecordWe establish our expectations of what calls will be made on our mock objects by separately executing those calls in a recording mode.
In this phase we also record any return/output values we want our mocks to return from those calls.
ReplayWe actually perform the test, calling upon some functions of the ADT under test that, in the run, we expect will make those calls on the mocks. The actualcalls made are compared against the “recording” we made earlier.
We fail the test if we do not see the expected (recorded) calls.
VerifyMore detailed checks are made to see if the test matched criteria other than simply having called the expected functions in the expected order.
We may check the parameters supplied to those calls and/or check to see that other mock functions were not called.
3 Case Studies3.1 Example 1 (Spreadsheet CLI)In our spreadsheet example, we had these stories in the first increment:
As a calculation author I would like to load a spreadsheet via the CLI.As a calculation author I would like to generate a value report via the CLI.
I decided to merge these as, together, they represented the minimum testable behavior on the CLI.
However, during this first project increment, the spreadsheet itself has not been implemented.
I had, for an earlier story, taken a first pass at designing the spreadsheet API:
package edu.odu.cs.espreadsheet; ⋮ public class Spreadsheet implements Iterable<CellName> { public Spreadsheet() { // TODO } ⋮ /** * Loads a spreadsheet from a file or other input source. Input format is * one assignment per line. * * @param input an input source * @throws IOException if assignments cannot be parsed form the input. */ public void load (Reader input) throws IOException { // TODO Auto-generated method stub } ⋮ /** * Write out the value report for the spreadsheet, giving the current value of * each non-empty cell. * * @param output output device */ public void report (Writer output) { // TODO Auto-generated method stub } }
The CLI
I expected the CLI to look something like:
package edu.odu.cs.espreadsheet.cli; ⋮ import edu.odu.cs.espreadsheet.Spreadsheet; public class Run { ⋮ public Run(String filename) { // TODO } /** * Load the indicated spreadsheet and generate its value report. * @throws IOException if the spreadsheet cannot be loaded */ public void doIt() throws IOException { ⋮ } public static void main (String[] args) throws FileNotFoundException, IOException { if (args.length != 1) { System.err.println("Usage: must supply a path to a spreadsheet file as a command line parameter."); System.exit(-1); } new Run(args[0]).doIt(); } }
because this is a pretty standard pattern for me.
Testing With a Spreadsheet
Now, if I had been working bottom-up, then by the time I looked at the CLI, I would have a working spreadsheet and could write some “real” tests of the CLI:
ITestCLI.java +
package edu.odu.cs.espreadsheet.cli; import static org.junit.Assert.assertTrue; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintStream; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import org.junit.BeforeClass; import org.junit.Test; public final class ITestCLI { static Path testDataDir = Paths.get("target/itest-data"); @BeforeClass public static void setupDir() { File dir = testDataDir.toFile(); dir.mkdir(); } @Test public void testRunNonExistentFile() throws FileNotFoundException, IOException { Run running = new Run("target/testdata/nonExistent.ss"); boolean thrown = false; try { running.doIt(); } catch (FileNotFoundException ex) { thrown = true; } assertTrue (thrown); } @Test public void testBadFile() throws FileNotFoundException, IOException { Path badDataPath = testDataDir.resolve("badData.ss"); Files.write(badDataPath, Arrays.asList("a12", "ab2=1"), Charset.forName("US-ASCII"));
Run running = new Run(badDataPath.toString()); boolean thrown = false; try { running.doIt(); } catch (IOException ex) { thrown = true; } assertTrue (thrown); } @Test public void testSimpleAssignments() throws FileNotFoundException, IOException { Path simpleDataPath = testDataDir.resolve("simpleData.ss"); Files.write(simpleDataPath, Arrays.asList("a12=12", "ab2=1"), Charset.forName("US-ASCII")); // Capture System.out ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintStream stringStream = new PrintStream(baos); PrintStream savedOut = System.out; System.setOut (stringStream); try { Run running = new Run(simpleDataPath.toString()); running.doIt(); } finally { //Restore System.out System.out.flush(); System.setOut(savedOut); } String reportOutput = baos.toString(); assertTrue (reportOutput.startsWith("a12=12")); assertTrue (reportOutput.contains("ab2=1")); } @Test public void testEvaluationPerformed() throws FileNotFoundException, IOException { Path simpleDataPath = testDataDir.resolve("evalData.ss"); Files.write(simpleDataPath, Arrays.asList("a12=10*ab2", "ab2=2"), Charset.forName("US-ASCII")); // Capture System.out ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintStream stringStream = new PrintStream(baos);
PrintStream savedOut = System.out; System.setOut (stringStream); try { Run running = new Run(simpleDataPath.toString()); running.doIt(); } finally { //Restore System.out System.out.flush(); System.setOut(savedOut); } String reportOutput = baos.toString(); assertTrue (reportOutput.startsWith("a12=20")); assertTrue (reportOutput.contains("ab2=2")); } }
This has tests for the cases:
Input file does not existInput file exists but has parsing errorsInput file OK, contains simple numeric assignmentsInput file OK, actually requires some expression evaluation and references to other cells.
I’m not attempting here to thoroughly test all value kinds and expression operators.That would take place i nthe tests of those other classes (Value, Expression, etc.)
Problems with that Test
I won’t be able to pass it until we are near the end of the overall implementation.It could be passed by other means (e.g., if the CLI used the Spreadsheet save operation instead of report, but save was buggy and listed values insteadof expressions).It is heavily dependent on exact I/O formatting and other details that might change when we consider the relevant classes in more detail.
i.e., it is dependent on the Spreadsheet and other classes.
This is actually an integration or system test, not a unit test.
Unit Testing Without a Spreadsheet
For a unit test of the CLI, I can mock the spreadsheet:
TestCLI.java +
package edu.odu.cs.espreadsheet.cli; import java.io.FileNotFoundException; import java.io.IOException; import java.io.Reader; import java.io.Writer; import mockit.Expectations; import mockit.Mocked; import org.junit.Test; import edu.odu.cs.espreadsheet.Spreadsheet; public final class TestCLI { @Mocked Spreadsheet ss; ➀ @Test public void testRun() throws FileNotFoundException, IOException { // Record phase: expectations on mocks are recorded; empty if there is nothing to record. new Expectations() {{ ➁ ss.load(withAny((Reader)null)); ➂ ss.report(withAny((Writer)null)); ➃ }}; // Replay phase: invocations on mocks are "replayed"; here the code under test is exercised. Run running = new Run("ivy.xml"); ➄ running.doIt(); // Verify phase: expectations on mocks are verified; empty if there is nothing to verify. ➅ } }
➀ Here I announce that I am mocking the spreadsheet class.
JMockit does not mock just this variable – it mocks all Spreadsheet objects in this test.A different approach than used in other mocking frameworks.
➁ The Expectations block starts the recording.
There are different varieties of Expectations. This one indicates that we expect to see the replayed function calls in the order we list them here.Other varieties allow the calls to occur in arbitrary order.
➂ First we expect to see a load call made on the spreadsheet. Because Java Reader classes can’t be compared for equality, the test says that any Readervalue is acceptable.
➃ Later we expect to see a report call made on the spreadsheet.
➄ Now we do the actual test.
➅ We don’t need any additional verification at the end.
Are We OK With This?
You might argue that this test is
Simple
Not necessarily a bad thing. It serves as a pretty clear specification of what we expect the CLI to do.
Not very thorough
It does not check to be sure that there aren’t defects in the SpreadsheetBut that’s the job of the future Spreadsheet unit tests, not of the CLI unit test.It does not check for unexpected bad interactions between the CLI and the Spreadsheet.
But interactions between classes are the province of integration tests, not unit tests.And we don’t throw out that first test. It’s an integration test for use later when the Spreadsheet is actually implemented.
3.2 Example 2: Adding Numeric Literals to the SpreadsheetShortly after the prior example, I had the story:
As an API user I would like to add a numeric literal to a cell in a spreadsheet.
The task list I came up with for this was:
1. Check Spreadsheet API2. Design Cell API3. Design Expression API4. Add Unit test to Spreadsheet5. Add Integration test to Spreadsheet6. Add Unit test to Expression
7. Add Unit test to Cell8. Implement Expression parse for numeric literals9. Implement Cell storage of expressions
10. Implement Spreadsheet expression load11. Check in changes.
That may look like a lot, but it boils down to a common pattern:
1. Make changes to the API to support the new functionality2. Write Unit tests for the new functionality3. If any of those tests used stubs or mocks, write integration or system tests for the behavior.4. Implement the new functionality5. Wrap things up
We expect that, at the end of step 4, we will pass all the new units tests devised in step 2.
We may not be able to pass the new integration & system tests until other parts of the system have been finished.
3.2.1 Adding to the APIs
I made a conscious decision not to expose the internal Expression class as part of the API:
old
package edu.odu.cs.espreadsheet; ⋮ public class Spreadsheet implements Iterable<CellName> { ⋮ /** * Get the formula stored in the indicated cell. * * @param cell a cell in the spreadsheet * @return the formula in that cell, or null if no formula has been placed there */ public Expression getFormula (CellName cell) { ⋮ } /** * Assigns a formula to the indicated cell. * * @param cell a cell in the spreadsheet
* @param newFormula an expression for future evaluation (may be null) * @throws ExpressionParseError if assignments cannot be parsed from the input. */ public void assignFormula (CellName cell, Expression newFormula) throws ExpressionParseError { ⋮ } ⋮ }
new
package edu.odu.cs.espreadsheet; ⋮ public class Spreadsheet implements Iterable<CellName> { ⋮ /** * Get the formula stored in the indicated cell. * * @param cell a cell in the spreadsheet * @return the formula in that cell, or null if no formula has been placed there */ public String getFormula (CellName cell) { ⋮ } /** * Assigns a formula to the indicated cell. * * @param cell a cell in the spreadsheet * @param newFormula an expression for future evaluation (may be null) * @throws ExpressionParseError if assignments cannot be parsed from the input. */ public void assignFormula (CellName cell, String newFormula) throws ExpressionParseError { ⋮ } ⋮ }
A welcome side effect of this change is to make independent testing of these classes even easier.
Cells contain Expressions
Cell.java +
package edu.odu.cs.espreadsheet; import edu.odu.cs.espreadsheet.expressions.Expression; import edu.odu.cs.espreadsheet.values.Value; /** * A single cell withing a spreadsheet. * * @author zeil * */publicclass Cell { private Spreadsheet ssheet; private CellName name; private Expression formula; private Value value; /** * Create a new cell * @param sheet the spreadsheet containing this cell * @param name the name of this cell */ public Cell (Spreadsheet ssheet, CellName name) { this.ssheet = ssheet; this.name = name; this.formula = null; this.value = null; } public CellName getName() { return name; } /** * Get the formula associated with this cell. * * @return an expression or null if the cell is empty
*/ public Expression getFormula() { return formula; } public void putFormula(Expression e) { formula = e; value = null; } public Value getValue() { if (value == null && formula != null) value = formula.evaluate(ssheet); return value; } public String toString() { return name.toString() + "::" + formula + "::" + value; } public int hashCode() { return name.hashCode(); } public boolean equals(Object obj) { if (obj instanceof Cell) { Cell other = (Cell)obj; return name.equals(other.name); } else return false; } }
Expressions can be Evaluated
Expression.java +
package edu.odu.cs.espreadsheet.expressions; import java.io.StringReader; import edu.odu.cs.espreadsheet.ExpressionParseError; import edu.odu.cs.espreadsheet.Spreadsheet; import edu.odu.cs.espreadsheet.values.Value; /** * Expressions can be thought of as trees. Each non-leaf node of the tree * contains an operator, and the children of that node are the subexpressions * (operands) that the operator operates upon. Constants, cell references, * and the like form the leaves of the tree. * * For example, the expression (a2 + 2) * c26 is equivalent to the tree: * * * * / \ * + c26 * / \ * a2 2 * * @author zeil * */public abstract class Expression implements Cloneable { /** * How many operands does this expression node have? * * @return # of operands required by this operator */ public abstract int arity(); /** * Get the k_th operand * @param k operand number * @return k_th operand if 0 < k < arity() * @throws IndexOutOfBoundsException if k is outside of those bounds */ public abstract Expression operand(int k) throws IndexOutOfBoundsException; /** * Evaluate this expression, using the provided spreadsheet to resolve * any cell referneces. * * @param usingSheet spreadsheet form which to obtain values of
* cells referenced by this expression * * @return value of the expression or null if the cell is empty */ public abstract Value evaluate(Spreadsheet usingSheet); /** * Copy this expression (deep copy), altering any cell references * by the indicated offsets except where the row or column is "fixed" * by a preceding $. E.g., if e is 2*D4+C$2/$A$1, then * e.copy(1,2) is 2*E6+D$2/$A$1, e.copy(-1,4) is 2*C8+B$2/$A$1 * * @param colOffset number of columns to offset this copy * @param rowOffset number of rows to offset this copy * @return a copy of this expression, suitable for placing into * a cell (ColOffSet,rowOffset) away from its current position. * */ public abstract Expression clone (int colOffset, int rowOffset); /** * Copy this expression. */ @Override public Expression clone () { return clone(0,0); } /** * Attempt to convert the given string into an expression. * @param in * @return */ public static Expression parse (String in) throws ExpressionParseError { try { parser p = new parser(new ExpressionScanner(new StringReader(in))); Expression expr = (Expression)p.parse().value; return expr; } catch (Exception ex) { throw new ExpressionParseError("Cannnot parse " + in); } }
@Override public String toString () { ⋮ } @Override public abstract boolean equals (Object obj); @Override public abstract int hashCode (); // The following control how the expression gets printed by // the default implementation of toString /** * If true, print in inline form. * If false, print as functionName(comma-separated-list). * * @return indication of whether to print in inline form. * */ public abstract boolean isInline(); /** * Parentheses are placed around an expression whenever its precedence * is lower than the precedence of an operator (expression) applied to it. * E.g., * has higher precedence than +, so we print 3*(a1+1) but not * (3*a1)+1 * * @return precedence of this operator */ public abstract int precedence(); /** * Returns the name of the operator for printing purposes. * For constants/literals, this is the string version of the constant value. * * @return the name of the operator for printing purposes. */ public abstract String getOperator(); }
A lot of this design was lifted from an earlier project of mine.
Expression is an abstract class – we never expect to see any actual objects of this type.
But we will have lots of interesting subclasses (one per operator) that will have “real” instances.
Numeric Literals
This story requires just enough expression handling to deal with numeric literals. So we’ll need an API for numeric literals. We’ve already covered that,however.
3.2.2 Writing the Unit tests
NumericLiteral
NumericLiteral needs a good set of unit test cases. We’ve looked at those tests earlier.
The NumericLiteral test is a good example of unit testing, but did not use stubs or mocks.
It is, in effect, a bottom-level class whose implementation does not require that we write any additional classes.
Cells
Although I had a task to create a unit test for Cell, currently it really only has encapsulated data member get/set functions, so I gave that a pass.
Spreadsheet
An existing unit test for spreadsheets was augmented to cover this story of inserting numeric literals.
MockTestSpreadSheet.java +
/** * */package edu.odu.cs.espreadsheet; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import mockit.Expectations;
import mockit.Mocked; import org.junit.Test; import edu.odu.cs.espreadsheet.expressions.Expression; import edu.odu.cs.espreadsheet.expressions.NumericLiteral; /** * @author zeil * */public class MockTestSpreadSheet { @Mocked Expression expr; @Test public final void testConstructor() { Spreadsheet s = new Spreadsheet(); assertEquals (0, s.getDimension()); assertNull (s.getFormula(new CellName("a1"))); assertFalse (s.iterator().hasNext()); } @Test public final void testNumericLiteralInsertion() throws ExpressionParseError { final Spreadsheet s = new Spreadsheet(); final String input = "1.234"; new Expectations() {{ Expression.parse(input); result = new NumericLiteral(input); expr.toString(); result = "1.234"; }}; CellName cn = new CellName("a1"); assertNull (s.getFormula(cn)); assertNull (s.getValue(cn)); s.assignFormula(cn, input); assertEquals (input, s.getFormula(cn)); } @Test(expected = ExpressionParseError.class) public final void testParsingError() throws ExpressionParseError { final Spreadsheet s = new Spreadsheet(); final String input = "1.23.4"; new Expectations() {{ Expression.parse(input); result = new ExpressionParseError(); }};
CellName cn = new CellName("b2"); assertNull (s.getFormula(cn)); assertNull (s.getValue(cn)); s.assignFormula(cn, input); assertTrue(false); } }
Two new test cases added to an existing test for spreadsheets
Inserting any formula/expression is done by passing a string to the spreadsheet, which must parse that string to yield the actual expression.
Parsing would not be implemented yet in this story.Calls for a Mock!
New Expression test case 1
Let’s break this testcase down:
public final void testNumericLiteralInsertion() throws ExpressionParseError { final Spreadsheet s = new Spreadsheet(); final String input = "1.234"; new Expectations() {{ ➀ Expression.parse(input); result = new NumericLiteral(input); expr.toString(); result = "1.234"; }}; CellName cn = new CellName("a1"); assertNull (s.getFormula(cn)); assertNull (s.getValue(cn)); s.assignFormula(cn, input); ➁ assertEquals (input, s.getFormula(cn)); ➂ }
➀ The Expectations block is our recording phase.
We expect that, once we try to assign a formula (a string) to a cell (➁ ) that the Expression.parse function will be called to convert the string into anexpression.
That parse function isn’t going to be implemented yet. But we expect that the result of parsing the input string for this test should simply be aNumericLiteral.
The result = establishes what return value should be supplied when we “play back” this recording.
Back to ➀ , again – we expect that when we call getFormula (➂ ), this process will need ot be reversed and the Expression toString() function will becalled to convert an expression back into string form.
New Expression test case 2
The other new test case starts with a similar structure:
@Test(expected = ExpressionParseError.class)public final void testParsingError() throws ExpressionParseError { final Spreadsheet s = new Spreadsheet(); final String input = "1.23.4"; new Expectations() {{ Expression.parse(input); result = new ExpressionParseError(); }}; CellName cn = new CellName("b2"); assertNull (s.getFormula(cn)); assertNull (s.getValue(cn)); s.assignFormula(cn, input); assertTrue(false); }
But in this case we are supplying an invalid input:
Here we tell JMockIt that we expect the spreadhseet to invoke the parse operation, but that we want the simulated parser to throw an exception.
Here we are telling JUnit that the expected outcome of this test case is that an exception will be thrown (and not caught).
3.2.3 Integration Tests
Because one of our new unit tests used mocks, we should add an integration to system test now that we will fail now but will pass some time in the future whenthe missing modules have been implemented:
ITestSpreadSheet.java +
package edu.odu.cs.espreadsheet; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.io.IOException; import java.io.StringReader; import org.junit.Test; import edu.odu.cs.espreadsheet.values.NumericValue; import edu.odu.cs.espreadsheet.values.Value; /** * @author zeil * */public class ITestSpreadSheet { @Test public final void testConstructor() { Spreadsheet s = new Spreadsheet(); assertEquals (0, s.getDimension()); assertNull (s.getFormula(new CellName("a1"))); assertFalse (s.iterator().hasNext()); } @Test public final void testNumericLiteralInsertion() throws ExpressionParseError { final Spreadsheet s = new Spreadsheet(); CellName cn = new CellName("a1"); assertNull (s.getFormula(cn)); assertNull (s.getValue(cn)); s.assignFormula(cn, "1.234"); assertEquals ("1.234", s.getFormula(cn)); Value v = s.getValue(cn); assertNotNull(v); assertTrue (v instanceof NumericValue); NumericValue nv = (NumericValue)v; assertEquals (1.234, nv.getDouble(), 0.0001); } @Test public final void testParsingError() {
final Spreadsheet s = new Spreadsheet(); CellName cn = new CellName("b2"); assertNull (s.getFormula(cn)); assertNull (s.getValue(cn)); boolean thrown = false; try { s.assignFormula(cn, "1.23.4"); } catch (ExpressionParseError e) { thrown = true; } assertTrue (thrown); assertNull (s.getFormula(cn)); Value v = s.getValue(cn); assertNull(v); } }
Basically, all we did here was to strip out the mocking portions of the unit tests!
4 Are Mock Frameworks Useful?My experience with them is still somewhat limited.
When they work, they seem wonderful.
many professionals swear by them
Some mock frameworks impose strong limits on the design of the classes that can be mocked
Mock proponents argue that this is a virtue, enforcing what they see as preferred design styles.
I’m not convinced.
One reason I prefer JMockit to other Java mock frameworks is that it imposes far fewer limitations on the design of the classes to be mocked.
Most of the published examples and tutorials focus on primitive types for function parameters and return values.
My experience suggests that functions taking ADTs as parameter values or returning ADTs can be handled. It’s a little more complicated, but notterribly so.
Version ControlSteven J Zeil
Last modified: Sep 14, 2017
Contents:1 Issues in Version Control2 Background - Changing the Code Base
2.1 Integrating Changes3 Approaches and Tools
Abstract
Version control (a.k.a. version management is concerned with the management of change in the software artifacts being developed.
In this lesson we look at the kind of practical problems that arise during software development and that can be addressed by proper version control.
maintaining the history of changes to the code baseexploring possible changes without breaking the code baseallowing collaboration among developers needing simultaneous access to the code base
Version control is sometimes considered a sub-area of configuration managementa.k.a., Software Configuration Management (SCM)Oddly enough, many tools labeled and marketed as SCM tools only address version control
1 Issues in Version ControlThe issues addressed by version control are:
History
How has the software changed since date-or-version-number? Who made those changes? Why were they made? Can we go back?
Exploration
Can we try out a set of plausible changes without affecting the “main” software build? Even if exploration of the effects of those changes may takea long time?
Collaboration
Can we have multiple developers working on the code without interfering with one another’s work?
2 Background - Changing the Code Base
ed
One of the earliest Unix text editors, ed applies a series of editing commands like ‘a’ to append to th end of a file, ‘i’ to insert a line at the current location, ‘d’to delete the current line, etc.
Few people use ed nowthough its line-oriented “child”, sed is still a popular scripting tool.
diff
diff compares two files line by line, listing the differences between them.
Differences are listed as a series of line replacements, insertions, and/or deletions
Reduces each line to a hash codeUses the dynamic programming algorithm for computing the Levenshtein distance between the two sequences of hash codes to obtain anapproximately shortest sequence of commands
Can emit differences as ed commands:
diff --ed file1 file2 > file12.diff echo w file2 >> file12.diff ## many days later...# ed file1 < file12.diff
would “rebuild” file2 from file1 and the diff.
patch
patch takes a slightly more sophisticated approach to the idea of applying a diff output to a file
diff file1 file2 > file12.diff ⋮ patch file1 file12.diff
Allows a variety of different diff variants
Can detect if file1 has already been changed so that the line numbers and other info in the patch file file12.diff are no longer accurate.
Attempts to compensate
2.1 Integrating ChangesSuppose that we have two patch files created from the same base file file1
patch -o file2a file1 patchA patch -o file2b file1 patchB
Change integration is the problem of combining both sets of changes to form a desired file file2.
Two-way Change Integration
patch -o file2a file1 patchA patch -o file2b file1 patchB
How do we know which patched file is “correct”, or whether we need some combination of the changes?
Two-way procedure:
1. Compare file2a and file2b2. Wherever the two are different, prompt the human to select the desired change.
Three-way Change Integration
Takes the base file into account as well as the two changed files.
patch -o file2a file1 patchA patch -o file2b file1 patchB
Three-way procedure:
1. Compare file1 and file2a, then file1 and file2b2. Any lines that differ from file1 in only one of the two other files can be applied automatically.3. Wherever both file2a and file2b are different from file1, prompt the human to select the desired change.
This is called a conflict.
3 Approaches and Tools
Version Control Systems
If we could extend patch multiple files at once, we could, in theory, patch an entire software system to move it from version 1 to version 2, then patch it againto move to version 3, etc.
This would be the heart of a version control system,
Approaches and Tools
Local version control systems manage history by setting aside directories on the same file system where the software under control is housed.
sccs, rcs
Centralized version control systems keep the system history at a centralized location accessible via the network.
Developers check out a copy of the current (or a desired older) version of the software onto their own machines.
CVS, Subversion
Distributed version control systems allow developers to keep the full system history on their own machines.
A central location may hold a base copy for management/distribution purposes, but this is not required.
git
Local Version Control (sccs, rcs)Steven J Zeil
Last modified: Dec 21, 2019
Contents:1 History2 Exploration3 Collaboration4 Strengths and Weaknesses
Abstract
Local version control stores the history of the code base on the same disk storage used for the code under development.
In this lesson we will look at how this approach supports the problems areas of
history maintenance,exploration of alternatives, andcollaboration among developers
Particular attention will be paid to the locking mechanism used to support safe simultaneous access.
The earliest version control systems in wide use were sccs and the open source rcs.
We’ll focus on rcs
The “repository” of historical information is kept as a “special” subdirectory, named RCS
Sharing of repositories is possible only via the operating system’s underlying mechanism for sharing access to directories
permissions, linking
Basic rcs Operations
ci Check In a file from the working directory into the repository
co Check Out a file from the repository into the working directory
rcsdiff Compare two versions of a file.
rcsmerge
1 Historymkdir RCS
Creates an RCS repository for the files in the current directory (only)
The repository is currently empty
ci filename
Checks files in to the repository
If the file is not in there yet, it is addedIf it is in there, then this becomes the new/current revisionEach check in is assigned a new, ascending revision number
Somewhat surprisingly, deletes the file from the current directory
co -l filename
Checks out the most recent version of that file from the repository, storing it in the working directory.
Adding a -r v option allows check out of a specific revision number
Revision Numbers
Clearly there was an intent that revision numbers also serve as version numbers.
A special option allows you to force a change to the leading digit,
e.g., to move from version 1.12 to 2.0
Problem is that each file’s revision number changes independently
So your intended release “version 2.1” might use revision 2.1 if adt.h, revision 2.5 of adt.cpp, revision 2.3 of main.cpp, etc.
Versions can be checked out by date instead:
“check out whatever version was current as of 12/13/2012”
Repeated over all files, would give a coherent view of the project status as of that date
Naming Revisions
Revisions can be named:
ci -N "v1.2" -t "Public release 1.2" *.h *.cpp
and later checked out by name instead of by exact revision number
Note also the option to add explanatory text at the time of the checkout
Later version managers would make this “mandatory”
Implementation
rcs is essentially a systematic way of creating and organizing patches.
The repository always contains the current version of the file plus enough diffs/patches to move back to any prior revision.
The current version is always available immediately.
Diffs are used to go back in time
Originally considered an important point in supporting efficient access to the most commonly needed file.
Now, probably not so important
2 Exploration
Exploring Alternatives
Suppose that we have worked through a few revisions and then get an idea that might not pay off.
We can start a branch to explore our idea while others continue work on the main trunk.
ci -r1.3.1 _filename_
Checks in our current version of filename as a new branch of development, numbered 1.3.1.1
1.3.1.1 is the trunk version from which we branched out
1.3.1.1 is the branch number
1.3.1.1 is the revision number within the branch
Working in a Branch
Subsequent check-ins of both the main trunk (1.3) and of our branch version will maintain separate revision numbers:
Note that checking out the most recent version along a branch is not as efficient as checking out the most recent version on the trunk.
Merging a Branch
If the idea in the branch does not pay off, the branch can simply be abandoned.
If you decide to adopt the changes in the branch, you can elect to merge it back into the trunk.
The rcsmerge command is used to conduct the merge,Need to resolve any conflicts introduced by continued development along the trunk.
then the resulting combined file checked in with a trunk number
Multiple Merges
After a merge
We might opt to discontinue using the branch
Or we might continue working along it, eventually generating more changes to be merged into the system
Combating Drift
Over time, a long-running branch can get so far out of sync with changes being made to the trunk that the final merge becomes difficult or even impossible.
An effective strategy for combating this is to periodically merge the trunk into the branchthe reverse of the “normal” merge direction
3 Collaborationrcs supports collaboration by locking files
Most checkouts like this
co filename
obtain a read-only copy of the file.
*nix permissions 400Can be used to compile system, but cannot be changed
(Of course, you can always chmod, but that’s cheating.
Locks
A checkout like this
co -l filename
requests a locked version of the file.
Request fails if a locked version already exists somewhere.If successful, programmer receives a copy with write permission.Lock persists until the programmer checks in changes or explicitly releases the lock (which deletes the file from their directory, forcing them tocheck out an unlocked, read-only version again).
4 Strengths and Weaknessesrcs addresses history, exploration, & collaboration concerns
but has weaknesses in each area
History
rcs tracks files in a directory.
Each file is tracked separately.
No support for deletion of file
Unless you know not to request a file, you will always get the last version before it was deleted.
No support for creation of new files
If you request revisions associated with very old dates, you will get version 1.1 even if the file did not actually exist as of that date.
No support for renaming files
Appears to be a deletion and a subsequent creation of a new, unrelated file
Each directory is tracked separately
Poor support for multi-directory projects
Exploration Issues
Branching and merging is often confusing.
Collaboration Issues
Locks are frequently abused
e.g., people forget to release a lock, forcing team members to waitPeople grab locks they don’t really need.
Cheating on locks is easy
People get in the habit of cheating to cope with lock abuseAnd eventually start cheating with less and less provocation.
Centralized Version Control (CVS, Subversion)Steven J Zeil
Last modified: Dec 21, 2019
Contents:1 CVS
1.1 History1.2 Exploration1.3 Collaboration
2 Subversion2.1 History2.2 Exploration2.3 Collaboration2.4 Eclipse Integration
Abstract
Centralized version control stores the history of the code base on a separate server from which copied can be provided to all developers.
In this lesson we will look at the advantages of centralized version control over local control. Particular attention will be paid to the area of collaboration wherelocal locking mechanisms are replaced by after-fact detection and resolution of conflicting changes.
We will look at one of the most influential version control systems, CVS.
Centralized Version Control
rcs kept its repository of history information in the “live” working directories.
Each directory of a project had a separate repository.
Sharing among team members relied on the rather limited Unix protection scheme.
Centralized version control systems keep a project repository in a location separate from the programmer’s work area.
Allows one repository to track a project’s entire directory tree
Encourages/requires use of a network-based sharing scheme
Allowing therefore a more distributed work effort
1 CVSBased on rcs
Many of the concepts and commands are similar
Uses a centralized store
can be on local or remote machine
1.1 HistoryCheck-in and check-out are similar to rcs
No locks, however
By default, scope of commands is an entire directory tree
cvs checkout projectRootDir
makes a copy of projectRootDir in the working directory, checking out the most recent version of everything in the project.
Similarly,
cd projectRootDir cvs commit -m "Added unit tests"
checks in any changes from the working directory on down.
Unix nerd alert: I often think of the cvs doSomething commands as if they were
find . -type f -exec rcsDoSomething {} \;
Where’s the Repository?
The earlier commands presume that we have, somehow, already established a connection with the repository.
In practice this is either done via earlier steps or added as additional options to individual commands
Local CVS Repositories
If the repository is on a local file file system,
you can specify it via -d:
cvs -d /usr/local/cvsroot checkout myProject/projectRootDir
or record this info in an environment variable:
setenv CVSROOT /usr/local/cvsroot cvs checkout myProject/projectRootDir
Remote Repositories
The same techniques (-d, CVSROOT) can be used to specify remote repositories.
Instead of simply specifying the path to the repository, give
[:method:][[user][:password]@]hostname[:[port]]/path/to/repository
Specifying the password in this way is a really bad idea
Usually, use
cvs login
to open a session (rather than get prompted for a password on every command).
Connection method: pserver
The pserver method is a built-in network connection method with password control.
Port 2401
Falling out of favor
pserver Exmaple
sirius:~/temp/tmp> setenv CVSROOT :pserver:[email protected]:/home/cvs/dlibsirius:~/temp/tmp> cvs loginLogging in to :pserver:[email protected]:2401/home/cvs/dlib CVS password: *****sirius:~/temp/tmp> cvs checkout AlgAE cvs checkout: Updating AlgAEU AlgAE/.project U AlgAE/AlgAE_layout.odg U AlgAE/LICENSE.txt U AlgAE/README.txt U AlgAE/build.xml U AlgAE/buttons.gif U AlgAE/buttons.odg U AlgAE/jhbasic.jar U AlgAE/junit-4.10.jar cvs checkout: Updating AlgAE/AlgAE_screenshot.png ⋮ sirius:~/temp/tmp> ls -ld AlgAE drwxrwxr-x 11 zeil faculty 1024 Feb 21 13:39 AlgAE/
ssh Connections
If you have ssh access to the machine running the CVS server, you can do
sirius:~/temp/tmp> setenv CVSROOT :extssh:[email protected]:/home/cvs/dlibsirius:~/temp/tmp> cvs checkout AlgAEThe authenticity of host 'cvs.cs.odu.edu (128.82.4.233)' can't be established. RSA key fingerprint ⋮ Password:
The History Commands
checkout: gets an initial copy of what’s on the server
update: checks to see if something newer than a local file copy is on the server, and if so, replaces the local copy by the remote one
commit: sends any local files that are newer than the server copy to the server as new revisions
add: places files that are not currently being tracked by CVS under version control
does not affect the server until committed
import: places a directory tree under version control
release: detaches the directory from the CVS server (if no files need to be committed)
1.1.1 Eclipse Integration
Eclipse includes CVS support in its normal distributions.
From Window->Open Perspective->Other..., select CVS Repository Exploring
Of course, you will need to have access to a repository before you see anything.
Checking Out an Existing Project
Pulling an existing project from CVS into Eclipse is pretty straightforward:
From the New Project menu, select “Project from CVS”Fill in the repository informationSelect the “module” (project)if given an option, request the use of the wizard to configure the checked-out projectSelect the “Head”, a branch, a version, or by date
Eclipse Integration: Creating a Project
To add a new project to an existing repository:
Set up the project as normal on your local machine
“Clean” the project of any object code, compiled classes, archives, etc. that are products of the project build are not present.This is optional, but saves work later.
Right-click on the project and select Team->Share Project...
Fill in the repository information
You will eventually be shown a list of files to be committed.
Review them carefully. If you check in something that you don’t want under version control, it’s virtually impossible to remove later.Think carefully about adding the Eclipse .project, .classpath, etc. files to the control set.
Working with History in Eclipse
Right-click on a file or directory and look in the “team” menu
Add to version control
Commit…
Update
Synchronize with repository
Combines commit and update, lets you select which files go in which directionBe careful of adding build products to the repository as you commit.
Exercise the options to “remove from view” or, for a more permanent solution, “Add to .cvsignore”
You might want to put .cvsignore under version control.
Tag (name)
Show History
Examining History in Eclipse
Right-click on a file or directory and look for
Compare (local copy) withhead, specific branches
1.2 ExplorationBranches
Same as rcs
Creating Branches
Make sure that you have checked in all the latest changes in your current working project.
Right-click on the project root directory and select Team <span>⇒</span> Branch\dots.
Enter a name for your branch.
Leave “Start working in the branch” checked. Click OK.
All the files under that root directory will be marked as belonging to a new branch.
As you work on this branch, you can make frequent use of the Compare With ... Head option to check for changes in the main trunk that you mightwant to incorporate into your branch code.
Merging a Branch Back Into the Main Trunk
1. Make sure that your copy of the branch is up-to-date, all changes checked in.If you want to allow for continued use of the branch after the merge, add a tag on the branch.
Right-click on a project with an up-to-date copy of the branch, and select “Team ⇒ Tag as Version … ”
1: To begin the actual merge, start with an up-to-date copy of the main trunk (HEAD).
You may already have one,Or you can right-click on the branc project, select “Team ⇒ Replace with ⇒ Branch or Version” and select HEAD.
This will actually check out a copy of the HEAD, replacing code in the branch.
That should not be a problem, since you made sure that you had checked in all changes in the 1st step. But it is a bit disconcerting, so I prefer tosimply work from s separate project that is already checked out from the HEAD branch.
You might consider placing a “tag” on the main branch (HEAD) to mark the spot on the branch just prior to the merge. that way, if you make amistake when resolving conflicts (see below), it will be easy to get back to the current version. To place a tag, right-click on a project with an up-to-date copy of the HEAD, and select “Team ⇒ Tag as Version … ”
2: Right-click on the HEAD project and select “Team ⇒ Merge … ”.
You will be asked what branch you want to merge with. Select the appropriate branch to be merged into HEAD.You will be asked to supply the common base version from which the branch was split - usually Eclipse will guess this correctly.You can also decide whether to have CVS automatically make any non-conflicting changes or to let you preview them.No matter what choice you make here, conflicts will still be presented to you for resolution.
3: CVS will compare the checked-in copy of the branch against the files in your local HEAD project.
You should see the familiar Synchronization view, in which you can preview changes and resolve conflicts.The goal of this process is to leave you with a local copy of all the desired changes.
This is not a commit of the merged changes to the HEAD branch.
4: Having resolved all changes, you now have a local copy of the project, associated with the HEAD branch in CVS, with a merged copy of code from theformer HEAD and from the other branch.
It has not been committed to the repository yet.You can test it out, and then decide whether to commit the changes into the HEAD.
1.3 CollaborationResolving Conflicts
During synchronization, changes are categorized as
Incoming: files that you have not edited your local copy (since it was last synchronized with the repository) but for which a revision has since beenchecked in.
These presumably need to be updated.
Outgoing: files that you have changed (since it was last synchronized with the repository) and for which no revisions have been checked in.
These presumably need to be committed.
Conflicts: Files that have been changed in your local directory and that have had revisions checked in to the repository.
These presumably need to be merged
The Conflict Editor
Eclipse has a built-in conflict editor tohelp merge conflicting versions.
It allows you to view thedifferences between the twoversions and decide whether
to keep the local version ofeach block of changedlines orto replace by therepository’s version.
You can save the merged file andthen “Mark as Merged”, whichmoves it from the “Conflicts”status to “Outgoing”
Or you can “Override andUpdate”, which indicates thatyou are abandoning your localchanges and changing the file’sstatus to “Incoming”
2 SubversionSubversion
Subversion (a.k.a. svn) is a later version control system based on the same centralized model as CVS.
For the most part, working with it is not all that different from CVS.
It rose to popularity in the open source community in part because of some useful tools for making read-only versions available on the web.
In fairness, this is not due to any particular strength of Subversion.Such tools could easily be targeted at CVS as well.
2.1 HistoryDifferences between CVS and Subversion w.r.t. history:
Subversion uses simpler revision numbers
Subversion tracks file and directory creation, deletion, renaming
2.2 Exploration
2.2.1 Subversion Doesn’t Understand Branches …
Subversion does not implement a distinct mechanism for branching.
Each revision (and each checked-out local copy) “knows” its parent revision number.
Revisions can be checked out into distinct local directories from where their parents were located.
Remember – Subversion tracks moving/renaming of files
… but it Does Branches Anyway
A typical subversion repository looks like this.
“Branches” are kept in a separate directory from the trunk.
But a checked-out version of a branch “knows” which revision it came fromwhether that revision is on the trunk or on the branch
“Merging” is a general operation between revisions
So it can be done as a regular merge to adopt changes made in the branch as part of the trunkor as a “sync merge” to add revisions from the trunk to the branch
Is This an Improvement?
I can’t decide if Subversion’s approach to branches is inspired or lazy.
The two are not, of course, mutually exclusive.
Branching and merging is CVS is just complicated enough that it’s easy to mess up.
Luckily, you can always recover from mistakes by checking out an older version and re-committing.
In svn, you typically check out the trunk or a branch – not the whole repository.
2.2.2 Subversion Doesn’t Understand Tags …
… but it does tags anyway, the same way it does branches.
Tag name becomes the folder name within the Tags directory.
2.3 CollaborationNo real differences between CVS and SVN
Conflicts can arise a check in and must be manually resolved.
2.4 Eclipse IntegrationNot part of the regular distrbution
has to be installed as a plug-in
Two plugins are availableSubversion is older and is an “incubating” part of the Eclipse project
But has been in that status for a long timeSubclipse is newer, seems to be more aggressively updated
Installation can be somewhat tricky (on Linux, at least)
Relies on some non-Eclipse libraries that must be installed separately.
Sometimes a period of instability after Eclipse updates before either plugin becomes usable.
Google for instructions on either plugin
Distributed Version ControlSteven J Zeil
Last modified: Dec 21, 2019
Contents:1 git
1.1 Where do files live?1.2 Revisions1.3 History1.4 Exploration
2 Collaboration3 Eclipse Integration
Abstract
Distributed version controls models relax the dependency upon a central repository as the keeper of the one true project.
Every developer has a snapshot of an entire development history.
In essence, you check out the entire past history of a project.And every checked out copy becomes an independent branch.
Developers may decide for themselves which of these branches should merge
Merging and conflict resolution, which are treated as exceptional operations in centralized systems, are regarded as the norm in this model.
We will look at git, a popular distributed version control system.
Sounds Like Anarchy
In practice, projects often due have a central repository for “official” releases.
But splinter projects are easier to form
and can continue to share some changes until the code base diverges too much.
A Synthesis of Local and Remote
In a distributed model, a developer maintains
a local repository
into which changes can be committed (as in local models like rcs)
and periodically may synchronize with a remote repository
which might be centralized or just another developer’s
Two-Level Commits
The local/remote division helps resolve a common dilemma in centralized VC systems:
When or how often should we commit changes?In a centralized system, we have conflicting goals
Safeguard against losing work: argues for committing frequentlyAvoid interfering with other developers by not checking in incomplete work
A newly checked-out copy should always compile and yield a (roughly) working product.
a.k.a., “Don’t break the build!”
In a two-level system, we can commit frequently to the local repository and only when a “unit” of work is completed, commit to the remote repository.
Matthew McCullough’s tongue-in-cheek critique: Please. Stop using Git.
1 git1.1 Where do files live?
Edit files in your work area
Your ordinary directories/folders of files
Stage the files that you want to commit.
The stage is also sometimes called the index.
A commit copies updates the local repository withthe files on the stage.
Push sends commits from your local repo to aremote one.
Pull fetches commits from the remote repo into yourlocal one.
If safe, merges changes into your work area aswell.
1.2 RevisionsUnlike earlier VC systems, a git revision is a state of the entire project rather than of a single file/directory.
After committing a change, the entire system, even unchanged files, advance to a new revision IDOf course, “behind the curtain” you are still going to have incremental diffs, but that does not affect our visible interactions
Because of the distributed model,
revision numbers cannot simply be incremented in any meaningful fashionthere is a need to easily determine when two revisions in two different repositories are, in fact, copies of the same system state
Revision numbers are therefore replaced by hash codes computed over the file set that constitutes the entire project
git Snapshots
A git repository contains, conceptually, a collection of snapshots (a.k.a., commit objects, a.k.a. revisions, a.k.a. versions).
Each snapshot contains
The set of files for the project
The name of this snapshot (hash code)
References to the parent snapshots
Most have one parentInitial commit would have zeroMerges can result in a snapshot with multipel parents
Heads
A git repository also contains a collection of heads.
These are human-assigned names for selected snapshots.
Heads refer to the most recent snapshot in a chain of commits
Hence heads actually identify branches
Every repository has a head “master”.
At any given time, one head is considered active. This one is aliased tothe head “HEAD”.
How shall I name thee?
Snapshots in a repository may be identified by giving
Its SHA1 hashcode
A long enough prefix of that hashcode to be unique
By a head
Relative to one of these: ^ means “parent-of”
e.g., HEAD^ would be the state before our most recent commit
1.3 History
Common Local History Commands
git add files stages modified files, scheduling the current version to be included in thenext commit (recursing through directories)
An intermediate step not needed in earlier VC systems
git commit -m message commits all staged changes to the local respository
Add a -a to add all modified files in the current directory and below to the stagingset
git status lists modified files
git diff file displays what was changed
1.4 ExplorationEvery Local Repository is a Branch
So one way to “branch” in git is to simply check out a new copy.
But sometimes we want to branch within a local repository
Branching Within a Local Repository
git branch newHeadName/*-i desiredParentSnapshot
creates a new branch
git checkout branchHead
switches to a new branch
Replaces the files in the current directory by a copy of the state for that branch.
When Should I Commit? (Another perspective)
git users consider branches to be cheap.
So some advocate
Always work in branches
Keep the master branch in a releasable state
Remember, every local copy of the repository a branch in its own right. So one way to achieve the same effect is to commit frequently in your local repositorybut only push to the central repository when you have something in a releasable state.
This approach delays making your unfinished code available to other members of your team. Whether this is a viable approach depends on
Whether your local repository is backed up.
The chances of other team members making conflicting changes.
The longer you go between pushes and pulls, the more likely you are to encounter merge conflicts and the harder they will be to resolve.
Merging Local Branches
git merge head
produces a new snapshot representing the merge of the current one (HEAD) with the named head.
The merged revision will have both HEAD and head as parents.
git identifies the more recent common ancestor of the two branches and performs a 3-way mergeIf a change (compared to the common ancestor) does not conflict (overlap) any changes from the other branch, the change is copiedautomatically into the merged state.If conflicts are determined, markers are inserted into the working copy of the file and the user alerted.
If the merge completes without conflict, the resulting merged state is committed.If conflicts were found, the working copy is updated but no commit takes place.
Branches not needed after a merge can be deleted
git branch -d head removes the head name from the repository (but does not actually delete the history of changes along the branch.
2 CollaborationCollaboration in git takes the form of interaction between your local repository and a remote repository.
Concepts (and, sometimes, commands) are much the same as in the local mode
Starting from a Remote Repository
If you are working with an existing remote repository
git clone remoteSpec
creates a new local repository as a copy of the remote one.
The remoteSpec names the remote repositoryCould be a simple file path if on the same machineCould be an http:// URL (generally for anonymous access)Could be an ssh address
Cloning
Suppose that we have a remote repository with two branches and a few commitobjects on each.
Our local cloned repository will remember its remote origin repository.
All heads from the remote repository will be cloned as origin/head
We will get a local master head
You can request local heads for non-master branches by tracking, e.g.
git branch --track enhanced origin/enhanced
Life after Cloning
Starting from this local repository, …
… suppose each repository adds a commit along the trunk:
Our local heads separate from the remembered positions of the remote ones.
Fetching Remote Changes
The basic command to get changes from the remote repository is git fetch
Remember, each repository is, in essence, a new (set of) branch(es)
If states are not identical, they are fetched as new branches
Local heads are unaffected
Pulling Remote Changes
More commonly used than fetching is pulling, which combines a fetch and a merge
Starting with this remote repository…
…and this local one.
(Note that commits (F, G) have been made to both repositories since the clone wascreated.)
Then
git pull origin master
yields this new version of the local:
Pushing to the Remote Repository
The push command
sends local commits to a remote repository
Advances the remote head marker to the end of the list of changes.
If the remote repository looks like this
and our local repository looks like this
git push origin master
yields this remote repository.
Push is NOT the Opposite of Pull
It’s actually the opposite of fetch
No merge is done when pushing
This leads to an important restriction
The remote head must point, before a push, to an ancestor of the commit that it would point to after the push.
This Push Will Fail
If the remote repository looks like this
and our local repository looks like this,
the push will fail
because if it went through, we would lose access to a state already committed in the remote repository.
Avoiding Bad Pushes
Easiest thing to do is to do a pull into the local repository first, then do the push.
And hope no one sneaks in ahead of you
An alternative is rebasing
Rebasing
Rebasing changes the parent relationship of the current head so that it appears to have been derived directly from some other selected head.
If we start with this and do
git rebase master
we get this.
Now looks like enhanced was derived directly from the master head.
The Perils of Rebasing
For all the talk about rebasing in the git literature, you would think it was a very common operation.
But,
rebasing loses information
with usually very little savings in storage
done at the wrong time, can make pushing to a remote server much harder
Why Rebase?
Generally recommended only for
Managing an unshared branch that you want to keep up to date with the master and don’t care if you lose history.
Recovering from a failed push when someone else sneaks in between your pull and your subsequent push.
You fetch their changesThen rebase your head to appear as if it were derived from their head.You can then push, because you have guaranteed that the remote head is an ancestor of yours
Unless someone else sneaks in yet another push while you are doing the rebase.
Isn’t distributed access fun?!
3 Eclipse IntegrationThe (Egit) plugin integrates git into Eclipse.
Egit has recently been incorporated into the Eclipse main update/plugin distribution site.Help is available within Eclipse, or from the reference manual on the Library page.
Operation is similar to the CVS and SVN plugins, except that
The Team menu gains new commands to push and pull.
There is no New ... Project from Git option.
Instead, use the Import menu.
A typical work session using egit
Eclipse, git, and a Forge
New projects:
1. A Forge environment will create an empty repository
2. Use the Git Repository Exploring perspective to clone the repository .
Store it outside your normal Eclipse workspace.
3. Create a directory to hold the project as a sibling of the .git directory you have just obtained.
Put at least one file of content (e.g., a build.xml file) in that directory.
4. In Eclipse, do File ⇒ Import and select Git. Follow the instructions to name your local repository that you just cloned and “Use New Project Wizard”.
5. When the regular project wizard starts, direct it to your project folder you created in step 3.
6. After the new project wizard is completed, Eclipse still will not show the project as managed by Git.
Use Team ⇒ Share project .... You’ll be asked what repository to use. Let Eclipse try to find it. (It should be able to do so).
7. Use Team ⇒ Add to index to add files to version control.
8. Team ⇒ Commit (or Synchronize).
9. Team ⇒ Push.
Existing Projects
1. A Forge environment will contain instructions/settings on how to access the existing repository
2. Use the Git Repository Exploring perspective to clone the repository.
Store it outside your normal Eclipse workspace.
3. In Eclipse, do
Create a new Java project, outside of the default workspace location. For the location, navigate to your newly created copy of the project directory,within the directory where you cloned the git repository.Or, for non-Java projects, File ⇒ Import and select Git. Follow the instructions to name your local repository that you just cloned and use the NewProject Wizard .
When the regular project wizard starts, direct it to your project folder you created in step 3.
4. After the new project wizard is completed, Eclipse might not show the project as being managed by Git.
Use Team ⇒ Share project .... You’ll be asked what repository to use. Let Eclipse try to find it. (It should be able to do so).
5. Use Team ⇒ Add to index to add files to version control.
6. Team ⇒ Commit (or Synchronize).
7. Team ⇒ Push.
from The System
Lab: Version Control with GitSteven J Zeil
Last modified: Dec 21, 2019
Contents:1 Gitting Started
1.1 Fetching changes1.2 Committing changes
2 Coping with Conflict3 Finishing Up
This is a self-assessment activity to give you practice in working with the git version control system. Feel free to share your problems, experiences, andchoices in the Forum.
1 Gitting Started1. Current versions of Eclipse have the Eclipse plugin for Git already installed. Take a moment and browse the EGit User Guide.
2. Open an ssh session on one of the CS Linux servers. Give the command
~zeil/Assignments/cs350/gitLab/step1
You will be given the URI of a git repository.
3. Fire up your installation of Eclipse. Go to Window ⇒ Open perspective... and open the Git perspective. Move your mouse across the controls at the topof the Git repositories view and select “Clone a git repository…”. Enter the URI that you were given. Fill in your user name, but leave the passwordblank.
Click through Next and Finish to complete the clone. As you do, take note of the directory in which your repository is being stored. (You can change thisto something more convenient if you like.)
Look in your git repository folder. You should see that a new directory has been added. Look in that directory (use -a on Linux). You will see that itcontains a .git directory, which is where the version control info is kept, and a directory for the real project.
4. In Eclipse, return to the Java perspective. (You can do this from Window ⇒ Open perspective... or by using the shortcut perspective button in the upperright corner of the Eclipse window.)
Follow the usual procedure for creating a new Java project, but instead of the default project location, select that project directory (the one containing thepastry directory).
5. Look around in the project. try compiling and running it. Don’t make any changes yet.
1.1 Fetching changes1. Back in your ssh session, give the command
~zeil/Assignments/cs350/gitLab/step2
This is simulating someone else on your team working on the same project.
2. In Eclipse, right-click on your project and select Compare with... ⇒ HEAD revision. This compares against the most recent check-in to your local copyof the repository. Since you have not made any changes yet, you should not see much of anything, maybe a few new files created when you compiled.
3. Now try Compare with... ⇒ branch, tag, … Select “Remote tracking” and then “master”. This compares against the remote repository, and youshould be able to see that your mysterious teammate has checked in some changes.
In the synchronization view, find the file that has changed and double-click on it to open up the comparison editor.
4. Most of the version control commands are accessed by right-clicking on the project and looking under the Team menu item. Return to the Java perspectiveand use that to pull the changes from the remote repository.
1.2 Committing changes1. Try making some simple changes, such as editing some of the comments in the source code.
2. From the Team menu item, commit your changes. As you examine the “commit” dialog, you will see that you have the option of doing a simple localcommit or of doing a commit followed by an immediate push to the remote repository. Go ahead and push.
2 Coping with Conflict1. Back in your ssh session, give the command
~zeil/Assignments/cs350/gitLab/step3
2. In your Eclipse session, refactor the Pie.getBaseColor and Pie.setBaseColor functions, renaming them to getBackgroundColor and setBackgroundColor.
(By using the “refactor” menu, you get all calls to that function to be changed at the same time, even calls in other .java files.)
3. Now try to commit and push your changes. The commit should work, but the push fails because your mysterious teammate has already checked in somemore changes to the project.
4. See if you can resolve this conflict and eventually commit and push a combination of your changes and those of your teammate. You will need to
fetch the changes from the remote repository,force a merge between your local HEAD revision and the remote’s “master” head, if that doesn’t happen when you fetch the changes
Although Eclipse and git will be able to handle the merge in some files automatically, they will not be able to handle all of them, becausethere are some lines in one file that have been changed by both you and your simulated teammate.
Use the Eclipse conflict editor to combine the changes between the opposing versions of the conflicted file.“Add” the resolved file back into the index as a signal that you are satisfied that the conflict has been resolved.Commit and push the change again.
3 Finishing UpThe remote repository for this lab was stored in your Linux account as ~/.cs350gitlab.
To clean up after the lab (or to reset to the beginning if you want to start it over from the beginning), do
rm -rf ~/.cs350gitlab
While you still have those keys handy, now would be a good time to go to the course forge, log in to GitLab. Go to your Profile settings, select “SSH Keys”,and add your public key. This is how GitLab will know that your Eclipse IDE is allowed to work with your project. (Remember, you have already told Eclipseabout the location of your private key. So, between the two of them, they have enough information to validate your credentials.)
ForgesSteven J Zeil
Last modified: Sep 14, 2017
Contents:
Abstract
A software forge is a collection of web services for the support of collaborative software development:
Project web sites
Networked access to version control
Release (download) support
Communications (e.g., messaging, wikis, announcements)
Bug reporting and tracking
Project personnel management
In this lesson, we will look briefly at some popular forges and then will explore the forge specifically constructed for your use in this course.
Forge Examples
Among the best known forges are
the original, SourceForge, (1999)
Google Code, (2006)
GitHub, (2008)
The CS 350 course has its own forge
GitLab for project management, version control, issue tracking, wiki
Jenkins for continuous integrationRedmine issue tracking
Build ManagersSteven J Zeil
Last modified: Mar 2, 2020
Contents:1 Some Sample Project Builds
1.1 Student Programming Assignment1.2 Code Annotation Tool1.3 Class Assignment Setup1.4 Posting Slides and Lecture Notes
2 Structural Architecture of a Development Project2.1 Projects and Sub-projects2.2 The Project Directory2.3 Sub-Project Directory (Java)2.4 Sub-Project Directory (C/C++)
3 Types of Build Managers3.1 IDE project managers3.2 Dependency-Based Managers3.3 Task-Based Managers
Abstract
A build manager is a tool for scripting the automated steps required to produce a software artifact.
We will start this module by looking at what types of services we would like to obtain from build managers.
These will be motivated by looking at some sample projects to consider the steps required to build them. An important lesson will be that builds often involvemore that the “obvious” reuqirement of compiling and linking the code.
We will then survey some of the options for build managers, including scripting, IDE project managers, and dependency-based and task-based buildmanagement tools.
What Should a Build Manager Do?
A good build manager should be
easy to use
easy to set up for a given project
efficient in performing the build
avoid redundant/unnecessary actionsdetect and abort bad builds in progress
incremental
allow focused/partial builds
flexible
allow for a variety of build actionson a variety of platforms
configurable
permit the management of multiple artifact configurations
1 Some Sample Project BuildsHere are some of the project builds I have had to automate in the opening weeks of one semester:
1.1 Student Programming AssignmentSet up to allow students to easily compile code for an assignment.
Build each missing or out-of-date .o file by compiling a corresponding .cpp file.
Record which .cpp files and .h files were used during the compilation so that future builds can determine what would future source code changeswould make this .o file outdated.
Link all .o files to produce an executable
1.2 Code Annotation ToolThe code annotation tool is a program I use to convert C++ and Java code with optional markup comments like this into this.
Building the Code Annotation Tool
The steps involved in building this tool are:
1. Run the program jflex on each file in src/main/jflex, generating a pair of .java files that get placed in src/main/java
2. Compile the Java files in src/main/java, placing the results in target/classes
3. Compile the Java files in src/test/java (using the target/classes compilation results, placing the results in target/test-classes.
4. Run the JUnit tests in target/test-classes.
5. If all tests pass, package the compiled classes in target/classes into a .jar file.
It’s worth noting how many of the steps in this project build are not simply compile and link steps.
1.3 Class Assignment SetupClass Assignment Setup
In preparing to release a programming assignment to a class, the steps are
1. Setup:
Copy all of the files that I will provide to students from a Public directory into a Work directory.Copy all of the files from my Solution directory into that Work directory
2. Build solution
Compile any .cpp files in the Work directoryLink the resulting .o files.
3. Run the executable produced in the last step on each test*.dat in the Tests directory, capturing the output as a corresponding .out file.
4. Copy all source code from the Work directory into a winWork directory.
5. Use a cross-compiler to compile and link the .cpp files in winWork into a Windows executable
6. Install:
Copy the two executables and the contents of the Public directory into a release area accessible to students.Set the permissions on the copied files so that they can be accessed.
Copy any .html and graphics files for the assignment to the course website.
1.4 Posting Slides and Lecture NotesThe lectures notes for this course are prepared through a process:
1. Setup
Convert all graphics to PNG or PDF:For each GIF file in the directory with no corresponding PNG file, run convert to produce a PNG.For each FIG file in the directory with no corresponding EPS file, run fig2dev to produce an EPS.For each Dia file in the directory with no corresponding EPS file, run dia to export as EPS.For each EPS file in the directory with no corresponding PDF file, run epstopdf to create a PDF. \eii Annotate source code:For each C++ or Java file with no corresponding HTML file, use the code annotation tool to generate an HTML file.For each C++ or Java file with no corresponding TeX file, use the code annotation tool to generate an TeX file.
2. Generation: For each desired document output format,
Run a preprecessor on the markdown file containing the document source.Run a markdown processor to generate HTML.Apply an XSLT stylesheet to add format info and to split the document into slides/pages.
3. Deployment:
Synchronize this directory with the corresponding directory of the website, orPrepare a zip file with the contents of this directory that can be uploaded to a remote webserver (e.g., Blackboard).
2 Structural Architecture of a Development ProjectLet’s talk about how development projects are typically organized into files, directories, etc.
2.1 Projects and Sub-projectsA project consists of one or more more sub-projects.
Even if we conceive of a project as a single entity, it’s worth treating it as having a single sub-projectA hedge against changing our minds later.Sets a standard for consistent tool use and setup
What Constitutes a Sub-project?
A sub-project is generally defined as the code and data that yields a single deliverable.
Examples of deliverables include
an executable programa library
Java .jar orC/C++ .a, .lib, .so, or .dll
a reference manual
Example: The AlgAE project has sub-projects
sub-project deliverablealgae-client-server algae-4.1.jar
algae-cppserver libalgaecpp.a
algae-referenceManual referenceManual.pdf
demos/FordToppBST FordToppBST.zip
demos/ReferenceManualJava algae-jrefman.jar
Why divide a project into multiple sub-projects rather than into multiple smaller independent projects?
The entire project is stored in a single location/repository.
The entire project can be built with a single command.
The entire project can share certain configuration data.Sub-projects may be more tightly coupled than would be desired of independent projects.
2.2 The Project DirectoryTypically contains
Top-level project documentation.Overall project build & configuration informationOne sub-directory per sub-projectVersion control and configuration management information
2.2.1 Example: AlgAE
Top-level directory contains:
README.md, LICENSE.mdbuild.xml: builds the sub-projects in the correct order.git, .gitignore, ivysettings.xml (version control and configuration management)Directories:
algae-client-serveralgae-cppserveralgae-reference-manualdemosReports
All but the last are sub-projects.
C/C++ projects might add directories that will (after the project is built) contain the various sub-projects’ deliverables:
binlib
2.3 Sub-Project Directory (Java)Contains:
sub-project build & configurationsource code directoryproject and test datatarget directory for deliverables and other build projects
When source code is compiled, output is placed here rather than in the source code directory
2.3.1 Apache Project Directories
The Apache Foundation hosts many open source projects, which organize their projects & sub-projects like this:
src/ # anything supplied/edited by the programmers target/ # initially empty, holds products of the compilation/build
The src/ directory is split into separate directories for the "real’ code and for the test code.
src/ | main/ # things that contribute directly to the deliverable | test/ # things used for testing but not delivered target/
“Deliverables” are usually an archive of some kind.
If the project is supposed to produce a Java application or a Java library, the deliverable is usually packaged in a Jar.
Server-side web applications are delivered in a War or an Ear.
Source code is sometimes packaged in a Jar, but more often in a Zip. (Usually, though, when we talk about deliverables in this section, we’ree referring to“binary” deliverables.)
Android apps are packaged in an APK.
The division of the source files into separate main/ and test/ makes it easier to eventually construct those deliverable archives because we won’t treat entiredirectories worth of stuff uniformly, rather than having to select desired materials on a file-by-file basis.
src/main/ is further subdivided:
src/ | main/ | | java/ # Java source code, compiled into target/classes | | resources/ # data files that will be included in the deliverable archive | | data/ # data files required during build but not part of deliverable | test/ target/ | classes/ # data and compiled code that are packed into the .jar deliverable | project.jar # the deliverable
(These directories can be omitted if they are empty.)
Java libraries and applications can read data from files within their own distribution archive with only slightly more difficulty than reading from an ordinaryfile. To do so, the Java code is written to search the Java CLASSPATH, the same path used to hunt for the compiled Java code.
They cannot, however, write to those data files. The data access is read-only.
The src/test/ directory is split in an analogous fashion:
src/ | main/
| | java/ | | resources/ | | data/ | test/ | | java/ # Java source code, compiled into target/test-classes | | resources/ # data files, available during testing via CLASSPATH | | data/ # test data target/ | classes/ | test-classes/ # data and compiled code for unit testing | project.jar
Test resources are intended to be accessible during testing via the code already written for accessing main (deliverable) resources. One way to support this is tocopy the src/test/resources contents into target/test-classes, so that the same CLASSPATH-based mechanisms to locate the compiled test code will alsofind the test resources.
2.3.2 Android/Gradle Project Directories
A similar directory structure is employed for Android projects. The Gradle build manager, which we will cover later in this section, has made the Androidstructure its default for Java projects, making it a popular organization for non-Apache projects.
The most obvious difference is that the products of the build are stored in build instead of target.
src/ # anything supplied/edited by the programmers build/ # initially empty, holds products of the compilation/build
The src/ directory is laid out identically to the Apache organization:
src/ | main/ | | java/ # Java source code. After compilation, is part of the deliverable. | | resources/ # Data files that will be included in the deliverable, accessible via CLASSPATH | | data/ # Data files needed for the build, but not part of the deliverable. | test/ | | java/ # Java source code for testing, will not be part of the deliverable | | resources/ # data files, available during testing via CLASSPATH | | data/ # test data build/
The build directory, however, has a more detailed breackdown than in the Apache project:
src/ | main/ | | java/ # Java source code. After compilation, is part of the deliverable. | | resources/ # Data files that will be included in the deliverable, accessible via CLASSPATH
| | data/ # Data files needed for the build, but not part of the deliverable. | test/ | | java/ # Java source code for testing, will not be part of the deliverable | | resources/ # data files, available during testing via CLASSPATH | | data/ # test data build/ | classes/ # Compiled code | | main/ # Compiled code from src/main/java/, will be part of the deliverable | | test/ # Compiled code from src/test/java/, will not be part of the deliverable | | libs/ # Deliverables are placed here. | | reports/ # Generated reports including documentation, test coverage, analysis, etc. | | test-results/ # Outputs from testing | | tmp/ # Work area for general temporary files
2.3.3 Variations
Additional files and directories in target/ are common.
I usually have a target/reports/ directory for testing and other report generated during the build.If my tests need to produce output files, I usually try to put those in target/test-outputs (so they can be easily cleaned up)
Analogous to main/java and test/java, we can have directories for source code in other programming languages.
For example, the code annotation project has source code in jflex, which actually is compiled to produce Jave code in target/gen-source/,which is then itself compiled into target/classes.
src/ # anything supplied/edited by the programmers | main/ # things that contribute directly to the deliverable | | java/ # Java source code, compiled into target/classes | | jflex/ # JFlex source code, compiled into target/gen-source | | resources/ # data files, copied into `target/classes` | | data/ # data files required during build but not part of deliverable | test/ # things used for testing but not delivered | | java/ # Java source code, compiled into target/test-classes | | resources/ # data files, copied into `target/test-classes` | | data/ # test data target/ # initially empty, holds products of the compilation/build | classes/ # data and compiled code that are packed into the .jar deliverable | gen-source/ # automatically generated Java source, compiled into target/classes | test-classes/ # data and compiled code for unit testing | project.jar # the deliverable
Later in a project, I might add separate src/ and target/ subdirectories for integration & system testing.
Example: see this structure in the Code Annotation project
2.4 Sub-Project Directory (C/C++)Much more variation exists. One possibility is:
include/ # header files | src/ # compilation units (.c and .cpp files) | bin/ # executables and .o files produced by compiling src/ | lib/ # libraries produced by combining object files
Sometimes .o files are placed in a separate obj/ directory.Sometimes executables and libraries are copied directly to a project-level bin/ or lib/ directory.
2.4.1 Android-ish structure
Increasingly common is this approach, inspired by the Apache/Android Java styles:
src/ | main/ | | cpp/ # C++ source code. .cpp files and local headers | | headers/ # Header (.h) files that need to be visible to main code and | | # to tests. | | public/ # For library projects, the header files that will be exported | | # as part of the delivered library. | test/ | | cpp/ # Unit test code | | data/ # test data build/ | exe/ # Executables | | main/ # - from main/cpp | | test/ # - from test/cpp | lib/ # libraries constructed from object code | | main/ # - from obl/main | obj/ # Compiled object code | | main/ # - from main/cpp | | test/ # - from test/cpp | tmp/ # Work area for general temporary files
3 Types of Build Managers
Why Not Just Write a Script?
We could simply write a “simple” script to perform each of the steps in sequence …
#!/bin/sh cp Public/* Work/ cp -f Solution/* Work/ g++ -o Work/program Work/*.cpp find Tests -name 'test*.dat' -exec sh runTest.sh Work/program {} \; mkdir WinWork cp Work/*.h Work/*.cpp WinWork x86_64-w64-mingw32-g++ -o WinWork/program WinWork/*.cpp --static mkdir $releaseDir/bin mkdir $releaseDir/bin/Linux cp Work/program $releaseDir/bin/Linux mkdir $releaseDir/bin/Windows cp WinWork/program $releaseDir/bin/Windows chmod 775 $releaseDir/bin/*/program cp *.html *.png $website/
Scripting
But how does this fare according to our earlier build manager goals?
easy to use? Y
easy to set up for a given project? N
efficient in performing the build?
avoid redundant/unnecessary actions N
detect and abort bad builds in progress ?
incremental?
allow focused/partial builds ?
flexible?
allow for a variety of build actions N
on a variety of platforms N
configurable?
permit the management of multiple artifact configurations ?
3.1 IDE project managersMost IDEs come with a built-in project manager.
typically limited to compiling and linking
maybe some support for packaging
3.2 Dependency-Based Managers Some build managers are based on the idea of a dependency graph:
Boxes are files.
Arrows denote dependencies. “A depends on B” means that if B ismissing or changed, then A must be (re)generated.
Labels on arrows indicate the program used to generate the file at thebase of the arrow.
Analysis of such a graph facilitates
efficiency - easy to tell what needs to be rebuilt after a change
incrementality - can determine required build step for any file, not justthe “final” one
make is the canonical example of a build manager of this type.
3.3 Task-Based Managers
Other managers are based on the idea of interdependent tasks.
Ellipses are tasks (activities). Each task can involve multiple steps.
Arrows denote success dependencies. “A depends on B” means that Awill be run after B and only if task B finished successfully.
This approach facilitates
ease of setup: usually less detailed than a full file-based dependencygraph
incrementality - can request any intermediate step
ant is based on this approach.
File Dependencies: makeSteven J Zeil
Last modified: Dec 21, 2019
Contents:1 The make Command2 makefiles
2.1 Rules2.2 Variables2.3 Implicit Rules and Patterns
3 Working with Make3.1 Touching Files3.2 Artificial Targets3.3 Dependency Analysis3.4 Managing Subproject Builds
4 Case Studies – Introduction4.1 Simple Java Build4.2 Java Build with Code Generation4.3 C++ Multi-project Build
5 Case Studies – Makefiles5.1 Simple Java Build – Makefiles5.2 Java Build with Code Generation – Makefile5.3 C++ Multi-project Build – Makefiles
Abstract
make is perhaps the oldest build management tool. It introduced the idea of dependency-based builds.
In this lesson we will look at the build model implemented by make and at how to describe projects to the make tool.
We will look at how make could be applied to some of our sample projects from the prior lesson.
make
make is a command/program that enacts builds according to a dependency graph expressed in a makefile.
make devised by Dr. Stuart Feldman of Bel Labs in 1977
It has long been a standard component of *nix systems
GNU make is a popular moden variant
1 The make Command
make looks for its instructions in a file named, by default, makefile orMakefile
The make command can name any file in the graph as the target to bebuilt, e.g.,
make CppJavaScanner
If no target is given, make builds the first file described in the makefile
make Options
Some useful options:
-nPrint the commands that make would issue to rebuild the target, but don’t actually perform the commands.
-k“Keep going.” Don’t stop the build at the first failue, but continue building any required targets that do not depend on the one whose construction hasfailed.
-f filenameUse filename instead of the default makefile or Makefile
2 makefilesAt its heart, a makefile is a collection of rules.
2.1 RulesA rule describes how to build a single file of the project.
Each rule indicates
The target file to be constructedThe dependencies: the other files in this project from which the target is constructed.The commands that must be executed to construct the target from its dependencies.
Rules may appear in any order
Except that the first rule’s target is the default built by make when no explicit target is specified in the command line.
The Components of a Rule
A rule has the form
target: dependencies commands
where
target is the target file,
dependencies is a space-separated list of files on which the target is dependent
commands is a set of zero or more commands, one per line, each preceded by a Tab character.
Rule Examples
codeAnnotation.jar: code2HTML.class CppJavaScanner.class jar tvf codeAnnotation.jar code2HTML.class CppJavaScanner.class
CppJavaScanner.class: CppJavaScanner.java javac CppJavaScanner.java code2HTML.class: code2HTML.java CppJavaScanner.java javac code2HTML.java CppJavaScanner.java: code2html.flex java -cp JFlex.jar JFlex.Main code2html.flex
Why is This Better than Scripting?
Suppose that we edit code2html.java and then invoke make
Only one javac will be issued, after which the jar command is run.
make has determined the minimum number of steps required to rebuild after a change.
How make Works
Construct the dependency graph from the target and dependency entries in the makefile
Do a topological sort to determine an order in which to construct targets.
For each target visited, invoke the commands if the target file does not exist or if any dependency file is newer
Relies on file modification dates
2.2 VariablesA makefile can use variables to simplify the rules or to add flexibility in configuring the makefile.
All variables hold strings.
Variables are initialized by a simple assignment
variable = value
Variables are immutable (constants)
Assignments may appear within the makefile or in the command line, e.g.:
make JOPTIONS=-g codeAnnotation.jar
Referencing Variables
Variables are referenced as $(variable) or ${variable}, e.g.,
CppJavaScanner.class: CppJavaScanner.java javac $(JOPTIONS) CppJavaScanner.java code2HTML.class: code2HTML.java CppJavaScanner.java javac $(JOPTIONS) code2HTML.java
Adding Power to Variables
GNU make adds some special extensions useful in setting up variables.
Globbing:
SOURCEFILES=$(wildcard src/*.cpp)
collects a list of all C++ compilation units in the filename{src} directory
Substitutions:
OBJFILES=$(SOURCEFILES:%.cpp=%.o)
collects a list of all object code files expected by compiling those compilation units.
Example: Using variables
This allows us to write a “generic” rule for compiling C++ programs:
PROGRAM=myProgramName SOURCEFILES=$(wildcard src/*.cpp) OBJFILES=$(SOURCEFILES:%.cpp=%.o) $(PROGRAM): $(OBJFILES) g++ -o $(PROGRAM) $(OBJFILES)
This is technically, incomplete.
We have not explained how to produce a .o file from a .cpp
Nonetheless, it would work on some systems for the initial build, because they have an “implicit” rule for working with C++
Still not a good solution by itself – dependencies on .h files have not been captured.
2.3 Implicit Rules and PatternsImplicit rules describe how to produce a single “kind” (extension) of file from another.
All make implementations will have some common implicit rules.You can modify the list of implicit rules.
Pattern rules are a GNU extension for writing “generic” rules
Implicit rules could, for the msot part, be written as patternsBut patterns offer some additional flexibility
Implicit Rules
An implicit rule looks like
.ext1.ext2: commands
where ext1 and ext2 are file extensions, and commands are the commands used to convert a file with the first extension into a file wit hthe second.
Example:
.cpp.o: g++ -g -c $<
the implicit variable $< holds the dependency file
Also commonly used, $@ denotes the target file.
Using Implicit Rules
The extensions used in implicit rules must be declared:
.SUFFIXES: .cpp .o
An implicit rule will be used when a target ends in one of these suffixes and
there is no rule listing that file as a target, or
the rule listing that file as a target has no commands
Implicit Rule Example
PROGRAM=myProgramName SOURCEFILES=src/main.cpp src/adt.cpp OBJFILES=$(SOURCEFILES:%.cpp=%.o) .SUFFIXES: .cpp .o .cpp.o:
g++ -g -c $< $(PROGRAM): $(OBJFILES) g++ -o $(PROGRAM) $(OBJFILES) src/adt.o: adt.cpp adt.h
Both main.cpp and adt.cpp will be compiled on the initial build.
If adt.h is subsequently modified, then adt.cpp would be re-compiled.
Pattern Rules
A pattern rule looks like a regular rule, but uses ‘%’ as a wildcard in the target and one of their dependencies:
src/test/java/%.class: src/test/java/%.java junit4.jar javac -cp junit4.jar -g src/test/java/$*.java
Another implicit variable, $* contains the string matched by the % wildcard.
One advantage of pattern rules, is that we can add dependencies on other files e.g., junit.jar
3 Working with Make3.1 Touching FilesModification Dates
make compares the modification dates of targets and dependencies to determine if the target is out of date.
It uses the success/fail status value returned by commands to determine if construction of a target was successful.
Although this is fairly robust, there are ways to fool make
Touching a File
The touch command in *nix sets a files modification date to the current time, without affecting the contents of the file.
Question: What would happen if we touched code2html.flex?
Answer +
The next time that make is run, it will
1. run jflex to produce a new CppJavaScanner.java2. run javac to produce a new CppJavaScanner.class3. run jar to produce a new codeAnnotation.jar
but it will not recompile code2HTML.java
Sometimes this is a useful thing to do on purpose.
Inadvertant Touches
Suppose we had our code annotation project in a directory project1 and did the following:
> cd project1 > make > cd .. > cp -rf project1 project2 > cd project2 > make
What would be re-built by the second make?
Almost impossible to tell. All of the files in project2 would have create/modify dates within a second of each other. Ordering, if any, would be arbitrary.
(better to have done cp -arf project1 project2)
Inadvertant Touches
Similarly, successive calls to make can sometimes be confused if the time between creation of some intermediate targets is within a single clock “tick”.
Clock drift between different machines can be particularly troublesome.
(particular between the server running the make command and a file server responsible for storing the files).
Created != Success
Some commands we might give to create a target will create no file if the command fails.
e.g., g++ does not create a .o file if compilation errors occur
Others will create some kind of file anyway.
E.g., any command that is invoked with output redirection,
command > target
which could cause make to assume that the target need not be re-constructed the next time around.
Some make programs explcitly delete targets if the command fails.
3.2 Artificial TargetsFooling make Again
A creative way to fool make:
What happens if we give a rule whose commands never actually create the target?
target: dependency1 dependency2 echo Nope. Not going make that target!
The first time we run make, the dependencies will be created and the echo performed.
Each subsequent time we run make, the dependencies will be re-created if necessary and the echo performed.
Artificial Targets
We can take advantage of this trick by adding artificial targets that serve as the names for tasks to be performed.
build: codeAnnotation.jar install: build cp codeAnnotation.jar $(INSTALLDIR) clean: rm *.class CppJavaScanner.java codeAnnotation.jar: code2HTML.class CppJavaScanner.class jar tvf codeAnnotation.jar code2HTML.class CppJavaScanner.class CppJavaScanner.class: CppJavaScanner.java
javac CppJavaScanner.java code2HTML.class: code2HTML.java CppJavaScanner.java javac code2HTML.java CppJavaScanner.java: code2html.flex java -cp JFlex.jar JFlex.Main code2html.flex
Common Artificial Targets
allOften made the first rule in the makefile so that it becomes the default. Builds everything. May also run tests.
buildBuild everything.
installBuild, then install
testBuild, then run tests
cleanDelete everything that would have been produced by the makefile in a build or test run.
3.3 Dependency AnalysisComing up with a list of dependencies (and keeping it current) can be troublesome.
Various tools exist for this purpose for programming languages
The gcc and g++ compilers have a compile-time option, -MMd, which emits a .d file containing a target and dependency line.
Use this with an implicit rule to give the actual command
Self-Building Makefile
selfBuilding.listing +
MAINPROG=testpicture CPPS:=$(wildcard *.cpp) CPPFLAGS=-g -D$(DISTR)CPP=g++
OBJS=$(CPPS:%.cpp=%.o)DEPENDENCIES = $(CPPS:%.cpp=%.d) %.d: %.cpp touch $@ %.o: %.cpp $(CPP) $(CPPFLAGS) -MMD -o $@ -c $*.cpp make.dep: $(DEPENDENCIES) -cat $(DEPENDENCIES) > $@ include make.dep
On the first make,
for each .cpp file, an empty .d file is created by touchAll *.d files are concatenated to for a file make.depThe file make.dep is included as part of the makefile.As the .cpp files are compiled, the .d are replaced by a rule making the .o file dependent on that .cpp file and on any .h files that it included.
On subsequent make runs,
the .d files contain the dependencies for each .cpp file.All *.d files are concatenated to for a file make.depThe file make.dep is included as part of the makefile.If any .h or .cpp file has been changed, the .o files dependent on it will be regenerated.
3.4 Managing Subproject BuildsSubprojects are generally handled by giving each subproject its own makefile and using a master makefile to invoke the artificial targets:
all: cd model; make cd vcncurses; make cd vcjava; make clean: cd model; make clean cd vcncurses; make clean cd vcjava; make clean
4 Case Studies – IntroductionFor make and for the other buiild managers we will be discussing, we will use 3 case studies.
4.1 Simple Java BuildThe simple Java build features
Java code to be compiled and built into a jar.
JUnit tests to be compiled and run.
Layout
We will follow the Apache/Android guidelines for source code layout.
project root/ |--src/ |--|--main/ |--|--|--java/ |--|--|--|-- ...java packages... |--|--test/ |--|--|--java/ |--|--|--|-- ...junit packages... |--|--|--data/ |--|--|--|-- ...test data ... |--lib/ |--|-- ...3rd party libraries (temporary hack)...
We will look at better ways to supply 3rd party libraries in later lessons on configuration management.
Layout
For outputs from the build, we will follow either the Apache
project root/ |--target/ |--|--classes/ |--|--|-- ...compiled code from src/main |--|--test-classes/
|--|--|-- ...compiled code from src/test |--|--reports/
or the Android standard
project root/ |--build/ |--|--classes/ |--|--|--main/ |--|--|--|-- ...compiled code from src/main |--|--|--test/ |--|--|--|-- ...compiled code from src/test |--|--lib/ |--|--reports/
whichever is more “natural” for the build manager under discussion.
4.2 Java Build with Code GenerationA slightly less conventional build:
Java code to be compiled and built into a jar.
Some of the Java code is generated automatically
JFlex program is used to produce lexical analyzers from collections of regular expressions.
JUnit tests to be compiled and run.
Layout
Project layout is the same as the simple Java build, except for an input directory for JFlex specifications and a working directory for the generated Java sourcecode:
project root/ |--src/ |--|--main/ |--|--|--java/ |--|--|--|-- ...java packages... |--|--|--jflex/ |--|--|--|-- ...JFlex input files... |--|--test/ |--|--|--java/
|--|--|--|-- ...junit packages... |--|--|--data/ |--|--|--|-- ...test data ... |--lib/ |--|-- ...3rd party libraries (temporary hack)... |--target/ (or build/) |--|--generated-src/ |--|--|-- ...Java code generated by JFlex |--|...
4.3 C++ Multi-project BuildC and C++ pose a challenge for the build systems that want to provide defaults for simple projects.
A collection of .o files can only be linked together if they have no more than one main() function.
If you want a project to produce more than one executable, you have to tell the build manager how to divide up the .o files for each one.
Most IDE build managers and most default rules for non-IDE build managers don’t allow that.
One project – one executable
But unit testing adds additional executables.
Unit testing and C/C++ Builds
Even simple projects with unit tests wind up being divided into multiple sub-projects.
One or more sub-projects for groups of ADTs with unit tests.A subproject for each “real” executable.If not all developers will have the C/C++ unit test framework pre-installed, that may be another subproject.
4.3.1 Source Layout
project root/ |--lib/ (ADTs with unit tests) |--|--src/ |--|--|--main/ |--|--|--|--cpp/ |--|--|--|--|-- .cpp and local .h files for ADTs |--|--|--|--headers/ |--|--|--|--|-- Header files "exported" to other subprojects |--|--|--mainTest/
|--|--|--|--cpp/ |--|--|--|--|-- .cpp unit tests (GTest) |--exe/ (may be named for executable, particularly if more than one) |--|--src/ |--|--|--main/ |--|--|--|--cpp/ |--|--|--|--|-- .cpp main function for one executable |--gtest/ (Google Test framework) |--|--include/ |--|--|-- Header files exported to unit tests |--|--|--src/ |--|--|--|-- .cpp and local .h files for framewwork
The gtest/ subproject has a slightly different layout because it’s a third party project and it’s not worth trying to repackage their sourcecode.
4.3.2 Output layout
The lib/ and gtest/ subprojects each produce a static library.The exe subproject produces an executable
project root/ |–lib/ (ADTs with unit tests) |–|–build/ |–|–|–objs/ holding area for .o files |–|–|–libs/ generated library goes here |–|–|–test-results/ reports fromunit test |–exe/ (may be named for executable, particularly if more than one) |–|–build/ |–|–|–objs/ holding area for .o files |–|–|–exe/ generated executablesgo here |–gtest/ (Google Test framework) |–|–build/ |–|–|–objs/ holding area for .o files |–|–|–libs/ generated library goes here
5 Case Studies – Makefiles5.1 Simple Java Build – Makefiles
TARGET=codeAnnotation.jar SRC=src/main/java ➀ CLASSDEST=build/classes JARDEST=build TESTSRC=src/test/java TESTCLASSDEST=build/testclasses JAVA=$(shell find $(SRC) -type f -name '*.java') ➁ TESTJAVA=$(shell find $(TESTSRC) -type f -name '*.java') CLASSES=$(patsubst $(SRC)/%, $(CLASSDEST)/%, $(JAVA:%.java=%.class)) TESTCLASSES=$(patsubst $(TESTSRC)/%, $(TESTCLASSDEST)/%, $(TESTJAVA:%.java=%.class)) TESTCLASSNAMES=$(subst /,.,$(subst $(TESTSRC)/, ,$(TESTJAVA:%.java=%)))
.SUFFIXES: ## Targets:#all: test $(JARDEST)/$(TARGET) build/setup: ➂ -mkdir -p $(JARDEST) -mkdir -p $(CLASSDEST) -mkdir -p $(TESTCLASSDEST) date > $@ $(JARDEST)/$(TARGET): $(CLASSES) ➃ cd $(CLASSDEST); jar cf temp.jar * mv $(CLASSDEST)/temp.jar $@ test: $(TESTCLASSES) $(CLASSES) ➄ java -cp lib/junit-4.10.jar:lib/junit/hamcrest-core-1.1.jar:$(CLASSDEST):$(TESTCLASSDEST) org.junit.runner.JUnitCore $(TESTCLASS $(CLASSDEST)/%.class: $(SRC)/%.java build/setup ➅ javac -g -cp $(CLASSDEST) -d $(CLASSDEST) -sourcepath $(SRC) $(SRC)/$*.java $(TESTCLASSDEST)/%.class: $(TESTSRC)/%.java build/setup $(CLASSES) javac -g -cp $(CLASSDEST):lib/junit-4.10.jar:lib/junit/hamcrest-core-1.1.jar -d $(TESTCLASSDEST) -sourcepath $(TESTSRC) $(TESTS clean: -rm -f build
➀ Symbolic names for directories make it easier to rearrange layout.
➁ Various functions for manipulating and rewriting lists of files
➂ Create output directories. Note that this appears as a dependency in most of the later compilation rules (to guarantee it’s done before compiling).
➃ Build a Jar file
➄ Run the unit tests
➅ Compile .cpp files.
5.2 Java Build with Code Generation – MakefileThis project adds a stage before compilation to generate some of the source code that then needs to be compiled.
TARGET=codeAnnotation.jar SRC=src/main/java CLASSDEST=build/classes JARDEST=build TESTSRC=src/test/java TESTCLASSDEST=build/testclasses GENSRC=build/gen/java GENDEST=build/classes FLEXSRC=src/main/jflex JAVA=$(shell find $(SRC) -type f -name '*.java') TESTJAVA=$(shell find $(TESTSRC) -type f -name '*.java') CLASSES=$(patsubst $(SRC)/%, $(CLASSDEST)/%, $(JAVA:%.java=%.class)) TESTCLASSES=$(patsubst $(TESTSRC)/%, $(TESTCLASSDEST)/%, $(TESTJAVA:%.java=%.class)) TESTCLASSNAMES=$(subst /,.,$(subst $(TESTSRC)/, ,$(TESTJAVA:%.java=%))) GENJAVA=$(GENSRC)/CppJavaScanner.java $(GENSRC)/CppJavaTeXScanner.java \ $(GENSRC)/ListingScanner.java $(GENSRC)/ListingTeXScanner.java GENCLASSES=$(GENDEST)/CppJavaScanner.class $(GENDEST)/CppJavaTeXScanner.class \ $(GENDEST)/ListingScanner.class $(GENDEST)/ListingTeXScanner.class .SUFFIXES: ## Targets:#all: test $(JARDEST)/$(TARGET) build/setup: -mkdir -p $(JARDEST) -mkdir -p $(GENSRC) -mkdir -p $(CLASSDEST) -mkdir -p $(TESTCLASSDEST) date > $@ $(JARDEST)/$(TARGET): $(CLASSES) cd $(CLASSDEST); jar cf temp.jar * mv $(CLASSDEST)/temp.jar $@
test: $(TESTCLASSES) $(CLASSES) java -cp lib/junit-4.10.jar:lib/junit/hamcrest-core-1.1.jar:$(CLASSDEST):$(TESTCLASSDEST) org.junit.runner.JUnitCore $(TESTCLASS $(GENSRC)/CppJavaScanner.java: $(FLEXSRC)/code2html.flex build/setup java -jar lib/jflex-1.4.3.jar -d $(GENSRC) $< $(GENSRC)/CppJavaTeXScanner.java: $(FLEXSRC)/code2tex.flex build/setup java -jar lib/jflex-1.4.3.jar -d $(GENSRC) $< $(GENSRC)/ListingScanner.java: $(FLEXSRC)/list2html.flex build/setup java -jar lib/jflex-1.4.3.jar -d $(GENSRC) $< $(GENSRC)/ListingTeXScanner.java: $(FLEXSRC)/list2tex.flex build/setup java -jar lib/jflex-1.4.3.jar -d $(GENSRC) $< $(GENDEST)/%.class: $(GENSRC)/%.java build/setup javac -g -d $(GENDEST) -sourcepath $(GENSRC) $(GENSRC)/$*.java $(CLASSDEST)/%.class: $(SRC)/%.java build/setup $(GENCLASSES) javac -g -cp $(CLASSDEST) -d $(CLASSDEST) -sourcepath $(SRC) $(SRC)/$*.java $(TESTCLASSDEST)/%.class: $(TESTSRC)/%.java build/setup javac -g -cp $(CLASSDEST):lib/junit-4.10.jar:lib/junit/hamcrest-core-1.1.jar -d $(TESTCLASSDEST) -sourcepath $(TESTSRC) $(TESTS clean: -rm -f build
5.3 C++ Multi-project Build – MakefilesBecause this project is divided into multiple subprojects, we will have multiple makefiles:
One per subprojectAnd a master to launch them all.
5.3.1 The master file
In the project root directory:
all: $(MAKE) -C gtest $(MAKE) -C lib $(MAKE) -C exe clean: $(MAKE) -C gtest $(MAKE) -C lib $(MAKE) -C exe
5.3.2 The lib subproject
TARGET=libmain.a ➀ CXX=g++ CXXFLAGS=-g -pthread -I ../gtest/include -I src/main/headers -std=c++11LINK=$(CXX) LFLAGS=../gtest/build/libs/libgtest.a -lpthread SRC=src/main/cpp OBJDEST=build/objs/main DEST=build/libs EXEDEST=build/exe TESTSRC=src/mainTest/cpp TESTOBJDEST=build/objs/test CPP=$(wildcard $(SRC)/*.cpp) OBJS=$(patsubst $(SRC)/%, $(OBJDEST)/%, $(CPP:%.cpp=%.o)) TESTCPP=$(wildcard $(TESTSRC)/*.cpp) TESTOBJS=$(patsubst $(TESTSRC)/%, $(TESTOBJDEST)/%, $(TESTCPP:%.cpp=%.o)) .SUFFIXES: ## Targets:#all: $(DEST)/$(TARGET) test build/setup: ➁ -mkdir -p $(DEST) -mkdir -p $(EXEDEST) -mkdir -p $(OBJDEST) -mkdir -p $(TESTOBJDEST)
date > $@ test: $(EXEDEST)/runtest ➂ $(EXEDEST)/runtest $(DEST)/$(TARGET): $(OBJS) test build/setup ➃ ar rcs $@ $(OBJS) ranlib $@ $(EXEDEST)/runtest: $(TESTOBJS) $(OBJS) ➄ $(LINK) -o $@ $(CPPFLAGS) $(TESTOBJS) $(OBJS) $(LFLAGS) $(OBJDEST)/%.o: $(SRC)/%.cpp build/setup ➅ $(CXX) $(CXXFLAGS) -o $@ -c $< $(TESTOBJDEST)/%.o: $(TESTSRC)/%.cpp build/setup ➆ $(CXX) $(CXXFLAGS) -o $@ -c $< clean: -rm -f build
➀ Symbolic names for directories, programs, and lists of files.
➁ Set up output directories
➂ Run the unit tests.
➃ Create the library from the .o files generated by compilation
➄ Generate the executable for running unit tests.
➅ Compile the ADTs source code
➆ Comile the unit test source code.
Task Dependencies: antSteven J Zeil
Last modified: Mar 25, 2020
Contents:1 The ant Command2 Build Files
2.1 Targets2.2 Ant Building Blocks and Concepts2.3 Tasks
3 Case Studies3.1 Simple Java Build3.2 Java Build with Code Generation3.3 C++ Multi-project Build
4 Eclipse/Ant Integration
Abstract
ant is a build manager based upon a task dependency graph expressed in an XML file
In this lesson we look at how ant addresses a number of shortcomings of make.
We will look at the task-based build model implemented by ant at how this differs from the dependency-based model of make, and at how to describe projectsto the ant tool.
We will look at how ant could be applied to some of our sample projects.
ant
ant devised by James Davidson of Sun, contributed to Apache project (along with what would eventually become TomCat), released in 2000
Quickly became a standard tool for Java projects
slower to move into other arenas
What’s Wrong with make?
ant is actually an acronym for Another Neat Tool.
But why do we need “another” tool, however neat, for build management?
make works by issuing commands to /bin/sh
That’s not portable.
The commands that people write into their makefile rules are generally not portable either:
Commands themselves are system-dependent (e.g., mkdir, cp, chmodPaths are system-dependent (/ in *nix versus \ in Windows, legal characters, quoting rules)Path lists are system-dependent (: in *nix versus ; in Windows)
Other Criticisms
Some feel that make is too low-level with its focus on individual files
Some will feel that ant is too high-level
But this is the apparent rationale for moving the focus from file dependencies to task dependencies.
The makefile syntax is arcane and hard to work with.
And XML syntax isn’t?
1 The ant Command
ant looks for its instructions in a file named, by default, build.xml
The ant command can name any target to be built, e.g.,
ant setup
If no target is given, ant builds a target explicitly listed in build.xml as a default for the project.
ant Options
Some useful options:
-k, -keep-going“Keep going.” Don’t stop the build at the first failue, but continue building any required targets that do not depend on the one whose construction hasfailed.
-f filenameUse filename instead of the default build.xml. Also -file or -buildfile
-Dproperty=valueSets a property (similar to make’s variables)
2 Build FilesThe ant build file is an XML file.
The build file describes a project.
The project has a name and a default target.
<project name="382Website" default="deploy"> <description> Extract Metadata Extractor - top level </description> ⋮ </project>
2.1 TargetsAt its heart, a build file is a collection of targets.
A target is an XML element and, as attributes, has a name and, optionally,
a list of dependenciesa conditiona human-readable description
The target can contain multiple tasks, which contain the actual “commands” to get things done.
ant targets correspond, roughly, to make’s “artificial targets”.
Example of Targets
simplebuild.xml.listing +
<project name="JavaBuild" default="deploy"> ➀ <description> Example of a simple project build </description> <target name="compile" description="Compile src/.../*.java into bin/"> ➁ <mkdir dir="bin" /> ➂ <javac srcdir="src" destdir="bin" debug="true" includeantruntime="false"/> <echo>compiled </echo> </target> <target name="unittest" depends="compile" unless="test.skip"> ➃ <mkdir dir="test-reports" /> <junit printsummary="on" haltonfailure="true" fork="true" forkmode="perTest">
<formatter type="plain" /> <batchtest todir="test-reports"> <fileset dir="bin"> <include name="**/Test*.class" /> <exclude name="**/Test*$*.class" /> </fileset> </batchtest> </junit> </target> <target name="deploy" depends="unittest" description="Create project's Jar file"> <jar destfile="myProject.jar"> <fileset dir="bin"/> </jar> </target></project>
➀ The project has a name and default target
➁ A basic target. It is named “compile” and has a description (which may be picked up by some IDEs)
➂ This target has 3 tasks. It creates a directory, compiles Java source code, and prints a message when completed.
The fact that the tag names resemble familiar commands is intended as self-documentation, but is not otherwise significant.The tag names actually map to Java class names that implement the task.
➃ This target illustrates both a dependency and a condition.
The tasks within this target would not be executed if I invoked ant like this:
ant -Dtest.skip=1
However, the unittest task would still be considered to have succeeded, in the sense that tasks that depend on it would be allowed to run.
Task versus File Dependencies
ant targets correspond, roughly, to make’s “artificial targets”.
So this build file
simplebuild.xml.listing +
<project name="JavaBuild" default="deploy"> ➀ <description>
Example of a simple project build </description> <target name="compile" description="Compile src/.../*.java into bin/"> ➁ <mkdir dir="bin" /> ➂ <javac srcdir="src" destdir="bin" debug="true" includeantruntime="false"/> <echo>compiled </echo> </target> <target name="unittest" depends="compile" unless="test.skip"> ➃ <mkdir dir="test-reports" /> <junit printsummary="on" haltonfailure="true" fork="true" forkmode="perTest"> <formatter type="plain" /> <batchtest todir="test-reports"> <fileset dir="bin"> <include name="**/Test*.class" /> <exclude name="**/Test*$*.class" /> </fileset> </batchtest> </junit> </target> <target name="deploy" depends="unittest" description="Create project's Jar file"> <jar destfile="myProject.jar"> <fileset dir="bin"/> </jar> </target></project>
is roughly equivalent to this makefile
simplemake.listing +
JAVAFILESsrc=$(shell find src/ -name '*.java') JAVAFILES=$(JAVAFILESsrc:src/%=%) CLASSFILES=$(JAVAFILES:%.java=%.class) TESTFILES=$(shell find src/ -name 'Test*.java') TESTS=$(TESTFILES:src/%.java=%) deploy: unittest cd bin; jar cvf myProject.jar `find . -name '*.class'` unittest: build cd bin; for test in $(TESTS); do \ java $$test; \ done
build: cd src; javac -d ../bin -g $(JAVAFILES)
though a “real” makefile author would probably write this:
simplemake2.listing +
JAVAFILESsrc=$(shell find src/ -name '*.java') JAVAFILES=$(JAVAFILESsrc:src/%=%) CLASSFILES=$(JAVAFILES:%.java=%.class) TESTFILES=$(shell find src/ -name 'Test*.java') TESTS=$(TESTFILES:src/%.java=%) deploy: myProject.jar unittest: testReport.txt build: $(CLASSFILES) myProject.jar: testReport.txt $(CLASSFILES) cd bin; jar cvf myProject.jar `find . -name '*.class'` testReport.txt: $(CLASSFILES) -rm testReport.txt cd bin; for test in $(TESTS); do \ java $$test >> testReport.txt; \ done bin/%.class: src/%.java cd src; javac -d ../bin -g $*.java
Make Efficiency
If we do
make make
The second command does not actually perform any steps.
Ant Efficiency
What happens if we do
ant ant -Dskip.test=1
Each of the tasks is executed, but
The javac task knows not to re-compile Java files with up-to-date class files
The jar task knows not to update Jar files that are newer than all of the files being added.
If we remove the -Dskip.test=1, however, the tests will be re-run.
So some level of incremental behavior gets built into many of the individual tasks.
The idea of avoiding unnecessary work is a fundamental part of make.But it’s not fundamental to ant. It depends on the implementation of each individual task.
2.2 Ant Building Blocks and Concepts(Optional reading. Read on if you actually want to use Ant.)
2.2.1 Properties
Properties are named string values.
Can be set from the command line or via <property and a few other tasks
Accessed as ${_propertyName_}
Properties are immutable: once set, attempts to re-assign their values are ignored
By convention, properties names are grouped into faux hierarchies with ‘.’
e.g., compile.src, compile.dest, compile.options
The <property Task
Two basic modes:
<property name="compile.options" value="-g -O1"/>
Sets this property to “-g -O1”
<property name="compile.src" location="src/main/java"/>
Sets this property to the absolute path of the directory/file named.
The / and \ characters are changed as necessary to conform to the OS on which ant is being run.
Additional <property Variants
<property file="project.default.properties"/>
Loads property values from a file, written as a series of property=_value_ lines
courseName=CS795baseurl=https://www.cs.odu.edu/~zeil/cs795SD/s13homeurl=https://www.cs.odu.edu/~zeil/cs795SD/s13/Directory/[email protected]
A common use of this it to load in a personal file of login credentials and other private data:
<property file="${user.home}/.ant-global.properties"/>
The property ${user.home} maps to the user’s home directory as appropriate to the operating system. If the file .ant-global.properties exists,can load private info.
Should be protected (e.g., Unix protection 600)
If the file does not exist, this does nothing at all.
<property environment="env"/>
Copies the OS environment variables into the build state, prefaced by the indicated prefix
e.g., ${env.PATH}
2.2.2 File Sets and Lists
A file set is a collection of existing files
can be specified using wild cards
A file list is a collection of files that may or may not exist
Must be specified explicitly without wild cards
File Sets
<fileset file="src/main.cpp"/><fileset dir="src" includes="main.cpp utility.h utility.cpp"/><fileset dir="src" includes="*.cpp,*.h"/>
More commonly seen as a nested form
<fileset id="unitTests" dir="bin"> <include name="**/Test*.class"/> <exclude name="**/*$*.class"/> <exclude name="**/*Debug.class"/></fileset>
The id in the prior example allows later references:
<fileset refid="unitTests"/>
File Lists
<filelist dir="src" files="main.cpp utilities.h utilities.cpp"/>
Can also use id or refid attributes
Mappers
Allow for a transformation of file names
Some commands use a file set to describe inputs, then a mapper to describe outputs
<fileset dir="src" includes="*.cpp"/><globmapper from="*.cpp" to="*.o"/>
would map each file in src/*.cpp to a corresponding .o file
And
<fileset dir="bin" includes="**/Test*.java"/><packagemapper from="*.class" to="*"/>
would map a compiled unit test file project/package/TestADT.class to project.package.TestADT
There are several other mappers as well
Selectors
Selectors provide more options for selecting files than simple include/exclude based on the names.
For example, our previous examples assumed that unit tests would be identified by file name Test*.java.
Here we look instead for any Java file containing the JUnit4 @Test annotation.:
<fileset id="unitTestSrc" dir="src"> <include name="**/Test*.java"/> <contains text="@Test" casesensitive="no"/></fileset>
Other selectors replicate several of the tests from the classic Unix find command
2.2.3 Path Sets
Used to specify a sequence of paths, usually to be searched.
<classpath> <pathelement path="${env.CLASSPATH}"/> <fileset dir="target/classes"> <include name="**/*.class"/> </fileset> <filelist refid="third-party_jars"/></classpath>
Referencing Path Sets
For reason unclear to me, you cannot name classpaths and re-use them directly, but must do it this way
<path name="test.compile.classpath"> <pathelement path="${env.CLASSPATH}"/> <fileset dir="target/classes">
<include name="**/*.class"/> </fileset> <filelist refid="third-party_jars"/></path> ⋮ <classpath refid="test.compile.classpath"/>
2.2.4 Filters
Filters are used to modify the outputs of some commands by performing various substitutions:
<copy file="../../templates/@{format}.tex" tofile="${doc}-@{format}.ltx"> <filterset> <filter token="doc" value="${doc}"/> <filter token="relPath" value="${relPath}"/> <filter token="format" value="@{format}"/> </filterset></copy>
A filter set replaces tokens like @doc@ by a string, in this case the value of the property ${doc}
Filter Chains
Filter chains offer a variety of more powerful options, e.g.,
<loadfile property="doctitle" srcfile="${doc}.info.tex"> <filterchain> <linecontains> <contains value="\title{"/> </linecontains> <tokenfilter> <replaceregex pattern=" *\\title[{]([^}]*)[}]" replace="\1"/> </tokenfilter> </filterchain></loadfile>
loadfile loads an entire file into a property
The linecontains filter limits the portion of the file loaded to any line containing a LaTeX \title{…} command.
The tokenfilter filter does a regular expression match and replace on that line to extract only the portion of that line between the { ... }.
2.3 TasksThe Ant Manual has a good breakdown on these.
Consistent with their XML structure, tasks can be parameterized via attributes or nested XML attributes
Sometimes you can do the same thing either way.
Look at:
File tasks: copy, delete, mkdir, move, fixcrlf, syncCompile tasks: javac, dependArchive, documentation, testing tasksExecution tasks: java, exec, apply
Extending Ant
Ant has a built-in macro capability
More powerful extension is accomplished by adding Java classes, mapped onto task names:
<project name="code2html" default="build"> <taskdef classpath="JFlex.jar" classname="JFlex.anttask.JFlexTask" name="jflex" /> ⋮ <target name="generateSource"> <mkdir dir="src/main/java"/> <jflex file="src/main/jflex/code2html.flex" destdir="src/main/java"/> <jflex file="src/main/jflex/code2tex.flex" destdir="src/main/java"/> ⋮
Finding Extensions
Many Java-oriented tools (e.g. JFlex) come with an ant task as part of the package.
Other are contributed by users of the tool, (e.g. LaTeX)
Some general-purpose Ant libraries.
e.g., antcontrib adds
C/C++ compilationIf and For-loopoutofdate (a make-like file dependency wrapper)enhanced property tasks (e.g., URL encoding)
3 Case Studies3.1 Simple Java Build
build.xml.sjb.listing +
<project name="codeAnnotation" basedir="." default="build" xmlns:ivy="antlib:org.apache.ivy.ant"> <record name="ant.log" action="start" append="false" /> ➀ <path id="testCompilationPath"> ➁ <fileset dir="lib" includes="*.jar"/> <pathelement path="target/classes"/> </path> <path id="testExecutionPath"> <fileset dir="lib" includes="*.jar"/> <pathelement path="target/classes"/> <pathelement path="target/test-classes"/> </path> <property name="build.dir" value="build"/> <property name="src.dir" value="src"/> <import file="ivyBuild.xml" as="ivy"/> ➂ <target name="compile" depends="ivy.resolve-ivy" ➃ description= "Compile all source code, including the lexical analysis code generated using jflex." > <mkdir dir="target/classes"/>
<javac srcdir="src/main/java" destdir="target/classes" source="1.8" includeantruntime="false"/> </target> <target name="compile-tests" depends="compile" ➄ description="Compile JUnit tests" > <mkdir dir="target/test-classes"/> <javac srcdir="src/test/java" destdir="target/test-classes" source="1.8" includeantruntime="false"> <classpath refid="testCompilationPath"/> </javac> </target> <target name="test" depends="compile-tests" description="Run all JUnit tests, producing a summary report in target/test-results." > <mkdir dir="target/test-results/details"/> <junit printsummary="yes" ➅ haltonfailure="no" fork="no" > <classpath refid="testExecutionPath"/> <formatter type="xml"/> <batchtest todir="target/test-results/details"> <fileset dir="target/test-classes"> <include name="**/*Test*.class"/> </fileset> </batchtest> </junit> <junitreport todir="target/test-results"> ➆ <fileset dir="target/test-results/details"> <include name="TEST-*.xml"/> </fileset> <report format="frames" styledir="junit-stylesheets" todir="target/test-results/html"/> </junitreport> </target> <target name="build" depends="test" description="Construct a jar file with the compiled code and a zip file with the project source code."> <jar destfile="codeAnnotation.jar" basedir="target/classes"> ➇ <manifest> <attribute name="Main-Class" value="edu.odu.cs.code2html.Code2HTML"/> </manifest> </jar>
</target> <target name="clean"> ➈ <delete dir="target"/> </target> </project>
➀ Copy all messages to a log file, ant.log
➁ Set up some useful names for lists of paths.
➂ Ignore all “ivy” stuff for now. We’ll cover this in an upcoming lesson.
(OK, if you must know, this retrieves the latest versions of the JUnit and JFlex libraries from the internet and makes them available to this project build.)
➃ Compiles Java source code from src/main/java/, putting the .class files into target/classes/.
➄ Compiles Java source code from src/test/java/, putting the .class files into target/testclasses/.
I compile the tests separately from the “real” code so that later we can easily omit the test drivers from the published library’s binary code.
➅ Now we run the JUnit tests
haltonfailure stops the build if we failed tests, so we never move on and produce a jar with known buggy code.
➆ This produces an HTML report with summaries of how well our tests did. Example
➇ The compiled binaries for the “real” code (not the tests) are packaged into a .jar file.
Note that the .jar production is simplified by having separated the project and test compilation results.
➈ Clean up is simple: delete the target directory
You can find this entire project, with the Ant files, here.
3.2 Java Build with Code GenerationNot All Sourcecode is Hand-Written
This project adds a stage before compilation to generate some of the source code that then needs to be compiled.
build.xml.jcg.listing +
<project name="codeAnnotation" basedir="." default="build" xmlns:ivy="antlib:org.apache.ivy.ant"> <record name="ant.log" action="start" append="false" /> <path id="testCompilationPath"> <fileset dir="lib" includes="*.jar"/> <pathelement path="target/classes"/> </path> <path id="testExecutionPath"> <fileset dir="lib" includes="*.jar"/> <pathelement path="target/classes"/> <pathelement path="target/test-classes"/> </path> <property name="build.dir" value="build"/> <property name="src.dir" value="src"/> <import file="ivyBuild.xml" as="ivy"/> <target name="generateSource" depends="ivy.resolve-ivy" description="Use jflex to process lexeme descriptions for C++ and Java, generating the Java source code for the resulting lexical analyzers." > <taskdef classpath="lib/jflex-1.4.3.jar" ➀ classname="JFlex.anttask.JFlexTask" name="jflex" /> <mkdir dir="target/gen/java"/> <jflex file="src/main/jflex/code2html.flex" ➁ destdir="target/gen/java"/> <jflex file="src/main/jflex/code2tex.flex" destdir="target/gen/java"/> <jflex file="src/main/jflex/list2html.flex" destdir="target/gen/java"/> <jflex file="src/main/jflex/list2tex.flex" destdir="target/gen/java"/> </target> <target name="compile" depends="generateSource" ➂ description= "Compile all source code, including the lexical analysis code generated using jflex." >
<mkdir dir="target/classes"/> <javac srcdir="target/gen/java" destdir="target/classes" ➃ source="1.7" includeantruntime="false"/> <javac srcdir="src/main/java" destdir="target/classes" source="1.7" includeantruntime="false"/> </target> <target name="compile-tests" depends="compile" description="Compile JUnit tests" > <mkdir dir="target/test-classes"/> <javac srcdir="src/test/java" destdir="target/test-classes" source="1.7" includeantruntime="false"> <classpath refid="testCompilationPath"/> </javac> </target> <target name="test" depends="compile-tests" description="Run all JUnit tests, producing a summary report in target/test-results." > <mkdir dir="target/test-results/details"/> <junit printsummary="yes" haltonfailure="no" fork="no" > <classpath refid="testExecutionPath"/> <formatter type="xml"/> <batchtest todir="target/test-results/details"> <fileset dir="target/test-classes"> <include name="**/*Test*.class"/> </fileset> </batchtest> </junit> <junitreport todir="target/test-results"> <fileset dir="target/test-results/details"> <include name="TEST-*.xml"/> </fileset> <report format="frames" styledir="junit-stylesheets" todir="target/test-results/html"/> </junitreport> </target> <target name="build" depends="test" description="Construct a jar file with the compiled code and a zip file with the project source code."> <jar destfile="codeAnnotation.jar" basedir="target/classes"> <manifest> <attribute name="Main-Class" value="edu.odu.cs.code2html.Code2HTML"/>
</manifest> </jar> </target> <target name="clean"> <delete dir="target"/> </target> <target name="cleaner" depends="clean"> <delete dir="lib"/> </target> <target name="cleanest" depends="cleaner"> <delete dir="ivy"/> </target> </project>
➀ The taskdef “declares” a new jflex task.
➁ Here we use jflex to generate a scanner from several sets of regular expressions.
The source code for the new scanner is placed in target/gen/java, keeping it separate from our hand-written code.
➂ Compilation can only occur if generateSource was run.
➃ Here I have modified my original compilation instructions to also compile any Java source code in the new src/generated/java directory.
You can find this entire project, with the Ant files, here.
3.3 C++ Multi-project BuildBecause this project is divided into multiple subprojects, we will have multiple build.xml files:
One per subprojectAnd a master to launch them all.
3.3.1 The master file
In the project root directory:
build.xml.manroot.listing +
<project name="manhattan" basedir='.' default="build"> <record name="ant.log" action="start" append="false" /> <!-- Invoke this build file as ant - to build the project ant clean - to clean out all generated files --> <target name="build"> <ant target="build" antfile="gtest/build.xml" inheritAll="false"/> <ant target="build" antfile="lib/build.xml" inheritAll="false"/> <ant target="build" antfile="exe/build.xml" inheritAll="false"/> </target> <target name="clean"> <ant target="clean" antfile="gtest/build.xml" inheritAll="false"/> <ant target="clean" antfile="lib/build.xml" inheritAll="false"/> <ant target="clean" antfile="exe/build.xml" inheritAll="false"/> </target> </project>
3.3.2 The lib subproject
build.xml.manlib.listing +
<project name="manhattan-lib" basedir="." default="build" xmlns:cpptasks="antlib:net.sf.antcontrib.cpptasks" > <description> Builds the ADTs for the manhattan program. </description> <property file="${user.home}/.ant-global.properties"/> <record name="ant.log" action="start" append="false" /> <taskdef classpath="../antcontrib-cpptasks.jar" ➀ resource="net/sf/antcontrib/cpptasks/antlib.xml" /> <target name="compile"> <mkdir dir="build/objs/main"/>
<mkdir dir="build/libs"/> <cc outtype="static" ➁ subsystem="console" name="g++" outfile="build/libs/main" objdir="build/objs/main"> <fileset dir="src/main/cpp"> <include name="**/*.cpp"/> </fileset> <includepath path="src/main/headers"/> </cc> </target> <target name="compiletest" depends="compile"> ➂ <mkdir dir="build/objs/test"/> <mkdir dir="build/exe"/> <cc outtype="executable" subsystem="console" name="g++" objdir="build/objs/test"> <fileset dir="src/mainTest/cpp"> <include name="**/*.cpp"/> </fileset> <compilerarg value='-g'/> <compilerarg value='-pthread'/> <includepath path="src/main/headers"/> <includepath path="../gtest/include"/> </cc> <apply executable="g++"> <!-- Using apply because I can't get ➃ linking to work in antcontrib --> <arg value="-o"/> <arg value="build/exe/runTests"/> <arg value="../gtest/build/libs/libgtest.a"/> <arg value="build/libs/libmain.a"/> <arg value="-lpthread"/> <fileset dir="build/objs/test"> <include name="*.o"/> </fileset> </apply> </target> <target name="test" depends="compiletest"> <exec executable="build/exe/runTests"/> ➄ </target> <target name="build" depends="test"/> <target name="clean"> <delete dir="build"/>
</target> </project>
➀ AntContrib provides a set of C++ tasks.
➁ Compile the source code. The outtype indicates that we are generating a (static) library.
➂ Compile the unit tests. The outtype indicates that we are generating an executable.
➃ Unfortunately, I cannot get linking to work properly with the cc task, so I forced that as an OS command.
➄ Run the unit test executable.
The other subproject build.xml files are similar, but simpler.
You can find this entire project, with the Ant files, here.
4 Eclipse/Ant IntegrationLimitations of Eclipse Builder
Cannot run code-proprocessing (e.g., JFlex)
An Eclipse project is oriented towards producing a single output product (program, library, … )
With C++ projects, a problem if you have a “real” product
(e.g., a library) and a set of test drivers, each of which yields a distinct program executable.
Java projects have fewer problems (because executables don’t need separate processing),but what if you are planning to generate both
a binary distribution jar, anda source distribution jar?
Project Dependencies
Eclipse supports the idea of projects that depend on other projects, so you could do
project1 produces the binary distribution jar
project2 depends on project1 and produces a source distribution jar
These projects must reside together in a known relative path from one anotherproject2 is not automatically rebuild if project1 has changed
Does not scale well.
For C++ are you going to have a distinct project for each test driver?
Eclipse/Ant Integration
Eclipse is generally ant-friendly.
Drop a build.xml file into a project and Eclipse will recognize it.
Right-clicking on it will bring up options to run it, or to configure how to run itincluding the selection of the targetsome preference given to targets with descriptions
Once ant has been run, the “Run Last Tool” button defaults to re-running it.
But the default build is still Eclipse’s default build manager
For projects with elaborate classpaths, requires keeping both the Eclipse project description and the build file up-to-date and consistent.Pre-compilation steps (e.g., tools that generate source code) are not re-run automatically when needed.
Eclipse Builders
Eclipse supports multiple, plug-able builders.
Open Project Properties and go to “Builders”
In a typical java project, you have just the “Java Builder”Click new to see options.
In this case, select “Ant Builder”.
Fill in the main screen. Leave “Arguments” blank.
Go to the Targets tab. Select appropriate targets for
Clean: Menu selection Project->clean
Manual build: What you want done after explicitly requesting a build
Auto build: What you want done after a file has been saved/changed
Return to the Builders list and uncheck the “Java Builder”
Managing Builds with MavenSteven J Zeil
Last modified: Mar 2, 2020
Contents:1 Why Maven?2 Maven as a Build Manager3 Case Studies
3.1 Simple Java Build3.2 Java Build with Code Generation3.3 C++ Multi-project Build
Abstract
Maven combines build management with configuration management (a future topic in this course). It attempts to standardize the build process in a way thatimproves consistency from project to project and enforces local best practices.
In this lesson, we will look at the capabilities and limitations of Maven and how projects can be structured to use it. We will look at how it works with some ofour sample projects.
1 Why Maven?Another Apache project, Maven came well after Ant had come to dominate the Java open source landscape.
Initially seen as a competitor or replacement for Ant
Maven addresses both
build management (as does Ant)and configuration management (which Ant does not)
Later, we’ll talk about Ivy, which adds configuration mgmt to Ant
Maven as a Build Manager
Maven uses an underlying task dependency model, but the tasks and their dependencies are pre-defined for a collection of archetype projects.
As a consequence, maven offers far less flexibility than ant, but a basic, unaltered maven build may include tasks that less experienced developers would neverthink to include or might have difficulty adding to a more conventional build manager.
Motivations for Maven
Grew out of an observation that many supposedly cooperative, related Apache projects had inconsistent and incompatible ant build structures.
Stated goals are
Making the build process easy
Providing a uniform build system
Providing quality project information
Providing guidelines for best practices development
Allowing transparent migration to new features
Uniform Build System
Maven supports archetype projects that standardize
directory structure
Source code kept in separate directory tree from both intermediate and final build productsTests occupy separate subtrees of the source and product trees
“Life Cycle”
Really, a presumptuous name on their part for a build processA sequence of goals
Archetypes can be obtained from the Maven project or tailored for an organization.
Providing quality project information
Provides easy access to report tools
Aids in building & maintaining project web sites (e.g.)
Providing guidelines for best practices development
Directory structures (already discussed)
Unit testing
Encourage familiarity with approved archetypes
2 Maven as a Build ManagerPerhaps the best way to illustrate this is to follow the steps in Maven in 5 Minutes
Start with the command
mvn archetype:generate -DgroupId=edu.odu.cs \ -DartifactId=codeAnnotation \ -DarchetypeArtifactId=maven-archetype-quickstart \ -DinteractiveMode=false
Lots of libraries (mainly comprising the latest version of Maven system itself) will be downloaded into your \string~/.m2 directory.A directory, codeAnnotation, will be created.
cd into the new directory and explore
src directory structurepom.xml is the build file for this project
Building with the Sample Source
Run
mvn package
Sample source code is compiledSample unit test is run and executed.A jar file is created with the sample source code
Explore the target directory to see what has been placed there
OK, that was fun…
Let’s try this with some real source code.
Delete the target directory (or run mvn clean)
Replace the contents of src/main/java and src/test/java by the corresponding contents from my code Annotation project.
Also, copy src/main/jflex while we’re at it, though we won’t use this right away.
Try mvn package again.
What Went Wrong?
A glance at the code with the error messages won’t show anything obvious.
But I happen to know that Maven defaults to running the Java compiler in Java 5 compatibility modeThis code uses Java 7 features
Edit the pom.xml file and, just above the <dependencies> section, add
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.7</maven.compiler.source> <maven.compiler.target>1.7</maven.compiler.target> </properties>
Then try mvn package again
That was a little better
The main source code compiled successfully.
The error was in the compilation of the unit tests
The first error message says
[ERROR] /home/.../codeAnnotation/src/test/java/edu/odu/cs/codeAnnotation/TestC2HOptions.java:[3,23] package org.junit does
Looks to be a problem with the JUnit library
In the original project, I was keeping a copy of junit4.jar in the project directory.
A clumsy solution
Which we’ll solve when we take up configuration management.
3 Case Studies3.1 Simple Java BuildWell, we pretty much just did that.
1. Use mvn archetype:generate to set up the directories and build file.
2. Replace maven’s “Hello World” code by the real source code.
3.2 Java Build with Code GenerationNot All Sourcecode is Hand-Written
This project adds a stage before compilation to generate some of the source code that then needs to be compiled.
pom.xml.listing +
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>edu.odu.cs</groupId> <artifactId>codeAnnotation</artifactId> <packaging>jar</packaging> <version>1.0</version> <name>codeAnnotation</name> <url>https://www.cs.odu.edu/~zeil/cs795SD/s13/Directory/topics.html</url> <description> This is a tool used to parse code listings and to generate syntax-highlighted C++/Java listings in both HTML and LaTeX. Markup can be added in the form of special comments that will be recognized as instructions to highlight blocks of code, to add callout symbols, or to insert vertical ellipses. </description> <!-- site generation: mvn test mvn surefire-report:report mvn site
--> <repositories> </repositories> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>de.jflex</groupId> <artifactId>jflex</artifactId> <version>1.4.3</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>de.jflex</groupId> <artifactId>maven-jflex-plugin</artifactId> ➀ <version>1.4.3</version> <executions> <execution> <goals> <goal>generate</goal> ➁ </goals> </execution> </executions> </plugin> </plugins> </pluginManagement> </build> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties></project>
The highlighted portions represent (most) of the changes required.
➀ Here we announce our intention to use a plugin that adds a jflex step to a java build.
➁ Here we declare that jflex will be invoked during the generate build step.
How do we know where to put our jflex input files?
We learn that by reading the documentation of the plugin.
How do we know that the proper time to use it is during the generate task?
We learn that by reading the documentation of the Java project archetype.
3.3 C++ Multi-project BuildNot even going to try and go there.
I’ve never had much luck getting maven to work with C++.
Task Dependencies: GradleSteven J Zeil
Last modified: Dec 21, 2019
Contents:1 Gradle Overview
1.1 What to keep and leave from Ant1.2 What to keep and leave from Maven1.3 What does Gradle offer?1.4 The Gradle Wrapper
2 Running Gradle3 Build Files
3.1 Gradle Tasks3.2 Anatomy of a Task3.3 Task Dependencies3.4 Tasks3.5 Doing Maven-Like Things with Gradle
4 Case Studies4.1 Simple Java Build4.2 Java Build with Code Generation4.3 C++ Multi-project Build
Abstract
Gradle is a build manager based upon an Ant-like task dependency graph expressed in a more human-friendly notation, with a Maven-like ability to expressstandard project layouts and build conventions.
In this lesson we look at how Gradle combines some of the better features of Ant and Maven, while providing a more convenient notation than either.
We will look at how Gradle could be applied to some of our sample projects.
1 Gradle OverviewGradle devised by GradleWare, founded by Hans Dockter, released in 2012
Has become the standard build tool for Android
Tries to strike a middle ground between Ant and Maven
1.1 What to keep and leave from AntKeep:
PortabilityBuild commands are described in a platform-independent manner.
FlexibilityAlmost any sequence of processing steps can be described.Allows for unconventional build sequencesAllows for unconventional build targets
Leave
XML as a build language.
“XML was an easy choice for a build tool a decade ago, when it was a new technology, developers were enthusiastic about it, and no one yet knewthe pain of reading it in large quantities. It seemed to be human-readable, and it was very easy to write code to parse it. However, a decade ofexperience has shown that large and complex XML files are only easy for machines to read, not for humans. Also, XML’s strictly hierarchicalstructure limits the expressiveness of the format. It’s easy to show nesting relationships in XML, but it’s hard to express program flow and dataaccess the way most common programming language idioms express them. Ultimately, XML is the wrong format for a build file.”
Tim Berglund, Learning and Testing with Gradle
Inability to express simple control flowe.g., loop through all files in a directory and do task
1.2 What to keep and leave from MavenKeep:
Dependency management
Gradle will work with Maven & Ivy repositories.Early versions of Gradle actually used Ivy, though eventually it gained its own dependency manager.
Standard directory layouts and build conventions for common project types.
If your project has nothing unusual about its build, you can sit back and use the defaults.
Leave
XML as a build language.
Inflexibility
If your project has anything unusual about its build, changing the defaults in Maven is frustrating.
Inability to express simple control flow
1.3 What does Gradle offer?Build files are written in Groovy
Groovy is a scripting language that runs in a Java JVMSyntax is Java-basedInterfacing with Java code is easy
Gradle adds build-specific functions as a Groovy library
Gradle is built on top of the Ant libraries.
All conventional Ant tasks are available.Ant build files can be incorporated as functions of a Gradle build.
Hans Dockter describes Gradle, compares it to Ant and Maven, and shows lots of examples (in Eclipse).
1.4 The Gradle WrapperSuppose that you are building your project with Gradle.
Other people may try to check out a copy of your code, including your Gradle build file.But maybe they won’t have Gradle on their machine.
If you set up your project with the Gradle Wrapper, you get a simple script named gradlew.
You invoke your build via ‘gradlew’ instead of ‘gradle’gradlew checks to see if the system on which it is running has Gradle installed.
If not, it downloads a copy and installs it under $USER_HOME/.gradleIt then invokes gradle (old installation or new), passing any parameters you supplied to the gradlew command.
This works nicely for projects that distribute their source code via any of the version control systems that we will be discussing later.
2 Running Gradle
gradle looks for its instructions in a file named, by default, build.gradle
The gradle command can name any task (or list of tasks) as targets to bebuilt, e.g.,
gradle setup compile
If no target is given, gradle can use a default task if one has beendeclared in the build file.
gradle Options
Some useful options:
-m, –dry-runList steps that would be run without actually doing anything.
-t, –tasksList tasks that can be used as targets.
-b filename, –build-file filename
Use filename instead of the default build.xml. Also -file or -buildfile-Dproperty=value
Sets a property (similar to make’s variables)-q, –quiet
Suppress most output except for error messages.
gradle Tasks
Some built-in tasks that you can use as targets:
taskslist all available tasks
helpBy itself, explains how to get more help on using Gradle. With --task, ask for a description of a specific task.
initUsed to set up a new project using Gradle default properties.
wrapperAdds the Gradle wrapper to the project.
Usually accompanied by an option
--gradle-version versionNumber
to generate a wrapper for a specific version of gradle.
3 Build FilesThe gradle build file is a Groovy script, with Java-like syntax. Many of the more common Java API packages are imported automatically.
task upper << { String myName = "Steven Zeil"; println myName; println myName.toUpperCase(); }
If this is placed in build.gradle, then we can run it:
$ gradle upper :upperSteven Zeil
STEVEN ZEIL BUILD SUCCESSFUL Total time: 1.747 secs $
3.1 Gradle TasksThe basic elements of a Gradle build file are tasks.
Gradle tasks can correspond Ant targets.
A Gradle task can perform multiple actions.
task upper { doLast { String myName = "Steven Zeil"; println myName; println myName.toUpperCase(); } }
Gradle tasks can correspond individual Ant tasks.
A Gradle task can perform multiple actions.
task copyResources (type: Copy) { from(file('src/main/resources')) into(file('target/classes')) }
In Ant, we would have used a <copy> task within a larger Ant target for this purpose.
3.2 Anatomy of a Task
3.2.1 The phases of a Gradle run
Before looking at the components of a task, we need to understand a bit about how Gradle works.
A Groovy function with parameters, e.g.,
void foo (int x, int y, int z) { ... }
can be called using positional arguments:
foo (12, 14, 0)
or by using named arguments:
A Gradle run occurs in four specific phases.
1. Initialization takes care of things that affect how Gradle itself will run. The most visible activity during this phase is loading Gradleplugins that add new behaviors.
2. Configuration involves collecting the build’s tasks, setting the properties of those tasks, and then deciding which tasks need to beexecuted and the order in which that will be done.
3. Execution is when the tasks that need to be executed get run.
4. Finalization covers any needed cleanup before the Gradle run ends.
Software developers will be mainly concerned with the middle two phases. For example, if we need to compile some Java source code for aproject, then we would want to configure that task by indicating the source code directory (or directories) and the destination for the generatedcode. With that information, Gradle can look to see if the .class files already exist from a prior run and whether the source code has beenmodified since then. Gradle can then decide whether or not the compilation task actually needs to be run this time. This decision isremembered during the execution phase when the task will or will not be run, accordingly.
3.2.2 Declaring tasks
A Gradle task can be declared as easily as:
task myTask
Some tasks may need parameters:
task copyResources (type: copy)
foo (z: 0, x: 12, y: 14)
One advantage of the latter form is that you don’t have to know the order inwhich the parameters appeared in the function declaration. The named form iseven more useful when all or most of the parameters have default values and, inyour call, you only want to supply the value to one or two parameters for whichyou don’t like the defaults.
3.2.3 Configuring tasks
You can add code to be run at configuration time by putting it in { } brackets just after the task name:
task copyResources (type: Copy) copyResources { description = 'Copy resources into a directory from which they will be added to the Jar' from(file('src/main/resources')) into(file('target/classes')) }
You can combine a configuration with the task declaration:
task copyResources (type: Copy) { description = 'Copy resources into a directory from which they will be added to the Jar' from(file('src/main/resources')) into(file('target/classes')) }
3.2.4 Executable Behavior
Task types often have pre-defined behaviors.
For example, the Copy type copies files at execution time
task copyResources (type: Copy) { from(file('src/main/resources')) into(file('target/classes')) }
The from and into calls are performed at configuration time.
The Copytype actually copies the files at execution time.
gradle will check to see, at configuration time, if the files already exist at the destination and appear to be no olderthan the ones at thesource. If so, the copyResources task will be skipped at execution time.
3.2.5 Adding Executable Behavior
You can add code to be run at execution time by attaching it, within { }, using the doLast operation.
task copyResources (type: Copy) { from(file('src/main/resources')) into(file('target/classes')) doLast { println 'Copy has been done.' } }
or you can use that operation to add the code to an already-declared task object.
task copyResources (type: Copy) { from(file('src/main/resources')) into(file('target/classes')) } ⋮ copyResources.doLast { println 'Copy has been done.' }
3.3 Task Dependencies
task setupFormat task setupGraphics task setupSourceCode task generatePDFs (dependsOn: 'setup') generatePDFs.doLast { ➀ println 'in task generatePDFs' }
task setup (dependsOn: ['setupFormat', 'setupGraphics', 'setupSourceCode']) setup.doLast { ➁ println 'in task setup' } task deploy (dependsOn: generatePDFs) deploy.doLast { println 'in task deploy' }
➀ : This line shows a dependency. Note that the task on which we aredepending has not been declared yet.
➁ : The [a, b, ...] notation introduces a Groovy array value.
The dependsOn parameter expects an array. But note that we did not usean array at ➀ . like most scripting languages, Groovy has lots of littleshortcuts designed to make like easier for programmers. At ➀ , we gotaway due to a shortcut that allows a single element to be passed as anarray of length 1.
Running this gives:
$ gradle deploy :setupFormat UP-TO-DATE:setupGraphics UP-TO-DATE:setupSourceCode UP-TO-DATE:setupin task setup :generatePDFsin task generatePDFs :deployin task deploy
3.3.1 Appending to Tasks
doLast (and doFirst) add actions to a task.
If we add the following to the previous script:
setup.doLast { println 'still in task setup' }
$ gradle deploy :setupFormat UP-TO-DATE:setupGraphics UP-TO-DATE:setupSourceCode UP-TO-DATE:setupin task setup still in task setup:generatePDFsin task generatePDFs :deployin task deploy
3.4 TasksAt its heart, a build file is a collection of tasks and declarations.
A task is a Groovy function. It has a name, a body, and, optionally,
a list of dependenciesa conditiona human-readable description
The body of a task can contain multiple declarations and commands.
Gradle tasks are equivalent to Ant “targets”.
Ant tasks are handled as function calls in the body of a Gradle script.
3.4.1 Using Java within tasks
task playWithFiles { doLast { def files = file('src/main/data').listFiles().sort() ➀ files.each { File file -> ➁ if (file.isFile()) { ➂ def size = file.length() ➃ println "** $file.name has length " + size ➄ } } } }
➀ There’s several things going on here. Let’s take it piece by piece.
def declares a variable. In this case, the variable is named files.
Like most scripting languages, Groovy (and therefore Gradle) is loosely (dynamically) typed, so we generally do not bother declaring variables bygiving their type, though that’s certainly possible.
Of course, that means that we have to pay close attention to the return values of our functions calls and operator uses if we, as programmers,want to know what kind of data will be stored in a variable.
file(...) returns a value of type File. In fact, this is a good old fashioned Java java.io.File, so we can look to the Java documentation to seewhat can be done with it.
One of the things we can do with it is to call listFiles(), which treats the File on the left of the ‘.’ as a directory and produces an array of Filerepresenting all the files in that directory.
And, knowing that listFiles() produces and array of files, it’s pretty obvious what .sort() would do.
We conclude that the variable files will hold a sorted list of all the files in directory src/main/data.
➁ The .each function is a Groovy function on arrays that allows us to iterate through the elements of the array, one at a time. On each iteration, we willstore the current array element in the variable file which we have declared, for clarity, as being of type File.
➂ Another thing we can do with Files is to check and see if they are “regular” files as opposed to being directories, links, or other special cases.
Again, this is not a special Gradle function, but is part of the normal Java behavior of a File class.
➃ The call to file.length() is just an ordinary Java function call.
➄ Several interesting things happen here.
The ‘$’ inside the quoted string allows us to request the replacement of a simple expression by its value. So we won’t actually print “file.name”.Instead the expressions file.name will be evaluated and the resulting value inserted into the string to be printed.
This “$” string substitution has been a common shortcut in scripting languages for decades.
Now, Java Files do not have a public data member called “name”, so the expression file.name would fail to compile in Java.
Java Files do, however, have a public function member getName(). And here we run into another one of those shortcuts that Groovy, as a scriptinglanguage, introduces. In Groovy, the notation x.data is considered a shorthand for x.getData() when we are trying to fetch data and forx.setData(..) when we are trying to store data. So the Groovy statement
x.data = y.member;
is actually considered shorthand for
x.setData(y.getMember());
The + operator is the conventional Java String concatenation operator.
3.4.2 Using Ant within Tasks
The ant tasks library is included within gradle. So any useful ant task can be called:
task compile { doLast { // Compile src/.../*.java into bin/ ant.mkdir (dir: 'bin') ant.javac (srcdir: 'src/main/java', destdir: 'bin', debug: 'true', includeantruntime: 'false') ant.javac (srcdir: 'src/test/java', destdir: 'bin', debug: 'true', includeantruntime: 'false', classpath: testCompilePath) println 'compiled' } }
However, we probably would not use any of these:
Most Gradle users would use Java constructs for file manipulations, e.g., instead of
ant.mkdir(dir: 'bin')
they would write
file('bin').mkdir();
Gradle provides a Java plugin to handle compilation more easily.
Moreover, if we already had the Ant build file simpleBuild,xml, we could actually simply import it into a Gradle build:
ant.importBuild 'simpleBuild.xml' task build (dependsOn: 'deploy') .doLast { // "deploy" target from the Ant build file println 'Done' }
3.5 Doing Maven-Like Things with GradleLike Maven, Gradle can be used to quickly create new projects with a standard directory/file layout and a standard sequence of build tasks. E.g.,
gradle init --type java-library
sets up a project with src/main/java and src/test/java directories.
4 Case Studies4.1 Simple Java BuildUsing the Java plugin, a basic build with unit tests is easy:
settings.gradle
// A simple Java project
This file needs to exist, but can be empty.
build.gradle
plugins { id 'java' ➀ } repositories { ➁ jcenter() }
➀ This loads and runs the Java plugin.
As long as our source code is arranged in the Apache/Android directory style, it will be handled correctly.
➁ The repositories section on is concerned with configuration management (covered later)
This is all you need to compile Java code stores in src/main/java.
If you have unit tests, we need to add a little more info.
build.gradle
plugins { id 'java' } repositories { jcenter() } dependencies { testImplementation("junit:junit:4.12") ➀ testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.5.2") ➁ } test { ➂ useJUnit() }
The two sections at the end establish that
➀ When compiling our unit tests, we need to have the JUnit library, version 4.12, available.➁ When running our unit tests, we need the library junit-vintage-engine, version 5.5.2, available.➂ The code in the src/test directory contains our JUnit tests.
You can find this entire project, with the Gradle files, here.
4.2 Java Build with Code GenerationThis project adds a stage before compilation to generate some of the source code that then needs to be compiled.
The settings.gradle file is unchanged.
Here is the build.gradle file:
build.gradle
plugins { id 'java' id 'org.xbib.gradle.plugin.jflex' version '1.2.1' ➀ } repositories {
jcenter() } dependencies { testImplementation("junit:junit:4.12") testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.5.2") / } test { useJUnit() }
➀ Here we apply the jflex plugin,which knows to run jflex on code located in src/main/jflex to generate Java source codeand to compile that generated Java source code along with the rest of the project.
You can find this entire project, with the Gradle files, here.
4.3 C++ Multi-project BuildA multi-project build in gradle is kept in a directory tree.
The top-most directory is the master project.
It contains the settings.gradle file.
Each subproject directory contains its own build.gradle file.
4.3.1 The master project
Any multi-project build in Gradle uses the settings.gradle file (usually in the common root containing the subproject directories) to identify the subprojects:
rootProject.name = 'manhattan' include "application", "geomlib"
In this case, we have two subprojects. One provides the executable, the other a library of ADTs, with unit tests, constituting the bulk of the code design.
There is no need for build.gradle file at this level, although it would be legal to provide one if you have project-level steps to be carried out.
4.3.2 The geomlib subproject
Of the two subprojects, the geomlib subproject is probably most interesting because it does the most. It compiles code into a library, then compiles and runsunit tests on that library code. By contrast, the application subproject only compiles code.
Here is the build.gradle file for the lib subproject:
build.gradle.lib.listing +
plugins { id 'cpp-library' id 'cpp-unit-test' } unitTest { binaries.whenElementKnown(CppTestExecutable) { binary -> if (binary.targetMachine.operatingSystemFamily.linux) { binary.linkTask.get().linkerArgs.add('-pthread') } } }
This listing starts off with two plugins, one for C++ compilation (into a reusable library) and the other for unit testing.
The unit test plugin can work with a variety of frameworks. Eventually, you should be able to load these frameworks as dependencies, much as you do inJava projects.
In this case, I am using CppUnitLite because it can be easily dropped into the directory with the unit tests.
Like most unit test frameworks, this one runs the tests in a separate thread (process). The unittest configuration here adds the required pthread librarywhen compiling under Linux.
4.3.3 The application subproject
The application subproject is simpler, because it only has one job to do – create an executable. Here it is:
build.gradle.exe.listing +
plugins { id 'cpp-application' } dependencies { implementation project(':geomlib') }
Very simple: we invoke the cpp-application plugin andIndicate that this depends on the :geomlib subproject
This dependency guarantees that the geomlib library will be constructed before this application is built and that it will be automatically included into theapplication compilation and link steps.
You can find this entire project, with the Gradle files, here.
Lab: Building with GradleSteven J Zeil
Last modified: Mar 7, 2020
Contents:1 Find That Project2 Compiling and Cleaning Up3 Restructuring Your Project4 Building with Gradle
This is a self-assessment activity to give you practice in working with the ant build manager. Feel free to share your problems, experiences, and choices in theForum.
1 Find That ProjectIn your assignment for the Unit Testing section of the course, you have been working with an ADT and accompanying tests.
We’ll use that code as a starting point for this lab. (If, for some reason, you don’t have working code for that assignment, you can use your project from thisearlier lab, but those tests were not very interesting.)
2 Compiling and Cleaning Up1. Copy the source code from your unit testing assignment, including both the instructor-provided code and your own unit tests, into a convenient directory.
2. Create a new Eclipse Java project at that directory. Remember to add the JUnit library to the build path of the new project.
3. Your code should compile under Eclipse to start with.
If your code was compiling but you have problem with this copied code, remember that a class packageName1.packageName2.className must be storedas .../packageName1/packageName2/className.java, where the “…” is the directory that serves as the common root of all of your source code.
4. Your project should include at least one unit test. Right-click on the .java file for such a test and select “Run As… JUnit test” to be sure that your testsrun.
3 Restructuring Your ProjectIn the lectures we talked about conventional ways of organizing the files that make up a project, including the Apache Foundation’s organization for Javaprojects.
Some important characteristics of this organization are
All the “source” files (code and data) written by the developers go into a src/ directory.All the files generated by the build process go into a target directory.The src directory is divided into src/main and src/test.
Files generated from src/main are assumed to become part of the final deliverable.Files generated from src/test are used during testing, but not included in the final deliverable.
The src/main and src/test directories are further divided according to programming language and/or “type” of data. Common subdirectories are java,data, and resources.
At a minimum, an Apache-style Java project should expect to have src/main/java and src/test/java directories.
Now we’ll look at restructuring a project to meet these guidelines.
1. Do this outside of Eclipse using normal operating system commands (command-line or GUI): Go to the directory holding your project. (If you aren’t surewhere that is, right-click on the project name in Eclipse, select Properties, and look at the Resource description.) Create src/main/java andsrc/test/java directories. Move your non-test Java files to src/main/java and move your Unit test Java files to src/test/java.
Remember that, in Java, package structure must match directory structure. So if your Java project includes a class MyClass in packagecs330.asst1, then you should currently have it in a location cs330/asst1/MyClass.java. When moving your code to the src/main/java andsrc/test/java directories, you need to keep that whole directory structure. In this example, you would wind up withsrc/main/java/cs330/asst1/MyClass.java.
2. Return to Eclipse, right-click on your project name, and select “Refresh”. Eclipse will discover your new directory structure, but won’t know what tomake of it. You’ll almost certainly wind up with error messages suggesting that your classes are in the wrong packages.
3. We need to tell Eclipse about our intentions to keep our source code in src/main/java and src/test/java. The key to doing that is the project BuildPath.
Right-click on any file of your project and select “Build path => Configure Build Path”. Go to the Source tab. This is where we tell Eclipse where to lookfor Java source code. Remove whatever folders are currently listed. Then add the folders src/main/java and src/test/java.
Click OK and, within a few seconds, you should see those error messages disappear.
4. One nice thing about adopting a standard project directory organization is that build files for that organization tend to be pretty similar. You can often re-use build files from one Apache-style project for another.
4 Building with GradleDo these steps from the command line, not in Eclipse:
1. Download and unpack this zip file in your project directory.
2. Use a text editor to create settings.gradle and build.gradle files mirroring the simple Java build
3. Check the build by asking for some basic information:
./gradlew tasks --all
(For Windows, omit the “./”).
4. Try running the actual build from the command line:
./gradlew build
(For Windows, omit the “./”).
Look through the build/ directory to see what was done.
5. Enter Eclipse, and delete your project from Eclipse (but do not delete the files).
6. Back in the command line, delete the files .project and .classpath.
(These are the Eclipse project settings from our earlier work with this directory. Since we are about to re-use this directory for a very different Eclipsesetup, it’s safer to start “fresh”.)
7. Right-click in the Eclipse Project Explorer area, and select “Import…”, Gradle, Gradle Project, and point it at the directory where your project waslocated.
For the gradle version, indicate that you are using the gradle wrapper.
Once you indicate that you are finished with the varios project settings, Eclipse will examine your settings.gradle and build.gradle files andconfigure its own settings accordingly. There may be a bit of a pause while Eclipse loads up-to-date versions of any third-party libraries (e.g., JUnit andHamcrest) listed in your build.grade file.
8. You should still be able to work in Eclipse and
edit your source code, getting instant response to many errors as you type.
see your project rebuild whenever you save a changed .java file.be able to run the Java program and your unit tests.
Try making some small changes to the code and verify that everything still works from Eclipse as normal.
9. Right-click on the project name, select “Build Path … Configure Build Path”.
Examine the source directories that have been set up. You should see that Eclipse/Gradle have recognized your src/main/java and src/test/javadirectories.
Look at the Libraries tab. You should see a library “Project and External Dependencies”. Expanding that will show you libraries that Eclipse/Gradle havedownloaded and incorporated into your project.
We’ll talk more about how that happens in an upcoming lesson.
10. Now try launching your project build from the Gradle Tasks tab in the lower central panel.
11. One oddity that you might notice if you look at your project directory using the operating system is that you have two sets of compiled class paths, one inbin/ (caused by Eclipse compilation) and another in build/ (caused by Gradle compilations). It’s possible to force Eclipse to use build/ as well, butprobably not worth the effort.
Oddly, though, you probably can’t see the build directory in Eclipse because the Eclipse Project Explorer has several selective “filters” that it appliesbefore deciding what to show you.
Let’s change these filters so that you can see stuff that’s important.
At the top of the Project Explorer pane, look for a small triangle indicating a drop-down menu. Click on it and select “Filters …”
Remove the check marks from “.* resources” and from “Gradle Build Folder”.
12. Now that you can actually see your build/ folder, browse through it. Among other things, you should see a report generated that summarizes the unittests that you have run.
Try reading that report.
When the Eclipse Project Explorer is showing you an HTML file, you can usually view it by right-clicking on the file and seelecting Openwith... then System Editor, as most systems will define the system “editor” for HTML files as your default web browser.
13. You have now practiced running Gradle from the command line and from within Eclipse. You can use either as you prefer.
Remember, however, that whenever you use programs or commands outside of Eclipse to make changes to the project directory, you need torefresh your Eclipse project when you return to Eclipse. (Right-click on the project name and select “Refresh”.)
It’s often convenient to run Gradle from within Eclipse to do things not supported by or not so easy to do directly in Eclipse.
Try running the Gradle task “jar” within Eclipse. Refresh Can you find the generated .jar file. (Hint: Gradle builds everything somewhere within thebuild/ directory.)
Now try running the Gradle javadoctask within Eclipse. Find the newly generated HTML and browse it. (We’ll talk more about Javadoc in comingweeks.)
Software Configuration ManagementSteven J Zeil
Last modified: Dec 21, 2019
Contents:1 Problems Addressed by SCM
1.1 Codelines and Baselines2 Environment Management3 Build Management as an SCM Activity
3.1 Baselines Managed by Build Manager3.2 Simpler Project Structure
4 Version Control as an SCM Activity4.1 Codelines == Branches
5 Change Management5.1 Making Decisions5.2 Change Propagation
Abstract
Over time, a software system can exist in many versions:
revisions created as developers check in changesconfigurations intended for different hardware, operating system, or application environmentsreleases issued to users
which, if under continued support, may have separate tracks of revisions recording separate bug fixes
Software Configuration Management (SCM) is concerned with all of these.
Some of the tools we have already looked at (build management, version control) can help. But in SCM we need to look at the strategic application of those tothe management of many different software versions.
1 Problems Addressed by SCMSCM Activities
Version control
Build management
Environment management
Change management
The last two are new.
The first two we have discussed in isolation.
But the broader SCM context may alter how we use some of them
1.1 Codelines and BaselinesA codeline is a sequence of revisions of a configuration item
In essence, a branch
A baseline is a collection of component versions that make up a system.
1.1.1 Codelines and Baselines: Example
A baseline merges components that we have written (a version out of our codelines) with versions of 3rd party components that we employ.
1.1.2 Baselines
A major challenge of SCM is coping with multiple baselines that must
co-exist andbe actively maintained.
Major issues are
deciding when to “freeze” on a version of an imported librarytracking the transitive closure of dependencies from libraries that we directly depend upon
finding a mutually compatible set of versions among all those external libraries
2 Environment ManagementCoping with the different environments in which the software may need to be installed and/or built.
Strategies includeseparate files
Easier to manage in the C/C++ world than in Javadeltas (patches)conditional compilation
Favored in the C/C++ worldHarder to support in Java world
dynamic loadingCommon in the Java worldOften controlled by “property files” that name modules to be employed for designated tasks in a given installation.
Example: the ODU Extract Project
Metadata extraction system, needed to support
One release version (thank the heavens)
Windows, Linux, & Mac platforms
Choice of 2 OCR programs (or none at all)
With local or network access to licensed copiesWith or without caching of OCR results
Statistical models trained on different document collections
Varying client requirements for data post-processing
Problem was not so much the number of choices as the combinatorics.
Third Party Libraries
Baselines may involve multiple 3rd-party libraries.
Different baselines may need differentlibraries.Or different versions of the same library
Luckily, there’s automated support for this aspect ofSCM, which we will cover separately.
3 Build Management as an SCM Activity
In an SCM context, we may have multiple baselines.
The build manager needs to be flexible enough to allow us to build each of those on command.
In some cases, using remote machines or virtual machines, to build all of them on (one) command.
3.1 Baselines Managed by Build ManagerBuild manager is told what external libraries are needed
including desired versionsThis is easier to do once we get a handle on 3rd party library management.
Build manager may be responsible for collecting desired versions of both external and internal code from version control.
Build files are themselves managed as part of each version.
3.2 Simpler Project StructureIn current practice,
Large projects composed of multiple subprojects are discouraged
in favor of smaller, independent projects (e.g., one per original subproject)
A common rule of thumb is that one project should produce one product (e.g., a single Jar file)
Plus, perhaps, a source distribution.
and those are increasingly being replaced by centralized VC repositories
4 Version Control as an SCM ActivityWe’ve spent time looking at version control already.
We have lots of flexibility in terms of
Access to the version historyBranchesDistributed vs central access.
SCM challenges us to think strategically about how to exploit that flexibility.
4.1 Codelines == BranchesMain trunk moves forward in time
Each planned release is a branch from the trunk
continues forward through its maintenance lifetime
5 Change Management
When do proposed changes become an official part of a version?When do changes propogate multiple versions?
5.1 Making DecisionsIn large organizations, changes are approved by a Change Management Board.
E.g., the team working in an exploratory branch has demonstrated an attractive new feature.
Should we adopt it?If so, which of the version code lines should it be added to?
5.2 Change PropagationEven in smaller projects, the issue of change propagation across code lines needs to be kept in mind.
The whole “main trunk moves forward” idea presumes that most release changes are syncedinto the trunk:
As a practical matter, someone has to decide whether bug fixes in older versions can andshould be merged into later versions.
Managing Third-Party LibrariesSteven J Zeil
Last modified: Mar 30, 2020
Contents:1 Issues2 Basic Concepts
2.1 Repositories2.2 Identifying Packages2.3 Scopes
3 Adding Libraries to a Java Project in Different Build Managers3.1 Maven3.2 Ivy (ant)3.3 Gradle
4 Eclipse and Library Management4.1 Eclipse and Maven4.2 Eclipse and Ant/Ivy4.3 Eclipse and Gradle
5 Complicating Factors5.1 Plug-ins5.2 Publishing5.3 Login Credentials
6 What about C++?6.1 What’s Holding Us Up?6.2 On the Horizon
Abstract
Modern build managers provide support for
importing 3rd-party libraries,managing dependencies among those libraries, andpublishing our own project deliverables.
so that all of these can be handled quickly and efficiently as part of the normal build.
1 IssuesBaselines may include multiple 3rd-party libraries.
We may rely on specific version of our chosen libaries.
Older versions may not provide the functions we need, or may have bugs that would affect our project.Newer versions may alter or remove functions we have been using, or introduce new bugs that affect our project.
Those libraries may require other libraries.
The library versions that we require might onloy be compatible with specific versions of those other libraries.
Example: Report Accumulator
A project of mine, this page lists the 3rd party libraries required by the project.
The page lists various stages of the build.
Expand each stage to see the libraries that I requested for that stage
E.g., for testCompile, I requested JUnit 4.12 8 Which in turned needed hamcrest-core 1.3
For the findbugs stage, I requested findbugs 3.0.1
And that required multiple libraries, including asm-commons 5.0.2asm-commons 5.0.2 required asm-tree 5.0.2asm-tree 4.0.2 asm-asm 5.0.2
2 Basic Concepts2.1 RepositoriesA repository is a collection of packages (usually libraries) and metadata about packages
The metadata identifiesthe package,the versions available,their dependencies on other packages, andthe location from which the package may be downloaded.
In some cases, the metadata may point to other repositories as the actual download location.
2.1.1 Common Java Repositories
The Java world is dominated by
Maven Central (a.k.a ibiblio)
JCenter
Try searching each of these for junit
Notice range of versions availableSelect one (e.g., 4.12)
Explore the snippets for the various build managersThese are instructions on how to add the library to your build.
2.1.2 Local Repositories
It’s common for development organizations to host their own repository.
A place to put their own deliverablesA “cache” to speed access to libraries commonly used by many developers within the organization.
preserve older versions needed for a project baseline
Local repositories can be
elaborate (Artifactory )
These may support a “pass-through” to Maven Central and/or JCenter for libraries that have not yet been locally cached.
or little more than a file drop
2.2 Identifying PackagesPackages are typically identified by
The group or organization
Generally given in the Java style of a reversed URL, e.g. org.apache.ant, edu.odu.cs.extractLike some Hollywood celebrities, some organizations become big enough stars to be simply known by a single name, e.g., junit
The artifact name
E.g., junit, hamcrest-core, apache-commons
Version number
Single versions are identified exactly: 4.12Depending on the build manger being used, you may be able to specify things like
4.11+: 4.11 or laterlatest.integration: latest official release
1.2-SNAPSHOT: a version under development. The stapshot modifier serves as a notice that the actual package may be updated without achange in version number.
2.3 ScopesNot every library that your project needs is needed all of the time.
If your “main code” uses a library, you would need that during compilation, testing, and possibly packaging.Libraries like junit would only be needed when compiling and running the unit tests.Libraries like findbugs, checkstyle (discussed later in the semester) are only used during report generation.A build manager may support “plug-ins” that extend or modify the build process. These plug-ins are typically packaged much like other 3rd-partylibraries, but are needed much earlier in the build process.
So build managers allow you to import libraries in selected scopes which indicate when the library is actually needed.
This affects
Whether a library is downloaded or updated during a given build.Java CLASSPATH and other settings made available to the build steps.
3 Adding Libraries to a Java Project in Different Build Managers3.1 Maven
3.1.1 Specifying a Dependency
Examine the pom.xml file and look for the dependencies section.
You’ll see something like
<dependencies> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> <scope>test</scope> </dependencies>
Could also say [4.12,] to get versions 4.12 or greater
3.1.2 Fetching Dependencies
When the build is run (e.g. mvn package):
Maven does a transitive search over the dependencies for a project
Tries to find a mutually compatible set of versionsHelps if you give it some flexibility
Maven then downloads the required libraries automatically
Downloaded libraries are cached (e.g., ~/.m2)
3.1.3 Transitive Dependencies
How does Maven know whether junit itself depends on other libraries?
Here is the actual published content for Junit 4.12.
The .pom file is the metadata.
Look inside. Search for <dependencies>. You’ll find
<dependencies> <groupId>org.hamcrest</groupId> <artifactId>hamcrest-core</artifactId> <version>1.3</version> <scope>compile</scope> </dependencies>
This is the same kind of info that we put into our own pom.xml file
And is, presumably, taken from the pom.xml that the JUnit team used to maintain their builds.Publishing the dependency information along with the libraries leads to an accumulated base of information on library dependencies.
3.1.4 Choosing Repositories
Technically, our project has two repositories
a remote repository, at ibiblioa local repository holding my cache
Team projects will often employ an intermediate shared repository
reduce cache duplicationprovide a mechanism for managing subproject modulesprovide a common storage area for libraries not available on ibibliopreserve older versions needed for a project baseline
3.1.5 Example: specifying a repository
In pom.xml:
<repositories> <repository> <snapshots> <enabled>false</enabled> </snapshots> <id>central</id> <name>libs-release</name> <url>http://archive350.cs.odu.edu:8080/artifactory/libs-release</url> </repository> </repositories>
We could then, for example, import early versions of zipdiff and trilead-ssh that were part of the baseline for the Extract projectAt the time they were added to the project baseline, they were not in the remote Maven repositoryAs time has passed, those libraries were added, but have started with versions later than the project baseline
3.2 Ivy (ant)
YAAP1 , Ivy adds the dependency management features pioneered by Maven to ant
Technically can run as a standalone application, but really the ant integration is key.
Use is similar to the dependencies/repositories portion of Maven POMs
Ivy can retrieve from and publish to the established Maven repositories
3.2.1 Getting Started with Ivy
The Ivy project pages include some “magic” ant code to * download the latest version of Ivy * add a resolve-ivy task to be invoked before you compile yourcode * This task will download any 3rd-party dependencies and add them to your compilation classpaths.
3.2.2 Specifying our Desired Libraries
We list the libraries our project needs in a separate ivy.xml file
<ivy-module version="2.0"> <dependencies> <dependency org="de.jflex" name="jflex" rev="1.4.3"/> <dependency org="junit" name="junit" rev="4.10"/> </dependencies></ivy-module>
This should look familiar to the section from the Maven pom:
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> <scope>test</scope> </dependency> </dependencies>
3.2.3 Invoking Ivy
Once Ivy is installed, we can invoke it:
build.xml2.listing +
<project name="codeAnnotation" basedir="." default="build" xmlns:ivy="antlib:org.apache.ivy.ant"> ⋮ <target name="resolve-ivy" depends="install-ivy"> <ivy:resolve/> ➀ <ivy:cachepath pathid="ivy.path" /> ➁ <taskdef classpath="JFlex.jar" classname="JFlex.anttask.JFlexTask" name="jflex" /> ➂ </target> <target name="generateSource" depends="resolve-ivy"> <mkdir dir="target/gen/java"/> <jflex file="src/main/jflex/code2html.flex" ➃ destdir="target/gen/java"/>
<jflex file="src/main/jflex/code2tex.flex" destdir="target/gen/java"/> ⋮
➀ The ivy:resolve task fetches the libraries we need.
They will be kept in a cache (~/.ivy).
➁ This creates an Ant path named ivy.path containing the list of all libraries fetched.
Includes both the ones we explicitly named in our ivy.xml and ones that those depended upon.
➂ Here we can bind the task name for the JFlex library that we have loaded,
➃ enabling us to later use the <jflex ... > task
Making the build depend on the Ivy sequence guarantees that the library availability will be managed at each build
3.2.4 Ivy & Repositories
Ivy can use most maven repositories.
Choice of repositories is controlled by an ivysettings.xml file.
By default, uses the same ibiblio service that is Maven’s default.
An example of an ivysettings.xml file:
ivysettings.xml.listing +
<?xml version="1.0" encoding="UTF-8"?><ivy-settings> <settings defaultResolver="default" /> <resolvers> <chain name="default"> ➀ <ibiblio name="central" m2compatible="true"/> ➁ </chain> </resolvers></ivy-settings>
➀ A “chain” means that we want Ivy to search these repositories in sequence.
In this case, I only have one thing in the chain, but for other projects I may add additional repositories.
➁ Look in the Maven Central archive.
3.2.5 Ivy & Eclipse
The developers of Ivy provide an Eclipse plugin, IvyDE.
Allows you to add a “Library” to your Eclipse project’s build pathThis library holds everything that Ivy fetches based upon your ivy.xml file.
3.3 GradleAs is often the case, gradle tends to provide simpler syntax to do much the same things as Maven and Ant.
3.3.1 Selecting libraries in Gradle
In build.gradle:
apply plugin: 'java' ⋮ repositories { jcenter() ➀ // Use my own CS dept repo ivy { ➁ url 'https://www.cs.odu.edu/~zeil/ivyrepo' } } dependencies { ➂ implementation 'net.sf.saxon:saxon-dom:8.7+' testImplementation 'junit:junit:4.12' testRuntimeOnly 'edu.odu.cs.zeil:reportAccumulator:1.1+'
}
Here you see both the choice of libraries ➂ and of repositories ➀, ➁.
The dependencies indicate
The scope (e.g., testCompile, which actually includes compiling and running of the unit tests)
The package, specified as organization:package:versionNumber
Scopes in Gradle
dependencies { ➂ implementation 'net.sf.saxon:saxon-dom:8.7+' testImplementation 'junit:junit:4.12' testRuntimeOnly 'edu.odu.cs.zeil:reportAccumulator:1.1+'
}
If project P depends on library L, then the most common scopes recognized by Gradle are:
apiP requires L to compile and to run. If P is itself a library, anyone who uses P must have L available to compile and run as well.
Generally this means that some class declared in L is used as a parameter or a return type by some public function in P.
implementationP requires L to compile and run the code, but this is hidden from users of P.
testImplementationP requires L to compile and run unit tests.
classpathP uses L as a plugin.
There are other variations possible: “Implementation” can be replaced by “CompileOnly” and “RuntomeOnly”, but those will be fairly uncommon.
In older Gradle examples (including a lot of mine in this course) you will see the scopes * compile * runTime * testCompile* testRuntime
These are now deprecated.
4 Eclipse and Library ManagementEach of the build/library managers we have discussed have support in Eclipse
May provide special editors for editing the build manager configuration files.
Provide ways to launch the build manager from within Eclipse
Sometimes it’s easier to launch separately in a separate window, e.g., from the command line from the Gradle GUI mode.
Remember to “refresh” the project in Eclipse afterwards
Most importantly, allows Eclipse Java projects to use any 3rd-party libraries fetched by the build/library manager.
Otherwise the built-in Eclipse compilation and smart-editing features would mistakenly believe that your code was full of references to non-existent classes and funnctions.
May allow some management of the cache of old libraries.
4.1 Eclipse and MavenCan also hope for convenience functions
In particular, POM editors
Two plugins currently
m2eclipse, older pluginEclipse IAM
Allows you to create new Maven projects or to import existing ones into Eclipse.
4.2 Eclipse and Ant/IvySupport for Ant is built-in to Eclipse
The developers of Ivy provide an Eclipse plugin, IvyDE.
Allows you to add a “Library” to your Eclipse project’s build path
This library holds everything that Ivy fetches based upon your ivy.xml file.
4.3 Eclipse and GradleEclipse has various Gradle plugins available.
The most popular seems to be the BuildShip package, now standard in most Eclipse distributions.
Allows import (only) of existing Gradle projects
Allows launching Gradle from within Eclipse
from a special Gradle Tasks panel, not from the package explorer.
Allows Eclipse Java projects to find 3rd-party libraries loaded by Gradle.
After any change to the dependencies in build.gradle, you must right-click on build.gradle and select Gradle -> refresh Gradleproject.
Eclipse re-reads your build.gradle file and obtains the desired libraries.
5 Complicating Factors5.1 Plug-insPlug-ins to build managers modify the build process itself
unlike “normal” 3rd-party libraries that affect how the code is compiled and run, but not when or whether the compilation steps take place.
Consequently, plug-ins generally need to be fetched and processed at the very beginning of a build, much earlier than most 3rd-party libraries.
Nonetheless, it is convenient to use the same dependency/repository scheme to publish and import plug-ins.
5.1.1 Separate but similar
Plug-ins to build managers are generally handled separately from other items, though the syntax is often similar.
unlike “normal” 3rd-party libraries that affect how the code is compiled and run, but not when or whether the compilation steps take place.
Consequently, plug-ins generally need to be fetched and processed at the very beginning of a build, much earlier than most 3rd-party libraries.
Nonetheless, it is convenient to use the same dependency/repository scheme to publish and import plug-ins.
5.1.2 Separate but similar
Plug-ins to build managers are generally handled separately from other items, though the syntax is often similar.
For example, in gradle, plug-ins are retrieved from repositories specified in a pluginManagement section in settings.gradle:
pluginManagement { repositories { jcenter() ivy { // for url 'https://www.cs.odu.edu/~zeil/ivyrepo' } } }
The syntax and options within the pluginManagement area are almost identical to the way we handle normal package repositories.
Then we load the plugins from a plugins section in build.gradle:
plugins { id 'java' id 'edu.odu.cs.report_accumulator' version '1.3' }
Maven has a similar approach, isolating plug-ins in a <plugins> section.
5.2 PublishingPublishing artifacts to a repository is also supported by maven, Ant/Ivy, & Gradle.
Typically more complicated.
mainly because most repositories are open to download, but require login credentials to upload.
Example: in Gradle build.xml
publishing { publications { ivyJava(IvyPublication) { organisation 'edu.odu.cs.zeil' module 'cowem-plugin' revision project.version descriptor.status = 'integration' // milestone, release descriptor.branch = 'v1'
from components.java } } repositories { ivy { name 'ivyRepo' url 'sftp://atria.cs.odu.edu:22/home/zeil/secure_html/ivyrepo' // Readable via https://www.cs.odu.edu/~zeil/ivyrepo credentials { ⋮ } } } }
5.3 Login CredentialsProviding login credentials to a build is problematic.
Can’t insert passwords into the build file, because the build file will be checked in to version control, so we would be publishing our credentials in plainview, for all time!
A common work-around is to store a few build “properties” in a file in the user’s home directory, not kept as part of the package version control.
Example: in Gradle build.xml
// Credentials are loaded from ~/.gradle/gradle.properties if(project.hasProperty("ivyRepoUser")){ ➀ ext.ivyRepoUser = "$ivyRepoUser"; } else { ext.ivyRepoUser = "user"; } if(project.hasProperty("ivyRepoPass")){ ext.ivyRepoPass = "$ivyRepoPass"; } else { ext.ivyRepoPass = "password"; } ⋮ publishing { ⋮ credentials { ➁ username project.ivyRepoUser
password project.ivyRepoPass } ⋮ }
➀ looks to see if properties ivyRepoUser and ivyRepoPass were loaded from ~/.gradle/gradle.properties. If so, it adds them as “extension”properties of the current gradle project.
➁ passes those new extension properties as login credentials to a repository.
If the person running the build di not have a ~/.gradle/gradle.properties or it did not contain the properties ivyRepoUser andivyRepoPassproeprties, then attempted uploads to the repository would use a default username and password that would, presumably, fail.
6 What about C++?Sadly, the situation for C++ is somewhat more complicated.
Should C++ libraries be deployed as source or binaries?
Binary library formats (e.g., DLL’s in windows, .a or .so in *nix) are OS-specific.Developers generally need header (.h) files as well as the compiled code if they are to compile their own applications.
There are Operating System-dependent solutions for deploying binaries.
Windows install files can check to see if required libraries for an application are already present and fetch and install them if necessary.Linux repositories provide a similar mechanism.
Even more than OS-dependent, these are distribution dependent.Different variants of Linux use different repository formats
These solutions work for deploying applications but not so well for developers.
6.1 What’s Holding Us Up?We Don’t Have Anything Equivalent to Maven Central for C++
There’s no recognized central place to publish and find open source C++ libraries.
No standard for what such a library would look like.
Compiled binaries would have to be provided for multiple OS variants.
Partly, that’s because we don’t have build managers that would take advantage of such libraries if they existed.
We Don’t Have Anything Equivalent to Maven/Ivy/Gradle Dependency Management for C++
There are no build managers capable of processing dependencies for C++ while simultaneously factoring in the platform-specific requirements.
Who would write such a tool when there’s no central repositories where such libraries can be stored?
Which Came First, the Chicken or the Egg?
Who wants to set up repositories without build managers that can use them?
Who wants to write such build managers if there are no repositories that they could work with?
6.2 On the HorizonGradle seems to be paying serious attention to C/C++ build support.
Gradle’s existing Java package management might be leveraged into C++ support.
Conan is an attempt to provide a packaging standard for C++ library repositories.
Good news: the same company that hosts JCenter is also hosting Conan artifacts.Bad news: Only a handful of rather specialized projects have been contributed so far (as of Jan 2018).
Encouraging news: the number is growing steadily (as of Oct 2019)Gradle support for this is still in the works.
In mid-summer of 2019, the Gradle project released a heavily revised set of C++ plugins, which greatly simplify building C++ with Gradle.
These include provisions for downloading both binary and source code libraries.
Not clear how stable these are, but it’s a promising sign.
1: Yet Another Apache Project
Managing Code VariantsSteven J Zeil
Last modified: Mar 30, 2020
Contents:1 Problems2 AUTOCONF
2.1 Compiling Software the Unix Way2.2 Generating The configure Script2.3 Example
3 Dynamic Loading
1 ProblemsCode Variations
Environment management, (previously identified as common SCM problems):
Coping with change in
hardware environmentsoftware environment
Can lead to need for variant code to support different configurations
The Sad Story of C/C++ Portability
Both C and C++ existed as popular languages long before being standardized
Widespread variations in the “system” headers
Even after standardization, many common functions are not standardized
GUIsmulti-threading and distributed operations
network communcations
Even things covered by the standard aren’t covered in enough detail
C Portability Quiz
How would you declare an integer counter capable of holding non-negative values up to one million? Up to one billion?
C90 requires sizeof(short) sizeof(int) sizeof(long)
Notice that’s , not <
A char must hold a “natural” byte (minimum addressable unit) on the machine architecture.
The C99 specification added long long and set minimum sizes as
char 8short 16int 16long 32long long 64
C++ Portability Quiz
How would you declare an integer counter capable of holding non-negative values up to one million? Up to one billion?
The C++ standard followed C90 (not 99!) until C++11
sizeof(short) sizeof(int) sizeof(long)
C++11 adds the C99 standards
Coping With Variants in the C/C++ World
Configuration headers used to define symbols describing selected variants, e.g.,
config.h +
#ifndef CONFIG_STD#define CONFIG_STD
≤ ≤
≤
≤ ≤
//// AlgAE Configuration file// // Currently recognizes g++, version 2.7.2 for Unix and 2.8.0 for GnuWin32// MS Visual C++, version 5.0// // Define this if the compiler does not support reassignment of iostream// buffers via the function rdbuf(streambuf&)#undef __bad_rdbuf__ #ifdef __GNUG__ /* Compiler is gcc/g++ */ #define MEM_INCL <mem.h> #define USING_STD#define STD#define USE_FORK #ifdef __CYGWIN32__ /* This is the GnuWin32 port for Windows 95/NT #define USE_WINSOCK #else /* This is some other port of g++, probably a Unix system. */#endif #elif defined(_MSC_VER)/* compiler is Microsoft Visual C++ */ #define MEM_INCL <alloc.h> #define USING_STD using namespace std;#define STD std:: #define MEMDC#define __bad_rdbuf__#define USE_WINSOCK #else #pragma warning "Possible configuration error: Compiler is not recognized." #define MEM_INCL <mem.h>
#endif #endif
Code uses symbols defined in there
direct substitution, e.g.
#include MEM
loads <alloc.h> or <mem.h>
or conditionally
#ifdef USE_WINSOCK#include <winsock2.h>#else#include <netinet/in.h>#include <sys/socket.h>#endif
2 AUTOCONF2.1 Compiling Software the Unix WayIf you’ve ever installed a Unix/Linux package from a source distribution, you’ve probably gotten used to the two-step process:
./configure make install
The configure script runs a series of tests on the compilation environment, e.g.,
operating systemcompiler nameavailability of selected libraries/header filesavailability and/or behavior of selected functions
Produces a Makefile and a configuration header config.h based upon the test results
Source code may use conditional compilation based on the header to select appropriate code
2.2 Generating The configure ScriptA rough outline:
1. Create a configure.ac2. Set up config.h.in (template for eventual config.h file)3. Set up Makefile.am4. Set up files NEWS README AUTHORS ChangeLog5. Run autoreconf
2.3 Example
2.3.1 1. Create a configure.ac
AC_INIT(cppSpreadsheet, 1.0, [email protected])AC_PREREQ([2.68]) ➀ AM_INIT_AUTOMAKE([1.16 foreign no-define])AC_CONFIG_HEADERS([config.h]) AC_PROG_CXX ➁ AC_CONFIG_FILES([Makefile]) AC_OUTPUT
➀ Make sure autoconf is at least version 2.68➁ Search for a C++ compiler and make sure one is available.Some common tests that might be added before the final AC_OUTPUT include:
AC_CHECK_FILE(file): check to see if a file existsAC_CHECK_LIB(library): check to see if a library is already installedAC_SEARCH_LIBS(function): search for an installed library that provides this functionAC_FUNC_MALLOC: check to see if the malloc function exists and appears to behave as expected.
This is one of a whole slew of AC_FUNC_… options
2.3.2 Preparing the templates
2: Set up config.h.in (template for eventual config.h file)
3: Set up Makefile.am
AM_INIT_AUTOMAKE([1.10 no-define foreign]) bin_PROGRAMS = testssheet testssheet_SOURCES=testssheet.cpp exprparser.cpp tokenizer.cpp exprfactory.cpp expression.cpp \ cellname.cpp numericnode.cpp stringnode.cpp cellrefnode.cpp negatenode.cpp \ absnode.cpp sqrtnode.cpp sumnode.cpp lessnode.cpp lesseqnode.cpp \ greaternode.cpp greatereqnode.cpp equalnode.cpp notequalnode.cpp plusnode.cpp \ subtractnode.cpp timesnode.cpp dividesnode.cpp ifnode.cpp \ numvalue.cpp strvalue.cpp errvalue.cpp spreadsheet.cpp cell.cpp \ observable.cpp observerptrseq.cpp cellptrseq.cpp cellnameseq.cpp \ absnode.h control.h lessnode.h ssi.h \ binarynode.h dividesnode.h minusnode.h ssview.h \ cell.h elementseq.h negatenode.h streamtok.h \ celllistenerseq.h equalnode.h notequalnode.h stringnode.h \ cellname0.h errvalue.h numericnode.h strvalue.h \ cellname.h expression.h numvalue.h subtractnode.h \ cellnameseq.h exprfactory.h observable.h sumnode.h \ cellptrseq.h exprparser.h observer.h timesnode.h \ cellrange.h greatereqnode.h observerptrseq.h unaryexpr.h \ cellrefnode.h greaternode.h plusnode.h unarynode.h \ clipboard.h ifnode.h spreadsheet.h unittest.h \ constantnode.h lesseqnode.h sqrtnode.h value.h
2.3.3 Generating the configure Script
4: touch NEWS README AUTHORS ChangeLog
or create real versions of these.
5: run autoreconf --force --install
Runs the sequence of programs: aclocal autoconf autoheader automakeCreates config.h.in Makefile.in & configure
Alternatives
imake for X code
3 Dynamic Loading
autoconf is C/C++-centric
The configure approach relies heavily on conditional compilation features.
Common in C++
Only in Java via non-standard techniques
Java: Abstraction
Java programs are more likely varied be altering entire classes at a time.
For example:
public abstract class OCRLauncher extends Thread { /** * Launch an OCR process to convert the input * PDF into some kind of File of OCR output. * * @param inputPDFfile The PDF file to be converted to IDM (XML) * @param outputFile The raw OCR output * @return */ public abstract boolean convertPDFtoOCR (File inputPDFfile, File outputFile) throws Exception; /** * Convert a file of OCR output into IDM * * @param inputOCRfile * * @return XML (IDM) document */ public abstract Document convertOCRtoIDM (File inputOCRfile) throws Exception; }
This class has distinct implementations for different OCR programs that might be installed on the running system.
Configuration via Property Files
A property file, loaded at run time, specifies which class is actually desired:
input.OCRLauncherClass=edu.odu.cs.extract.input.OCRBatchLauncher input.OCRProgram=OCR input.OCRBatch=Batch input.ocr.in_dir=c:/Luratech/ocr_in input.ocr.out_dir=c:/Luratech/ocr_out
Reflection: Dynamic Loading
And the desired class is loaded dynamically:
String OCRLauncherName = p.getProperty(Properties.Names.OCR_LAUNCH_CLASS); Class<?> ocrLauncherClass = Class.forName(OCRLauncherName); ocr = (OCRLauncher)ocrLauncherClass.newInstance(); idmDoc = ocr.convertOCRtoIDM(inputOCR);
Documentation and Documentation GeneratorsSteven J Zeil
Last modified: Apr 1, 2020
Contents:1 Source Code Documentation
1.1 Comments1.2 Self-Documenting Code1.3 Charting
2 API Documentation2.1 javadoc2.2 doxygen2.3 Other API Documentation Generators
… because everyone loves writing documentation.
1 Source Code Documentation1.1 Comments
widely used
widely abused
1.1.1 Do Comments Matter?
McConnell has a good & balanced discussion on this.
Source code commenting is often a crutch to hide
poor naminga failure to extract code blocks into recognizable functionspoor designlack of quality tools (version control, issue tracking, source formatters)
Still useful for
Explaining why a thing is being doneDocumenting a pseudo-code based designCross-referencing related items
Modern focus has shifted considerably away from commenting bodies towards API documentation.
1.1.2 Which is better?
double m; // mean averagedouble s; // standard deviation
double meanAverage double standardDeviation
1.1.3 Which is better?
// Sum up the datadouble sum = 0.0; double sumSquares = 0.0; // Add up the sumsfor (double d: scores) { sum += d; sumSquares += d*d; } // Compute the average and standard// deviationdouble meanAverage = sum / numScores; double standardDeviation = sqrt ((sumSquares - numScores*sum*sum) /(numScores - 1.0)); // Subtract the average from each data// item and divide by the standard// deviation.for (int i = 0; i < numScores; ++i) { scores[i] = (scores[i] - meanAverage) / standardDeviation; }
// Compute summary statisticsdouble sum = 0.0; double sumSquares = 0.0; for (double d: scores) { sum += d; sumSquares += d*d; } double meanAverage = sum / numScores; double standardDeviation = sqrt ((sumSquares - numScores*sum*sum) / (numScores - 1.0)); // Normalize the scoresfor (int i = 0; i < numScores; ++i) { scores[i] = (scores[i] - meanAverage) / standardDeviation; }
1.1.4 Which is better?
// Compute summary statisticsdouble sum = 0.0; double sumSquares = 0.0; for (double d: scores) { sum += d; sumSquares += d*d; } double meanAverage = sum / numScores; double standardDeviation = sqrt ((sumSquares - numScores*sum*sum) /(numScores - 1.0)); // Normalize the scoresfor (int i = 0; i < numScores; ++i) scores[i] = (scores[i] - meanAverage) / standardDeviation;
void computeSummaryStatistics ( const double* scores, // inputs int numScores, double& meanAverage, // outputs double& standardDeviation) { double sum = 0.0; double sumSquares = 0.0; for (double d: scores) { sum += d; sumSquares += d*d; } meanAverage = sum / numScores; standardDeviation = sqrt ((sumSquares - numScores*sum*sum) /(numScores - 1.0)); } void normalizeData (double* data, int numData, double center, double spread) { for (int i = 0; i < numData; ++i) data[i] = (data[i] - center) / spread; } ⋮ double meanAverage; double standardDeviation; computeSummaryStatistics (scores, numScores, meanAverage, standardDeviation); normalizeData (scores, numScores, meanAverage, standardDeviation);
1.1.5 Which is better?
void computeSummaryStatistics ( const double* scores, // inputs int numScores, double& meanAverage, // outputs double& standardDeviation) { double sum = 0.0; double sumSquares = 0.0; for (double d: scores) { sum += d; sumSquares += d*d; } meanAverage = sum / numScores; standardDeviation = sqrt ((sumSquares - numScores*sum*sum) /(numScores - 1.0)); } void normalizeData (double* data, int numData, double center, double spread) { for (int i = 0; i < numData; ++i) data[i] = (data[i] - center) / spread; } ⋮ double meanAverage; double standardDeviation; computeSummaryStatistics (scores, numScores, meanAverage, standardDeviation); normalizeData (scores, numScores, meanAverage, standardDeviation);
void computeSummaryStatistics ( const double* scores, // inputs int numScores, double& meanAverage, // outputs double& standardDeviation) { double sum = accumulate( scores, scores+numScores); double sumSquares = accumulate( scores, scores+numScores, [](double x, double y) {return x + y*y;}); meanAverage = sum / numScores; standardDeviation = sqrt ((sumSquares - numScores*sum*sum) /(numScores - 1.0)); } ⋮ // Normalize the scoresdouble meanAverage; double standardDeviation; computeSummaryStatistics (scores, numScores, meanAverage, standardDeviation); transform ( scores, scores+numScores, scores, [] (double d) { return (d - meanAverage) / standardDeviation});
1.1.6 Kinds of Comments
Repeat of the code
Useless
Explanation of the code
Only useful if the code is confusing
In which case, first priority should be to simplify the code.
Markers
notes not intended to be left in final code
If standardized, useful
e.g., // TODO in Eclipse
flagged in editoreasily searched for
Summary of the code
applied to entire code “paragraphs”useful to allow easy skimming of the code
Description of intent
similar to summary, but describes problem rather than solution
Information that cannot be expressed in the code
e.g., authors, copyright, date of modification
1.2 Self-Documenting CodeSelf-Documenting code relies on good programming style to perform most of the documentation.
“the Holy Grail of legibility” (McConnell)
1.2.1 Characteristics of Self-Documenting Code
Classes
Does the class’s interface present a consistent abstraction?
Is the class well named, and does its name describe its central purpose?
Does the class’s interface make obvious how you should use the class?
Is the class’s interface abstract enough that you don’t have to think about how its services are implemented? Can you treat the class as a blackbox?
Routines
Does each routine’s name describe exactly what the routine does?
Does each routine perform one well-defined task?
Have all parts of each routine that would benefit from being put into their own routines been put into their own routines?
Is each routine’s interface obvious and clear?
Data Names
Are type names descriptive enough to help document data declarations?
Are variables named well?
Are variables used only for the purpose for which they’re named?
Are loop counters given more informative names than i, j, and k?
Are well-named enumerated types used instead of makeshift flags or boolean variables?
Are named constants used instead of magic numbers or magic strings?
Do naming conventions distinguish among type names, enumerated types, named constants, local variables, class variables, and globalvariables?
Data Organization
Are extra variables used for clarity when needed?
Are references to variables close together?
Are data types simple so that they minimize complexity?
Is complicated data accessed through abstract access routines (abstract data types)?
Control
Is the nominal path through the code clear?
Are related statements grouped together?
Have relatively independent groups of statements been packaged into their own routines?
Does the normal case follow the if rather than the else?
Are control structures simple so that they minimize complexity?
Does each loop perform one and only one function, as a well-defined routine would?
Is nesting minimized?
Have boolean expressions been simplified by using additional boolean variables, boolean functions, and decision tables?
Layout
Does the program’s layout show its logical structure?
Design
Is the code straightforward, and does it avoid cleverness?
Are implementation details hidden as much as possible?
Is the program written in terms of the problem domain as much as possible rather than in terms of computer-science or programming-language structures?
(McConnell, ch 32)
1.3 ChartingHow many forms of software documentation charting do you know?
Control Flow
Flowcharts
Nassi-Schneidermann Charts
State diagrams
UML interaction diagramsModule relationships
Structure (call) charts
Data-Flow Diagrams
SADT (Structured Analysis and Design Technique)E-RUML class relationship diagrams
1.3.1 From Code to Charts
For as long as people have been writing source code, they’ve been looking for ways to ease the effort of documenting that code.
Often after-the-fact
Earliest examples were automatic flowchart generators
Generating flowcharts from source code.
Raw results were poor qualityBut still could be claimed to satisfy client requirements
As flowcharts declined in popularity, so did the demand for these tools.
Still offered in reverse engineering tools ( e.g. )
Flowchart synced to code viewerHuman retitles blocks as “understanding” of the code progresses
1.3.2 From Charts to Code
A hallmark of so-called CASE (Computer-Aided Software Engineering) systems
Modern versions generate class declarations from UML class diagrams
2 API DocumentationAPI documentation tools are now more common
Reflect modern emphasis on re-usable interfaces
Combine info from
a (limited) language parser
Extracts info about module/function structure and function parameters
and specially formatted blocks of comments embedded in the source code
Encourages updating comments as code is modified
Comments become a legitimately useful tool for application writers.
Application writers have less need to access actual code.
Generate linked documents to facilitate browsing of referenced type names and other entities
Some IDEs understand this markup as well and use it enhance “live” help while editing code.
2.1 javadocPerhaps the best known tool in this category
part of the standard Java distribution
achieved prominence when Sun used it to document the Java “standard library”.
E.g., 1.6, 1.8
2.1.1 Javadoc Comments
Javadoc markup is enclosed in comments delineated by /** ... */
And therefore processed as normal comments by the Java compiler.
A comment block precedes the entity that it describes
e.g., This page is generated from this source code.
In addition to “free-form” text, can contain special markup
Common Javadoc Markup
@author authorName
@version versionNumber
@param name description
@return description
@throws exceptionClassName description
@see crossReference
Running javadoc
Command line
javadoc -d destinationDir -sourcepath sourceCodeDir \ -link http://docs.oracle.com/javase/7/docs/api/
Can add multiple source paths, links to external librariesCan also specify which packages from source code to document
Eclipse: Project ⇒ Generate Javadoc ...
2.1.2 JavaDoc and build managers
Ant
Ant has a javadoc task among its default task set.
A typical invocation might be:
<javadoc packagenames="edu.odu.cs.*" destdir="target/javadoc" classpathref="javadoc.classpath" Author="yes" Version="yes" Use="yes" defaultexcludes="yes"> <fileset dir="." defaultexcludes="yes"> <include name="extractor/src/main/java/**" /> <exclude name="**/*.html" /> </fileset> <doctitle><![CDATA[<h1>ODU CS Extract Project</h1>]]></doctitle>
</javadoc>
Gradle
Gradle provides a javadoc plugin that provides a javadoc task.
Here is the full build.gradle for a simple Java build with Javadoc generation.
Click to reveal +
plugins { id 'java' id 'javadoc' ➀ } repositories { jcenter() } dependencies { testImplementation("junit:junit:4.12") testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.5.2") } test { useJUnit() ignoreFailures = true } javadoc { ➁ options.with { links 'https://docs.oracle.com/javase/8/docs/api/', 'gradle/javadocs/jdk' } failOnError = false; ➂ } build.dependsOn javadoc ➃
➀ use the javadoc plugin.➁ Include links to the online Java API documentation.➂ Don’t stop the build just because javadoc finds a problem➃ Tell Gradle to run the javadoc task before the build task.
2.2 doxygenthe most popular API generator for C/C++
Also works with Objective-C, C#, Java, IDL, Python, PHP, VHDL, and FORTRAN
Markup is essentially identical to javadoc
Internal formatting available via Markdown
Output can be HTML, LaTeX, or RTF
Can also generate
various non-quite-UML diagramsand hyperlinked source code
Running doxygen
Command line
doxygen configFile
The config file can contain any of a bewildering set of options in typical property-file style:
PROJECT_NAME = C++ SpreadsheetINPUT = src/modelOUTPUT_DIRECTORY = target/docEXTRACT_ALL = YESCLASS_DIAGRAMS = YESGENERATE_HTML = YESGENERATE_LATEX = YESUSE_PDFLATEX = YES
Eclipse: Eclox plugin
Ant task for doxygen
Gradle plugins for doxygen (untried)
2.3 Other API Documentation GeneratorsBecause a documentation generator needs to module and function structure and function parameters, a distinct parser is needed for each programming language.
This leads to a variety of language-specific tools, e.g.,
jsDoc for Javascript
YARD for Ruby
sandcastle for .Net
Could not parse reporting...skipping
Program Analysis ToolsSteven J Zeil
Last modified: Apr 6, 2020
Contents:1 Representing Programs
1.1 Abstract Syntax Trees (ASTs)1.2 Control Flow Graphs
2 Style and Anomaly Checking2.1 Lint2.2 Static Analysis by Compilers2.3 CheckStyle2.4 SpotBugs2.5 PMD
3 Reverse-Engineering Tools3.1 Reverse Compilers3.2 Java Obfuscators3.3 Obfuscation Example
4 Dynamic Analysis Tools4.1 Pointer/Memory Errors4.2 Profilers
Abstract
In this lesson we look at a variety of code analysis tools available to the practicing software developer. These include static analysis tools that examine codewithout executing it, and dynamic analysis tools that monitor code while it is being run on tests or in operation.
We will look at the kinds of information that developers can obtain from these tools, the potential value offered by this information, and how such tools can beintegrated into an automated build or a continuous integration setup.
Classifying Analysis Tools
Static Analysis
style checkersdata flow analysis
Dynamic Analysis
Memory use monitorsProfilers
Analysis Tools and Compilers
Analysis tools, particularly static, share a great deal with compilers
Need to parse code & understand at least some language smeantics
Data flow techniques originated in compiler optimization
1 Representing ProgramsMost static analysis is based upon one of these graphs
That’s “graphs” in the discrete mathematics (CS 381) or data structures (CS 361) sense: a collection of nodes connected by edges, not the sense of points plottedon X-Y axes.
Abstract syntax trees
Control Flow Graphs
1.1 Abstract Syntax Trees (ASTs)
Output of a language parser
Simpler than parse trees
Generally viewed as a generalization of operator-applied-to-operands
Abstract Syntax Trees (cont.)
ASTs can be applied to larger constructions than just expressions
Abstract Syntax Trees (cont.)
In fact, generally reduce entire program or compilation unit to one AST
1.1.1 Abstract Syntax Graphs
In most programming languages, any given variable name (e.g., i, x) could actually refer to many different objects depending upon the scope rules of thelanguage.The semantic analysis portion of a compiler pairs uses of variable names with the corresponding declarations
What we have left is no longer a tree, but a graph.
1.2 Control Flow GraphsRepresent each executable statement in the code as a node,
with edges connecting nodes that can be executed one after another.Nodes for conditional statements have two or more outgoing edges.
1.2.1 Sample CFG
01: procedure SQRT (Q, A, B: in float;02: X: out float);03: // Compute X = square root of Q,04: // given that A <= X <= B
05: X1, F1, F2, H: float;06: begin07: X1 := A;08: X2 := B;09: F1 := Q - X1**210: H := X2 - X1;11: while (ABS(H) >= 0.001) loop12: F2 := Q - X2**2;13: H := - F2 * ((X2-X1)/(F2-F1));14: X1 := X2;15: X2 := X2 + H;16: F1 := F217: end loop;18: X := (X1 + X2) / 2.;19: end SQRT;
Simplifying CFGs: Basic Blocks
procedure SQRT (Q, A, B: in float; // node 0 X: out float); // Compute X = square root of Q,// given that A <= X <= B X1, F1, F2, H: float; begin X1 := A; X2 := B; // node 1 F1 := Q - X1**2 H := X2 - X1; while (ABS(H) >= 0.001) loop // node 2 F2 := Q - X2**2; H := - F2 * ((X2-X1)/(F2-F1)); X1 := X2; // node 3 X2 := X2 + H; F1 := F2 end loop; X := (X1 + X2) / 2.; // node 4 end SQRT; // node 5
1.2.2 Data Flow Analysis
All data-flow information is obtained by propagating data flow markers through the program.
The usual markers are
di(x) : a definition of variable x (any location where x is assigned a value) at node ir<sub>i</sub>(x): a reference to x (any location where the value of x is used) at node iu<sub>i</sub>(x): an undefinition of x (any location where x becomes undefined/illegal) at node i
Data flow problems are solved by propagating markers around a control flow graph
Data-Flow Annotated CFG
procedure SQRT (Q, A, B: in float; // node 0 X: out float); // Compute X = square root of Q,// given that A <= X <= B X1, F1, F2, H: float; begin X1 := A; X2 := B; // node 1 F1 := Q - X1**2 H := X2 - X1; while (ABS(H) >= 0.001) loop // node 2 F2 := Q - X2**2; H := - F2 * ((X2-X1)/(F2-F1)); X1 := X2; // node 3 X2 := X2 + H; F1 := F2 end loop; X := (X1 + X2) / 2.; // node 4 end SQRT; // node 5
1.2.3 Reaching Definitions
A definition di(x) reaches a node nj iff there exists a path from ni to nj on which x is neither defined nor undefined.
What definitions reach the reference to X1 in node 4?
What definitions reach the reference to H in node 2?
1.2.4 Data Flow Anomalies
The reaching definitions problem can be used to detect anomolous patterns that may reflect errors.
ur anomalies: if an undefinition of a variable reaches a reference of the same variable
dd anomalies: if a definition of a variable reaches a definition of the same variable
du anomalies: if a definition of a variable reaches an undefinition of the same variable
1.2.5 Available Expressions
An expression e is available at a node n iff every path from the start of the program to n evaluates e, and iff, after the last evaluation of e on each such path,there are no subsequent definitions or undefinitions to the variables in e.
procedure SQRT (Q, A, B: in float; // node 0 X: out float); // Compute X = square root of Q,// given that A <= X <= B X1, F1, F2, H: float; begin
X1 := A; X2 := B; // node 1 F1 := Q - X1**2 H := X2 - X1; while (ABS(H) >= 0.001) loop // node 2 F2 := Q - X2**2; H := - F2 * ((X2-X1)/(F2-F1)); X1 := X2; // node 3 X2 := X2 + H; F1 := F2 end loop; X := (X1 + X2) / 2.; // node 4 end SQRT; // node 5
Is the expression X2 - X1 available at the start of node 3?
At the end of node 3?
Same questions for Q - X2**2
1.2.6 Live Variables
A variable x is live at node n iff there exists a path starting at n along which x is used without prior redefinition.
In what nodes in H live?
In what nodes is X1 live?
What does this tell you about memory allocation within this function?
1.2.7 Data Flow and Optimization
Optimization Technique Data-Flow InformationConstant Propagation reachCopy Propagation reachElimination of Common Subexpressions availableDead Code Elimination live, reachRegister Allocation liveAnomaly Detection reachCode Motion reach
2 Style and Anomaly CheckingA common form of static analysis:
2.1 LintPerhaps the first such tool to be widely used, lint (1979) became a staple tool for C programmers.
Combines static analysis with style recommendations, e.g.,
data flow anomalies
potential arithmetic overflow
e.g., storing an int calculation in a char
conditional statements with constant values
potential = versus == confusion
Is there room for lint-like tools?
lint was a response, in part, to the weak capabilities of early C compilers
Much of what lint does is now handled by optimizing compilers
However compilers seldom do cross-module or even cross-function analysis
2.2 Static Analysis by CompilersOver time, compilers offer more and more static analysis features.
E.g., GNU g++
One caution is that these are often not turned on by default, but need to be added as command line flags.
IDEs often do not use these flags by default.
Analysis Options for g++
g++ offers several “collections” flags that turn on multiple warnings (which could have been turned on individually).
You explored these in an earlier lab.
-Wall: warnings that GNU considers “useful” and “easily avoidable”
Examples include:
out of bound accesses to arrays, (when compiled with -O2)
use of C++11 features when the explicit option for these has not been supplieduse of char in array subscriptsabuse of enumeration types in comparisonsbad formats in printfpossibly uninitialized variables
-Wextra: warnings that GNU considers “useful” but that can create false positives that can be hard to avoid.
Examples:
empty loop and if bodies,comparisons between signed and unsigned integers,unused function parameters.
-pedantic: warnings required by ISO C++ as for non-standard code.
e.g., non-standard file extensions
-Weffc++: warnings about violations of Scott Meyer’s Effective C++
Examples:
Failing to implement your own version of the copy constructor and assignment operator for classes that have dynamic allocation.operator= implementations that fail to return *this,the use of assignment rather than initialization within constructors
2.3 CheckStylecheckstyle is a tool for enforcing Java coding standards.
Focus is on the more cosmetic aspects of coding, e.g.:
Non-empty Javadoc comments for all classes and members.No * imports.Lines, function bodies, files not too long.{ } used around even single-statement loop bodies and if-then-else bodies.Whitespace used uniformly.
Sample report
Can be run via
AntMavenGradleeclipse
Checkstyle messages appear as warnings/errors in the Java editor.
2.3.1 Example: Adding Checkstyle to gradle
In build.gradle:
apply plugin: 'checkstyle' ⋮ checkstyle { ignoreFailures = true ➀ showViolations = false } ⋮ tasks.withType(Checkstyle) { ➁ reports { html.destination project.file("build/reports/checkstyle/main.html") } } checkstyleTest.enabled = false ➂
➀ ignoreFailures determines whether the gradle build should stop when CheckStyle reports a problem.
Usually, that’s not a good idea because not all CheckStyle problems are “real” – some are “false positives”.
➁ This section specifies where the report file should be stored.
Usually this should be somewhere in build/reports/.
➂ Suppresses running CheckStyle on code in src/test/java.
Report is generated on target check.
Which checks are run and which are suppressed is determined by a file config/checkstyle/checkstyle.xml, which is generally modified from one ofthese.
2.4 SpotBugsUnlike Checkstyle, SpotBugs goes well beyond cosmetics:
“Bugs” categorized as
Correctness bug: an apparent coding mistakeBad Practice: violations of recommended coding practices.Dodgy: code that is “confusing, anomalous, or written in a way that leads itself to errors”
Bugs are also given “priorities” (p1, p2, p3 from high to low)
Sample report
2.4.1 SpotBugs in Gradle
In build.gradle:
buildscript { repositories { jcenter() maven { url "https://plugins.gradle.org/m2/" // for spotbugs } } dependencies { ⋮ classpath "gradle.plugin.com.github.spotbugs:gradlePlugin:1.6.0" } } apply plugin: 'com.github.spotbugs' spotbugsMain { ignoreFailures = true effort = 'max' reportLevel = 'medium' reports { xml.enabled = false html.enabled = true } } spotbugsTest.enabled = false
2.5 PMDAnother good tool for finding non-cosmetic problems in your code:
PMD, source analysis for Java, JavaScript, XSL
CPD, “copy-paste-detector” for many programming languages
Can find large repeated code segments that might be better pulled out into a single function.
Works on source code
Sample reports (PMD & CPD)
Can be run via
AntMavenEclipse (see below)Gradle
2.5.1 PMD Reports
Configured by selecting “rule set” modules
Otherwise, appears to lack categories & priorities
Reports provide cross reference to source location
2.5.2 Example: Adding PMD to gradle
In build.gradle:
apply plugin: 'pmd' ⋮ pmd { ignoreFailures = true consoleOutput = false } pmdTest.enabled = false
Report is generated on target check.
2.5.3 Example: Adding PMD to Eclipse
Open the Eclipse Marketplace (Help menu) and search for “pmd”Select the “pmd-eclipse-plugin” and install
Open your Java project Properties and look for PMD.Select “Enable PMD”, then “Apply and Close”.
Look at all the colorful flags!
Open the PMD perspective (Window -> Perspective -> Open Perspective) to examine and manage messages.
You can ask for Details, remove a flag, mark a statement as approved under review, or suppress a rule entirely.
2.5.4 Customizing PMD
In Gradle and Eclipse, you customize by giving your own ruleset, like this one.
Gradle:
pmd { ruleSetFiles = ["config/pmd/ruleset.xml"] }
Removing a Rule
A common thing to do in a custom ruleset is to remove a rule entirely:
<rule ref="rulesets/java/comments.xml"> <exclude name="CommentSize"/></rule>
(This rule is well-intentioned, but tends to flag Javadoc-style comments that often have good reason to exceed it’s limit of 6 lines per comment.)
Modifying a Rule
<rule ref="category/java/codestyle.xml/ClassNamingConventions"> <properties> <property name="utilityClassPattern" value="[A-Z][a-zA-Z0-9]+"/> </properties></rule>
This rule defaults to insisting that all Java “utility” classes (ones that have no constructors) should have names ending with “Helper” or “Util”.
Often violated by main classes.Often given more specific names indicating what kind of help they provide e.g., “Factory” or “Wrapper”Always violated by unit tests.
Excluding Source Code
<ruleset ⋮> <description>PMD rule set - java applications</description> <exclude-pattern>.*/src/test/java/.*</exclude-pattern>
I don’t find PMD checks on Unit test code to be particularly useful.
Unit test code is often deliberately rougher than the application code.
3 Reverse-Engineering ToolsReverse engineering makes heavy use of static analysis, and is even more closely tied to compiler technology than the tools we have looked at so far.
3.1 Reverse Compilersa.k.a. “uncompilers”
Generate source code from object code
Originally clunky & more of a curiosity than usable tools
Improvements based on“deep” knowledge of compilers (aided by increasingly limited field of available compilers)Information-rich object codes (e.g., Java bytecode formats)
Legitimate uses include
reverse-engineeringgenerating input for source-based analysis tools
But also great tools for plagiarism
Java and Decompilation
Java is a particularly friendly field for decompilersRich object code formatNearly monopolistic compiler suite
3.1.1 Example of Java Decompilation
For example, I might write the following code:
void drawGraphics(Graphics g, Point[] pts) { double xMin = pts[0].x; double xMax = pts[0].x; double yMin = pts[0].y; double yMax = pts[0].y;
If I compile this with the -g debugging option on (which saves variable names and other internal information so that a debugger can access them), andrun any of several well-known decompilers, I would get back the same code, with only formatting changes.
If I compile this code without debugging info, one well-known decompiler would give me this:
void drawGraphics(Graphics g, Point[] pts) { double d0 = pts[0].x; double d1 = pts[0].x; double d2 = pts[0].y; double d3 = pts[0].y;
Compiled Java .class files always preserve the API info.
Defending Against Decompilers
Options for “protecting” programs compiled in Java:gjc: compile into native code with a far less popular compilerobfuscators…
3.2 Java ObfuscatorsWork by a combination of
Renaming variables, functions, and classes to meaningless, innocuous, and very similar name sets
Challenge is to preserve those names of entry points needed to execute a program or applet or make calls upon a library’s public API
Stripping away debugging information (e.g., source code file names and line numbers associated with blocks of code)
Applying optimization techniques to reduce code size while also confusing the object-to-source mapping
Replacing some expressions by calls to “dummy” functions that actually simply compute the replaced expression.
3.3 Obfuscation ExampleExample, given the compiled code from
void drawGraphics(Graphics g, Point[] pts) { double xMin = pts[0].x; double xMax = pts[0].x; double yMin = pts[0].y; double yMax = pts[0].y;
the obfuscator yguard will rewrite the code so that the best that a decompiler could produce is:
void a(Graphics a, Point[] b) { double d0; double d1; double d2; double d3; _mthfor(d0, _mthdo(b, 0)); _mthfor(d1, _mthdo(b, 0)); _mthfor(d2, _mthif(b, 0)); _mthfor(d3, _mthif(b, 0));
4 Dynamic Analysis ToolsNot all useful analysis can be done statically
Profiling
Memory leaks, corruption, etc.
Data structure abuse
Abusing Data Structures
Traditionally, the C++ standard library does not check for common abuses such as over-filling and array or accessing non-existent elements
Various authors have filled in with “checking” implementations of the library for use during testing and debugging
In a sense, the assert command of C++ and Java is the language’s own extension mechanism for such checks.
4.1 Pointer/Memory ErrorsMemory Abuse
Pointer errors in C++ are both common and frustrating
Traditionally unchecked by standard run-time systems
Monitors can be added to help catch these
In C++, link in a replacement for malloc & free
How to Catch Pointer Errors
Use fenceposts around allocated blocks of memory
check for unchanged fenceposts to detect over-writesCheck for fenceposts before a delete to detect attempts to delete addresses other than the start of an allocated block
Add tracking info to allocated blocks indicating location of the allocation call
Scan heap at end of program for unrecovered blocks of memoryReport on locations from which those were allocated
Add a “freed” bit to allocated blocks that is cleared when first allocated and set when the block is freed
Detect when a block is freed twice
Memory Analysis Tools
Purify is a well-known commercial (pricey) tool
At the other end of the spectrum, LeakTracer is a small, simple, but capable open source package that I’ve used for many years
Works with gcc/g++/gdb compiler suiteSample of Leaktracer Output +
Gathered 8 (8 unique) points of data. (gdb) Allocations: 1 / Size: 360x80608e6 is in NullArcableInstance::NullArcableInstance(void) (Machine.cc:40). 39 public: 40 NullArcableInstance() : ArcableInstance(new NullArcable) {} Allocations: 1 / Size: 80x8055b02 is in init_types(void) (Type.cc:119). 118 void init_types() { 119 Type::Integer = new IntegerType; Allocations: 1 / Size: 132 (new[]) 0x805f4ab is in Hashtable<NativeCallable, String, false, true>::Hashtable(unsigned int) (ea/h/Hashtable.h:15). 14 Hashtable (uint _size = 32) : size(_size), count(0) { 15 table = new List<E, own> [size];
Newer versions of g++ and clang offer Address sanitizer and Leak Sanitizer
Other sanitizers are worth checking out.
4.2 ProfilersProfilers provide info on where a program is speding most of its execution time
May express measurements in
Elapsed timeNumber of executions
Granularity may be at level of
functionsindividual lines of code
Measurement may be via
Probes inserted into codeStatistical sampling of CPU program counter register
Profiling Tools
gprof for C/C++, part of the GNU compiler suite
Refer back to earlier lesson on statement and branch coveragegprof is, essentially, the generalization of gcov
jvisualm for Java, part of the Java SDK
Provides multiple monitoring tools, including both CPU and memory profiling
Continuous IntegrationSteven J Zeil
Last modified: Dec 21, 2019
Contents:1 Big Builds2 Continuous Integration
2.1 Key Ideas2.2 Continuous Integration Systems
3 Case study: Jenkins3.1 Projects on Jenkins
4 Case study: gitlab-ci4.1 gitlab-ci setup
5 gitlab-ci vs Jenkins6 Case Study: Enhanced reporting
6.1 Generating Graphs6.2 Generating the Data6.3 Report Accumulator
7 Related ideas
Abstract
In continuous integration, the practices of version control, automated building, automated configuration, and automated testing are combined so that, as changesare checked in to the version control repository, the system is automatically rebuilt, tested, reports generated, and the results posted to a project website.
1 Big BuildsThink of everything we have started to put into our automated builds:
fetching and setup of 3rd party librariesstatic analysiscompilationunit testingdocumentation generationstatic analysis reports
packaging of artifactsdeployment/publication of artifactsupdating of project website
and, coming up, we will want to expand our testing to include
integration testingtest coverage reportingsystem testing
There’s a danger of the builds becoming so unwieldy and slow that programmers will start to look for ways to circumvent steps,
Do We Need to do All of Those Steps, All of the Time?
One possible breakdown:
Every build Occasionalfetching and setup of 3rd party libraries documentation generationstatic analysis static analysis reportscompilation deployment/publication of artifactsunit testing updating of project websitepackaging of artifacts integration testing
test coverage reportingsystem testing
This should provide someone actively working on a specific module/story the info they need, deferring some of the more time-consuming build activities.
How do we divide these steps in the build?
Even the “occasional” activities may be done many times over the history of a project.
So we want to keep them automated, both for ease of performing them and to ensure they are performed consistently each time.
With make/ant/maven, we can have different targets/goals for the frequent and the occasional cases.
But we have to remember to use the proper targets at the right time.Maybe not a bid deal…
But there’s an opportunity here to do something much more interesting…
2 Continuous IntegrationWhen we combine
Automated testing (unit, integration, system, and regression)Centralized version control
or distributed VC with a central “official” repositoryAutomated builds, capable of running tests, running analysis tools, and publishing the results on a project web site
we can rebuild and retest automatically as developers check in changes.
2.1 Key IdeasOur project should have the characteristics:
Version control with a clearly identified main branch or set of main development branches.
Automated build is set up as usual.
Developers commit frequently (maybe many times per day)
Commits to “private” branches (or local copies of a distributed repository) are ignored.Every commit of a tracked branch to the main repository is built on a separate server
The build includes all integration-related tasks (for early detection of integration problems.Can also include more time-consuming reporting tasks.
Testing is done, ideally, in a clone of the production environment(s)
May differ from development environmentsProbably not checked frequently under normal practiceCan use multiple remote machine “runners” to provide varying target operating systems and environments.
Make the results highly visible
2.1.1 Advantages
Integration problems caught early and fixed fastavoids “integration hell”
Immediate testing of all changesEmphasis on frequent check-ins encourages modularity
Visible code quality metrics motivate developers.
2.1.2 Disadvantages
Initial setup effort to set upLevel of sophistication required of team to put build, configuration mgmt, testing, reporting, into an automated build
2.2 Continuous Integration SystemsA CI system consists of a server/manager and one or more runners…
2.2.1 The Continuous Integration Server
A continuous integration server is a network-accessible machine that
Can be told of development projects under way, including
location & access info to version control (VC) repositorywhich branch(es) to watchhow to build the projectwhat reports are produced by the build
Monitors, in some fashion, the VC repository for commits
When a commit (to a monitored branch) takes place, the CI server notifies one or more runners.
2.2.2 Continuous Integration Runners
A CI runner (a.k.a., nodes or slave processors) is a process on a machine that
has the the necessary compilers and other tools for building a project.
is managed by the CI server.
When notified by the server, the runner
1. Checks out a designated branch of a project from its version control system.
2. Runs the build.
3. Publishes reports on the results of the build(s).
Runners are usually separate machines from the CI server.
A CI project may launch several different runners, each with a different configuration environment (e.g., different operating systems) to test the buildunder multiple configurations.
3 Case study: JenkinsJenkins is a popular CI server.
The CS Dept runs its own Jenkins server
An example of a Jenkins project
3.1 Projects on JenkinsWhen you set up a project on Jenkins you must supply:
Basic project info:
name and description,public/private,who can access.
Version control:
What kind of version control is used,URL and access info to check out a copy of the project.
Build management:
What build manager is used,where the build file can be found within the project directorieswhat target/goal to use with the build
(I usually add a special “jenkins” target to my Ant build.xml files.)
Which of Jenkin’s nodes can be used for the build.
Reporting
What reports Jenkins should publish.Where in your project directories the raw data for these reports can be found.
3.1.1 Jenkins and Project Reports
Many report-generating programs (e.g., JUnit, FindBugs, etc.) have separate “collection” and “reporting” stages.
Typically the collection step writes raw data out in an XML format.
Normally, you then run a separate task to reformat that XML into HTML or some other readable format.
Jenkins, however, has its own formatting functions for many common reports.
Among other things, these often add “historical” or “trend” reporting on how the collected data has varied over a period of time.
4 Case study: gitlab-cigitlab-ci is a CI server integrated into Gitlab.
Project build status is integrated into the version control activity reports,
Example
Click on Green checkmarks and Red X’s to see successful and failed builds.
Setup is generally easier if your project is already hosted on Gitlab.
4.1 gitlab-ci setup1. Projects must activate gitlab-ci by designating a runner, generally on a remote machine under the developer’s control.
You can have multiple runners. For example, you could have a Linux runner, a Windows 10 runner, and a MacOS runner.
2. Then add a file .gitlab-ci.yml to the repository root directory.
This is a YAML script that gets run after each new commit.
Script can limit which branches it applies to
4.1.1 An example of .gitlab-ci.yml
stages: - build - test - deploy build-job: tags: - e-3208 stage: build script: - eval $(ssh-agent -s -t 600) - ssh-add <(echo "$REPORTS_SSH_KEY") - cd codeCompCommon - ./gradlew build deployReports only: - master artifacts: paths: - codeCompCommon/build/libs/codeCompCommon-1.3.jar - README.md
tags: identifies the runner to be selected.script: gives the build commands to be run on the runner machine.only: limits these runs to checkins on the “master” branch.artifacts: lists files that will be kept after the run and made available for downloading from the GitLab project page.
4.1.2 The script dissected
script: - eval $(ssh-agent -s -t 600) ➀ - ssh-add <(echo "$REPORTS_SSH_KEY") ➁ - cd codeCompCommon ➂ - ./gradlew build deployReports ➃
1. This line launches an ssh key agent. The -t 600 option limits this agent to a maximum of 600 seconds before it shuts itself down.2. We know the ssh-add command as a way to add private ssh keys to the agent.
In this case, the text of the (passphrase-free) private key is being supplied by GitLab as a secret project variable REPORTS_SSH_KEY.
These are created as part of the project settings and visible only to project “masters”.
They provide a useful way to add private keys and other secret credentials without putting them into the repository files where anyone might beable to download them.
3. cd into the subproject directory containing the code and the Gradle files
4. Run the build with targets build and deployReports.
We’ll look at deployReports shortly.
5 gitlab-ci vs JenkinsReporting
Jenkins provides fancier reporting options. It composes a nice-looking project summary page.
Such activities must be scripted as part of the build to work in gitlab-ci.
Discussed later.
Flexibility
Jenkins has a definite Java bias.
gitlab-ci can run any language you can script a build for.
Setup
Jenkins setup can be confusing.
gitlab-ci setup is easier, but requires a properly setup remote runner.
Runners:
Jenkins allows remote runners, which are easily shared among projects.gitlab-ci requires remote runners, often requiring each project to set up their own.
6 Case Study: Enhanced reportingGoal: add project website pages with reports from analysis tools and trend information (historical graphs) similar to those offered by Jenkins.
We will build on the example of our JBake-generated website.
An example of a page we would like to generate is this PMD report page.
6.1 Generating GraphsHighcharts is a Javascript package that can generate plots from data captured in CSV (Comma-Separated-Values) format.
For example, the plot on this page is generated from a file pmd.csv that looks like:
pmd,Violations 2019-03-23T14:30,22.0 2019-03-23T14:33,22.0 2019-03-23T15:21,22.0 2019-03-23T15:45,22.0 2019-03-24T21:59,22.0 2019-03-31T19:48,22.0 2019-03-31T20:26,22.0 2019-04-03T20:42,11.0
Each line (after the headers) represents one data point in the chart.
Highcharts requires a bit of Javascript to inject a chart into an HTML div element.
We load the Highcharts code in our website header
header.ftl.listing +
<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8"/> <title><#if (content.title)??><#escape x as x?xml>${content.title}</#escape><#else>codecentric</#if></title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="description" content=""> <meta name="author" content=""> <meta name="keywords" content=""> <meta name="generator" content="JBake"> <link href="<#if (content.rootpath)??>${content.rootpath}<#else></#if>css/base.css" rel="stylesheet"> <link href="<#if (content.rootpath)??>${content.rootpath}<#else></#if>css/projectReports.css" rel="stylesheet"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js" type="text/javascript"></script> <script src="https://code.highcharts.com/highcharts.js"></script> <script src="https://code.highcharts.com/modules/data.js"></script> <script src="<#if (content.rootpath)??>${content.rootpath}<#else></#if>js/projectReports.js"></script> </head> <body>
<div id="mainBody">
This includes my own Javascript file to make it easier to share the code among several pages.
projectReports.js.listing +
/* * Register a div in the webpage as a HighCharts (http://www.highcharts.com/) chart * portraying a series of data in a CSV file */ /* register1 * For CSV files containing a single series of data. Column A contains the * Build numbers used as x values, column B the y values. * * @param graphName The id of the div to hold this chart. * @param csvURL URL to the .csv file * @param title Title for this chart. * @param yAxistitle Title for the Y axis. (Series title is taken from * row 1 of the CSV file). */ function register1(graphName, csvURL, title, yAxisTitle) { var divName = "#" + graphName; $(document).ready(function() { $.get(csvURL, function(csv) { $(divName).highcharts({ chart: { type: 'area' }, data: { csv: csv }, title: { text: title }, yAxis: { title: { text: yAxisTitle } }, xAxis: { title: { text: "Build #" } }, plotOptions: {
series: { stacking: 'normal' } }, series: [{ color: "#0000cc" } ] }); }); }); } /* register2 * For CSV files containing two series of data. Column A contains the * Build numbers used as x values, columns B and C the y values. * * @param graphName The id of the div to hold this chart. * @param csvURL URL to the .csv file * @param title Title for this chart. * @param yAxistitle Title for the Y axis. (Series title is taken from * row 1 of the CSV file). */ function register2(graphName, csvURL, title, yAxisTitle) { var divName = "#" + graphName; $(document).ready(function() { $.get(csvURL, function(csv) { $(divName).highcharts({ chart: { type: 'area' }, data: { csv: csv }, title: { text: title }, yAxis: { title: { text: yAxisTitle } }, xAxis: { title: { text: "Build #" } },
plotOptions: { series: { stacking: 'normal' } }, series: [{ color: "#009933" }, { color: "#cc0000" } ] }); }); }); }
Which makes for a reasonably straightforward content page:
pmd.html.listing +
title=CodeCompCommon PMD Report type=page status=published ~~~~~~ </p><div class=reportGraphs> <div id="theGraph" class="graph">PMD</div> ➀ </div> <iframe class="docFrame" src="pmd/main.html"> </iframe> ➁ <script type="text/javascript"> register1("theGraph", "pmd.csv", "PMD", "Warnings"); ➂ </script> <p>
1. This is the div that will be replaced by the chart.2. The body of the report.
This is very similar to the way we loaded our Javadoc and other reports earlier.
3. This calls my Javascript function, which in turn calls the Highchart functions, to schedule replacement of the above div by a chart derived from thedata in pmd.csv.
6.2 Generating the DataWhere does the data for the plots come from?
Individual data points are extracted from the reports generated by PMD and other tools.
Each analysis tool requires some custom coding to get the data point.
Those data points are accumulated into a .csv file for the report by
1. Downloading the old CSV file (if there is one) fro mthe project website.2. Adding the new data point as a new line at the end of the CSV file.
3. When the website is uploaded, it will include the new, one-line-longer, CSV file,
These steps are carried out by my own Report Accumulator Gradle plugin.
6.3 Report AccumulatorBack to build.gradle to load another plugin:
build.gradle.listing +
buildscript { repositories { ⋮ ivy { // for report-accumulator url 'https://www.cs.odu.edu/~zeil/ivyrepo' ➀ } } dependencies { ⋮ classpath 'edu.odu.cs.zeil:report_accumulator:1.2' ⋮ } } ⋮ // Reporting
import edu.odu.cs.zeil.report_accumulator.ReportStats ➁ import edu.odu.cs.zeil.report_accumulator.ReportsDeploy task collectStats (type: ReportStats, dependsOn: ['build','reports']) { ➂ description "Collect statistics from various reports & analysis tools" reportsURL = 'https://www.cs.odu.edu/~zeil/gitlab/' + project.name + '/reports' } task site (dependsOn: ['copyBake', 'copyJDocs', 'collectStats']){ ➃ description "Build the project website (in build/reports)" group "reporting" } task deployReports (type: ReportsDeploy, dependsOn: 'site') { ➄ description 'Deploy website to remote server' group 'reporting' deployDestination = 'rsync://[email protected]:codeCompCommon/reports/' }
1. The usual steps to include a plugin.2. Import the new task types created by the plugin3. A ReportStats task
scans the build/reports directory for reports from various tools,extracts a new data point where it can,downloads an existing CSV file for that report from the reportsURLadds the new data point to the end of the CSV file
4. A slight tweak to our earlier site target to make sure that the ReportStats task is performed
5. A ReportsDeploy task uploads the build/reports directory to the deployDestination URL.Assumes that any required ssh keys are already in an agent.
7 Related ideasContinuous deployment publishes snapshots of deliverables as changes are checked in.
A variation of the rather common “daily build” practice seen on many projects.Some Maven repositories (including our own Artifactory instance) provide separate “snapshot” repositories for this purpose.The idea of “artifacts”, seen earlier, is one way to carry this out.
GitHub and later versions of GitLab now use the artifacts mechanism to deploy entire project websites, making it unnecessary to work with aseparate web server.
Some organizations actually wire up build light indicators to provide a highly visible indicator of the status of the latest integration build.
Some then point a webcam at the light and broadcast their status.Others opt for publishing a software analog of such lights on their project website.
Lab: Continuous IntegrationSteven J Zeil
Last modified: Apr 8, 2020
Contents:1 Setting up CI2 Deploying Artifacts3 Deploying Reports
This is a self-assessment activity to give you practice in working with the gitlab-ci continuous integration system. Feel free to share your problems,experiences, and choices in the Forum.
1 Setting up CIIn a previous assignment, you set up a project on GitLab with an automated build and some unit tests, all controlled by gradle. We’ll use that as the startingpoint for this lab.
Running continuous integration on gitlab-ci requires a gitlab project with
an automated build,
a designated runner machine/process to execute that build, and
a configuration script, .gitlab-ci.yml that tells the runner what to do to run your build.
1. Make sure that you have a fresh copy of your GitLab1 project from GitLab, and that it builds successfully using the Gradle wrapper.
2. Visit your GitLab1 project on GitLab. Go to the “Settings” page, then select “CI/CD”.
3. Expand “General Pipelines”. Set the Timeout to “5 minutes”, so that a “stuck” or looping build will terminate after a few minutes.
4. Scroll down to “Runners” and expand it.
On this page, you can see runners that are available (“shared”) for general use or that have been registered to work specifically for this project. A specificrunner requires installing special software on a machine that can function as a server and is available whenever you are likely to be committing changes toyour project. Since not everyone may be in a position to set something like that up, we will used a shared runner instead.
The shared runners are identified by various tags, which you will see listed under each one. In your project work, you have been assigned to a teamidentified by a color and a number. For this lab, use the runner matching your team name, e.g., “red1”, “green4”, “blue2”, etc.
5. Return to your local copy of your project. Find the root directory of your project.
If you followed the guidelines for project directory structure carefully, the root directory of your project contains a README file, a .git directorycontaining your git repository, and a sub-directory with your build files and your src/ directory.
If so, your Eclipse project should actually be pointing at that sub-directory. You might find it useful to create a second Eclipse project, GitLab1-root, this one a “General Project” instead of a Java or Gradle project, pointed at the true root directory. This will make it easier to edit files in yourroot directory.
If you were a bit more loose in your structuring, your project’s root directory may have your build files and your src directory in the same directoryas your README and .git repo.
6. In that root directory, create a .gitlab-ci.yml text file. The overall structure of this file should be
stages: - build build-job: tags: - # runner tag name stage: build script: - # script line 1 - # script line 2 - # ... only: - master
In the tags area, insert the identifying tag of the shared runner you selected earlier.
In the script area, you replace the # lines with shell script statements to actually run your build. Typically, this should be short and simple, because all ofthe complicated stuff is already set up to be handled by your build manager.
The script will be started in your project root directory, so if your build files and src directory are actually in a sub-directory, you may want to cdinto that sub-directory.
After that, something like ./gradlew build should actually run your build.
Here is a suggested starting script, liberally sprinkled with ls commands so that you will be able to see what was going on:
script: - ls
- cd relativePaths - ls - chmod +x gradlew - ./gradlew build - ls build/*
7. When you have finished editing that file, commit your changes and push it to your GitLab project.
8. On GitLab, go to your Repository -> Commits page and you should see your new commit. On the right, you may see a checkmark or other symbolindicating that a build was attempted for this commit.
On your Repository -> Files page you should see your new .gitlab-ci.yml file.
9. Go to your CI/CD -> Pipelines page. You should see a line indicating that a “build” has been added. Depending on how quickly you got here, it mayhave a status of “Pending”, “Running”, “Passed”, or “Failed”.
If you don’t see a build listed, you probably have either failed to commit a .gitlab-ci.yml file to your root directory. Go back and check your Files listingcarefully. If that looks OK, check to be sure that you really did assign a runner to your project.
1. Click on the status symbol to be taken to a listing of what has happened or, if it is running, is currently happened with your running build. If it’s stillrunning, you can watch it in more-or-less real time.
2. If your build failed, try to figure out why and fix it. Common issues include:
Errors in the script portion of the .gitlab-ci.yml file.
Look particularly at whether you have cd’d to the wrong place.
Errors in your build file. Do your builds actually work when invoked from the command line?
A common mistake is to embed absolute paths to libraries and other files within your build instructions. Those paths are specific to your ownmachine and will likely not match anything on the runner machine.
Missing files in your repository. You may have files in your local copy of your project that have not been getting committed to your git repository.Compare your local project listing carefully against the Files page of your project on GitLab. Make sure that your all of your build files (includingthe gradlew wrappers), your source code, and, if you have any, test data input files are being uploaded to the repository.
The build runs as expected, but stops/fails because your code fails one or more unit tests.
When running builds manually, it’s common to stop if any tests fail.
When running in a continuous integration setting, we often want to continue on and collect statistics and reports for the project website.
You can tell your Gradle build to keep going even if some unit tests fail by adding
test { ignoreFailures = true }
to your build.gradle file.
2 Deploying Artifacts“Artifacts” are the intended final products of a build. For a java project, the artifacts are generally jar files.
When we studied configuration management we saw that projects often deploy their “milestone” releases to large distributed repositories such as MavenCentral, JCenter, or the various Linux distribution repositories.
You may have encountered some projects, however, that have less official releases, e.g., “daily builds”. Continuous deployment is the practice of providing acopy of the project artifacts as a side effect of the continuous integration build.
Examine the build directory for your project. You should find that a Jar file has been created in build/libs/. When your build is done on the gitlab-cirunner, that same file is built, but then sits un the runner, unavailable to anyone. We’re going to set that up to be released on each build of your project.
1. If you don’t have one, add a README.txt or README.md file to your project.
2. Then edit your .gitlab-ci.yml file to add an “artifacts” section:
build-job: tags: ⋮ only: - master artifacts: paths: - path-to-your-README - path-to-your-Jar-file
The paths that you add should be relative paths starting from your project root directory.
3. Commit your changes.
Your project should build on the runner as before. When it is done, however, you should find a download button/link as shown on theright has been to your project’s home page. That provides access to a .zip archive holding the two files we named as artifacts.
4. Try downloading that file and unpacking it. You can see that the original directory structure separating the two files is preserved.
Something to think about: suppose you wanted to have both files in the top level of the .zip instead of buried in a directory. How could you do that?
3 Deploying ReportsExamine your project again. in build/reports/tests/, you should find a nice little collection of files providing a web-ready summary of your unit testperformance. Suppose that we wanted to put that up on a project website?
The artifact mechanism doesn’t help here, because it would bury the web pages inside a .zip file, and you can’t browse to the interior of an archive.
There is a gradle-ssh-plugin that can be used to copy files. It would be a tad cumbersome to copy the files to the web server one at a time, but we could add acommand to our .gitlab-ci.yml script to zip up the whole report, then use the gradle-ssh-plugin to copy that zip file to our web server, then use it again toissue an unzip command remotely to that web server.
Of course, to use ssh we need to supply a login and password for the remote machine. And we don’t want to put that kind of into our repository where anyonemight read it.
gitLab has a mechanism of “secret variables” that we could use to store a login name and password. We would need to be careful how we make use of thosevariables, to make sure that they don’t have their values listed in the build transcripts where anyone could read them, but it’s definitely possible.
Secret variables can only be examined by the “master” of a GitLab project. But what if a project has more than one “master”? We can improve on this a bit.Remember those command-limited ssh keys you created a long time ago to allow files to be uploaded to your website?
1. In the Settings -> CI/CD area of your GitLab project, go to Variables.
Create a new variable named RSYNC_KEY and, for the value, insert the private key that you created for this purpose.
2. Edit your .gitLab-ci.yml file. We’re going to augment the script for the build-job:
build-job: ⋮ script: ⋮ - eval $(ssh-agent -t 5m -s) - ssh-add <(echo "$RSYNC_KEY") - rsync -auvz -e "ssh" build/reports/tests/ [email protected]:GitLab1/ - ssh-agent -k
making an appropriate substitution for the tags and for yourLoginName.
In the script area,
The first line starts an ssh agent.
The “-t 5m” means that, after you add a key, it will continue to offer that key for up to 5 minutes, which should be more than enough time for whatwe are going to do.
The second line registers the private key stored in your RSYNC_KEY secret variable with that agent.
The third line should use that key to publish your test report to your website.
The final line shuts down your ssh agent. Otherwise the process tends to hang around forever.
3. Commit your changes. Examine the pipeline listing to see if your build completes successfully. If not, try to fix it.
4. Use your web browser to visit https://www.cs.odu.edu/~yourLoginName/cs350/sshAsst/GitLab1/.`
Can you find your test reports?
Something to try: Modify your gradle invocation to build javadocs:
./gradlew build javadoc
Can you modify your .gitlab-ci.yml script to publish those to your website as well? (Hints: you could add a second rsync command, or add acommand to copy the javadoc report into build/reports and modify your rsync command to publish the entire reports directory.)
System TestingSteven J Zeil
Last modified: Dec 21, 2019
Contents:1 Integration and system testing
1.1 Unit to Integration1.2 Integration to System
2 Testing Phases and the Build Manager2.1 How often should we run integration/system tests?2.2 Separating the Tests2.3 Building the Tests
3 Test Coverage3.1 Coverage Measures3.2 C/C++ - gcov3.3 Java
4 Oracles4.1 expect4.2 *Unit4.3 Testing GUI systems4.4 Web systems4.5 Selenium
Abstract
System testing adds particular challenges to testing because it is constrained to work with the actual system inputs and outputs. Compared to unit testing, wehave less control over both the input supply and the capture and evaluation of the output.
In this lesson we look at tools for measuring quality of system testing. We will explore the difficulties that arise when dealing with non-text input and output,particularly graphical interfaces, and will look at some of the support available for testing at this level.
1 Integration and system testingexercise larger potions of code than does unit testing.validates the interactions between separate code features.
1.1 Unit to IntegrationStart with your unit tests.
Replace stubs and mocks by real code.Now they are integration tests!
More to the point, the problem is usually not converting from unit tests to integration tests. As we saw when looking at stubs & mocking, it’s oftencommon to come up with integration tests that we have to work hard at to turn into properly isolated unit tests.
So those “first drafts” at unit tests can often be saved to serve as integration tests.
Supplement with tests of “interesting” interactions among modules.
1.2 Integration to SystemSystem testing is the limiting case of integration testing.
Works with entire program’s inputs and outputs
A challenge when inputs and outputs involve GUIs, databases, and other non-text, non-API components.
Generally fewer tests than under unit and integration testing.
But may run a lot longer
2 Testing Phases and the Build ManagerGenerally, unit tests should run quickly (a matter of seconds)
So we design the build to re-run these every time the code is recompiled.
Integration tests and systems tests may take considerably longer.
Might only want to rerun these during more occasional “reporting” runs when we want a full report on the overall state of the project.
Suggests distinct targets in the build manager.Probably easiest if we keep the different kinds of tests in separate directories
2.1 How often should we run integration/system tests?Plausible answers:
Once daily.When we push a set of local changes to the central repository.When merging changes into the master branch.
These kinds of runs are often triggered automatically.
Daily runs cane be a simple timed script.Runs triggered by repository actions are triggered using continuous integration.
2.2 Separating the TestsFor Java projects, we already have a separation of code into src/main and src/test. We might consider adding src/integrationTest and src/systemTest.
I sometimes use src/itest and src/systest.
Each test directory ty[ically has source code (e.g., src/itest/java) and maybe data files (e.g., src/systest/data).
2.3 Building the TestsIn Gradle, we can add test directories with the TestsSets plugin.
In build.gradle:
plugins { id 'java' } repositories { jcenter() } dependencies { testImplementation("junit:junit:4.12") testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.5.2") } test { useJUnit()
} // Add integration test directory itest sourceSets { iTest { compileClasspath += sourceSets.main.output runtimeClasspath += sourceSets.main.output } } configurations { iTestImplementation.extendsFrom implementation iTestRuntimeOnly.extendsFrom runtimeOnly } dependencies { iTestImplementation 'junit:junit:4.12' }
This adds to a Java project new tasks to compile and run tests from src/iTest/java.
You can configure these tasks independently, e.g.,
iTest.mustRunAfter test ➀ iTest { ignoreFailures = true ➁ } test { ignoreFailures = false ➂ }
➀ Makes sense to run the integration tests only after the normal unit tests.
➁ If we want to generate reports of how many tests passed and failed, we probably need to make sure the build keeps going (so that we can get to thereporting tasks) even if some tests fail.
Keep in mind also that our integration tests are likely to start as things that fail, and will continue to fail until we actually get far enough in the projectdevelopment for losts of the missing pieces to have been finally implemented.
➂ This is for the purpose of illustration only. I don’t know why you would want to kill your reporting after unit test failures. After all, in TDD, we expectsuch failures to be common and to persist for some time.
3 Test Coverage
Although we can monitor test coverage during unit test, it’s more common to do this during integration and system test.
During Unit test, we are working with a lot of “fake” code (drivers and stubs/mocks).
We certainly don’t care how well our drivers and stubs were covered!
Integration and system testing gets to more realistic ’combinations" of operations.
3.1 Coverage MeasuresWe have previously reviewed:
Black-Box Testing
Equivalence partitioningBoundary-value testingSpecial-values testing
White-Box Testing
Structural Testing (a.k.a., “path testing”
Statement CoverageBranch CoverageCyclomatic coverage (“independent path testing”)Data-flow Coverage
Mutation testing
3.2 C/C++ - gcovMonitoring Statement Coverage with gcov
coverage tool includes with the GNU compiler suite (gcc, g++, etc.)
As an example, look at testing the three search functions in
arrayUtils.h +
#ifndef ARRAYUTILS_H#define ARRAYUTILS_H
// Add to the end// - Assumes that we have a separate integer (size) indicating how// many elements are in the array// - and that the "true" size of the array is at least one larger // than the current value of that countertemplate <typename T> void addToEnd (T* array, int& size, T value) { array[size] = value; ++size; } // Add value into array[index], shifting all elements already in positions// index..size-1 up one, to make room.// - Assumes that we have a separate integer (size) indicating how// many elements are in the array// - and that the "true" size of the array is at least one larger // than the current value of that counter template <typename T> void addElement (T* array, int& size, int index, T value) { // Make room for the insertion int toBeMoved = size - 1; while (toBeMoved >= index) { array[toBeMoved+1] = array[toBeMoved]; --toBeMoved; } // Insert the new value array[index] = value; ++size; } // Assume the elements of the array are already in order// Find the position where value could be added to keep// everything in order, and insert it there.// Return the position where it was inserted// - Assumes that we have a separate integer (size) indicating how// many elements are in the array// - and that the "true" size of the array is at least one larger // than the current value of that counter template <typename T> int addInOrder (T* array, int& size, T value)
{ // Make room for the insertion int toBeMoved = size - 1; while (toBeMoved >= 0 && value < array[toBeMoved]) { array[toBeMoved+1] = array[toBeMoved]; --toBeMoved; } // Insert the new value array[toBeMoved+1] = value; ++size; return toBeMoved+1; } // Search an array for a given value, returning the index where // found or -1 if not found.template <typename T> int seqSearch(const T list[], int listLength, T searchItem) { int loc; for (loc = 0; loc < listLength; loc++) if (list[loc] == searchItem) return loc; return -1; } // Search an ordered array for a given value, returning the index where // found or -1 if not found.template <typename T> int seqOrderedSearch(const T list[], int listLength, T searchItem) { int loc = 0; while (loc < listLength && list[loc] < searchItem) { ++loc; } if (loc < listLength && list[loc] == searchItem) return loc; else return -1; } // Removes an element from the indicated position in the array, moving// all elements in higher positions down one to fill in the gap.template <typename T>
void removeElement (T* array, int& size, int index) { int toBeMoved = index + 1; while (toBeMoved < size) { array[toBeMoved] = array[toBeMoved+1]; ++toBeMoved; } --size; } // Search an ordered array for a given value, returning the index where // found or -1 if not found.template <typename T> int binarySearch(const T list[], int listLength, T searchItem) { int first = 0; int last = listLength - 1; int mid; bool found = false; while (first <= last && !found) { mid = (first + last) / 2; if (list[mid] == searchItem) found = true; else if (searchItem < list[mid]) last = mid - 1; else first = mid + 1; } if (found) return mid; else return -1; } #endif
with test driver
gcovDemo.cpp +
#include <cassert>#include <iostream>#include <sstream>#include <string> #include "arrayUtils.h" using namespace std; // Unit test driver for array search functions int main(int argc, char** argv) { // Repeatedly reads tests from cin // Each test consists of a line containing one or more words. // The first word is one that we want to search for. The // remaining words are placed into an array and represent the collection // we will search through. string line; getline (cin, line); while (cin) { istringstream in (line); cout << line << endl; string toSearchFor; in >> toSearchFor; int nWords = 0; string words[100]; while (in >> words[nWords]) ++nWords; cout << seqSearch (words, nWords, toSearchFor) << " " << seqOrderedSearch (words, nWords, toSearchFor) << " " << binarySearch (words, nWords, toSearchFor) << endl; getline (cin, line); }
return 0; }
which reads data from a text stream (e.g., standard in), uses that data to construct arrays, and invokes each function on those arrays, printing the results of each.
Compiling for gcov Statement Coverage
To use gcov, we compile with special options
-fprofile-arcs -ftest-coverage
When the code has been compiled, in addition to the usual files there will be several files with endings like .gcno
These hold data on where the statements and branches in our code are.
Running Tests with gcov
Run your tests normally.
As you test, a *.gcda file will accumulate data on your test coverage.
Viewing Your Report
Run: gcov_mainProgram_The immediate output will be a report on the percentages of statements covered in each source code file.Also creates a *.gcov detailed report for each source code file. e.g.,
Sample Statement Coverage Report
-: 69:template <typename T> -: 70:int seqSearch(const T list[], int listLength, T searchItem) -: 71:{ 1: 72: int loc; -: 73: 2: 74: for (loc = 0; loc < listLength; loc++) 2: 75: if (list[loc] == searchItem) 1: 76: return loc; -: 77: #####: 78: return -1; -: 79:}
Report lists number of times each statement has been executedLists #### if a statement has never been executed
Monitoring Branch Coverage with gcov
gcov can report on branches taken.
Just add options to the gcov command:gcov -b -c_mainProgram_
Reading gcov Branch Info
gcov reportsNumber of times each function call successfully returnedNumber of times a branch was executed (i.e,, how many times the branch condition was evaluated)
and number of times each branch was taken
For branch coverage, this is the relevant figure
But What is a “Branch”?
A “branch” is anything that causes the code to not continue on in straight-line fashion
Branch listed right after an “if” is the “branch” that jumps around the “then” part to go to the “else” part.&& and || operators introduce their own branchesOther branches may be hidden
Contributed by calls to inline functionsOr just a branch generated by the compiler’s code generator
In practice, this can be very hard to interpret
Example: gcov Branch Coverage report
-: 84:template <typename T> -: 85:int seqOrderedSearch(const T list[], int listLength, T searchItem) -: 86:{ 1: 87: int loc = 0; -: 88: 1: 89: while (loc < listLength && list[loc] < searchItem)
branch 0 taken 0 call 1 returns 1 branch 2 taken 0 branch 3 taken 1 -: 90: { #####: 91: ++loc; branch 0 never executed -: 92: } 1: 93: if (loc < listLength && list[loc] == searchItem) branch 0 taken 0 call 1 returns 1 branch 2 taken 0 1: 94: return loc; branch 0 taken 1 -: 95: else #####: 96: return -1; -: 97:}
Report is organized by basic blocks, straight-line sequences of code terminated by a branch or a call
Hard to map to specific source code constructs
lowest-numbered branch is often the leftmost conditionFact of life that compilers insert branches and calls that are often invisible to us
3.3 JavaJava Coverage Tools
Clover
JaCoCo
Part of the EclEmma project (Eclipse plugin for Emma)Emma, an older coverage tool, now replaced by JaCoCo
Clover
Commercial product, currently free for open-source projects
integrates with Ant, Mavenlots of reporting features
Works in “traditional” coverage tool fashion
Requires a “fork” of the build process to build a monitoring versionInjects monitors into compiled code
Test optimization: can re-run only those tests that covered changed code
JaCoCo
Java Code Coverage
line and branch coverage
Instrumentation is done on the fly
An “agent” monitors execution of normally compiled bytecodeNo special build required
Works with Eclipse
JaCoCo “started” as an Eclipse plug-in
Works with Maven & Ant
In Ant, wrap normal <java> and <junit> tasks inside a <jacoco:coverage> element
Works with Gradle
Just apply the plug-in.
Example: JaCoCo in Eclipse
Using JaCoCo in Eclipse
Once you have the plugin installed,
1. Open any Eclipse Java project.2. Right-click on a unit test or executable. Look for “Coverage As”
Function of this is the same as “Run As” and “Debug As”, but monitors coverage during execution.
3. After execution has completed coverage results are shown
A summary in the console area under the “Coverage” tabDetails in the Java editor, as color coding
Green means that all branches were coveredRed means that none were covered.Yellow means that some were covered.
Example: JaCoCo in Gradle
In build.gradle:
plugins { id 'java' id 'jacoco' } ⋮ check.dependsOn jacocoTestReport
The last line is because I typically have a task named “check” that is my target for report generation. In other words, I plan to use
./gradlew check
to prepare all of my project reports, so I add a dependency between each kind of report I add and the check task.
Example: JaCoCo Report
Report
4 OraclesA testing oracle is the process, person, and/or program that determines if test output is correct
4.1 expectCovered previously, expect is a shell for testing interactive programs.
an extension of TCL (a portable shell script).
Largely confined to text streams as input/output
4.2 *UnitCan we use *Unit-style frameworks as oracles at the system test level?
The very question is heresy to many *Unit advocates
Particularly runs counter to the goals of the various Mock Objects projects
But, why not?
Such tests do not (should not) be at the expense of having done earlier “proper” unit testing.
Particularly in Java, MyClass.main(String[]) can be called just like any other function
And System.in (cin) and System.out (cout) can be rerouted to/from files or internal strings
Major limitation is the accessibility of system inputs & outputs.
GUIs, data bases, etc.
4.3 Testing GUI systemsScripting or record/playback: playing back input events for
convenience & efficiencyconsistent reproducibility
Capture of results
Can occur at different levelsevent/message levelgraphics level
Some Open Alternatives
Marathon - free in limited version
Jemmy
Marathon
For Java GUIs
Recorder captures AWT/swing events as JRuby scripts
Scripts can then be edited to alter inputs, add assertions, etc.
def test $java_recorded_version = "1.6.0_24" with_window("Simple Widgets") { select("First Name", "Jalian Systems") select("Password", "Secret") assert_p("First Name", "Text", "Jalian Systems") } end
Jemmy
Also for Java GUIs
Tests scripted as Java
Integrates with JUnit
Example
4.4 Web systemsA subproblem of GUI testing
Simpler because input structure more constrainedOutput detail level is fixed (http: events)
Some Open Alternatives
Selenium
antEater
Watir
4.5 SeleniumBrowser automation (SeleniumIDE - Firefox add-on)
Record & playbackOr scripted (Selenium Webdriver)
Firefox, IE, Safari, Opera, Chrome
Selenium Scripting
Actions do things to elements.
E.g., click buttons, select options
Accessors examine the application state
Assertions validate the state
Each assertion has 3 modes
assert: failure aborts the testverify: test continues, but failure is loggedwaitFor: conditions that may be true immediately or may become true within a specified time interval
Selenese
A typical scripting statement has the form
command parameter1 [parameter2]
Parameters can be
locators for finding a UI element within a page (xpath)
text patterns
variable names
A Sample Selenium Script
<table> <tr><td>open</td><td>http://mySite.com/downloads/</td><td></td></tr> <tr><td>assertTitle</td><td></td><td>Downloads</td></tr> <tr><td>verifyText</td><td>//h2</td><td>Terms and Conditions</td></tr> <tr><td>clickAndWait</td><td>//input[@value="I agree"]</td><td></td></tr> <tr><td>assertTitle</td><td></td><td>Product Selection</td></tr></table>
That’s right – it’s an HTML table:
open http://mySite.com/downloads/assertTitle DownloadsverifyText //h2 Terms and ConditionsclickAndWait //input[@value=“I agree”]assertTitle Product Selection
A Selenium “test suite” is a web page with a table of links to web pages with test cases.
Selenium Webdriver
An alternate version of Selenium is more code-oriented. It provides APIs to a variety of languages allowing for very similar capabilities:
Select select = new Select(driver.findElement( By.tagName("select"))); select.deselectAll(); select.selectByVisibleText("Edam");
Selenium works by interacting with a “real” web browser (Firefox, Chrome) to simulate actions like clicking on or sending keystrokes to a web page element.
I’ve used Selenium Webdriver to implement some very nice web scraper applications.
Waiting
A tricky thing about testing web applications is the unknwon amount of time that may be required to respond to a click or other interaction.
Valid tests often need to give very specific instructions to wait for pages to be loaded and for elements to become visible and clickable.
WebDriver driver = new FirefoxDriver(); driver.get("http://somedomain/url_that_delays_loading"); WebElement myDynamicElement = ( new WebDriverWait(driver, 10)) .until(ExpectedConditions.elementIsClickable( By.id("myDynamicElement")));
Waits up to 10 seconds for an expected element to load and become active.
Regression TestingSteven J Zeil
Last modified: Dec 21, 2019
Contents:1 Regression Frameworks
1.1 DejaGnu1.2 fitnesse
2 Maveryx
Abstract
Regression testing monitors the changes, for better or worse, of the system as we make changes to it. Regression testing generally involves large numbers oftests, often selected as a mixture of test cases originally developed as unit, integration, and system tests.
In this lesson we will look at what is required for support of regression testing.
1 Regression FrameworksWhat Makes a Testing Framework a Regression Framework?
Little agreement, but IMNSHO
System-level orientation
Technically, regression tests can come from any level of normal testing
High degree of automation
Regression sets can be huge
Allowance for both expected pass and expected failure cases
Regression testing is about learning what has changed, not what has passed
Open Possibilities
DejaGNU
fitnesse
Maveryx
1.1 DejaGnuPosix-compliant regression test framework
Written in expect
Strong support for multiple configurations
Test Results
Standard for test frameworks.
Possible outputs for any test:
PASS: test has succeeded
FAIL: test has failed
XFAIL: test has failed, but the failure was expected
XPASS: test has passed, but was expected to fail
UNRESOLVED: The test has produced indeterminate results and requires human review for resolution
UNTESTED: The test was not run.
This is a placeholder for tests that have not yet been written.
UNSUPPORTED: The test cannot be run because of external conditions
(e.g., it is OS-specific and we are testing in a different OS).
Sample Test
set testdata { {"addition" "22 + 1" "23"} {"multiplication" "21 * 2" "42"} {"division" "14 / 3" "4"} ⋮ } foreach pattern $testdata { eval "spawn ./calc [lindex $pattern 1]" expect { -re [lindex $pattern 2] { pass [lindex $pattern 0] } default {fail [lindex $pattern 0] } } }
Running DejaGnu
runtests --tool calcTests
runs all tests in the calcTests directory, producing output like:
PASS addition PASS multiplication ⋮ # of expected passes 12 # of unexpected failures 2
1.2 fitnesseTests composed on Wiki pages
Encourages collaborative dvelopment & readable documentation
Tool walks pages to run tests
example
Is fitnesse a Regression Framework?
Advertises itself as such.
Not clear to me how to extend it past simple text I/O
Is this really a system-level approach?
Concept of expected fail is missing
2 MaveryxJava & Android GUI testing
Traditional GUI element searching replaced by “object recognition” expertsIs this really different from programmed searches?Features some “fuzzy” search criteria
Scripting in Java
Use JUnit 4 assertions to write your oracle code.
Is Maveryx a Regression Framework?
Advertises itself as such.
Appears to have legitimate system-level focus
Expected fail?
Issue TrackingSteven J Zeil
Last modified: Dec 21, 2019
Contents:1 Issue Tracking
1.1 Basic Questions2 Bugzilla3 Other Systems4 Eclipse - Mylyn
Abstract
Issue Tracking has long been recognized as an essential part of software maintenance.
In this lesson we will look at the kinds of information managed in typical issue tracking systems, and will discuss some of the ways in which an issue trackingsystem can impact even pre-maintenance development activities.
1 Issue TrackingAn apparently mundane task with significant impact on
maintenance cost
customer satisfaction
project personnel management
1.1 Basic QuestionsRoles in an Issue Tracking System
Users (internal or external to the project team) report problems
Project administrators prioritize problems and assign some to project staff
Project staff report on status of problem solution
What Questions Does an Issue Tracking System Answer??
What problems are currently open?
Is anyone working on these problems?What’s their status?
Which of those are the most severe?
Have similar problems been reported in the past?
What is tracked?
Which release(s) does the problem occur in?
What are the environmental factors?
Operating system3rd party software
Problem history
Can the problem be reproduced? How?
Expected behavior
Observed behavior
Open Alternatives
Bugzilla was one the earliest well-known such systems
Defined much of the feature set and organization still used by its successors
Commonly integrated into Forges to take advantage of infrastructure already provided for project management
particularly personnel mgmt.
2 BugzillaOriginally for Mozilla/Netscape project (1998)
Major updates have continued through 2011 (v4.0)
Bugzilla Problem Report
Bugzilla Classifies Problems by
Severity
Blocker (a.k.a Showstopper): blocks development or testingCritical: crashes, loss of dataMajor: loss of important functionalityNormalMinor: minor functionality loss and/or a workaround is availableTrivial: e.g., misspelled messagesEnhancement: request for new features
Priority
Numeric, assigned by project managers
Bugzilla also Tracks
Comments (forum-style)
Notifications desired
The Life-Cycle of a Problem
What Goes into the System?
Problems can be reported by end-users or team members
Team members will generally not enter simple test failures
“Test cases make problems reports obsolete”Unless the fix is outside their personal area of responsibility or they want someone else to step in
Team members may use to track ideas and feature requests
3 Other SystemsForges
Most forge systems integrate some form of issue tracking
Already provide personnel management
E.g., FusionForge provides separate tracking of bug reports and feature requests
Provides for attachment of files (test cases)Assignment of a report to a project member generates a task in that person’s to-do listCan track scm commits that fix problem via Tracker links on SCM pages
Compare to our own Gitlab
Other Possibilities
Search capabilities vary widely
Some systems link problem reports to regression tests
More general project task managers exist
E.g., Agile developers allocate “stories”
4 Eclipse - MylynPersonal & shared task manager for Eclipse
Generally included with most programming language support packages
Mylyn supports two kinds of tasks
Local tasks, stored in the Eclipse workspaceShared tasks, from an issue tracking system or project task manager
e.g., Bugzilla, github, FusionForge
Mylyn distinguishes between due dates and schedule dates
Task management with Mylyn
MyLyn and Project Perspectives
1. personal task list, including Bugzilla reports
2. Change set related to task
3. Task editor
4. Context-based task info
Working with an Issue-Tracking System
Add a connector to the issue trackerGitLab connector at Eclipse update site http://pweingardt.github.com/mylyn-gitlab
Define one or more queries indicting what issue reports you want to be displayed by mylyn
E.g., in OurProject, display all reports assigned to me as tasks
Select a task to update info (will synchronize with remote manager)
Or create new tasks
Task List
Can display by category/query or by date
Searching & filtering are possible“Focus on work week” option
Color changes indicate scheduling & status
red for overdue tasksblue for tasks scheduled for todayblack for tasks scheduled for later in the weekgreen for tasks completed todaygrey for tasks completed earlier
Context Management
The context of a task is the collection of files & information related to the task.
E.g., Java files related to a bugChanges made in the course of fixing it
When you activate a task, Eclipse/Mylyn tracks the context
The Focus on Active Task button causes the package explorer to show only elements in the active task’s context
Establishing Context
From the task list, activate a task.
The package explorer switches to “Focus on Active Task” modeIf you are starting work on this task, this is empty
Clear by releasing the Focus on Active Task button or alt-clicking to expand an element in the explorer tree.
Start working
As you open files directly from the package manager, or indirectly (e.g., using Show Declaration (F3), Open Type (Ctrl-Shift-T), or ShowReferences (Ctrl-Shift-G), they are added to the focused package manager view.
Restoring Context
You can deactivate the currently active task (or activate a different task that you want to switch to).
Your various open editor windows close and the package manager returns to its normal unfocused state
You “return” to a task by re-activating it.
Your editor windows for that task context are restored
Contexts and Tests
You can create a Context Test Suite of those JUnit tests that you will be frequently re-running as you work on your active task.
Contexts and Change Sets
Changes made while working on an activated tasks are checked
Change sets can be manipulated in the synchronize viewCVS & SubversionSaid to work with Egit, but I’m not seeing it in practice
Machine Hopping
A major limitation (IMO) of the Mylyn context support is its limitation to a single workspace
I frequently work on the same project on different machines (office and home)
Things I have yet to try
Exporting and importing of tasksNot clear if context is included
Changing the task list data directory to a dropbox folder
Agile MethodsSteven J Zeil:
Last modified: Dec 21, 2019
Contents:1 Agile as a Social Entity
1.1 Agile Development is1.2 Variations
2 Common Practices of Agile Development2.1 Fundamentals2.2 Common Intersections
Abstract
Agile methods are a modern approach to incremental development.
They emphasze:
Iterative & incremental developmentFrequent communication with customer representativesShort “time-boxed” development cyclesFocus on quality as a matter of professional pride
This lesson is a discussion of the origins of Agile Development and a quick overview of the basic principles.
1 Agile as a Social Entity1.1 Agile Development is
A reaction against heavily-managed, documentation-heavy processesA social movement within the software development profession
Introduced in the Agile Manifesto (2001)
1.1.1 The Agile Manifesto
We are uncovering better ways of developing software by doing it and helping others do it. Through this work we have come to value:
Individuals and interactions over processes and toolsWorking software over comprehensive documentationCustomer collaboration over contract negotiationResponding to change over following a plan
That is, while there is value in the items on the right, we value the items on the left more.
1.1.2 The Twelve Principles of Agile Software
1) Our highest priority is to satisfy the customer through early and continuous delivery of valuable software.
2) Welcome changing requirements, even late in development. Agile processes harness change for the customer’s competitive advantage.
3) Deliver working software frequently, from a couple of weeks to a couple of months, with a preference to the shorter timescale.
4) Business people and developers must work together daily throughout the project.
5) Build projects around motivated individuals. Give them the environment and support they need, and trust them to get the job done.
6) The most efficient and effective method of conveying information to and within a development team is face-to-face conversation.
7) Working software is the primary measure of progress.
8) Agile processes promote sustainable development. The sponsors, developers, and users should be able to maintain a constant pace indefinitely.
9) Continuous attention to technical excellence and good design enhances agility.
10) Simplicity – the art of maximizing the amount of work not done – is essential.
11) The best architectures, requirements, and designs emerge from self-organizing teams.
12) At regular intervals, the team reflects on how to become more effective, then tunes and adjusts its behavior accordingly.
1.2 VariationsExtreme programmingScrum
2 Common Practices of Agile Development
source: The Agile Alliance
2.1 Fundamentals
2.1.1 User stories
Key idea in all agile variations.
A user story is a mechanism for incremental requirements elicitation and analysis.
Stories are not Requirements
…despite the focus on functionality and non-functional characteristics.
Stories are “a promissory note for a future conversation” (Cockburn)A use-case is a transcript of that conversation (Standley)
Stories are a way of prioritizing workIncluding the work of eliciting detailed requirements
Story Boards
Stories grouped on a story board to help “enrich” the stories
Story boards can be organized in many different waysOur project boards have emphasized TDDMore commonly, related stories are grouped into subtasks that are shown in a common swimlane.
2.1.2 Teams
small groups, largely full-timecontains all required skills (technical and domain expertise)
Stakeholder representative(s) is a regular team member
2.1.3 Iterative development
Activities repeat (analysis, design, testing, …)Work products can be revisited (enhancements, refactoring, etc.)Scheduling may focus on short, repeated development cycles
2.1.4 Incremental development
Each successive version of the productis “usable”adds user-visible functionality
Contrast this with a strategy of delivering successive, complete subsystems that will not be revisitedThink of building a house by adding room after room, as opposed to pouring the whole foundation, then framing all the exterior walls, then laying all thefloors, then doing all the electrical wiring, then doing all the plumbing, then…
When can you start using the house?
2.1.5 Version Control
’nuff said
2.2 Common Intersections
2.2.1 Iteration Planning
Teams typically try for 4-10 stories per iteration
Select stories to implement in current implementation
Often organized onto a task board
Velocity
Rate at which functionality (user stories) completed per iteration.
Most agile approaches define a fixed number of days per iteration
Add up the effort estimates of all stories completed during an iteration. This is the team’s current velocity.
Used to estimate time remaining to complete the project
Task Board
Contains stories to be completed in current iteration
Combines story cards and task notes
task notes indicate team member responsiblecolor coding may designate feature, bug, general noteseach task is marked with an estimate of hours required
Task notes organized in columns
To do: tasks yet to be startedIn process: task that team members are working onTo verify: believed completed, needs to be checkedDone: verified as completed. Some boards also list
Backlog: related stories not being handled this iteration
2.2.2 Sustainable pace
Setting a work pace that can be sustained indefinitely
overtime only in critical, unusual, and limited circumstancesSome evidence shows that routine overtime reduces productivity
Once a pace has been established, teams can measure velocity
2.2.3 Meetings
Emphasis on co-location of teams to enhance communicationDaily meetings to review key coordination points
time-boxed to very short time periods (e.g., 15 min.)Any topic that starts a discussion is cut short and discussed by interested parties after the meetingThree Questions (Scrum)
1. What have you completed since the last meeting?2. What do you plan to complete by the next meeting?3. What is getting in your way?
Avoid “Yesterday I did X. Today I will continue working on X”the Scrum Zombie patternhelps if user stories define units of work that can be completed in a day
2.2.4 Rules of Simplicity
(Kent Beck)
Each code unit
is verified by automated testscontains no duplicationexpresses separately each distinct idea or responsibilitycontains the minimum number of components compatible with the first 3 rules
2.2.5 TDD
Test-Driven Development goes beyond our previously-cited “test first, code later” rule
1. write a “single” unit test describing an unimplemented functionality2. run the test (it should fail at this stage)3. write “just enough” code to make the test pass4. refactor the code until it conforms to the rules of simplicity
The four steps above are repeated, adding new, tested functionality each time.
2.2.6 Simple Design
Design is on-going
Principles include
refactoringYAGNI
“You Ain’t Gonna Need It” - argument against early design of elaborate components for future use: “We going to need X eventually. We might waswell design it now.” “YAGNI”
Design should be emergent
good global design will emerge from careful attention to local design questionsWishful thinking?
2.2.7 Familiar Intersections
RefactoringContinuous Integration
Extreme Programming (XP)Steven J Zeil:
Last modified: Dec 21, 2019
Contents:1 Values2 Principles3 Practices
3.1 Teamwork3.2 Energized Work3.3 Planning3.4 Integration3.5 Programming
Abstract
Extreme Programming (Kent Beck, 1999) is an agile approach characterized by
pair programming,test firstshort development cycles (7-10 days) between releases,sustainable paceSearch for simplest increments with no “big design up front”
In this lesson we will explore these component ideas and how they combine into a cohesive development model.
1 ValuesCommunicationSimplicity
“What is the simplest thing that could possibly work?”
Beck notes that many people don’t hear the 2nd half of the question and respond “We can’t possibly make this system simple because…” If it really won’twork, it won’t work. But that’s not the point. There still is a simplest thing that will work.
“bias your thinking toward eliminating wasted complexity”
Feedback
Understanding what has changed & what we are getting wrongIncludes opinions & observations on what turned out to be complicated, hard to do, hard to get right, etc.
Courage
a bias toward action
Respect
care about the team members
care about he project
2 PrinciplesHumanity: concern for the team members
Economics: Make sure that what you are doing has value to the customer.
Mutual Benefit: “Every activity should benefit all concerned.”
e.g., automated tests help me (the developer) design & code today. Also benefit maintainers down the road. Benefit customers by improvingreliability and reducing costs
negative example: extensive internal documentation
Self-similarity
when you find a pattern (code or work practices) that works, stick with it
Improvement
“In software development, ‘perfect’ is a verb, not an adjective.”
Diversity: skills attitudes, and perspectives
Reflection:
time is set aside to review how well development is going,informal reflection within team is encouraged
Flow: continuous flow of activities rather than discrete phases
Opportunity (rather than problems)
Redundancy
critical, difficult problems approached many ways
Failure
should not be fearedprototypes and experiments are useless if only employed when we are sure they will succeed
Quality
Quality is not a control variable. Projects don’t go faster by accepting lower quality."
Baby Steps
Organizations can add XP practices incrementally
Accepted Responsibility
“Responsibility cannot be assigned. It can only be accepted.”Whoever signs up to do work also estimates it.The person implementing a story is responsible for design, implementation, and testing.
3 Practices
(Beck, Extreme Programming Explained, Figure 3)
We’ll focus on the primary ones in the upper half of this diagram.
3.1 Teamwork
Whole Team
Team must span the required skill set for the project
People need a sense of belonging, of sharedpurpose
Splitting people across projectsdetracts from these
Team size (Gladwell):
12 is the max number of people that caninteract with each other in a day
150 is the limit to recognizing the faces ofeveryone on the team
3.1.1 Sit Together
Open space big enough for whole teamsmall spaces nearby for privacy
3.1.2 Informative Workspace
“An interested observer should be able to walk into the team space and get an idea of how the project is going in fifteen seconds.”Story & task boards“big, visible charts”
3.2 Energized Work
“Work only as many hours as you can beproductive”
“and only as many hours as you can sustain”
3.2.1 Pair Programming
Write all production programs with two people sitting at one machine.
Pairs…
keep each other on taskclarify ideasavoid frustrating “stuck” pointskeep each other accountable for team quality standards
Thinking an be done alone, but not coding
Rotate pairs frequently
e.g., every hour or soEventually, each invidiual should pair with everyone on the team
3.3 Planning
Stories
Units of planning.
“the word ‘requirement’ is just plainwrong. Out of one thousand pages of‘requirements’, if you deploy a systemwith the right 20% or 10% or even 5%,you will likely realize all of the businessbenefit envisioned for the whole system.So what were the other 80%? Not ‘requirements’; they weren’t really mandatory or obligatory.”
Stories lead to earlier estimation than requirementsEncourages team to think in terms of greatest return from smallest investment.
3.3.1 Weekly cycle
Opening meeting
Review progress so farHave customers pick a week’s worth of storiesBreak stories into tasks.
Start week by writing automated tests for the stories
Rest of week is implementation and debugging
At end of week, software is deployable with new functionality
Why a week? Beck suggests that it is a natural unit for teams to work in. Everyone focuses on Friday. It is easy to tell if you have fallen behind and need to re-allocate tasks or stories so that you will have something deployable,
3.3.2 Quarterly Cycle
At the start of a quarter, reflection and planning
Reflection
Planning
identify bottlenecks, especially externaldevise a reaction or workaround
Pick the “themes” for the quarterPick a quarter’s worth of stories to meet that themeInclude some slack: minor stories that can be dropped if you fall behind
Quarterly planning meshes well with traditional business schedules.
“Themes” help refocus team o nthe “big picture”.
3.4 Integration
Ten-Minute Build
Automated build should build and run tests in ten minutes
Anything longer should be done less frequently.
“A build that takes longer than ten minuteswill be used much less often, missing theopportunity for feedback. A shorter builddoesn’t give you time to drink your coffee.”
3.4.1 Continuous Integration
Integrate and test changes every couple of hours
“Team programming isn’t a divide and conquer problem. It’s a divide, conquer, and integrate problem”
Asynchronous (sometime after checking of changes)Synchronous (on demand by team)
provides an opportunity for reflection
Integration should produce the complete product
3.5 Programming
Test-First Programming
Write a failing test before changing any code.
Addresses:
scope creep: putting in code “just incase”coupling and cohesion: If it’s hard towrite a test, you have a designproblem, not a testing problem.Trust: (team building)
Rhythm
3.5.1 Incremental Design
Create conditions where changes in design do not have exaggerated costs (a la Boehm)
Adapt the design to changes in (or discovery of) requirements
ScrumSteven J Zeil:
Last modified: Dec 21, 2019
Contents:1 Values2 Practices
2.1 Scrum Teams2.2 Scrum Events2.3 Backlogs
3 Scaling Up
Abstract
An agile approach characterized by
small teams with a few distinct but clearly definedrolesDevelopment cycles through sprints of 7-30 days
Planning meeting at beginningDaily scrum meeting (< 15 min.)Sprint Review meeting at end to review workcompleted (or not)Sprint Retrospective meeting at end to discussprocess
In this lesson we will look at the component activities andstrategies of Scrum We will pay particular attention to theways in which Scrum differs (in practice or in emphasis)from Extreme Programming.
Scrum in Context
As the name suggests, Scrum
focuses a bit more on the team than onthe toolsenvisions a kind of controlled chaos
1 ValuesTransparency
Process must be visible to those responsible for the outcomeCommon standards contribute to understandability
Examples:
ubiquitous languagedefinition of “Done”
Inspection
Scrum participants inspect one anothers’ work
Contrast with XP’s pair programming
Adaptation
Inspections lead to adjustment of process (not products)
2 Practices2.1 Scrum Teams
Product owner
representative or agent of stakeholdersmanages the project backlog (list of unimplemented work)
Scrum master
interface between project owner and development teamaids project owner in arranging backlogensures that development team adheres to Scrum practicesremoves impediments to team’s progress“facilitates” Scrum events
Development team
3–9 membersmust span required skill setany internal roles are self-organized
2.2 Scrum Events
2.2.1 Sprints
7–30 day, time-boxed iterationsproduces a new product incrementfocused on a Sprint Goal
2.2.2 Sprint Planning
Held at beginning of sprint:
What will be delivered in the next increment?Stories selected from project backlogdefines the Sprint Goal
How will the work be done?Work planned for first few days of springDecomposed into tasks.
2.2.3 Daily Scrum
15-minute daily meeting
Each development team member explains
What did I do yesterday?What will I do today?Are there any impediments that will prevent us from reaching the Spring Goal?
2.2.4 Sprint Review
Held at end of sprint4-hour time-boxed meeting (for 1-month sprints)
Product owner
explains what items are Done and not DoneReviews the new state of the project backlog
Development Team
explains what went well and poorlydemonstrates the increment and answers questions
Entire team
discuss what to do nextdiscusses external changes
2.2.5 Sprint Retrospective
After sprint review, before next sprint planning3-hour time-boxed meeting
Team inspects its own performance
people, relationships, process, toolsIdentifies potential improvementsSets quality goals
adjusting definition of “Done”
2.3 Backlogs
2.3.1 Project Backlog
Unimplemented requirementsoften, but not always stories
2.3.2 Sprint Backlog
Unimplemented tasks that are part of the Sprint GoalManaged on task board
2.3.3 Burndown Charts
A burndown chart shows amount of work remaining in the sprint backlog
updated dailydisplayed in team work area
provides visual indicator of progress
3 Scaling UpAn extension to Scrum for larger projects:
Scrum of ScrumsEach Daily Scrum appoints one team member to serve as the day’s “ambassador” to a daily Scrum of Scrums meetingSame basic questions as Daily Scrum, adjusted for team level
What has your team done since we last met?What will your team do before we meet again?What impediments (especially those that will affect other teams) does your team foresee?
Project Phase 2: User StoriesCS350
Last modified: Feb 20, 2020
Contents:1 Team Sign-up
1.1 Civility among Team Members2 Group Information3 Project Setup4 Compose Your Stories5 Plan Your Increments6 Peer Evaluation7 Submission8 Evaluation
8.1 Grading of Project Phases8.2 Common Problems with Stories
In this phase of the project, you will prepare user stories for the project and enter them into an issue tracking system. These stories will help to organize yourteam’s work in the later phases.
Your reference materials for this phase include
the requirements definition from the previous phaseyour SRS from the previous phase (depending upon your team membership, you may have more than one to look at).these design notes, which may be particularly useful when you are trying to estimate the effort required to implement your stories.
1 Team Sign-upTeams for this and the remaining phases will be selected by sign-up.
Go to the groups area in your recitation section on Blackboard. Each recitation section has been assigned an arbitrary color, and groups are available with namesconsisting of that color plus a number. Sign up for one of those groups.
All team members must be enrolled in the same recitation date/time.
You should only sign up as the 6th member of a team only if all other teams for that recitation have at least 5 members already.
After the sign-up due date I will arbitrarily assign students who have not signed up and will shift people out of teams with 5 or 6 members to teams with 3or fewer members. I may merge 2-person teams to create a 4-person team.
I will post an announcement when this has been done.
After signing up for your group, log in to the CS350 GitLab and the CS350 OpenProject tracking board.
This establishes your credentials on each of those systems, which must be done before projects can be set up.
1.1 Civility among Team MembersYou will be working with your team for many weeks, and there will be a lot of communication expected among team members.
In accordance with the Monarch Creed and Code of Ethics, I expect all students to maintain civility in their dealings with one another.
Language that is abusive, harassing, or threatening to members of the class or that fosters high levels of personal and emotional anxiety may, at theinstructor’s discretion, result in expulsion from the team. Given the importance of the team project to this course, that is likely to result in a failinggrade. Egregious or repeated violations will be referred to appropriate authorities for possible disciplinary action.
2 Group InformationWithin 24 hours of my announcement that the groups have been formed, post to your group’s discussion area:
1. Your name.
2. Your CS login name.
3. Your @odu.edu email address.
3 Project SetupOnce 24 hours have passed since my announcement of the groups being formed, the projects can be set up.
1. One team member should create a new project on the OpenProject tracking board and add the other team members to that project (as “Project admin”s).
Note that you cannot add people to a project until they have logged into the tracker for the first time. But they should have done this by now.
The project name should match the group name from Blackboard, dropping any blank spaces. For example, if your group is “Blue 2”, your projectname should be “Blue2”.
On the New project page for the project, make sure that the project is not public.
This can also be set from the Project settings page after the project has been created.
On the New project page for the project, select/check Backlogs and Work package tracking. I recommend clearing the other modules.
This can also be set from the Project settings page after the project has been created.
Go the the Backlogs page. Use the +Version button to create a new “version” of the project titled “Project Backlog”. For the “start date”, give thecurrent date. For the “Finish date”, give the date of the end of Phase 5 from the course Outline. Status should be “Open”, Sharing “Not shared”, andColumn should be “left”.
2. One team member should also create a new project on Gitlab and add the other team members to that project (as “masters”). The project name must bethe same as on OpenProject – your group name.
Again, this project should be private, not public.
4 Compose Your Stories1. Your project area on Gitlab includes a Wiki. You will use this as the base for your project’s web page. In this phase, you will mainly be documenting your
plans for the first two increments of development, corresponding to phases 3 and 4 of the project.
Set up the opening page (the page that you reach by clicking on the “Wiki” link from your project home page) of your group’s Wiki with three sections.The Membership section should contain a list of all members of the team.
The Reports section will, for now, be empty.
The Stories section should contain links to three pages titled “Roles”, “Increment 1”, and “Increment 2”. These pages can be empty for now.
2. Discuss within your team: What are the user roles relevant to this project?
In your Wiki, record your conclusions on the “Roles” page. List each user role and give a brief one or two sentence description.
3. Compose your stories. The sum total of your stories should span the required behaviors of the system.
Stories will be entered into the OpenProject tracking system.
In the later project phases, having these in OpenProject will help in tracking who is working on what stories, which stories are completed, andwhich have not yet been started.
Create a new story using the drop-down menu on the right of the Project Backlog header.
Mark as a “User story” (not “Feature”). Fill in the “As a … ” description. Leave the status as “New”.Hit “Enter” to create that story.
Click on the number of the newly created story to open up its full page.
Do not mark your stories as “assigned” to a team member. You will use that ability later when someone takes responsibility for implementingthat story.
In the Description, add any additional explanation necessary.
In particular, explain how implementing this story will affect visible behavior of the system. Will it produce an output report? Will itcause certain data appearing to become more accurate or more complete in some fashion?
You might need to speculate a bit on when you expect this story to be implemented, i.e., what parts of the system might not be workingyet. It’s OK to suggest that data being shown in output, for example, is likely to be “fake” values coming from a stub until some otherstory gets implemented.
If you like, you can use the Relations tab to indicate that this story needs to follow another one that has already been created.
Ideally, a story should indicate a unit of work that a programmer can complete within a single increment of the iterative development process.For our purposes, that amounts to something that a student programmer could complete within a week or two. (That should take into accountthe fact that students are not assigned full-time to this project.)
For each story, enter the effort level expected in the “Story Points” field.
If, upon review, you decide that a story is too large (an “epic”), do not delete it. Instead, change its type from “User Story” to “Epic”, split it intosmaller stories that use Relations to indicate that they are “part of” that epic.
5 Plan Your Increments1. In the Backlogs area, create two more Versions called “Increment 1” and “Increment 2”.
2. Agree on a collection of stories to serve as the first two increments (project phases 3 and 4, respectively).
Drag those stories from the Project Backlog to the appropriate Increments.
Important: Remember that each increment must add observable functionality to the system. That means that you must choose at least onestory that generates visible output. Think in vertical slices!
3. In the “Increment” pages of your Gitlab Wiki, add a paragraph or two of text describing what observable functionality will be available upon completionof that increment.
If you can’t do this easily, then you need to either redesign your stories or re-think your choice of stories for that increment.
Please remember that updating an internal data structure is not, in and of itself, observable.
And compiling your code and/or passing your unit tests does not count as functionality.
6 Peer EvaluationAfter your work has been completed, each team member must individually take the peer evaluation survey. The purpose of this survey is to assess the relativecontributions made by individual team members.
7 SubmissionThere is no formal submission activity – when the due date comes, I will examine your OpenProject and GitLab project areas.
8 EvaluationEvaluations will be a combination of what the group has accomplished and how much each individual contributed to that group’s effort.
8.1 Grading of Project PhasesThis phase will be graded as
score = quality * indiv
where:
quality is an assessment of the completeness, correctness, and quality of the stories and Wiki pages prepared by the team.
indiv is a scaling factor for an individual’s effort. A normal effort level is 1.0. This factor will be raised or lowered by a combination of
score on peer evaluation surveyswhether the individual contributed to the surveyactivity in the OpenProject tracker (as shown on each member’s Activity page), with a bonus for work done early in the phase.
Note: if you allow another team member to enter your stories into OpenProject for you, they will get credit for that activity and you will not.
Everyone in this course is supposed to demonstrate their ability to work with the various tools covered in this class. Do not appoint a team“scribe”, “secretary”, or “librarian” to do tool interaction for everyone else.
8.2 Common Problems with StoriesAs your stories are graded, I may make comments directly in your OpenProject tracker (these will appear as comments in the story detail) or in the spreadsheetin which your overall evaluation is tallied.
Some shorthand annotations that I may use for common problems are:
8.2.1 Definite Problems
ODCOnly the Developers Care
Stories about getting the build to work or getting unit tests written are not of interest to the end users, only to the developers.
That’s not to say that there isn’t effort required to do this stuff. but the effort is generally considered to be factored in to the effort to implement “real”stories.
Almost any story that begins “As a programmer…” or “As a software developer…” will be ODC (unless the project is itself a library or tool for use byother programmers and developers who are not members of the system’s own development team).
UTFUlysses Traveled Far
Refers to stories that aren’t merely epics, but summarize the working of the entire system, or of major subsystems, in a single sentence. Such storiescannot be completed in a single increment and are unhelpful in breaking the development effort into practical units of work.
Examples would be “Evaluate all expressions in a spreadsheet loaded from a file.” or “Generate monthly sales reports broken down by customers.”
These epic stories need to be broken down. Ask yourself why you wanted to write this in the first place. Is this, for example, the only mention of a report?Then devise a story to generate that report from data already loaded and summarized in the system, so that the loading and summarization can be added asseparate stories. Is this the only mention of reading data from a file? Then have a story (even if IB, see below) to load the data, and separate stories towork with it and to produce visible output after that work.
NUWNot a Unit of Work
This describes stories that cannot be assigned as units of work during the development phase. Reasons might include that this is not done duringdevelopment (e.g., system reliability and usability requirements must be measured after the development is completed), and/or is an over-arching concern(e.g., “maintain communication with end users”, “use pair-programming”, “satisfy all mandatory requirements”).
NESNever Ending Story
A special case of NUW, this describes stories that cannot be assigned as distinct units of work because they will never be finished until all developmentwork is complete.
For example, you might have a requirement that the code must compile with the Java 8.0.1 compiler. But a story
As a developer, I want the code to compile with the Java 8.0.1 compiler.
would be an NES story (_and_ an ODC story). You can’t assign it to someone as a unit of work and expect them to complete it, because as long as anyoneelse is contributing any code, they could contribute something that breaks the requirement.
MTAIMy Teammates Are Idiots
Indicates a story that assumes a teammate will do a poor job on another, closely related story.
For example, suppose that you have a requirement stating that the system must keep sales data for a company’s customers and another requirement thatthe system must be capable of storing data on at least 500 customers.
If you wrote separate stories
As a ..., I would like to see sales data for customers. As a ..., I would like to see that data for up to 500 customers.
the second story would be an MTAI story. It assumes that whoever implements the first story will not have read the requirements and so will choose aninappropriately small data structure to store the data.
RITMRolling In The Mud
A common special case of MTAI. People like to pad out their stories by focusing on error conditions and messages.
For example, you might have a requirement to read in and process a positive number, with a required error message if the number is zero or negative.That’s one story - it doesn’t naturally break down into a “read and process this number” and a separate “Print the error message if the number is not
positive” story.
Anyone implementing the first story will naturally inject the logic required for the second story. Certainly it makes little sense to assign one person theerror message story and someone else the read-and-process story (or even worse, to assign someone the error message story and defer the read-and-process story to a later increment). Yet, I’ve seen students who, after three increments of the project, have worked on nothing but printing error messages.
8.2.2 Possible problems
IBInvisible Behavior
Be careful of stories like “As a …, I want the system to read a data file X.”
Reading data into an internal data structure is not an observable behavior. You might claim to have done this, but how does an end user know that youreally have done it unless it directly affects the output somehow. (And, no, passing a unit test does not count because ODC.)
There’s two ways to deal with this kind of story.
1) Rewrite the story to establish a link to behavior, e.g., As a … I would like the customer listing to reflect the customers listed in the data file X."
2) Plan your increments so that an IB story is paired with an output-generating story (e.g., As an …, I would like to see a report listing all customers.) andmake sure that someone is assigned the output story before the input story, so that if you don’t finish all scheduled stories by the end of the increment,you still have a visible behavior to demonstrate to the end user.
Add comments in the tracker to your IB story indicating which out-producing stories it is intended to be paired with.
Project Phase 3: Build Management and Version ControlCS350
Last modified: Feb 20, 2020
Contents:1 Building
1.1 Demonstrating Test-Driven Development2 Submission3 Peer Evaluation4 Tips
4.1 What to Work on4.2 Working as a Team
5 Evaluation5.1 Grading of Project Phases
6 Design Brainstorming Session6.1 Prior to the meeting6.2 During the Meeting
7 Project Review Meetings7.1 Conduct of the Meeting7.2 Meeting Via Network Conferencing
In this phase of the project, you will begin the software construction process. Your priorities during this phase will be setting up and learning to use yourproject’s version control repository and automating your build.
Your reference materials for this phase include
the requirements definitionthese design notes
Review your grades from Phase 2 (stories). The detailed grade spreadsheet contains my own list of stories. If you are missing any of them, you may havefailed to account for portions of the work required to finish this project. You should consider adding some of those to your own list of stories, because I willcontinue to use those, in the future phases, as a basis for estimating how much of the project functionality you have implemented. If you do not have statementsthat describe the work you have done, you might not get credit for having done it.
1 Building
1. Working from your GitLab project area, create a git repository for your project.
Your project directories must be organized according to the Android conventions.
All code for this project should reside in the package edu.odu.cs.cs350. Sub-packages of that are permitted.
Each team member should follow the procedure outlined in the GitLab assignment to identify yourself so that all commits you make are listedunder your CS login name.
If you work on multiple clones of the repository (e.g., on different machines), you need to do this on each clone.
2. Work on implementing the stories for at least the first increment identified by your team.
Follow the process described below in Demonstrating Test-Driven Development to track your progress on this increment.
All APIs contributing to a story must be validated by appropriate unit tests.All classes must include meaningful documentation.
3. Using gradle, automate the build of the project components so far. This must include all unit tests. The build must run all unit tests and (for now)whether those are passed or not, package the non-test components of the project as a Jar file.
Use the gradle wrapper so that all team members are using a consistent builder.
4. As you add components to the project, check them into the repository using git.
Material placed under version control should include all source code, hand-edited data files, etc., as well as the build manager settings files.
Do not include binaries produced by compiling your own code, Jars or other binaries of 3rd-party libraries you have imported, etc.
Do not include the Eclipse .project and .classpath files.
Include the .gitignore file(s). In fact add this file first!.
Do include the gradle wrapper gradlew* files and the associated gradle/ directory.
You may use branches to explore implementation alternatives. But your team’s main codeline should be the master/origin branch. That is where Iwill look for your code, and your grade will be based in part upon your contributions to that branch.
5. Your project will eventually make use of a number of third-party libraries. Refer to the Design Notes linked at the top of this document for details.
When starting out, you may not need all of these. Refer to the design notes for instructions on how to load these libraries automatically as part of yourbuild. (We’ll examine how this really works in more detail later.)
1.1 Demonstrating Test-Driven DevelopmentYou are required to follow the principles of test-driven development and to document your practices in doing so.
1. Your stories are divided into increments on the Open Project Board. For each story that you are working on, create tasks to guide and capture yourprogress.
2. At a minimum, you should have the following tasks for each story:
Develop the code.
This includes writing the tests to drive the code.This task is not completed until you have passed all of your unit tests for this story.This task is not completed unless the code is committed and pushed, with any version conflicts resolved.If the story is large and/or complicated and naturally decomposes into separate pieces, you may want to have several of these “develop”tasks, one for each piece.
Create a system test.
You will have developed unit/integration tests already. But you need at least one system test designed to demonstrate that the story will hold whenthe entire system is available.
Integrate the new code
Show that any new or modified functions involved in this story are used elsewhere in the existing code base, so that your API interfaces developedin this story are known to be usable.
3. As you make progress on your stories, drag the tasks to the appropriate column of your task board.
4. Carry your tasks through to completion – don’t flit from one to the other.
As a general rule, any one person should have only one task “in progress” at a time.
Exceptions to this rule should be rare, and you should be prepared to be questioned on any violations.
If you find it impossible to make progress on a task that you have started, move it to the “On hold” column. Then add a “task” to the SprintImpediments row containing the story # of the task you are abandoning and a brief explanation of why you chose to set it aside.
If you fail to update your agile board, your group may not get credit for work accomplished. If you move the cards forward without adding theexplanatory notes, I might not believe your claim to have finished something.
2 Submission“Submit” your project for this phase by tagging the version as “phase3”. Note: this must be a simple tag, a name attached to a particular snapshot in themain/origin branch, not a new branch.
3 Peer EvaluationEach team member must individually take the peer evaluation survey at the end of the phase. The purpose of this survey is to help assess the contribution madeby individual team members.
4 Tips4.1 What to Work on
Be incremental, not just iterative.
Incremental development is characterized by the addition of observable behavior of value to the end users by the end of each increment. Each of phases3..5 should be an increment.
It’s essential, therefore, that one of the first stories tackled should be one that produces a report or other observable output. The output for the firstincrement may be of data that has been largely faked. That’s OK, in later increments you can point out the changes in that output as “real” datastarts getting produced. Those changes will be the evidence that progress is being made.
This requires coordination among your team members in your choice of initial stories to tackle. So talk to each other. Make sure that you have ateam plan, not a half dozen individuals’ plans.
There seems to be a real temptation for people to jump on “input” stories early on. I’ve seen teams where everyone wants to work on the CLI.Resist that temptation.
It’s easy to provide “cooked” or faked inputs to various phases of the program. It’s much harder to claim that you are making progress if yourchosen stories have no visible effect on the output.
Stories like “As a … I want to load the input file.” are treacherous in the early increments. When you are demonstrating your program, you may tellme that you have loaded data, but I can’t see (and wouldn’t care to see) your internal data structures. For all I know, all you have done is to print amessage saying that you laoded it (if you have even done that much).
Go deep rather than broad.
Each team member should pick a story and push it through interface design, test case design, and implementation to the start of integration.
Don’t pick 4 stories and move them into “Interface Design” and then leave them there. That does not actually contribute any observable functionality tothe increment.
Get the infrastructure done early.
Each of phases 3..5 will require you to add some project infrastructure such as standardized directories, git repositories, build files, etc. Get that done asearly as possible.
These aren’t part of any stories. But until the infrastructure isn’t set up, no one will make appreciable progress on their stories.
4.2 Working as a TeamFor this and all subsequent phases, remember that you are working as a team. That does not mean that you are yoked together like a team of horses that arriveat a job together, pull together in lockstep, and then are sent off at the end of the task all at the same time. You’re going to be working independently, but have tomake sure that you don’t abuse that independence to the detriment of the team.
It is expected in a team project that other people will be using or building upon the code or other software artifacts that you are working on. Yourteammates are depending upon you.
If you wait until the last day or two of each phase to do your tasks, you’ve let your team down because they will not have time to build upon yourwork or to make sure that your work integrates properly with theirs.
Even if you have done the work early, if you do not check it in to the team’s repository and make it available to them, you’ve still let your teamdown.
Set aside regular periods of time to work on this project.
Your team will be much better served by your working 30 minutes every other day for 14 days of a phase than by your working 7 hours on the 14th day.
Don’t think for a moment that your instructor hasn’t noticed that 75% of the class never submit an assignment more than 24 hours before it are due,or that 50% of the emailed questions about assignments come in the last 4-8 hours before the final deadline.
That sort of procrastinatory brinkmanship might work for you on an individual assignment, but is deadly to team projects.
Some of your credit for your individual contribution to the project will be weighted according to how early you acted.
Try to set up each work period with a specific goal in mind. For example, you might devote one work period to writing one or two unit tests. If that’s allyou get done, fine. You still have made observable progress.
Most people work much better if they have a specific goal in mind, and this also helps limit the scope of the changes so that you can easily commit them.Which brings us to the next point…
At the end of your work period, commit your changes. If your work is seriously incomplete and might break things for your teammates, check it in toyour local copy of the repository but don’t push it to the team shared repository.
But don’t go too long before pushing your work to the team repository. That risks version conflicts that will be time-consuming to resolve.
Get into the habit of beginning each work session by pulling all changes from the team repository, so that, if your work is in conflict, you resolvethat as early as possible.
Your team should try to get any shared “infrastructure” for the phase completed within the first first days of the phase. Examples would be setting up therepository, setting up the project directory structure, and setting up the build manager files.
That way the support structure is already in place when you get down to the “real” development work.
If you make changes to any public ADT interface, commit and push those changes as soon as possible. By definition, such changes have the potential tostrongly affect the work of your teammates.
For example, if you are following the principles of TDD, you will start by asking whether the ADTs involved in your current story present the appropriatepublic functions to allow you to test the story’s behavior. If you add add public functions or modify the names/parameters of existing ones, you shouldcommit and push those changes ASAP.
Don’t wait until you have completed the unit tests or, worse, until you have actually finished implementing the behavior. You might not get allthat done until days later. Changes to public interfaces are things that your teammates need to know about as soon as possible.
5 EvaluationEvaluations will be a combination of how much the group has accomplished and how much each individual contributed to that group’s effort.
The login name / account used to create or edit stories or to commit code changes to the project will be also used as evidence of the individual’s efforts. Thismeans that you should not appoint some students as scribes/librarians to record others’ work. Doing so will guarantee that the librarian gets credit for everyoneelse’s work.
Your team’s progress will be measured both in terms of what has been submitted and in a project review meeting. Refer to the more detailed description of theproject review meetings below.
5.1 Grading of Project PhasesPhases 3..5 will be graded as
score = ( process * progress + review ) * indiv
where:
progress is an assessment of the completeness, correctness, and quality of the work completed on the project.
This is primarily based on stories completed, with the understanding that stories were scaled to reflect the amount of work that one student couldcomplete in one 2-3 week increment.
The amount of code checked in is also considered, but the weight shifts more and more towards the stories as we progress to the later phases.
A normal progress level is 1.0. Teams that complete more stories will receive numbers above 1.0 and teams that complete fewer will receivenumbers below 1.0.
process is an assessment of how well your team has set up its process, including use of required/recommended tools.
You can find the details of the items assessed for process in this spreadsheet.Some of the items in the PROCESS section of that spreadsheet are not evaluated until later phases. The starting phase number for each item isindicated separately. The relative weights given to each item will depend on what phase we are in.
review refers to the individual’s performance in the review meeting.
indiv is a scaling factor for an individual’s effort. A normal effort level is 1.0. This factor is evaluated by a combination of
peer evaluation surveystasks completed on user stories as documented on the task board,number of commitsnumber of work sessions
A work session is an 8-hour period within which you make one or more commits. This measure is intended to reward people who work steadilyduring the weeks that make up the phase, making their work available to their teammates, and to penalize the people who show up for work in thefinal 12-24 hours and try to rush in a bunch of stuff in panic mode.
6 Design Brainstorming SessionNear the beginning of Phase 3, we will conduct a design “brainstorming” session during your recitation period. The purpose of this session is to:
1. arrive at a group consensus of the major components of the system design.2. check the status of your stories in Redmine.3. check the status of your Git Repository
Attendance is mandatory.
6.1 Prior to the meeting1. review our earlier discussion of Object-Oriented Analysis, particularly the object-oriented philosophy and the idea of modeling in terms of interacting
objects. CS330 students may want to review the process of “classification”, which lies at the heart of the analysis that we will be doing.
6.2 During the MeetingDon’t wait for the instructor to join your session. Start to work immediately.
1. At the start of your meeting, one person should create a Google Doc editable by all team members and by [email protected] and share the URL to that.Make sure that your group name is part of the document file name.
2. Based upon the requirements and design notes, propose classes that you think will be essential to this project. Propose the major operations on these.Sketch these out in your shared document.
Document your discussion continuously.
As you discover useful classes, document their names, attributes, and operations. You may use UML diagrams as you practiced in our earliersession or in plain text as C++/Java class interfaces.
If you discover useful relationships between classes, show those as UML diagrams.
Don’t worry about syntax and don’t waste time trying for perfection here. But do try to record things clearly enough so that someone canidentify the classes, operations, and the major inputs/outputs of those functions.
Your first concern should be identifying classes that simulate the critical real-world objects.
When in doubt, “keep it real”!
A secondary (but still legitimate) concern is identifying classes that will be used as the implementing data structure for classes you have alreadydevised. Don’t lock yourself into too much detail here. It’s too early, for example, to worry about whether something will be an array, vector, or list.But you may want to note that “class A will store a sequence of B”.
In assigning operations to classes, remember this rule:
If A does B to C in the real world then, in most cases, our model of the world as interacting objects should say that
B(...) is a function method of class C (not A).Somewhere inside the body of some other method of class A, we expect to find that A calls C.B(...).
3. After teams have had a little while to get the process going, the instructor will start moving from one team meeting to another to see how you are doingand offer suggestions where appropriate. The timing will depend on how much time is spent with other teams. Yours may be visited once, or multipletimes.
4. After the meeting has concluded, add a link to the home page of your project Wiki to your shared design document.
7 Project Review MeetingsThese meetings will be held during the recitation periods, as announced on the course outline.
Meetings will be held by network conferencing (Google Meet). More details on network conferencing meetings is below.
This course is about tools and techniques to support software construction, not about programming specifically. Getting the project to work will count as part ofthe grade, but is not the only part and may not be the most important part, except in as much as it demonstrates mastery of the relevant tools and techniques.
7.1 Conduct of the MeetingAttendance at the scheduled review meeting is mandatory and counts toward the individual component of your score.
Please be on time. I will have only limited time with each team before I need to move on to my next meeting with another team. Making everyone waitwhile you struggle to enter the conference, play with your settings, etc., is unacceptable.
You should be in the conference area and ready to go at the announced starting time for the meeting. You can (and probably should) arrive early to getyourself set up.
At the start of the meeting, you must already have Eclipse open and ready with your own copy of your team’s project.
This needs to be opened as an Eclipse java and/or Gradle project in the Java project view, not simply as an unused clone in the Git repository view.
The version you have open should be the one submitted at the end of the phase. E.g., for phase 3 of the project, this would be the version that wastagged by your team as “phase3”.
If you have done additional work since the submission and are worried about losing it, make a fresh clone of your team’s project in a separatedirectory, check out the “phase?”-tagged version, and use that for the meeting.
You must be prepared to show your session via screen sharing. That includes having re-sized the window to a conservative size (e.g., 800x600) sothat the shared image will be readable on other peoples’ screens.
During the meeting, I may ask the team to show that they have completed part of the assignment or to explain part of what they have done. I may callupon specific individual team members to explain or to demonstrate techniques relevant to the phase.
At the end of the meeting, your team may be given a short task to perform on an individual basis. You will have a limited time (less than a day, possiblyas little as one hour) to do this and to submit the results.
An individual who is unable to complete a required task or answer a directed question to my satisfaction will lose individual score points. If a second individualin likewise unable to complete that task or to answer the question, then the overall team score will be reduced.
7.2 Meeting Via Network ConferencingMeetings will be conducted via Google Meet. You will receive an email prior to the meeting containing a link that will take you to the meeting.
You must have a functioning microphone & audio setup for the meeting. A web camera is optional but recommended.
You must be in a place where you can both hear and be heard. Avoid trying to participate from locations with high levels of background noise (e.g.,public areas in Webb center).
You should be able to enter the meeting area early.
This will allow you to be sure that your audio/video setup is working and that you have up-to-date versions of the browser plugins required.
If there are multiple teams being reviewed in a given recitation/class period and yours is not the first, the instructor will be in a separate hangoutsarea with the earlier team. Please be patient if the instructor is a bit late getting to you.
By the same token, the time to meet with your team is limited. You may be penalized if you are not ready to meet at the appointed time.
Project Phase 4: Reporting and Continuous IntegrationCS350
Last modified: Mar 18, 2020
Contents:1 Adjust Your Planning2 Continue Building3 Evaluation4 Project Review Meetings
In this phase of the project, you will continue the software construction process. Your priorities during this phase will be adding reporting features to your buildto update your website with JavaDoc documentation and test reports, configuration management to your automated build and exploiting continuous integrationto perform system analysis and testing.
Your reference materials for this phase include
the requirements definitionthese design notes
1 Adjust Your PlanningIn the 1st 72 hrs. of the phase:
Your team should do the following, in consultation with one another:
1. Go to Open Project and examine your progress in Phase 3. How many story points worth of stories did your team complete? You can allow partial creditfor stories advanced but not completed.
In your project Wiki on GitLab, go to your Increment 1 page and document that number of story points.
2. Some teams will do better in phase 4 than in phase 3, sometimes because they are now more familiar with the development process and sometimesbecause a poor phase 3 lends a sense of urgency to phase 4.
Set a target for number of story points to complete in phase 4. Unless you think you were really loitering in phase 3, this should probably be no more than1.5 to 2.0 times the number of points completed in phase 3. (On the other hand, if that still works out to less than one point per team member, you mightwant to question why.)
In your project Wiki on GitLab, go to your Increment 2 page and document that number of story points that you plan to complete.
3. Move any stories that were started but uncompleted in phase 3 from your Increment 1 backlog to your Increment 2 backlog.
4. Review your plans for what observable behavior you expect to be able to demonstrate at the end of Increment 2. If this needs to changed, document thenew planned observable behavior in your Increment 2 page.
5. Move stories out of your Increment 2 backlog, returning them to the Project Backlog, until the number of points worth of stories in Increment 2 isapproximately equal to the planned number you selected in step 2.
Choose the stories to remove in a manner consistent with your planned demonstration from step 4. Remove those stories that do not contribute to theplanned demonstration.
2 Continue Building1. Continue your build, following the TDD procedures of the prior phase.
2. Choose a location from which to serve web pages containing project status reports. You will need a mechanism by which you can permit any teammember to update these pages. The file drop areas you created using ssh keys would be ideal for this purpose.
Create a “Reports” web page there with the names of and links to the reports that you are posting (see below).
Add a link to your reports web page in the Reports section of your Wiki opening page.
3. Modify your build so that an appropriate target (e.g., “reports”) guarantees that both updated Javadocs and JUnit test reports are generated.
4. Place the project under continuous integration, using the gitlab-ci function of GitLab. * Your GitLab project should automatically build your projectafter each commit to the master branch. * It should upload updated Javadocs and JUnit reports to the website after each build. If you do not have a targetin the build to accomplish this, you may do so via the CI script. * The required ssh key should be supplied as a GitLab secret variable. * Each team hastheir own runner, identified by the color-number convention, e.g., “blue1”, “yellow2”, “green1”, etc. Select your team’s runner by entering that name inthe tags area of your gitlab-ci.yml file.
Do not enter any other tags, and do not use another team's runner!
Do this as early in phase 4 as practical. Part of your grade will depend on how complete a historical record you establish during this phase.
5. Add test coverage and at least one static analysis tool to the build.
Coverage should be computed using both unit and integration tests.Publish your coverage and static analysis reports on your Reports web page.
1. Add integration and systems tests. A “systems test” in this project would be an execution from the Jar file you have constructed as a build artifact.
Note that you should have been designing systems tests as part of your normal procedure for implementing stories.
2. “Submit” your project for this phase by tagging the version to be reviewed as “phase4”.
3. Each team member must individually take the peer evaluation survey.
3 EvaluationEvaluations will be conducted as was done in Phase 3.
4 Project Review MeetingsThis phase concludes with a project review meeting, conducted according to the same guidelines as in phase 3.