From 9cb28354443c8c984512694090ebe9dc2f2e5ea1 Mon Sep 17 00:00:00 2001 From: christie Date: Sun, 11 Oct 2015 11:53:32 -0700 Subject: [PATCH 1/4] Add all of the solutions --- README.md | 5 +++-- tests/test_catactivities.py | 17 ++++++++------- tests/test_catmath.py | 11 +++++++--- tests/test_cattery.py | 42 ++++++++++++++++++++----------------- tests/test_safecatmath.py | 16 ++++++++------ 5 files changed, 54 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 70a2e41..0e6672d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ # catinabox - Intro to Testing and Test Automation in Python + +**This branch includes all solutions!** + [![Build Status](https://travis-ci.org/keeppythonweird/catinabox.svg?branch=master)](https://travis-ci.org/keeppythonweird/catinabox) Coverage status: @@ -35,6 +38,4 @@ exploring these features of unit testing in general and pytest in particular: 7. [Parameterized tests](./steps/7-params.md) 8. [Refactoring for unit testability](./steps/8-refactor.md) -Solutions are visible by viewing [the solutions branch](https://github.com/keeppythonweird/catinabox/tree/solutions). - ![cattery](pics/cattery.png) diff --git a/tests/test_catactivities.py b/tests/test_catactivities.py index e2051b1..2d6f50c 100644 --- a/tests/test_catactivities.py +++ b/tests/test_catactivities.py @@ -1,14 +1,17 @@ -# import pytest -# import time +import pytest +import time -# from catinabox import catactivities +from catinabox import catactivities def test__cat_nap__satisfying_nap(mocker): - # mock_sleep = mocker.patch.object(time, "sleep", autospec=True) - assert True + mock_sleep = mocker.patch.object(time, "sleep", autospec=True) + catactivities.cat_nap(500) + mock_sleep.assert_called_with(500) def test__cat_nap__not_satisfying(mocker): - # mock_sleep = mocker.patch.object(time, "sleep", autospec=True) - assert True + mock_sleep = mocker.patch.object(time, "sleep", autospec=True) + with pytest.raises(catactivities.NapWillNotBeSatisfying): + catactivities.cat_nap(5) + assert mock_sleep.call_count == 0 diff --git a/tests/test_catmath.py b/tests/test_catmath.py index ced6358..748dafe 100644 --- a/tests/test_catmath.py +++ b/tests/test_catmath.py @@ -2,15 +2,20 @@ def test__cat_years_to_hooman_years__middle_age__succeeds(): - assert True + cat_age = 7 + hooman_age = catmath.cat_years_to_hooman_years(cat_age) + assert hooman_age == 35 def test__cat_years_to_hooman_years__less_than_one_year__succeeds(): - assert True + cat_age = 0.1 + hooman_age = catmath.cat_years_to_hooman_years(cat_age) + assert hooman_age == 0.5 def test__cat_years_to_hooman_years__0__returns_0(): - assert True + hooman_age = catmath.cat_years_to_hooman_years(0) + assert hooman_age == 0 # BONUS MATERIAL FOR STEP 2 diff --git a/tests/test_cattery.py b/tests/test_cattery.py index b36692c..e6ddf82 100644 --- a/tests/test_cattery.py +++ b/tests/test_cattery.py @@ -1,39 +1,43 @@ import pytest -from catinabox import cattery +from catinabox import cattery, mccattery + + +@pytest.fixture(params=[ + cattery.Cattery, + mccattery.McCattery +]) +def cattery_client(request): + return request.param() ########################################################################### # add_cats ########################################################################### -def test__add_cats__succeeds(): - c = cattery.Cattery() - c.add_cats(["Fluffy", "Snookums"]) - assert c.cats == ["Fluffy", "Snookums"] - assert c.num_cats == 2 +def test__add_cats__succeeds(cattery_client): + cattery_client.add_cats(["Fluffy", "Snookums"]) + assert cattery_client.cats == ["Fluffy", "Snookums"] + assert cattery_client.num_cats == 2 ########################################################################### # remove_cat ########################################################################### -def test__remove_cat__succeeds(): - c = cattery.Cattery() - c.add_cats(["Fluffy", "Junior"]) - c.remove_cat("Fluffy") - assert c.cats == ["Junior"] - assert c.num_cats == 1 +def test__remove_cat__succeeds(cattery_client): + cattery_client.add_cats(["Fluffy", "Junior"]) + cattery_client.remove_cat("Fluffy") + assert cattery_client.cats == ["Junior"] + assert cattery_client.num_cats == 1 -def test__remove_cat__no_cats__fails(): - c = cattery.Cattery() +def test__remove_cat__no_cats__fails(cattery_client): with pytest.raises(cattery.CatNotFound): - c.remove_cat("Fluffles") + cattery_client.remove_cat("Fluffles") -def test__remove_cat__cat_not_in_cattery__fails(): - c = cattery.Cattery() - c.add_cats(["Fluffy"]) +def test__remove_cat__cat_not_in_cattery__fails(cattery_client): + cattery_client.add_cats(["Fluffy"]) with pytest.raises(cattery.CatNotFound): - c.remove_cat("Snookums") + cattery_client.remove_cat("Snookums") diff --git a/tests/test_safecatmath.py b/tests/test_safecatmath.py index 3f85a35..b872b59 100644 --- a/tests/test_safecatmath.py +++ b/tests/test_safecatmath.py @@ -1,4 +1,4 @@ -# import pytest +import pytest from catinabox import safecatmath @@ -19,17 +19,21 @@ def test__cat_years_to_hooman_years__0__returns_0(): def test__cat_years_to_hooman_years__less_0__raises(): - assert True + with pytest.raises(safecatmath.InvalidAge): + safecatmath.cat_years_to_hooman_years(-4) def test__cat_years_to_hooman_years__older_than_1000__raises(): - assert True + with pytest.raises(safecatmath.InvalidAge): + safecatmath.cat_years_to_hooman_years(1000.1) def test__cat_years_to_hooman_years__string__raises(): - assert True + with pytest.raises(safecatmath.InvalidAge): + safecatmath.cat_years_to_hooman_years("five") def test__cat_years_to_hooman_years__nan__raises(): - # hooman_age = float('nan') - assert True + hooman_age = float('nan') + with pytest.raises(safecatmath.InvalidAge): + safecatmath.cat_years_to_hooman_years(hooman_age) From 66fe6fc6c700257240167cccee48931b93d64779 Mon Sep 17 00:00:00 2001 From: bobcatfish Date: Sat, 28 May 2016 16:28:25 -0700 Subject: [PATCH 2/4] Add solution for refactoring exercise This solution refactors `cat_generator` into a glue between two responsibilities: generating a name and generating a birthday. --- catinabox/catgenerator.py | 35 ++++++++++++++++++++--------------- tests/test_catgenerator.py | 38 +++++++++++++++++++++++++++++++++----- 2 files changed, 53 insertions(+), 20 deletions(-) diff --git a/catinabox/catgenerator.py b/catinabox/catgenerator.py index b9b1df0..098e0eb 100644 --- a/catinabox/catgenerator.py +++ b/catinabox/catgenerator.py @@ -18,20 +18,25 @@ def __init__(self, e): ) +def get_name(): + try: + result = requests.get(NAME_GENERATOR_API_ENDPOINT) + return result.json()[0] + except requests.exceptions.RequestException as e: + raise CouldNotGetNameError(e) + + +def get_birthday(): + current_time = int(time.time()) + birthday = random.randint( + current_time - (SECONDS_IN_YEAR * MAX_YEARS_OLD), + current_time) + birthday_datetime = time.strftime('%Y-%m-%d %H:%M:%S', + time.gmtime(birthday)) + return birthday_datetime + + def cat_generator(): while True: - try: - result = requests.get(NAME_GENERATOR_API_ENDPOINT) - name = result.json()[0] - except requests.exceptions.RequestException as e: - raise CouldNotGetNameError(e) - - current_time = int(time.time()) - birthday = random.randint( - current_time - (SECONDS_IN_YEAR * MAX_YEARS_OLD), - current_time) - birthday_datetime = time.strftime('%Y-%m-%d %H:%M:%S', - time.localtime(birthday)) - - yield {"name": name, - "birthday": birthday_datetime} + yield {"name": get_name(), + "birthday": get_birthday()} diff --git a/tests/test_catgenerator.py b/tests/test_catgenerator.py index dd7ed44..90e08fa 100644 --- a/tests/test_catgenerator.py +++ b/tests/test_catgenerator.py @@ -1,9 +1,37 @@ -# import pytest +import pytest +import requests -# from catinabox import catgenerator +from catinabox import catgenerator -# Write tests for the refactored `catinabox.catgenerator` +def test__get_name(mocker): + mocker.patch("requests.get").return_value.json.return_value = ["Francis"] + cat_name = catgenerator.get_name() + assert cat_name == "Francis" -def test__(): - pass + +def test__get_name__requests_error__raises(mocker): + mocker.patch("requests.get", side_effect=requests.exceptions.Timeout) + with pytest.raises(catgenerator.CouldNotGetNameError): + catgenerator.get_name() + + +def test__get_birthday(mocker): + mocker.patch("time.time", return_value=catgenerator.SECONDS_IN_YEAR * 35) + randint = mocker.patch("random.randint", + return_value=catgenerator.SECONDS_IN_YEAR * 2) + birthday = catgenerator.get_birthday() + assert birthday == "1972-01-01 00:00:00" + randint.assert_called_with(catgenerator.SECONDS_IN_YEAR * 5, + catgenerator.SECONDS_IN_YEAR * 35) + + +def test__cat_generator(mocker): + mocker.patch.object(catgenerator, "get_name", return_value="Moe") + mocker.patch.object(catgenerator, "get_birthday", return_value="birthday") + cat_generator = catgenerator.cat_generator() + + assert next(cat_generator) == {"name": "Moe", + "birthday": "birthday"} + assert next(cat_generator) == {"name": "Moe", + "birthday": "birthday"} From 0a9acb7c91605ace15a7f089a379e81b2670e340 Mon Sep 17 00:00:00 2001 From: bobcatfish Date: Tue, 12 Jul 2016 20:24:58 +0100 Subject: [PATCH 3/4] Make cattery fixture solution simpler When we gave this tutorial at Pycon 2016, some of the students found the solution confusing since it uses parameterized fixtures, which at this point in the tutorial we have not introduced. I have kept the parameterized fixtures around so that we can refer to them in step 7 on parameterization. --- tests/test_cattery.py | 11 ++++------- tests/test_mccattery.py | 43 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 7 deletions(-) create mode 100644 tests/test_mccattery.py diff --git a/tests/test_cattery.py b/tests/test_cattery.py index e6ddf82..98f3f28 100644 --- a/tests/test_cattery.py +++ b/tests/test_cattery.py @@ -1,14 +1,11 @@ import pytest -from catinabox import cattery, mccattery +from catinabox import cattery -@pytest.fixture(params=[ - cattery.Cattery, - mccattery.McCattery -]) -def cattery_client(request): - return request.param() +@pytest.fixture() +def cattery_client(): + return cattery.Cattery() ########################################################################### diff --git a/tests/test_mccattery.py b/tests/test_mccattery.py new file mode 100644 index 0000000..e6ddf82 --- /dev/null +++ b/tests/test_mccattery.py @@ -0,0 +1,43 @@ +import pytest + +from catinabox import cattery, mccattery + + +@pytest.fixture(params=[ + cattery.Cattery, + mccattery.McCattery +]) +def cattery_client(request): + return request.param() + + +########################################################################### +# add_cats +########################################################################### + +def test__add_cats__succeeds(cattery_client): + cattery_client.add_cats(["Fluffy", "Snookums"]) + assert cattery_client.cats == ["Fluffy", "Snookums"] + assert cattery_client.num_cats == 2 + + +########################################################################### +# remove_cat +########################################################################### + +def test__remove_cat__succeeds(cattery_client): + cattery_client.add_cats(["Fluffy", "Junior"]) + cattery_client.remove_cat("Fluffy") + assert cattery_client.cats == ["Junior"] + assert cattery_client.num_cats == 1 + + +def test__remove_cat__no_cats__fails(cattery_client): + with pytest.raises(cattery.CatNotFound): + cattery_client.remove_cat("Fluffles") + + +def test__remove_cat__cat_not_in_cattery__fails(cattery_client): + cattery_client.add_cats(["Fluffy"]) + with pytest.raises(cattery.CatNotFound): + cattery_client.remove_cat("Snookums") From 4db7b359091eba38109a0703284a5d3bf18fbb55 Mon Sep 17 00:00:00 2001 From: bobcatfish Date: Tue, 12 Jul 2016 20:46:10 +0100 Subject: [PATCH 4/4] Add leap year coverage Added tests that provide full coverage for the is leap year algorithm. The rules seem pretty arbitrary but I referred to the wikipedia article for more clarity: https://en.wikipedia.org/wiki/Leap_year#Gregorian_calendar --- tests/test_catmath.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/test_catmath.py b/tests/test_catmath.py index 748dafe..f6dea46 100644 --- a/tests/test_catmath.py +++ b/tests/test_catmath.py @@ -20,5 +20,17 @@ def test__cat_years_to_hooman_years__0__returns_0(): # BONUS MATERIAL FOR STEP 2 -def test__is_cat_leap_year__succeeds(): - assert catmath.is_cat_leap_year(2016) is True +def test__is_cat_leap_year__not_divisible_by_4__isnt_leap_year(): + assert catmath.is_cat_leap_year(1757) is False + + +def test__is_cat_leap_year__divisible_by_100__isnt_leap_year(): + assert catmath.is_cat_leap_year(1900) is False + + +def test__is_cat_leap_year__centurial_leap_year__is_leap_year(): + assert catmath.is_cat_leap_year(2000) is True + + +def test__is_cat_leap_year__typical_leap_year__is_leap_year(): + assert catmath.is_cat_leap_year(2004) is True