Programing

하나의 유닛이 .NET MVC 컨트롤러를 어떻게 테스트해야합니까?

lottogame 2020. 11. 28. 08:34
반응형

하나의 유닛이 .NET MVC 컨트롤러를 어떻게 테스트해야합니까?


.NET mvc 컨트롤러의 효과적인 단위 테스트에 대한 조언을 찾고 있습니다.

내가 일하는 곳에서 이러한 많은 테스트는 moq를 사용하여 데이터 레이어를 모의하고 특정 데이터 레이어 메서드가 호출된다는 것을 주장합니다. 이것은 본질적으로 API를 테스트하는 것보다 구현이 변경되지 않았 음을 확인하기 때문에 유용하지 않은 것 같습니다.

또한 반환 된 뷰 모델 유형이 올바른지 확인하는 것과 같은 것을 권장하는 기사를 읽었습니다. 약간의 가치를 제공하는 것만으로는 여러 줄의 모의 코드를 작성하는 노력을 할 가치가없는 것 같습니다 (애플리케이션의 데이터 모델은 매우 크고 복잡합니다).

누구든지 컨트롤러 유닛 테스트에 대한 더 나은 접근 방식을 제안하거나 위의 접근 방식이 유효 / 유용한 이유를 설명 할 수 있습니까?

감사!


컨트롤러 단위 테스트는 데이터 계층이 아닌 작업 메서드에서 코드 알고리즘을 테스트해야합니다. 이것이 이러한 데이터 서비스를 조롱하는 한 가지 이유입니다. 컨트롤러는 저장소 / 서비스 등에서 특정 값을 수신하고 다른 정보를 수신 할 때 다르게 행동 할 것으로 예상합니다.

컨트롤러가 매우 특정한 시나리오 / 상황에서 매우 특정한 방식으로 작동한다고 주장하는 단위 테스트를 작성합니다. 데이터 레이어는 컨트롤러 / 작업 방법에 이러한 상황을 제공하는 앱의 한 부분입니다. 컨트롤러가 서비스 메서드를 호출했다고 주장하는 것은 컨트롤러가 다른 위치에서 정보를 가져 왔음을 확신 할 수 있기 때문에 중요합니다.

잘못된 viewmodel 유형이 반환되면 MVC에서 런타임 예외가 발생하므로 반환 된 viewmodel의 유형을 확인하는 것이 중요합니다. 단위 테스트를 실행하여 프로덕션에서 이러한 일이 발생하지 않도록 할 수 있습니다. 테스트가 실패하면 뷰가 프로덕션에서 예외를 throw 할 수 있습니다.

단위 테스트는 리팩토링을 훨씬 쉽게 해주기 때문에 유용 할 수 있습니다. 구현을 변경하고 모든 단위 테스트를 통과했는지 확인하여 동작이 여전히 동일하다고 주장 할 수 있습니다.

댓글 # 1에 대한 답변

테스트 대상 메서드의 구현을 변경하여 하위 계층 모의 메서드를 변경 / 제거해야하는 경우 단위 테스트도 변경해야합니다. 그러나 이것은 생각만큼 자주 발생해서는 안됩니다.

일반적인 red-green-refactor 워크 플로 에서는 테스트하는 메서드를 작성 하기 전에 단위 테스트 작성해야합니다. (이는 짧은 시간 동안 테스트 코드가 컴파일되지 않음을 의미하며 많은 젊은 / 미숙 한 개발자가 빨간색 녹색 리팩터링을 채택하는 데 어려움을 겪는 이유입니다.)

단위 테스트를 먼저 작성하면 컨트롤러가 하위 계층에서 정보를 가져와야한다는 것을 알 수있는 지점에 도달하게됩니다. 그 정보를 얻으려고한다는 것을 어떻게 확신 할 수 있습니까? 정보를 제공하는 하위 레이어 메서드를 조롱하고 컨트롤러가 하위 레이어 메서드를 호출한다고 주장합니다.

"구현 변경"이라는 용어를 사용했을 때 잘못 이해했을 수 있습니다. 모의 방법을 변경하거나 제거하기 위해 컨트롤러의 동작 방법 및 해당 단위 테스트를 변경해야하는 경우 실제로 컨트롤러의 동작을 변경하는 것입니다. 정의에 따라 리팩토링은 전체 동작과 예상 결과를 변경하지 않고 구현을 변경하는 것을 의미합니다.

