단위 테스트를 어떻게 단위 테스트합니까? [닫은]
MVCStoreFront 앱에서 Rob Connerys 웹 캐스트를보고 있었는데, 그가 다음과 같은 가장 평범한 것조차도 단위 테스트하고 있다는 것을 알았습니다.
public Decimal DiscountPrice
{
get
{
return this.Price - this.Discount;
}
}
다음과 같은 테스트가있을 것입니다.
[TestMethod]
public void Test_DiscountPrice
{
Product p = new Product();
p.Price = 100;
p.Discount = 20;
Assert.IsEqual(p.DiscountPrice,80);
}
저는 모두 단위 테스트에 관심이 있지만, 예를 들어 실제 프로세스에서 코드 (비즈니스 요청, 요구 사항 문서, 아키텍처 문서) 위에 3-4 개의 레이어가있는 경우와 같이 이러한 형태의 테스트 우선 개발이 정말 유익한 지 궁금합니다. , 여기서 실제 정의 된 비즈니스 규칙 (할인 가격은 가격-할인)이 잘못 정의 될 수 있습니다.
그럴 경우 단위 테스트는 아무 의미가 없습니다.
또한 단위 테스트는 또 다른 실패 지점입니다.
[TestMethod]
public void Test_DiscountPrice
{
Product p = new Product();
p.Price = 100;
p.Discount = 20;
Assert.IsEqual(p.DiscountPrice,90);
}
이제 테스트에 결함이 있습니다. 분명히 간단한 테스트에서 큰 문제는 아니지만 복잡한 비즈니스 규칙을 테스트했다고 가정 해 보겠습니다. 여기서 우리는 무엇을 얻습니까?
유지 관리 개발자가 유지 관리하는 응용 프로그램 수명으로 2 년을 앞 당깁니다. 이제 비즈니스가 규칙을 변경하고 테스트가 다시 중단되고 일부 신참 개발자가 테스트를 잘못 수정합니다. 이제 또 다른 실패 지점이 있습니다.
내가 볼 수있는 것은 실제 수익이없는 더 많은 실패 지점입니다. 할인 가격이 잘못된 경우 테스트 팀은 여전히 문제를 찾을 수 있습니다. 단위 테스트가 작업을 어떻게 저장 했습니까?
내가 여기서 무엇을 놓치고 있습니까? 지금까지 TDD를 유용하게 받아들이는 데 어려움을 겪고 있으므로 TDD를 사랑하도록 가르쳐주세요. 나도 원한다. 왜냐하면 나도 진보적이기를 원하기 때문이다. 그러나 그것은 나에게 말이되지 않는다.
편집 : 몇 사람이 테스트가 사양을 시행하는 데 도움이된다고 계속 언급했습니다. 스펙도 틀렸다는 것은 내 경험 이었지만, 스펙을 작성해서는 안되는 사람들이 스펙을 작성하는 조직에서 일할 운명이 아닐 수도 있습니다.
첫째, 테스트는 보안과 같습니다. 100 % 확신 할 수는 없지만 각 계층은 더 많은 신뢰와 프레임 워크를 추가하여 남아있는 문제를보다 쉽게 해결할 수 있습니다.
둘째, 테스트를 자체적으로 테스트 할 수있는 서브 루틴으로 나눌 수 있습니다. 20 개의 유사한 테스트가있을 때 (테스트 된) 서브 루틴을 만든다는 것은 메인 테스트가 더 정확할 가능성이있는 서브 루틴의 간단한 호출 20 개라는 것을 의미합니다.
셋째, 일부는 TDD 가이 문제를 해결 한다고 주장 할 것 입니다. 즉, 20 개의 테스트를 작성하고 통과하면 실제로 어떤 것을 테스트하고 있다는 확신이 없습니다. 하지만 처음에 작성한 각 테스트가 실패한 다음 수정했다면 실제로 코드를 테스트하고 있다는 확신이 훨씬 더 커집니다. IMHO이 앞뒤로 가치가있는 것보다 더 많은 시간이 걸리지 만 귀하의 우려를 해결하려는 과정입니다.
테스트가 잘못되면 프로덕션 코드가 손상 될 가능성이 없습니다. 적어도 검사가 전혀없는 것보다 나쁘지는 않습니다. 따라서 "실패 지점"이 아닙니다. 제품이 실제로 작동하기 위해 테스트가 정확할 필요는 없습니다. 작동하는 것으로 승인되기 전에 정확해야 할 수 있지만 손상된 테스트를 수정하는 프로세스가 구현 코드를 위험에 빠뜨리지는 않습니다.
테스트, 심지어 이와 같은 사소한 테스트를 코드가 수행해야하는 작업에 대한 2 차 의견으로 생각할 수 있습니다. 한 의견은 테스트이고 다른 의견은 구현입니다. 그들이 동의하지 않는다면, 당신은 문제가 있다는 것을 알고 당신은 더 가까이 봅니다.
미래의 누군가가 처음부터 동일한 인터페이스를 구현하려는 경우에도 유용합니다. 할인의 의미를 알기 위해 첫 번째 구현을 읽을 필요는 없으며 테스트는 인터페이스에 대한 서면 설명에 대한 명확한 백업 역할을합니다.
즉, 당신은 시간을 거래하고 있습니다. 이 사소한 테스트를 건너 뛰고 절약 한 시간을 사용하여 작성할 수있는 다른 테스트가 있다면 더 가치가있을 수 있습니다. 실제로 테스트 설정과 응용 프로그램의 특성에 따라 다릅니다. 할인이 앱에 중요하다면 어쨌든 기능 테스트에서이 방법의 버그를 잡을 것입니다. 모든 단위 테스트는 앱이 함께 통합되고 오류 위치가 명확하지 않을 때까지 기다리지 않고 오류 위치가 즉시 분명해질 때이 단위를 테스트하는 지점에서이를 포착 할 수 있도록합니다.
그건 그렇고, 개인적으로 나는 테스트 케이스의 가격으로 100을 사용하지 않을 것입니다 (또는 오히려 내가 그렇게했다면 다른 가격으로 다른 테스트를 추가 할 것입니다). 그 이유는 미래의 누군가가 할인이 백분율이라고 생각할 수 있기 때문입니다. 이와 같은 사소한 테스트의 한 가지 목적은 사양을 읽는 실수가 수정되었는지 확인하는 것입니다.
[편집에 관하여 : 잘못된 사양이 실패의 지점 인 것은 불가피하다고 생각합니다. 앱이 무엇을해야하는지 모르는 경우 앱이 작동하지 않을 가능성이 있습니다. 그러나 사양을 반영하기 위해 테스트를 작성한다고해서이 문제가 확대되는 것은 아니며 단순히 해결하지 못할뿐입니다. 따라서 새로운 실패 지점을 추가하는 것이 아니라 와플 문서 대신 코드에서 기존 오류를 나타내는 것입니다 .]
내가 볼 수있는 것은 실제 수익이없는 더 많은 실패 지점입니다. 할인 가격이 잘못된 경우 테스트 팀은 여전히 문제를 찾을 수 있습니다. 단위 테스트가 작업을 어떻게 저장 했습니까?
단위 테스트는 실제로 작업을 저장하는 것이 아니라 버그를 찾고 예방하는 데 도움이됩니다. 그건 더 일하지만, 작품의 오른쪽 종류입니다. 가장 낮은 수준의 세분화 수준에서 코드를 생각 하고 주어진 입력 세트에 대해 예상 된 조건에서 작동 함을 입증하는 테스트 케이스를 작성 합니다. 변수를 분리하므로 버그가 나타날 때 올바른 위치를 찾아서 시간 을 절약 할 수 있습니다. 그건 저장 당신이 도로 아래로 변경해야 할 때 다시하고 다시 사용할 수 있도록 테스트의 제품군을.
저는 개인적으로 대부분의 방법론이 TDD를 포함 하여 화물 컬트 소프트웨어 엔지니어링 에서 제거 된 많은 단계가 아니라고 생각 하지만 단위 테스트의 이점을 얻기 위해 엄격한 TDD를 고수 할 필요는 없습니다. 좋은 부품은 유지하고 거의 이익이없는 부품은 버리십시오.
마지막으로, " 단위 테스트를 어떻게 단위 테스트합니까? " 라는 제목의 질문에 대한 대답 은 그럴 필요가 없다는 것입니다. 각 단위 테스트는 매우 간단해야합니다. 특정 입력이있는 메서드를 호출하고 예상 출력과 비교합니다. 메서드의 사양이 변경되면 해당 메서드에 대한 일부 단위 테스트도 변경해야 할 것으로 예상 할 수 있습니다. 이것이 그렇게 낮은 수준의 단위 테스트를 수행하는 이유 중 하나이므로 일부 단위 테스트 만 변경하면됩니다. 요구 사항의 한 가지 변경 사항에 대해 여러 가지 방법에 대한 테스트가 변경되는 경우 충분히 세분화 된 수준에서 테스트하지 못할 수 있습니다.
단위 테스트는 단위 (방법)가 원하는대로 수행 할 수 있도록합니다. 테스트를 먼저 작성하면 코드를 작성 하기 전에 예상 한 바를 생각하게 됩니다. 하기 전에 생각하는 것은 항상 좋은 생각입니다.
단위 테스트는 비즈니스 규칙을 반영해야합니다. 물론 코드에 오류가있을 수 있지만 먼저 테스트를 작성하면 코드를 작성하기 전에 비즈니스 규칙의 관점에서 작성할 수 있습니다. 나중에 테스트를 작성하면 코드가이를 구현하는 방법 을 알고 있고 의도가 옳다는 것이 아니라 구현이 올바른지 확인하려는 유혹을 받기 때문에 설명하는 오류가 발생할 가능성이 더 높다고 생각 합니다.
Also, unit tests are only one form -- and the lowest, at that -- of tests that you should be writing. Integration tests and acceptance tests should also be written, the latter by the customer, if possible, to make sure that the system operates the way it is expected. If you find errors during this testing, go back and write unit tests (that fail) to test the change in functionality to make it work correctly, then change your code to make the test pass. Now you have regression tests that capture your bug fixes.
[EDIT]
Another thing that I have found with doing TDD. It almost forces good design by default. This is because highly coupled designs are nearly impossible to unit test in isolation. It doesn't take very long using TDD to figure out that using interfaces, inversion of control, and dependency injection -- all patterns that will improve your design and reduce coupling -- are really important for testable code.
How does one test a test? Mutation testing is a valuable technique that I have personally used to surprisingly good effect. Read the linked article for more details, and links to even more academic references, but in general it "tests your tests" by modifying your source code (changing "x += 1" to "x -= 1" for example) and then rerunning your tests, ensuring that at least one test fails. Any mutations that don't cause test failures are flagged for later investigation.
You'd be surprised at how you can have 100% line and branch coverage with a set of tests that look comprehensive, and yet you can fundamentally change or even comment out a line in your source without any of the tests complaining. Often this comes down to not testing with the right inputs to cover all boundary cases, sometimes it's more subtle, but in all cases I was impressed with how much came out of it.
When applying Test-Driven Development (TDD), one begins with a failing test. This step, that might seem unecessary, actually is here to verify the unit test is testing something. Indeed, if the test never fails, it brings no value and worse, leads to wrong confidence as you'll rely on a positive result that is not proving anything.
When following this process strictly, all ''units'' are protected by the safety net the unit tests are making, even the most mundane.
Assert.IsEqual(p.DiscountPrice,90);
There is no reason the test evolves in that direction - or I'm missing something in your reasoning. When the price is 100 and the discount 20, the discount price is 80. This is like an invariant.
Now imagine your software needs to support another kind of discount based on percentage, perhaps depending on the volume bought, your Product::DiscountPrice() method may become more complicated. And it is possible that introducing those changes breaks the simple discount rule we had initially. Then you'll see the value of this test which will detect the regression immediately.
Red - Green - Refactor - this is to remember the essence of the TDD process.
Red refers to JUnit red bar when a tests fails.
Green is the color of JUnit progress bar when all tests pass.
Refactor under green condition: remove any dupliation, improve readability.
Now to address your point about the "3-4 layers above the code", this is true in a traditional (waterfall-like) process, not when the development process is agile. And agile is the world where TDD is coming from ; TDD is the cornerstone of eXtreme Programming.
Agile is about direct communication rather than thrown-over-the-wall requirement documents.
While, I am all for unit testing, I sometimes wonder if this form of test first development is really beneficial...
Small, trivial tests like this can be the "canary in the coalmine" for your codebase, alerting of danger before it's too late. The trivial tests are useful to keep around because they help you get the interactions right.
For example, think about a trivial test put in place to probe how to use an API you're unfamiliar with. If that test has any relevance to what you're doing in the code that uses the API "for real" it's useful to keep that test around. When the API releases a new version and you need to upgrade. You now have your assumptions about how you expect the API to behave recorded in an executable format that you can use to catch regressions.
...[I]n a real process, you have 3-4 layers above your code (Business Request, Requirements Document, Architecture Document), where the actual defined business rule (Discount Price is Price - Discount) could be misdefined. If that's the situation, your unit test means nothing to you.
If you've been coding for years without writing tests it may not be immediately obvious to you that there is any value. But if you are of the mindset that the best way to work is "release early, release often" or "agile" in that you want the ability to deploy rapidly/continuously, then your test definitely means something. The only way to do this is by legitimizing every change you make to the code with a test. No matter how small the test, once you have a green test suite you're theoretically OK to deploy. See also "continuous production" and "perpetual beta."
You don't have to be "test first" to be of this mindset, either, but that generally is the most efficient way to get there. When you do TDD, you lock yourself into small two to three minute Red Green Refactor cycle. At no point are you not able to stop and leave and have a complete mess on your hands that will take an hour to debug and put back together.
Additionally, your unit test is another point of failure...
A successful test is one that demonstrates a failure in the system. A failing test will alert you to an error in the logic of the test or in the logic of your system. The goal of your tests is to break your code or prove one scenario works.
If you're writing tests after the code, you run the risk of writing a test that is "bad" because in order to see that your test truly works, you need to see it both broken and working. When you're writing tests after the code, this means you have to "spring the trap" and introduce a bug into the code to see the test fail. Most developers are not only uneasy about this, but would argue it is a waste of time.
What do we gain here?
There is definitely a benefit to doing things this way. Michael Feathers defines "legacy code" as "untested code." When you take this approach, you legitimize every change you make to your codebase. It's more rigorous than not using tests, but when it comes to maintaining a large codebase, it pays for itself.
Speaking of Feathers, there are two great resources you should check out in regard to this:
Both of these explain how to work these types of practices and disciplines into projects that aren't "Greenfield." They provide techniques for writing tests around tightly coupled components, hard wired dependencies, and things that you don't necessarily have control over. It's all about finding "seams" and testing around those.
[I]f the discount price is wrong, the test team will still find the issue, how did unit testing save any work?
Habits like these are like an investment. Returns aren't immediate; they build up over time. The alternative to not testing is essentially taking on debt of not being able to catch regressions, introduce code without fear of integration errors, or drive design decisions. The beauty is you legitimize every change introduced into your codebase.
What am I missing here? Please teach me to love TDD, as I'm having a hard time accepting it as useful so far. I want too, because I want to stay progressive, but it just doesn't make sense to me.
I look at it as a professional responsibility. It's an ideal to strive toward. But it is very hard to follow and tedious. If you care about it, and feel you shouldn't produce code that is not tested, you'll be able to find the will power to learn good testing habits. One thing that I do a lot now (as do others) is timebox myself an hour to write code without any tests at all, then have the discipline to throw it away. This may seem wasteful, but it's not really. It's not like that exercise cost a company physical materials. It helped me to understand the problem and how to write code in such a way that it is both of higher quality and testable.
My advice would ultimately be that if you really don't have a desire to be good at it, then don't do it at all. Poor tests that aren't maintained, don't perform well, etc. can be worse than not having any tests. It's hard to learn on your own, and you probably won't love it, but it is going to be next to impossible to learn if you don't have a desire to do it, or can't see enough value in it to warrant the time investment.
A couple people keep mentioned that testing helps enforce the spec. It has been my experience that the spec has been wrong as well, more often than not...
A developer's keyboard is where the rubber meets the road. If the spec is wrong and you don't raise the flag on it, then it's highly probable you'll get blamed for it. Or at least your code will. The discipline and rigor involved in testing is difficult to adhere to. It's not at all easy. It takes practice, a lot of learning and a lot of mistakes. But eventually it does pay off. On a fast-paced, quickly changing project, it's the only way you can sleep at night, no matter if it slows you down.
Another thing to think about here is that techniques that are fundamentally the same as testing have been proven to work in the past: "clean room" and "design by contract" both tend to produce the same types of "meta"-code constructs that tests do, and enforce those at different points. None of these techniques are silver bullets, and rigor is going to cost you ultimately in the scope of features you can deliver in terms of time to market. But that's not what it's about. It's about being able to maintain what you do deliver. And that's very important for most projects.
Unit testing works very similar to double entry book keeping. You state the same thing (business rule) in two quite different ways (as programmed rules in your production code, and as simple, representative examples in your tests). It's very unlikely that you make the same mistake in both, so if they both agree with each other, it's rather unlikely that you got it wrong.
How is testing going to be worth the effort? In my experience in at least four ways, at least when doing test driven development:
- it helps you come up with a well decoupled design. You can only unit test code that is well decoupled;
- it helps you determine when you are done. Having to specify the needed behavior in tests helps to not build functionality that you don't actually need, and determine when the functionality is complete;
- it gives you a safety net for refactorings, which makes the code much more amenable to changes; and
- it saves you a lot of debugging time, which is horribly costly (I've heard estimates that traditionally, developers spend up to 80% of their time debugging).
Most unit tests, test assumptions. In this case, the discount price should be the price minus the discount. If your assumptions are wrong I bet your code is also wrong. And if you make a silly mistake, the test will fail and you will correct it.
If the rules change, the test will fail and that is a good thing. So you have to change the test too in this case.
As a general rule, if a test fails right away (and you don't use test first design), either the test or the code is wrong (or both if you are having a bad day). You use common sense (and possilby the specs) to correct the offending code and rerun the test.
Like Jason said, testing is security. And yes, sometimes they introduce extra work because of faulty tests. But most of the time they are huge time savers. (And you have the perfect opportunity to punish the guy who breaks the test (we are talking rubber chicken)).
Test everything you can. Even trivial mistakes, like forgetting to convert meters to feet can have very expensive side effects. Write a test, write the code for it to check, get it to pass, move on. Who knows at some point in the future, someone may change the discount code. A test can detect the problem.
I see unit tests and production code as having a symbiotic relationship. Simply put: one tests the other. And both test the developer.
Remember that the cost of fixing defects increases (exponentially) as the defects live through the development cycle. Yes, the testing team might catch the defect, but it will (usually) take more work to isolate and fix the defect from that point than if a unit test had failed, and it will be easier to introduce other defects while fixing it if you don't have unit tests to run.
That's usually easier to see with something more than a trivial example ... and with trivial examples, well, if you somehow mess up the unit test, the person reviewing it will catch the error in the test or the error in the code, or both. (They are being reviewed, right?) As tvanfosson points out, unit testing is just one part of an SQA plan.
In a sense, unit tests are insurance. They're no guarantee that you'll catch every defect, and it may seem at times like you're spending a lot of resources on them, but when they do catch defects that you can fix, you'll be spending a lot less than if you'd had no tests at all and had to fix all defects downstream.
I see your point, but it's clearly overstated.
Your argument is basically: Tests introduce failure. Therefore tests are bad/waste of time.
While that may be true in some cases, it's hardly the majority.
TDD assumes: More Tests = Less Failure.
Tests are more likely to catch points of failure than introduce them.
Even more automation can help here ! Yes, writing unit tests can be a lot of work, so use some tools to help you out. Have a look at something like Pex, from Microsoft, if you're using .Net It will automatically create suites of unit tests for you by examining your code. It will come up with tests which give good coverage, trying to cover all paths through your code.
Of course, just by looking at your code it can't know what you were actually trying to do, so it doesn't know if it's correct or not. But, it will generate interesting tests cases for you, and you can then examine them and see if it is behaving as you expect.
If you then go further and write parameterized unit tests (you can think of these as contracts, really) it will generate specific tests cases from these, and this time it can know if something's wrong, because your assertions in your tests will fail.
I've thought a bit about a good way to respond to this question, and would like to draw a parallel to the scientific method. IMO, you could rephrase this question, "How do you experiment an experiment?"
Experiments verify empirical assumptions (hypotheses) about the physical universe. Unit tests will test assumptions about the state or behavior of the code they call. We can talk about the validity of an experiment, but that's because we know, through numerous other experiments, that something doesn't fit. It doesn't have both convergent validity and empirical evidence. We don't design a new experiment to test or verify the validity of an experiment, but we may design a completely new experiment.
So like experiments, we don't describe the validity of a unit test based on whether or not it passes a unit test itself. Along with other unit tests, it describes the assumptions we make about the system it is testing. Also, like experiments, we try to remove as much complexity as we can from what we are testing. "As simple as possible, but no simpler."
Unlike experiments, we have a trick up our sleeve to verify our tests are valid other than just convergent validity. We can cleverly introduce a bug we know should be caught by the test, and see if the test does indeed fail. (If only we could do that in the real world, we'd depend much less on this convergent validity thing!) A more efficient way to do this is watch your test fail before implementing it (the red step in Red, Green, Refactor).
You need to use the correct paradigm when writing tests.
- Start by first writing your tests.
- Make sure they fail to start off with.
- Get them to pass.
- Code review before you checkin your code (make sure the tests are reviewed.)
You cant always be sure but they improve overall tests.
Even if you do not test your code, it will surely be tested in production by your users. Users are very creative in trying to crash your soft and finding even non-critical errors.
Fixing bugs in production is much more costly than resolving issues in development phase. As a side-effect, you will lose income because of an exodus of customers. You can count on 11 lost or not gained customers for 1 angry customer.
참고URL : https://stackoverflow.com/questions/244345/how-do-you-unit-test-a-unit-test
'code' 카테고리의 다른 글
ostringstream을 지우는 방법 (0) | 2020.09.07 |
---|---|
클래스 상수 대 속성 재정의 (0) | 2020.09.07 |
패키지 개체 (0) | 2020.09.07 |
패키지 개체 (0) | 2020.09.07 |
InnoDB : mmap (x 바이트) 실패로 인해 Amazon EC2, mysql 중단 시작 (0) | 2020.09.07 |