Skip to content

Commit

Permalink
Increase test coverage (#3)
Browse files Browse the repository at this point in the history
* Checkpoint.

* Checkpoint.

* Remove Checker.Interfaces. More tests.

* A few more tests and a fix for a bug that they turned up.

* Expand Readme.

* More test coverage, and fix a test bug.
  • Loading branch information
bobg committed Jul 8, 2023
1 parent 2489332 commit 011ef0a
Show file tree
Hide file tree
Showing 9 changed files with 431 additions and 148 deletions.
96 changes: 93 additions & 3 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,96 @@ if it has a more-specific type than it actually needs.
For example,
if your function takes a `*os.File` parameter,
but it’s only ever used for its `Read` method,
it could be specified as an abstract `io.Reader` instead,
making it possible to use the function
on a much wider variety of concrete types.
it could be specified as an abstract `io.Reader` instead

## Why decouple?

When you decouple a function parameter from its too-specific type,
you broaden the set of values on which it can operate.

You also make it easier to test.
For a simple example,
suppose you’re testing this function:

```go
func CountLines(f *os.File) (int, error) {
var result int
sc := bufio.NewScanner(f)
for sc.Scan() {
result++
}
return result, sc.Err()
}
```

Your unit test will need to open a testdata file and pass it to this function to get a result.
But as `decouple` can tell you,
`f` is only ever used as an `io.Reader`
(the type of the argument to [bufio.NewScanner](https://pkg.go.dev/bufio#NewScanner)).

If you were testing `func CountLines(r io.Reader) (int, error)` instead,
the unit test can simply pass it something like `strings.NewReader("a\nb\nc")`.

## Installation

```sh
go install github.com/bobg/decouple/cmd/decouple@latest
```

## Usage

```sh
decouple [-v] [DIR]
```

This produces a report about the Go packages rooted at DIR
(the current directory by default).
With -v,
very verbose debugging output is printed along the way.

The report will be empty if decouple has no findings.
Otherwise, a report will look something like this:

```
$ decouple
/home/bobg/kodigcs/handle.go:105:18: handleDir
req: [Context]
w: io.Writer
/home/bobg/kodigcs/handle.go:167:18: handleNFO
req: [Context]
w: [Header Write]
/home/bobg/kodigcs/handle.go:428:6: isStale
t: [Before]
/home/bobg/kodigcs/imdb.go:59:6: parseIMDbPage
cl: [Do]
```

This is the output when running decouple on [the current commit](https://github.com/bobg/kodigcs/commit/f4e8cf0e44de0ea98fa7ad4f88705324ff446444)
of [kodigcs](https://github.com/bobg/kodigcs).
It’s saying that:

- In the function [handleDir](https://github.com/bobg/kodigcs/blob/f4e8cf0e44de0ea98fa7ad4f88705324ff446444/handle.go#L105),
The `req` parameter is being used only for its `Context` method
and so could be declared as `interface{ Context() context.Context }`,
allowing objects other than `*http.Request` values to be passed in here;
- Also in [handleDir](https://github.com/bobg/kodigcs/blob/f4e8cf0e44de0ea98fa7ad4f88705324ff446444/handle.go#L105),
`w` could be an `io.Writer`,
allowing more types to be used than just `http.ResponseWriter`;
- Similarly in [handleNFO](https://github.com/bobg/kodigcs/blob/f4e8cf0e44de0ea98fa7ad4f88705324ff446444/handle.go#L167),
`req` is used only for its `Context` method,
and `w` for its `Write` and `Header` methods
(more than `io.Writer`, but less than `http.ResponseWriter`);
- Anything with a `Before(time.Time) bool` method
could be used in [isStale](https://github.com/bobg/kodigcs/blob/f4e8cf0e44de0ea98fa7ad4f88705324ff446444/handle.go#L428),
it does not need to be limited to `time.Time`;
- The `*http.Client` argument of [parseIMDbPage](https://github.com/bobg/kodigcs/blob/f4e8cf0e44de0ea98fa7ad4f88705324ff446444/imdb.go#L59)
is being used only for its `Do` method.

Note that,
in the report,
the presence of square brackets means “this is a set of methods,”
while the absence of them means “this is an existing type that already has the right method set”
(as in the `io.Writer` line in the example above).
Decouple can’t always find a suitable existing type even when one exists,
and if two or more types match,
it doesn’t always choose the best one.
Loading

0 comments on commit 011ef0a

Please sign in to comment.