From 615d2f0e3382da46fbbb86ea763e3132fa0014c0 Mon Sep 17 00:00:00 2001 From: Mark Bell Date: Tue, 1 Oct 2019 22:51:29 +0100 Subject: [PATCH 1/8] Testing and SCM work in progress --- 08_code_management/00_Git_Basics.md | 80 ++++++++ 08_code_management/01_Branching.md | 86 +++++++++ 08_code_management/02_Remotes_and_GitHub.md | 59 ++++++ 08_code_management/03_README_and_Markdown.md | 86 +++++++++ .../04_Pull_Request_and_Forking.md | 21 +++ 08_code_management/05_Issues.md | 11 ++ 08_code_management/06_Other_GIT_commands.md | 0 08_code_management/feature_branching.svg | 171 ++++++++++++++++++ 09_testing/00_TDD.md | 63 +++++++ 09_testing/01_Mocks.md | 62 +++++++ 09_testing/02_CI.md | 37 ++++ 09_testing/03_Code_Coverge.md | 0 09_testing/04_Static_Analysis.md | 0 09_testing/05_Functional_Testing.md | 154 ++++++++++++++++ 14 files changed, 830 insertions(+) create mode 100644 08_code_management/00_Git_Basics.md create mode 100644 08_code_management/01_Branching.md create mode 100644 08_code_management/02_Remotes_and_GitHub.md create mode 100644 08_code_management/03_README_and_Markdown.md create mode 100644 08_code_management/04_Pull_Request_and_Forking.md create mode 100644 08_code_management/05_Issues.md create mode 100644 08_code_management/06_Other_GIT_commands.md create mode 100644 08_code_management/feature_branching.svg create mode 100644 09_testing/00_TDD.md create mode 100644 09_testing/01_Mocks.md create mode 100644 09_testing/02_CI.md create mode 100644 09_testing/03_Code_Coverge.md create mode 100644 09_testing/04_Static_Analysis.md create mode 100644 09_testing/05_Functional_Testing.md diff --git a/08_code_management/00_Git_Basics.md b/08_code_management/00_Git_Basics.md new file mode 100644 index 0000000..e539e82 --- /dev/null +++ b/08_code_management/00_Git_Basics.md @@ -0,0 +1,80 @@ +# Git Basics + +## What is SCM and why Git is different +### Software Configuration Management +- Track changes to code +- Facilitate collaboration working on the same code base +- Identifying causes of defects +- Facilitate build & release process including continuous integration, continuous delivery. + +### GIT +- Decentralised +- Light weight branching +- No "Trunk"/Special branches - All branches are equal + + +## Getting Started +### Some terms we will be using +- Working Tree +- Index +- Staging +- Head +### Demonstration 00.1 +Taking a directory on the file system initialise as a GIT repo and commit files + + - Initialise a repo (run at the root of the project) + > `git init` + - Stage files - Current state of selected files are stage for the next commit + > `git add ` + - Commit - Commits changes to the repository + ``` + git commit + git commit -m "My commit message" + ``` + - Viewing the status of the working tree +``` +git status +``` + + +*Note on staging*: Once a file is stage and further changes made before the commit will not be committed unless those changes are also staged. + +### Exercise 00.1 +Take your existing code initialise, stage and commit + +## Log and Diff +### Demonstration 00.2 +Makes changes in the working directory and view differences from HEAD + +- View all differences +> `git diff` + +- View diffences for specific files +> `git diff ` + +Commit some more changes a view the log +- View the log +> `git log` + +View difference between HEAD and a previous commit +> `git diff HEAD` + +View difference between working directory and a previous commit +> `git diff ` + +View difference between two commits +> `git diff ` + +### Exercise 00.2 +1. Initialise a new git project to experiment with (we will call this the sandbox project). Try out making changes in working tree and commit changes using the `git diff` command to view the changes. +2. Initialise a new git project for you project code. + +### Other index/staging commands +These command update both the Working tree and the index +- Move/rename a files +> `git mv ` +- Remove files +> `git rm ` + +## Demonstration 00.2 +Working with GIT in PyCharm/CLion diff --git a/08_code_management/01_Branching.md b/08_code_management/01_Branching.md new file mode 100644 index 0000000..aa8e139 --- /dev/null +++ b/08_code_management/01_Branching.md @@ -0,0 +1,86 @@ +# Branching and Merging + +## Working with branches in Git +- Git has no special branches. No trunk (though it does have a default branch "master") +- Git branching is lightweight mean developers can create, merge and discard branches freely +- Can branch from any branch +- Can merge to any branch + +Create a branch as a copy of the current branch +``` +git branch +``` + +Switch branch +``` +git checkout +``` + +Create a branch and switch to that branch +``` +git checkout -b +``` + +Comparing current branch to another +``` +git diff +``` + +Comparing two other branches +``` +git diff +``` + +This can also be done in PyCharm and CLion. + +## Merging and rebasing + +Merging combined the history for the source branch with that of the target. + +To merge a branch to the current working branch: +``` +git merge +``` + +Rebasing applies the changes from the branch after the changes from another + +To rebase: +``` +git rebase +``` + +### Exercise 01.01 +In your sandbox project experiment with branch and rebasing: +1. Create some branches +2. Commit different new files to each to the branches +3. Use both merging and rebasing +4. Observer how the git log looks after these changes + +## Conflicts +The merge command shown earlier will fail if there are changes to the same files in both branches. +No fast forward merge and resolve conflicts +``` +git merge --no-ff +``` +Resolve the conflicts and use `git add` to mark as merged. Committing will apply the merge. + +Rebasing your source branch may also solve or reduce the merge conflicts + +### Exercise 01.02 +In pairs take a sandbox project and make changes to the same files + +## Branching Strategy +There are many complex branching strategies (see Git Flow for example). However, it is best to keep it simple. + +"Trunk" Development: Keep the latest on master + +## Feature branching +- Branch to develop a feature +- Keep features small +- Merge back to master as soon it is ready + +![alt text](feature_branching.svg "The feature branching strategy") + +## Release Branching +- Required only if multiple versions are to be maintain e.g 2.3.n and 3.0.n + diff --git a/08_code_management/02_Remotes_and_GitHub.md b/08_code_management/02_Remotes_and_GitHub.md new file mode 100644 index 0000000..ba920b5 --- /dev/null +++ b/08_code_management/02_Remotes_and_GitHub.md @@ -0,0 +1,59 @@ +# Remotes and GitHub + +## Git Remotes +- A remote a is repository on a server that is tracked by the local repository +- Git is decentralised and can have multiple remote servers +- Local branches can be configured track remote branches +- Different branches can track different remotes + +Adding a remote: +``` +git remote add +``` +Default remote name is `origin` + +fetching branches and tags from remote +``` +git fetch +``` +If fetching from remote called `orgin` +``` +git fetch +``` + +Push a branch to a remote branch +``` +git push --set-upstream +``` + +pushing changes from local branch to remote branch +``` +git push +``` + +pull changes from remote branch to local tracking branch +``` +git pull +``` + +## GitHub +- GitHub is a hosted Git Server at https://github.com +- Free account allow unlimited public and private projects. Though only 3 collaborates are permitted on private projects is restricted + + +### Exercise 02.01 +1. Register a at github.com +2. Create a public repository for your project with MIT licence +3. Configure the remote and push master +4. Create a private repository for your sandbox +5. Configure the remote and push all branches + +*Note:* You will be creating your repository with MIT licence. + +## Cloning a existing Repository +``` +git clone +``` + +### Exercise 02.02 +Create and new project in GitHub and clone it locally \ No newline at end of file diff --git a/08_code_management/03_README_and_Markdown.md b/08_code_management/03_README_and_Markdown.md new file mode 100644 index 0000000..e2253b3 --- /dev/null +++ b/08_code_management/03_README_and_Markdown.md @@ -0,0 +1,86 @@ +# Living Documentation + +## README +- Plain text file traditionally distributed with source to provide documentation on the software +- GitHub will display README files to the root of the project and any directories that contain them +- README files written in markdown (README.md) will be display as formatted HTML in GitHub +- These form part living documentation of the code as it can be update alongside the code the in code repository + +## Markdown +A plain text readable format that can also be converted to HTML or displayed in other rich format + +Headings +```markdown + # Top level heading + + ## Level two heading + + ### Level three heading + +``` + + +Bullets lists and numbered lists +```markdown +- Point 1 +- Another point + +1. The first item +2. Yet other item +``` +- Point 1 +- Another point + +1. The first item +2. Yet other item + +Note: Number list that are non-sequential will be displayed sequentially + +Code blocks +````markdown +```python +def add_one(n): + return n + 1 +``` +```` +```python +def add_one(n): + return n + 1 +``` +Tables +```markdown +|Name of Fruit |Price of Fruit| +|:-------------|-------------:| +|Orange |40p | +|Apple |20p | +|Banana |50p | +``` +|Name of Fruit |Price of Fruit| +|:-------------|-------------:| +|Orange |40p | +|Apple |20p | +|Banana |50p | + +*_Plus more!!!_* + +## Exercise +Create a README file for your assignment providing a description of what the software will be written in markdown. + +## Documentation comments +Many languages have built in support or tools for generating documentation or for provide help in IDEs. + +Python has docstrings + +```python +def add_one(n): + """ + Adds one to a number + :param n: The number one will be added to + :return: the number with one added + """ + return n + 1 +``` + +Doxygen can be used to generate documentation for C and C++ (as well as many other languages) +### Exercise +Experiment with creating Doxygen (http://www.doxygen.nl) comments in C++ \ No newline at end of file diff --git a/08_code_management/04_Pull_Request_and_Forking.md b/08_code_management/04_Pull_Request_and_Forking.md new file mode 100644 index 0000000..10200a8 --- /dev/null +++ b/08_code_management/04_Pull_Request_and_Forking.md @@ -0,0 +1,21 @@ +# Pull Requests and Forking + +## Pull request +- Pull requests are means are developers can collaborate by sharing and reviewing their changes. +- Pull requests can be crate from branches, forks and patches. +- Provide a means of peer reviewing and reviewing feature branches as they merge to master + +*Demonstration* + +### Exercise +1. In pairs add each other as collaborators to each others sandbox projects +2. Push changes to branches to each others projects and raise pull requests +3. View merges, provide comments and view comments (see emails sent) +4. Merge a pull requests and close merge another request (rejecting request) +5. Raise draft pull request + +## Forks +- Forking allows one to take a copy of project and makes changes with out effecting the original project +- Pull requests can be raise back to the original project + +*Demonstration* \ No newline at end of file diff --git a/08_code_management/05_Issues.md b/08_code_management/05_Issues.md new file mode 100644 index 0000000..e5b1692 --- /dev/null +++ b/08_code_management/05_Issues.md @@ -0,0 +1,11 @@ +# GitHub Issues + +- Allow one to create a list of task that need to be done to deliver the softare +- Can be used to track features to be delivered +- Can be used to track defects in the software +- Allows other to raise defects and provide feature requests + +*demonstration* + +## Exercise +- Raise issues in your assignment project for some initial task you need to p \ No newline at end of file diff --git a/08_code_management/06_Other_GIT_commands.md b/08_code_management/06_Other_GIT_commands.md new file mode 100644 index 0000000..e69de29 diff --git a/08_code_management/feature_branching.svg b/08_code_management/feature_branching.svg new file mode 100644 index 0000000..f10b691 --- /dev/null +++ b/08_code_management/feature_branching.svg @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Merge + + + + + + Master + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + feature/abc + + + + + + feature/efg + + + + + + bug/123 + + + + + + + + \ No newline at end of file diff --git a/09_testing/00_TDD.md b/09_testing/00_TDD.md new file mode 100644 index 0000000..3397831 --- /dev/null +++ b/09_testing/00_TDD.md @@ -0,0 +1,63 @@ +# Test Driven Development +## Unit testing +- Testing of units of code, i.e. testing only part/section of the code +- Written by the developer to validate the their code + + +### Unit testing in Python +The _doctest_ approach: +```python +def add_stuff(self, n, m): + """ Pointless function that adds two numbers + + >>> add_stuff(3, 5) + 8 + """ + return n + m + + +``` + +To execute the test form the command line: +``` + python -m doctest -v myfunctions.py +``` +Or you can run doctests in PyCharm + +The _unittest_ apporach +```python +import unittest +import myfunctions + +class SomeTest(unittest.TestCase): + + def test_itadds(self): + self.assertEqual(myfunctions.add_stuff(4, 7), 11) + +``` + +To execute the test form the command line: +``` + python -m unittest some_tests.py +``` +Or you can run unittests in PyCharm + + +## Developing following TDD + +### The Cycle +- Red +- Green +- Refactor + +### The Ground rules +1. Write one test a time and follow the cycle +2. Assume the code exists +3. Let the IDE do the work +4. Write only the code required to pass the test + +## Demonstration +Leap year function + +## Exercise +Write a function that can take a String containing Roman numerals and return a the equivalent number. \ No newline at end of file diff --git a/09_testing/01_Mocks.md b/09_testing/01_Mocks.md new file mode 100644 index 0000000..b18b9bb --- /dev/null +++ b/09_testing/01_Mocks.md @@ -0,0 +1,62 @@ +# Mocking +Mocking is the practice of using mock implementation of classes called by the class under test. + +## Why mock +- Separation of Concerns +- External dependencies +- Predictable behaviour + +## Dependency Injection +In Dependency Injection classes do not instantiate the objects they use, they are injected by an injector. +- Supports Separation of concerns +- Supports Modularisation +- Supports mocking in testing + +Ways of achieving +### Constructor injection + +In Python +```python +class MyClass(object): + + def __init__(self, injectedObject): + self.injected = injectedObject + + def do_something(self): + self.injected.calculate() +``` +in C++ +```c++ + +``` +### Method/variable injection +In Python: +```$xslt +class MyClass(object): + + def do_something(self): + self.injected.calculate() + +``` +In C++ +- Injection framework + +A hybrid approach of default instance which may be useful to get started with +```$xslt +class MyClass(object): + + def __init__(self): + self.injected = MyCalculator() + + def do_something(self): + self.injected.calculate() + +``` + + + +In unit testing the test can be the injector to inject mock objects. Test injection frameworks are available. + +## Exercise +Name picker: +Implement a function that can provides a random name from the top 100 list of baby names in the UK in 2018 \ No newline at end of file diff --git a/09_testing/02_CI.md b/09_testing/02_CI.md new file mode 100644 index 0000000..7c9dd2b --- /dev/null +++ b/09_testing/02_CI.md @@ -0,0 +1,37 @@ +# Continuous Integration +- The practice of frequent and small merges of features +- Supported by automated building, testing and analysis of all branches +- Supports collaboration of large number of developers + +## Travis CI +- Online Continuous integration tool +- Integrates with Github +- Very easy to set-up. Minimal configuration files with defaults, which can over ridden if the code is built differently + +Minimal Travis `.travis.yml` file for Python: +```$xslt +language: python +script: + - pytest +``` + +Minimal Travis `.travis.yml` file for C++: +```$xslt +language: cpp +``` + +## Demonstration +Setting up travis for a Python project in GitHub + +## Exercise 02.01 +1. Create a .travis.yml file in your sandbox GitHub project +2. Sign-in to https://travis-ci.org/ with your GitHub credentials +3. Configure you project +4. Create a branch with a add a new unit that does not pass +5. Create pull request +6. Ensure you can see icon in the +7. Repeat steps 1 to 3 for your main project + + + + diff --git a/09_testing/03_Code_Coverge.md b/09_testing/03_Code_Coverge.md new file mode 100644 index 0000000..e69de29 diff --git a/09_testing/04_Static_Analysis.md b/09_testing/04_Static_Analysis.md new file mode 100644 index 0000000..e69de29 diff --git a/09_testing/05_Functional_Testing.md b/09_testing/05_Functional_Testing.md new file mode 100644 index 0000000..f36b62d --- /dev/null +++ b/09_testing/05_Functional_Testing.md @@ -0,0 +1,154 @@ +# Function Test + +## Behaviour Driven Development (BDD) + +- Builds on the concepts of Test Driven Development (TDD) +- Outside looking in - What is does not how it does it +- Uses Structured natural language to capture specification +- Tests are constructed from the specifications +- Implement one feature at a time +- Can be used to measure development progress +- The specification forms a living documentation of what the software does + +TODO: Show chart + +### Some Examples of Specification Languages + +"It Should" + +``` +Alarm clock + should display the time + should wake up owner + +``` + +Gherkin + +``` +Given Alarm set +When when time of Alarm reached +Then Alarm rings + + +``` + +## Gherkin in more detail +### Basic Format +``` +Feature: Here is free form description of the feature that can be several lines long. + +Scenario: This would be a description of one scenario +Given precondition +When action +Then expected outcome + +Scenario: A description of another scenario + +``` + +### And and But: +``` +Scenario: A more complex scenario +Given a precondition +And another precondition +When action +And another action +Then expected outcome +And another expected outcome +But a negative expected outcome +``` + +### An Example +```$xslt +Scenario: Valid login +Given a registered user +When user supplies correct login details +Then user is logged in + +Scenario: Invalid password +Given a registered user +When user supplies incorrect password +Then user is shown "Username/Password incorrect" error message +And user not logged in + +Scenario: Invalid username +Given a registered user +When user supplies incorrect username +Then user is shown "Username/Password incorrect" error message +And user not logged in + +``` + +### Using Example tables +```$xslt +Feature: Fruit Multi-purchase discounts. Apples are €0.50 each for up to 4 (inclusive), then €0.45 each, for 10 or over they are €0.40. Oranges are €0.80 each for the first 12, then €0.75. + +Scenario: Shopper buys single type of fruit. +Given buying +When shopper picks +Then total price comes to € + +Examples: + | fruit | number | price | + | apple | 1 | 0.50 | + | apple | 3 | 1.50 | + | apple | 4 | 2.00 | + | apple | 5 | 2.25 | + | apple | 8 | 3.60 | + | apple | 10 | 4.00 | + | apple | 12 | 4.80 | + | orange | 1 | 0.80 | + | orange | 6 | 4.80 | + | orange | 12 | 9.96 | + | orange | 13 | 9.75 | + +``` +TODO: Add reading suggestion Specification by Example + +### Imperative and Declarative +```$xslt +Imperative +Given … +When User arrives a journal land clicks on login field +Then User is redirected to login screen +When enters text "myUserId" in field "Username" +And enters text "myPassw0rd" in field "Password" +And user clicks "Login“ +Then … + +Declarative +When user supplies correct login details + +``` + +### Sensible Defaults +```$xslt +Scenario: “Default” user logs in +Given a registered user +When user supplies correct login details +Then user is logged in + +Scenario: User with expired password +Given a registered user +And password has expired +When user supplies correct login details +Then user is shown change password screen +And user not logged in + +``` + +## BDD tools +- Parse specification and executes test code +- Typically can create code place holders from specification +- Reporting of test results + +Many tools are available that support many different test written in many different lanaguages. We will look a Behave with Python. + +## Demonstration +An example of using Behave. + +## Exercise +Create and execute a test against your software project using Behave + + From 2ef005100304bfab5d717b5ef1dcaceedb4d2ca4 Mon Sep 17 00:00:00 2001 From: Mark Bell Date: Wed, 2 Oct 2019 13:19:53 +0100 Subject: [PATCH 2/8] Change licence type --- 08_code_management/02_Remotes_and_GitHub.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/08_code_management/02_Remotes_and_GitHub.md b/08_code_management/02_Remotes_and_GitHub.md index ba920b5..9deb0da 100644 --- a/08_code_management/02_Remotes_and_GitHub.md +++ b/08_code_management/02_Remotes_and_GitHub.md @@ -43,12 +43,12 @@ git pull ### Exercise 02.01 1. Register a at github.com -2. Create a public repository for your project with MIT licence +2. Create a public repository for your project with BSE3 licence 3. Configure the remote and push master 4. Create a private repository for your sandbox 5. Configure the remote and push all branches -*Note:* You will be creating your repository with MIT licence. +*Note:* You will be creating your repository with BSE3 licence. ## Cloning a existing Repository ``` @@ -56,4 +56,4 @@ git clone ``` ### Exercise 02.02 -Create and new project in GitHub and clone it locally \ No newline at end of file +Create and new project in GitHub and clone it locally From 649ff2d307c9388dadf95476a5fb031a1a9fc2cd Mon Sep 17 00:00:00 2001 From: Mark Bell Date: Tue, 22 Oct 2019 22:16:10 +0100 Subject: [PATCH 3/8] Updates --- 08_code_management/00_Git_Basics.md | 1 + 09_testing/01_Mocks.md | 28 ++++++++++++++++++++++++---- 09_testing/02_CI.md | 4 ++-- 09_testing/05_Functional_Testing.md | 2 +- 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/08_code_management/00_Git_Basics.md b/08_code_management/00_Git_Basics.md index e539e82..6e25088 100644 --- a/08_code_management/00_Git_Basics.md +++ b/08_code_management/00_Git_Basics.md @@ -9,6 +9,7 @@ ### GIT - Decentralised +- not locking - Light weight branching - No "Trunk"/Special branches - All branches are equal diff --git a/09_testing/01_Mocks.md b/09_testing/01_Mocks.md index b18b9bb..d20511a 100644 --- a/09_testing/01_Mocks.md +++ b/09_testing/01_Mocks.md @@ -15,7 +15,7 @@ In Dependency Injection classes do not instantiate the objects they use, they ar Ways of achieving ### Constructor injection -In Python +#### In Python ```python class MyClass(object): @@ -25,20 +25,40 @@ class MyClass(object): def do_something(self): self.injected.calculate() ``` -in C++ +#### in C++ +Interface +```c++ +class InjectedInterface { + public: + virtual int addNumbers(int n, int m) = 0; +} +``` +Implementation class ```c++ +class InjectedImplmentation: public InjectedInterface { + public: + int addNumbers(int n, int m) { + return + } +} +``` +```c++ +class MyClass { + public: + MyClass(InjectedInterface ) +}; ``` ### Method/variable injection In Python: -```$xslt +```python class MyClass(object): def do_something(self): self.injected.calculate() ``` -In C++ + - Injection framework A hybrid approach of default instance which may be useful to get started with diff --git a/09_testing/02_CI.md b/09_testing/02_CI.md index 7c9dd2b..3a2848f 100644 --- a/09_testing/02_CI.md +++ b/09_testing/02_CI.md @@ -9,14 +9,14 @@ - Very easy to set-up. Minimal configuration files with defaults, which can over ridden if the code is built differently Minimal Travis `.travis.yml` file for Python: -```$xslt +```yaml language: python script: - pytest ``` Minimal Travis `.travis.yml` file for C++: -```$xslt +```yaml language: cpp ``` diff --git a/09_testing/05_Functional_Testing.md b/09_testing/05_Functional_Testing.md index f36b62d..988a8c5 100644 --- a/09_testing/05_Functional_Testing.md +++ b/09_testing/05_Functional_Testing.md @@ -60,7 +60,7 @@ But a negative expected outcome ``` ### An Example -```$xslt +``` Scenario: Valid login Given a registered user When user supplies correct login details From 34b21b2d2680ae93daf3bc146a1629cc67afdffc Mon Sep 17 00:00:00 2001 From: Mark Bell Date: Tue, 22 Oct 2019 23:16:37 +0100 Subject: [PATCH 4/8] C++ example --- 09_testing/01_Mocks.md | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/09_testing/01_Mocks.md b/09_testing/01_Mocks.md index d20511a..21d3433 100644 --- a/09_testing/01_Mocks.md +++ b/09_testing/01_Mocks.md @@ -28,27 +28,43 @@ class MyClass(object): #### in C++ Interface ```c++ -class InjectedInterface { +class InjectionInterface { public: - virtual int addNumbers(int n, int m) = 0; -} + virtual int addNumbers(int n, int m) = 0; +}; + ``` Implementation class ```c++ -class InjectedImplmentation: public InjectedInterface { +class InjectionImplementation: public InjectionInterface{ public: - int addNumbers(int n, int m) { - return + int addNumbers(int n, int m){ + return n + m; } -} +}; + ``` +Class injected to: ```c++ class MyClass { - public: - MyClass(InjectedInterface ) + public: + MyClass(InjectionInterface &dependecy) { + dep = dependecy; + } + + private: + InjectionInterface &dep; }; ``` +Injection +```c++ +int main() { + InjectionImplementation myDep; + MyClass mc(myDep); +} +``` + ### Method/variable injection In Python: ```python @@ -62,7 +78,7 @@ class MyClass(object): - Injection framework A hybrid approach of default instance which may be useful to get started with -```$xslt +```python class MyClass(object): def __init__(self): From da2134dc401aa2f707468e40284c43288ec64043 Mon Sep 17 00:00:00 2001 From: Joe Rogers Date: Thu, 24 Oct 2019 13:38:06 +0100 Subject: [PATCH 5/8] Added Code Coverage and Behave demo --- 09_testing/03_Code_Coverge.md | 73 +++++++++++++++++++++++++++++ 09_testing/05_Functional_Testing.md | 41 ++++++++++++++++ 2 files changed, 114 insertions(+) diff --git a/09_testing/03_Code_Coverge.md b/09_testing/03_Code_Coverge.md index e69de29..53ccd64 100644 --- a/09_testing/03_Code_Coverge.md +++ b/09_testing/03_Code_Coverge.md @@ -0,0 +1,73 @@ +#Code Coverage + +##What is Code Coverage + +- Measures which parts of a codebase are covered by automated tests +- Can be used to identify untested code +- Useful to ensure standards of testing are maintained throughout a project + +##Types of code coverage + +There are several ways to measure code coverage + +- Function coverage: how many of the functions defined have been called. +- Statement coverage: how many of the statements in the program have been executed. +- Branches coverage: how many of the branches of the control structures (if statements for instance) have been executed. +- Condition coverage: how many of the boolean sub-expressions have been tested for a true and a false value. +- Line coverage: how many of lines of source code have been tested. + +##Gotchas +- Code coverage is not a panacea +- High Coverage alone is not a guarantee of well tested, maintainable code +- 100% is often unattainable and may well not be worth the level of effort required - better to maintain a moderate but consistent level throughout the application +- It is entirely possible to write bad unit tests which have high code coverage - code coverage only tells you that the code was executed, not that it was well tested. + +##Code Coverage in Python - coverage.py +###Install It +First, install Coverage.py: + +``` +pip install coverage +``` + +###Gather Data +Run your test suite, but replace python blah blah with coverage run blah blah + +For example: + +``` +coverage run Test.py +``` + +or, to collect branch coverage data + +``` +coverage run --branch Test.py +``` + +Ideally your tests should be outside of your source directory, so that you don't have to configure excludes or package them. It's much neater that way. + +###Print Coverage Reports +####Command-Line Report +To print a quick command line report, maximize your command prompt and type: + +``` +coverage report -m +``` +####HTML Report +To print a fancier HTML report: + +``` +coverage html +open html_cov/index.html +``` + + +##Demo - Person.py + +##Exercise + +Now try running the test suite you wrote earlier using Coverage. + +Since you have been using TDD, you should find high code coverage, +try adding an extra untested method or an untested branch to your code diff --git a/09_testing/05_Functional_Testing.md b/09_testing/05_Functional_Testing.md index 988a8c5..2204165 100644 --- a/09_testing/05_Functional_Testing.md +++ b/09_testing/05_Functional_Testing.md @@ -148,6 +148,47 @@ Many tools are available that support many different test written in many differ ## Demonstration An example of using Behave. +###Install Behave + +``` +pip install behave +``` + +Now make a directory called “features”. In that directory create a file called “tutorial.feature” containing: + +``` +Feature: showing off behave + + Scenario: run a simple test + Given we have behave installed + When we implement a test + Then behave will test it for us! +``` + +Make a new directory called “features/steps”. In that directory create a file called “tutorial.py” containing: + +```python +from behave import * + +@given('we have behave installed') +def step_impl(context): + pass + +@when('we implement a test') +def step_impl(context): + assert True is not False + +@then('behave will test it for us!') +def step_impl(context): + assert context.failed is False +``` + +Run behave: + +``` +behave +``` + ## Exercise Create and execute a test against your software project using Behave From 8a97948204272af4d7ac50b771bfa2e12009f90f Mon Sep 17 00:00:00 2001 From: Joe Rogers Date: Thu, 24 Oct 2019 13:43:40 +0100 Subject: [PATCH 6/8] fixed code coverage heading lines --- 09_testing/03_Code_Coverge.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/09_testing/03_Code_Coverge.md b/09_testing/03_Code_Coverge.md index 53ccd64..45ff8e2 100644 --- a/09_testing/03_Code_Coverge.md +++ b/09_testing/03_Code_Coverge.md @@ -1,12 +1,12 @@ -#Code Coverage +# Code Coverage -##What is Code Coverage +## What is Code Coverage - Measures which parts of a codebase are covered by automated tests - Can be used to identify untested code - Useful to ensure standards of testing are maintained throughout a project -##Types of code coverage +## Types of code coverage There are several ways to measure code coverage @@ -16,21 +16,21 @@ There are several ways to measure code coverage - Condition coverage: how many of the boolean sub-expressions have been tested for a true and a false value. - Line coverage: how many of lines of source code have been tested. -##Gotchas +## Gotchas - Code coverage is not a panacea - High Coverage alone is not a guarantee of well tested, maintainable code - 100% is often unattainable and may well not be worth the level of effort required - better to maintain a moderate but consistent level throughout the application - It is entirely possible to write bad unit tests which have high code coverage - code coverage only tells you that the code was executed, not that it was well tested. -##Code Coverage in Python - coverage.py -###Install It +## Code Coverage in Python - coverage.py +### Install It First, install Coverage.py: ``` pip install coverage ``` -###Gather Data +### Gather Data Run your test suite, but replace python blah blah with coverage run blah blah For example: @@ -47,14 +47,14 @@ coverage run --branch Test.py Ideally your tests should be outside of your source directory, so that you don't have to configure excludes or package them. It's much neater that way. -###Print Coverage Reports -####Command-Line Report +### Print Coverage Reports +#### Command-Line Report To print a quick command line report, maximize your command prompt and type: ``` coverage report -m ``` -####HTML Report +#### HTML Report To print a fancier HTML report: ``` @@ -63,9 +63,9 @@ open html_cov/index.html ``` -##Demo - Person.py +## Demo - Person.py -##Exercise +## Exercise Now try running the test suite you wrote earlier using Coverage. From 1c167aec9c969a7911fea21849a592815a751e73 Mon Sep 17 00:00:00 2001 From: Joe Rogers Date: Thu, 24 Oct 2019 16:11:37 +0100 Subject: [PATCH 7/8] fixed code coverage heading lines --- 09_testing/05_Functional_Testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/09_testing/05_Functional_Testing.md b/09_testing/05_Functional_Testing.md index 2204165..4bd73bb 100644 --- a/09_testing/05_Functional_Testing.md +++ b/09_testing/05_Functional_Testing.md @@ -148,7 +148,7 @@ Many tools are available that support many different test written in many differ ## Demonstration An example of using Behave. -###Install Behave +### Install Behave ``` pip install behave From 46bb7f3ac7e94cf91a92e9b198611abb6428ac02 Mon Sep 17 00:00:00 2001 From: Mark Bell Date: Fri, 25 Oct 2019 16:21:39 +0100 Subject: [PATCH 8/8] Update post course --- 08_code_management/01_Branching.md | 5 +- 08_code_management/02_Remotes_and_GitHub.md | 22 ++-- .../04_Pull_Request_and_Forking.md | 9 -- 08_code_management/05_Issues.md | 5 - 09_testing/00_TDD.md | 23 +++-- 09_testing/01_Mocks.md | 96 +++++++++++++++++- 09_testing/02_CI.md | 6 +- 09_testing/05_Functional_Testing.md | 26 ++--- 09_testing/TDD.gif | Bin 0 -> 13740 bytes 9 files changed, 141 insertions(+), 51 deletions(-) create mode 100644 09_testing/TDD.gif diff --git a/08_code_management/01_Branching.md b/08_code_management/01_Branching.md index aa8e139..935519e 100644 --- a/08_code_management/01_Branching.md +++ b/08_code_management/01_Branching.md @@ -60,14 +60,13 @@ In your sandbox project experiment with branch and rebasing: The merge command shown earlier will fail if there are changes to the same files in both branches. No fast forward merge and resolve conflicts ``` -git merge --no-ff +git merge ``` Resolve the conflicts and use `git add` to mark as merged. Committing will apply the merge. Rebasing your source branch may also solve or reduce the merge conflicts -### Exercise 01.02 -In pairs take a sandbox project and make changes to the same files + ## Branching Strategy There are many complex branching strategies (see Git Flow for example). However, it is best to keep it simple. diff --git a/08_code_management/02_Remotes_and_GitHub.md b/08_code_management/02_Remotes_and_GitHub.md index 9deb0da..79df587 100644 --- a/08_code_management/02_Remotes_and_GitHub.md +++ b/08_code_management/02_Remotes_and_GitHub.md @@ -12,15 +12,6 @@ git remote add ``` Default remote name is `origin` -fetching branches and tags from remote -``` -git fetch -``` -If fetching from remote called `orgin` -``` -git fetch -``` - Push a branch to a remote branch ``` git push --set-upstream @@ -36,10 +27,21 @@ pull changes from remote branch to local tracking branch git pull ``` +To make you local branch be aware of latest set of remote branches you will need to fetch from the remote + +fetching branches and tags from remote +``` +git fetch +``` +If fetching from remote called `orgin` +``` +git fetch +``` + + ## GitHub - GitHub is a hosted Git Server at https://github.com - Free account allow unlimited public and private projects. Though only 3 collaborates are permitted on private projects is restricted - ### Exercise 02.01 1. Register a at github.com diff --git a/08_code_management/04_Pull_Request_and_Forking.md b/08_code_management/04_Pull_Request_and_Forking.md index 10200a8..6454d6b 100644 --- a/08_code_management/04_Pull_Request_and_Forking.md +++ b/08_code_management/04_Pull_Request_and_Forking.md @@ -5,15 +5,6 @@ - Pull requests can be crate from branches, forks and patches. - Provide a means of peer reviewing and reviewing feature branches as they merge to master -*Demonstration* - -### Exercise -1. In pairs add each other as collaborators to each others sandbox projects -2. Push changes to branches to each others projects and raise pull requests -3. View merges, provide comments and view comments (see emails sent) -4. Merge a pull requests and close merge another request (rejecting request) -5. Raise draft pull request - ## Forks - Forking allows one to take a copy of project and makes changes with out effecting the original project - Pull requests can be raise back to the original project diff --git a/08_code_management/05_Issues.md b/08_code_management/05_Issues.md index e5b1692..9287767 100644 --- a/08_code_management/05_Issues.md +++ b/08_code_management/05_Issues.md @@ -4,8 +4,3 @@ - Can be used to track features to be delivered - Can be used to track defects in the software - Allows other to raise defects and provide feature requests - -*demonstration* - -## Exercise -- Raise issues in your assignment project for some initial task you need to p \ No newline at end of file diff --git a/09_testing/00_TDD.md b/09_testing/00_TDD.md index 3397831..a7c9ede 100644 --- a/09_testing/00_TDD.md +++ b/09_testing/00_TDD.md @@ -15,7 +15,6 @@ def add_stuff(self, n, m): """ return n + m - ``` To execute the test form the command line: @@ -31,24 +30,34 @@ import myfunctions class SomeTest(unittest.TestCase): - def test_itadds(self): + def test_it_adds(self): self.assertEqual(myfunctions.add_stuff(4, 7), 11) ``` -To execute the test form the command line: -``` +To execute the test form the command line for a single file: +``` python -m unittest some_tests.py ``` + +To execute the test form the command line for a directory/module : +``` + python -m unittest +``` + + Or you can run unittests in PyCharm ## Developing following TDD ### The Cycle -- Red -- Green -- Refactor +- Red - Write a single test and ensure it fails +- Green - Make the test pass +- Refactor - refactor the code and rerun the test +Repeat above + +![alt text](TDD.gif "The Test Driven Development cycle") ### The Ground rules 1. Write one test a time and follow the cycle diff --git a/09_testing/01_Mocks.md b/09_testing/01_Mocks.md index 21d3433..ecab9ae 100644 --- a/09_testing/01_Mocks.md +++ b/09_testing/01_Mocks.md @@ -75,8 +75,6 @@ class MyClass(object): ``` -- Injection framework - A hybrid approach of default instance which may be useful to get started with ```python class MyClass(object): @@ -89,10 +87,102 @@ class MyClass(object): ``` +## Mocking in Python + +Example of Python file being tested: +```python +from temperature_sensor import TemperatureSensor + + +class TemperatureTracker: + + def __init__(self) -> None: + self.sensor = TemperatureSensor() + self.start_temp = 0 + + def record_initial_temperature(self): + self.start_temp = self.sensor.check_temperature() + + def find_temperature_change(self): + return self.sensor.check_temperature() - self.start_temp +``` + +## Mocking with Mock class +```python +import unittest +from temperature_tracker import TemperatureTracker +from unittest.mock import Mock + + +class TestWithMock(unittest.TestCase): + + def test_tracks_temperature_change(self): + tracker = TemperatureTracker() + mock_sensor = Mock() + tracker.sensor = mock_sensor + + mock_sensor.configure_mock(**{'check_temperature.return_value': 12}) + tracker.record_initial_temperature() + mock_sensor.configure_mock(**{'check_temperature.return_value': 22}) + self.assertEqual(10, tracker.find_temperature_change()) -In unit testing the test can be the injector to inject mock objects. Test injection frameworks are available. +if __name__ == '__main__': + unittest.main() + +``` + +## Mock with patch +```python +import unittest +from temperature_tracker import TemperatureTracker +from temperature_sensor import TemperatureSensor +from unittest.mock import patch + +results = [12, 22] + + +class TestWithPatch(unittest.TestCase): + + @patch.object(TemperatureSensor, 'check_temperature', side_effect=results) + def test_tracks_temperature_change(self, mock): + tracker = TemperatureTracker() + tracker.record_initial_temperature() + self.assertEqual(10, tracker.find_temperature_change()) + + +if __name__ == '__main__': + unittest.main() + +``` + +## Using patch with built in function +Python file under test: +```python +import time + + +def whats_the_time(): + return time.time() +``` +Test code: +```python +import unittest +import clock +from unittest.mock import patch + + +class MyTestCase(unittest.TestCase): + + @patch('time.time', return_value=1571871846.8861961) + def test_the_time(self, mock): + self.assertEqual(clock.whats_the_time(), 1571871846.8861961) + + +if __name__ == '__main__': + unittest.main() +``` ## Exercise Name picker: Implement a function that can provides a random name from the top 100 list of baby names in the UK in 2018 \ No newline at end of file diff --git a/09_testing/02_CI.md b/09_testing/02_CI.md index 3a2848f..58f09a0 100644 --- a/09_testing/02_CI.md +++ b/09_testing/02_CI.md @@ -8,11 +8,13 @@ - Integrates with Github - Very easy to set-up. Minimal configuration files with defaults, which can over ridden if the code is built differently -Minimal Travis `.travis.yml` file for Python: +A start Travis `.travis.yml` file for Python 3.8 using unittest module: ```yaml language: python +python: + - 3.8 script: - - pytest + - python -m unittest discover test ``` Minimal Travis `.travis.yml` file for C++: diff --git a/09_testing/05_Functional_Testing.md b/09_testing/05_Functional_Testing.md index 4bd73bb..748a248 100644 --- a/09_testing/05_Functional_Testing.md +++ b/09_testing/05_Functional_Testing.md @@ -16,7 +16,7 @@ TODO: Show chart "It Should" -``` +```gherkin Alarm clock should display the time should wake up owner @@ -25,17 +25,16 @@ Alarm clock Gherkin -``` +```gherkin Given Alarm set When when time of Alarm reached Then Alarm rings - ``` ## Gherkin in more detail ### Basic Format -``` +```gherkin Feature: Here is free form description of the feature that can be several lines long. Scenario: This would be a description of one scenario @@ -48,7 +47,7 @@ Scenario: A description of another scenario ``` ### And and But: -``` +```gherkin Scenario: A more complex scenario Given a precondition And another precondition @@ -60,7 +59,7 @@ But a negative expected outcome ``` ### An Example -``` +```gherkin Scenario: Valid login Given a registered user When user supplies correct login details @@ -81,7 +80,7 @@ And user not logged in ``` ### Using Example tables -```$xslt +```gherkin Feature: Fruit Multi-purchase discounts. Apples are €0.50 each for up to 4 (inclusive), then €0.45 each, for 10 or over they are €0.40. Oranges are €0.80 each for the first 12, then €0.75. Scenario: Shopper buys single type of fruit. @@ -107,7 +106,7 @@ Examples: TODO: Add reading suggestion Specification by Example ### Imperative and Declarative -```$xslt +```gherkin Imperative Given … When User arrives a journal land clicks on login field @@ -123,7 +122,7 @@ When user supplies correct login details ``` ### Sensible Defaults -```$xslt +```gherkin Scenario: “Default” user logs in Given a registered user When user supplies correct login details @@ -145,7 +144,10 @@ And user not logged in Many tools are available that support many different test written in many different lanaguages. We will look a Behave with Python. -## Demonstration +## Behave +Behave is a BDD framework for Python (https://behave.readthedocs.io). + +### Demonstration An example of using Behave. ### Install Behave @@ -153,10 +155,10 @@ An example of using Behave. ``` pip install behave ``` - +### Using behave Now make a directory called “features”. In that directory create a file called “tutorial.feature” containing: -``` +```gherkin Feature: showing off behave Scenario: run a simple test diff --git a/09_testing/TDD.gif b/09_testing/TDD.gif new file mode 100644 index 0000000000000000000000000000000000000000..a32af8597f4f4d06a4c17caec823f4353a6cbdc7 GIT binary patch literal 13740 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 literal 0 HcmV?d00001