Red-green-refactor는 코드의 버그 및 결함이 나타나기 전에 예방하는 데 도움이되는 품질 보증 접근 방식입니다. 일반적으로 개발자는 버그가 나타난 후이를 제거하기 위해 구현을 변경합니다. 다시 말하지만, 걱정되는 경우는 생각만큼 자주 발생하지 않아야합니다.


먼저 컨트롤러를 식단에 넣어야합니다. 그런 다음 재미있는 단위 테스트 를 할 수 있습니다. 그들이 뚱뚱하고 당신의 모든 비즈니스 로직을 그 안에 넣었다면, 나는 당신의 삶이 단위 테스트에서 물건을 조롱하고 이것이 시간 낭비라고 불평하는 것에 동의합니다.

복잡한 로직에 대해 이야기 할 때 이것이 반드시이 로직이 다른 레이어로 분리 될 수없고 각 방법이 분리되어 단위 테스트된다는 것을 의미하지는 않습니다.


예, DB까지 테스트해야합니다. 조롱에 넣는 시간이 적고 조롱을 통해 얻는 가치도 매우 적습니다 (시스템에서 발생할 수있는 오류의 80 %는 조롱으로 선택할 수 없습니다).

컨트롤러에서 DB 또는 웹 서비스까지 모든 과정을 테스트하면 단위 테스트가 아니라 통합 테스트라고합니다. 저는 개인적으로 단위 테스트와는 반대로 통합 테스트를 믿습니다 (둘 다 다른 목적으로 사용되지만). 그리고 통합 테스트 (시나리오 테스트)를 통해 테스트 기반 개발을 성공적으로 수행 할 수 있습니다.

다음은 우리 팀에서 작동하는 방식입니다. 처음의 모든 테스트 클래스는 DB를 재생성하고 최소한의 데이터 세트 (예 : 사용자 역할)로 테이블을 채우거나 시드합니다. 컨트롤러의 필요성에 따라 DB를 채우고 컨트롤러가 작업을 수행하는지 확인합니다. 이는 다른 방법으로 남겨진 DB 손상 데이터가 테스트에 실패하지 않도록 설계되었습니다. 실행하는 데 걸리는 시간을 제외하고는 단위 테스트의 거의 모든 품질 (이론이더라도)을 얻을 수 있습니다. 컨테이너를 사용하면 순차적으로 실행하는 데 걸리는 시간을 줄일 수 있습니다. 또한 컨테이너를 사용하면 모든 테스트가 컨테이너에 새 DB를 가져 오므로 DB를 다시 만들 필요가 없습니다 (테스트 후 제거됨).

더 현실적인 데이터 소스를 만드는 것이 불가능했기 때문에 모의 / 스텁을 사용하도록 강요 당했을 때 내 경력에서 2 %의 상황 (또는 매우 드물게) 만있었습니다. 그러나 다른 모든 상황에서는 통합 테스트가 가능했습니다.

이 접근 방식으로 성숙한 수준에 도달하는 데 시간이 걸렸습니다. 우리는 테스트 데이터 채우기 및 검색 (일류 시민)을 다루는 멋진 프레임 워크를 가지고 있습니다. 그리고 그것은 큰 시간을 지불합니다! 첫 번째 단계는 모의와 단위 테스트에 작별을 고하는 것입니다. 모의가 말이되지 않는다면 그들은 당신을위한 것이 아닙니다! 통합 테스트는 좋은 수면을 제공합니다.

=================================

아래 댓글 후 편집 : 데모

