Skip to main content
Tweeted twitter.com/StackCodeReview/status/1227336652227477504
Became Hot Network Question
added 53 characters in body
Source Link
gerrit
  • 905
  • 6
  • 9

I have a function that constructs a path based on a function of inputs and creates the parent directory.

When writing a unit test for this function, I want to mock the I/O part of the function, namely the .exists() and .mkdir() methods of the pathlib.Path objects. However, since both are not called until the path has been constructed, I find myself reconstructing the implementation using a long chain of return_value.__truediv__ accesses, such as below:

import pathlib
import unittest.mock
import datetime


def get(bbase, ttimestamp, cchannel):
    bbase = pathlib.Path(bbase)
    ppath = bbase / "tofu" / ttimestamp.strftime("%Y%m%d") / f"{cchannel:>02d}" / "quinoa.txt"
    if ppath.exists():
        print(ppath, "exists")
    else:
        ppath.parent.mkdir(exist_ok=True, parents=True)
    return p
path


@unittest.mock.patch("pathlib.Path", autospec=True)
def test_get(pP):
    pP.return_value.__truediv__.return_value.__truediv__.return_value.\
                    __truediv__.return_value.__truediv__.return_value.\
                    exists.return_value = False
    get("quorn", datetime.datetime.now(), 42)
    pP.return_value.__truediv__.return_value.__truediv__.return_value.\
                    __truediv__.return_value.__truediv__.return_value.\
                    parent.mkdir.assert_called_once()

This test passes, but I have several concerns:

  • It is testing implementation rather than functionality. If I were to rewrite the second line in get to read ppath = bbase / ttimestamp.strftime("tofu%Y%m%d") / f{cchannel:>02d} / "quinoa.txt", the functionality would be identical, but the test would fail as I wouldn't be mocking the right value anymore.
  • It is hard to read and easy to miscount the depth of __truediv__.return_value chain.

Apart from moving the I/O out of get, can I mock this in another way, that does not share those limitations? Or does the difficulty of writing a good test (with mock) telling me my function is too complex, despite it's small size?

I have a function that constructs a path based on a function of inputs and creates the parent directory.

When writing a unit test for this function, I want to mock the I/O part of the function, namely the .exists() and .mkdir() methods of the pathlib.Path objects. However, since both are not called until the path has been constructed, I find myself reconstructing the implementation using a long chain of return_value.__truediv__ accesses, such as below:

import pathlib
import unittest.mock
import datetime


def get(b, t, c):
    b = pathlib.Path(b)
    p = b / "tofu" / t.strftime("%Y%m%d") / f"{c:>02d}" / "quinoa.txt"
    if p.exists():
        print(p, "exists")
    else:
        p.parent.mkdir(exist_ok=True, parents=True)
    return p



@unittest.mock.patch("pathlib.Path", autospec=True)
def test_get(pP):
    pP.return_value.__truediv__.return_value.__truediv__.return_value.\
                    __truediv__.return_value.__truediv__.return_value.\
                    exists.return_value = False
    get("quorn", datetime.datetime.now(), 42)
    pP.return_value.__truediv__.return_value.__truediv__.return_value.\
                    __truediv__.return_value.__truediv__.return_value.\
                    parent.mkdir.assert_called_once()

This test passes, but I have several concerns:

  • It is testing implementation rather than functionality. If I were to rewrite the second line in get to read p = b / t.strftime("tofu%Y%m%d") / f{c:>02d} / "quinoa.txt", the functionality would be identical, but the test would fail as I wouldn't be mocking the right value anymore.
  • It is hard to read and easy to miscount the depth of __truediv__.return_value chain.

Apart from moving the I/O out of get, can I mock this in another way, that does not share those limitations? Or does the difficulty of writing a good test (with mock) telling me my function is too complex, despite it's small size?

I have a function that constructs a path based on a function of inputs and creates the parent directory.

When writing a unit test for this function, I want to mock the I/O part of the function, namely the .exists() and .mkdir() methods of the pathlib.Path objects. However, since both are not called until the path has been constructed, I find myself reconstructing the implementation using a long chain of return_value.__truediv__ accesses, such as below:

import pathlib
import unittest.mock
import datetime


def get(base, timestamp, channel):
    base = pathlib.Path(base)
    path = base / "tofu" / timestamp.strftime("%Y%m%d") / f"{channel:>02d}" / "quinoa.txt"
    if path.exists():
        print(path, "exists")
    else:
        path.parent.mkdir(exist_ok=True, parents=True)
    return path


@unittest.mock.patch("pathlib.Path", autospec=True)
def test_get(pP):
    pP.return_value.__truediv__.return_value.__truediv__.return_value.\
                    __truediv__.return_value.__truediv__.return_value.\
                    exists.return_value = False
    get("quorn", datetime.datetime.now(), 42)
    pP.return_value.__truediv__.return_value.__truediv__.return_value.\
                    __truediv__.return_value.__truediv__.return_value.\
                    parent.mkdir.assert_called_once()

This test passes, but I have several concerns:

  • It is testing implementation rather than functionality. If I were to rewrite the second line in get to read path = base / timestamp.strftime("tofu%Y%m%d") / f{channel:>02d} / "quinoa.txt", the functionality would be identical, but the test would fail as I wouldn't be mocking the right value anymore.
  • It is hard to read and easy to miscount the depth of __truediv__.return_value chain.

Apart from moving the I/O out of get, can I mock this in another way, that does not share those limitations? Or does the difficulty of writing a good test (with mock) telling me my function is too complex, despite it's small size?

Source Link
gerrit
  • 905
  • 6
  • 9

Mocking pathlib.Path I/O methods in a maintainable way which tests functionality not implementation

I have a function that constructs a path based on a function of inputs and creates the parent directory.

When writing a unit test for this function, I want to mock the I/O part of the function, namely the .exists() and .mkdir() methods of the pathlib.Path objects. However, since both are not called until the path has been constructed, I find myself reconstructing the implementation using a long chain of return_value.__truediv__ accesses, such as below:

import pathlib
import unittest.mock
import datetime


def get(b, t, c):
    b = pathlib.Path(b)
    p = b / "tofu" / t.strftime("%Y%m%d") / f"{c:>02d}" / "quinoa.txt"
    if p.exists():
        print(p, "exists")
    else:
        p.parent.mkdir(exist_ok=True, parents=True)
    return p



@unittest.mock.patch("pathlib.Path", autospec=True)
def test_get(pP):
    pP.return_value.__truediv__.return_value.__truediv__.return_value.\
                    __truediv__.return_value.__truediv__.return_value.\
                    exists.return_value = False
    get("quorn", datetime.datetime.now(), 42)
    pP.return_value.__truediv__.return_value.__truediv__.return_value.\
                    __truediv__.return_value.__truediv__.return_value.\
                    parent.mkdir.assert_called_once()

This test passes, but I have several concerns:

  • It is testing implementation rather than functionality. If I were to rewrite the second line in get to read p = b / t.strftime("tofu%Y%m%d") / f{c:>02d} / "quinoa.txt", the functionality would be identical, but the test would fail as I wouldn't be mocking the right value anymore.
  • It is hard to read and easy to miscount the depth of __truediv__.return_value chain.

Apart from moving the I/O out of get, can I mock this in another way, that does not share those limitations? Or does the difficulty of writing a good test (with mock) telling me my function is too complex, despite it's small size?