통합 테스트 또는 기능 테스트는 DB / 소스를 직접 처리해야합니다. 조롱이 없습니다. 그래서 이것이 단계입니다. getEmployee (emp_id) 를 테스트하고 싶습니다 . 아래의 5 단계는 모두 단일 테스트 방법으로 수행됩니다.

  1. DB 삭제
  2. DB 생성 및 역할 및 기타 인프라 데이터 채우기
  3. ID로 직원 레코드 만들기
  4. 이 ID를 사용하고 getEmployee (emp_id)를 호출합니다. // 이것은 api-url 호출 일 수 있습니다 (그러면 db 연결 문자열이 테스트 프로젝트에서 유지 될 필요가 없으며 단순히 도메인 이름을 변경하여 거의 모든 환경을 테스트 할 수 있습니다).
  5. 이제 Assert () / 반환 된 데이터가 올바른지 확인

    이것은 getEmployee ()가 작동 한다는 것을 증명 합니다. 3 단계까지의 단계에서는 테스트 프로젝트에서만 코드를 사용해야합니다. 4 단계는 애플리케이션 코드를 호출합니다. 내가 의미하는 바는 직원 생성 (2 단계)은 애플리케이션 코드가 아닌 테스트 프로젝트 코드로 수행되어야한다는 것입니다. 직원을 생성하기위한 애플리케이션 코드가있는 경우 (예 : CreateEmployee () )이 코드를 사용 해서는 안됩니다. 같은 방식으로 CreateEmployee ()테스트 할 때 GetEmployee () 응용 프로그램 코드를 사용해서는 안됩니다. 테이블에서 데이터를 가져 오기위한 테스트 프로젝트 코드가 있어야합니다.

이렇게하면 모의가 없습니다! DB를 삭제하고 생성하는 이유는 DB의 데이터 손상을 방지하기 위함입니다. 우리의 접근 방식을 사용하면 테스트를 몇 번 실행해도 테스트가 통과됩니다.

Special Tip: In step 5 getEmployee() returns an employee object. If later a developer removes or changes a field name the test breaks. What if a developer adds a new field later? And he/she forgets to add a test for it (assert)? Test would not pick it up. The solution is to add a field count check. eg: Employee object has 4 fields (First Name, Last Name, Designation, Sex). So Assert number of fields of employee object is 4. So when new field is added our test will fail because of the count and reminds the developer to add an assert field for the newly added field.

And this is a great article discussing the benefits of integration testing over unit testing because "unit testing kills!" (it says)


The point of a unit test is to test the behaviour of a method in isolation, based on a set of conditions. You set the conditions of the test using mocks, and assert the method's behaviour by checking how it interacts with other code around it -- by checking which external methods it tries to call, but particularly by checking the value it returns given the conditions.

So in the case of Controller methods, which return ActionResults, it is very useful to inspect the value of the returned ActionResult.

Have a look at the section 'Creating Unit Tests for Controllers' here for some very clear examples using Moq.

Here is a nice sample from that page which tests that an appropriate view is returned when the Controller attempts to create a contact record and it fails.

[TestMethod]
public void CreateInvalidContact()
{
    // Arrange
    var contact = new Contact();
    _service.Expect(s => s.CreateContact(contact)).Returns(false);
    var controller = new ContactController(_service.Object);

    // Act
    var result = (ViewResult)controller.Create(contact);

    // Assert
    Assert.AreEqual("Create", result.ViewName);
}

I don't see much point in unit testing the controller, since it is usually just a piece of code that connects other pieces. Unit testing it typically includes lots of mocking and just verifies that the other services are connected correctly. The test itself is a reflection of the implementing code.

I prefer integration tests -- I start not with a concrete controller, but with an Url, and verify that the returned Model has the correct values. With the help of Ivonna, the test might look like:

var response = new TestSession().Get("/Users/List");
Assert.IsInstanceOf<UserListModel>(response.Model);

var model = (UserListModel) response.Model;
Assert.AreEqual(1, model.Users.Count);

I can mock the database access, but I prefer a different approach: setup an in-memory instance of SQLite, and recreate it with each new test, together with the required data. It makes my tests fast enough, but instead of complicated mocking, I make them clear, e.g. just create and save a User instance, rather than mock the UserService (which might be an implementation detail).


Usually when you're talking about unit tests, you're testing one individual procedure or method, not an entire system, while trying to eliminate all external dependencies.

In other words, when testing the controller, you're writing tests method by method and you should not need to even have the view or model loaded, those are the parts you should "mock out". You can then change the mocks to return values or errors that are hard to reproduce in other testing.

참고URL : https://stackoverflow.com/questions/8818207/how-should-one-unit-test-a-net-mvc-controller

반응형